mirror of
https://github.com/Foundry376/Mailspring.git
synced 2025-02-22 15:15:12 +08:00
fix(attachments): When downloads fail, don't resolve to chained actions
Summary: Fixes Sentry 3319 and 3303 Test Plan: Run three new tests Reviewers: dillon, evan Reviewed By: dillon, evan Differential Revision: https://phab.nylas.com/D2118
This commit is contained in:
parent
20490e8161
commit
ebdf06b183
2 changed files with 70 additions and 25 deletions
|
@ -42,6 +42,15 @@ describe "FileDownloadStore", ->
|
||||||
spyOn(shell, 'showItemInFolder')
|
spyOn(shell, 'showItemInFolder')
|
||||||
spyOn(shell, 'openItem')
|
spyOn(shell, 'openItem')
|
||||||
@testfile = new File(filename: '123.png', contentType: 'image/png', id: "id", size: 100)
|
@testfile = new File(filename: '123.png', contentType: 'image/png', id: "id", size: 100)
|
||||||
|
@testdownload = new Download({
|
||||||
|
state : 'unknown',
|
||||||
|
fileId : 'id',
|
||||||
|
percent : 0,
|
||||||
|
filename : '123.png',
|
||||||
|
filesize : 100,
|
||||||
|
targetPath : '/Users/testuser/.nylas/downloads/id/123.png'
|
||||||
|
})
|
||||||
|
|
||||||
FileDownloadStore._downloads = {}
|
FileDownloadStore._downloads = {}
|
||||||
FileDownloadStore._downloadDirectory = "/Users/testuser/.nylas/downloads"
|
FileDownloadStore._downloadDirectory = "/Users/testuser/.nylas/downloads"
|
||||||
|
|
||||||
|
@ -83,24 +92,24 @@ describe "FileDownloadStore", ->
|
||||||
FileDownloadStore._checkForDownloadedFile(f).then (downloaded) ->
|
FileDownloadStore._checkForDownloadedFile(f).then (downloaded) ->
|
||||||
expect(downloaded).toBe(false)
|
expect(downloaded).toBe(false)
|
||||||
|
|
||||||
describe "_startDownload", ->
|
describe "_runDownload", ->
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
spyOn(FileDownloadStore.Download.prototype, 'run').andCallFake -> Promise.resolve()
|
spyOn(FileDownloadStore.Download.prototype, 'run').andCallFake -> Promise.resolve(@)
|
||||||
spyOn(FileDownloadStore, '_prepareFolder').andCallFake -> Promise.resolve(true)
|
spyOn(FileDownloadStore, '_prepareFolder').andCallFake -> Promise.resolve(true)
|
||||||
spyOn(FileDownloadStore, '_cleanupDownload')
|
spyOn(FileDownloadStore, '_cleanupDownload')
|
||||||
|
|
||||||
it "should make sure that the download file path exists", ->
|
it "should make sure that the download file path exists", ->
|
||||||
FileDownloadStore._startDownload(@testfile)
|
FileDownloadStore._runDownload(@testfile)
|
||||||
expect(FileDownloadStore._prepareFolder).toHaveBeenCalled()
|
expect(FileDownloadStore._prepareFolder).toHaveBeenCalled()
|
||||||
|
|
||||||
it "should return the promise returned by download.run if the download already exists", ->
|
it "should return the promise returned by download.run if the download already exists", ->
|
||||||
existing =
|
existing =
|
||||||
fileId: @testfile.id
|
fileId: @testfile.id
|
||||||
run: jasmine.createSpy('existing.run').andCallFake ->
|
run: jasmine.createSpy('existing.run').andCallFake ->
|
||||||
Promise.resolve(true)
|
Promise.resolve(existing)
|
||||||
FileDownloadStore._downloads[@testfile.id] = existing
|
FileDownloadStore._downloads[@testfile.id] = existing
|
||||||
|
|
||||||
promise = FileDownloadStore._startDownload(@testfile)
|
promise = FileDownloadStore._runDownload(@testfile)
|
||||||
expect(promise instanceof Promise).toBe(true)
|
expect(promise instanceof Promise).toBe(true)
|
||||||
waitsForPromise ->
|
waitsForPromise ->
|
||||||
promise.then ->
|
promise.then ->
|
||||||
|
@ -113,7 +122,7 @@ describe "FileDownloadStore", ->
|
||||||
|
|
||||||
it "should resolve with a Download without calling download.run", ->
|
it "should resolve with a Download without calling download.run", ->
|
||||||
waitsForPromise =>
|
waitsForPromise =>
|
||||||
FileDownloadStore._startDownload(@testfile).then (download) ->
|
FileDownloadStore._runDownload(@testfile).then (download) ->
|
||||||
expect(FileDownloadStore.Download.prototype.run).not.toHaveBeenCalled()
|
expect(FileDownloadStore.Download.prototype.run).not.toHaveBeenCalled()
|
||||||
expect(download instanceof FileDownloadStore.Download).toBe(true)
|
expect(download instanceof FileDownloadStore.Download).toBe(true)
|
||||||
expect(download.data()).toEqual({
|
expect(download.data()).toEqual({
|
||||||
|
@ -131,7 +140,7 @@ describe "FileDownloadStore", ->
|
||||||
Promise.resolve(false)
|
Promise.resolve(false)
|
||||||
|
|
||||||
it "should register the download with the right attributes", ->
|
it "should register the download with the right attributes", ->
|
||||||
FileDownloadStore._startDownload(@testfile)
|
FileDownloadStore._runDownload(@testfile)
|
||||||
advanceClock(0)
|
advanceClock(0)
|
||||||
expect(FileDownloadStore.downloadDataForFile(@testfile.id)).toEqual({
|
expect(FileDownloadStore.downloadDataForFile(@testfile.id)).toEqual({
|
||||||
state : 'unstarted',fileId : 'id',
|
state : 'unstarted',fileId : 'id',
|
||||||
|
@ -143,13 +152,13 @@ describe "FileDownloadStore", ->
|
||||||
|
|
||||||
it "should call download.run", ->
|
it "should call download.run", ->
|
||||||
waitsForPromise =>
|
waitsForPromise =>
|
||||||
FileDownloadStore._startDownload(@testfile)
|
FileDownloadStore._runDownload(@testfile)
|
||||||
runs ->
|
runs ->
|
||||||
expect(FileDownloadStore.Download.prototype.run).toHaveBeenCalled()
|
expect(FileDownloadStore.Download.prototype.run).toHaveBeenCalled()
|
||||||
|
|
||||||
it "should resolve with a Download", ->
|
it "should resolve with a Download", ->
|
||||||
waitsForPromise =>
|
waitsForPromise =>
|
||||||
FileDownloadStore._startDownload(@testfile).then (download) ->
|
FileDownloadStore._runDownload(@testfile).then (download) ->
|
||||||
expect(download instanceof FileDownloadStore.Download).toBe(true)
|
expect(download instanceof FileDownloadStore.Download).toBe(true)
|
||||||
expect(download.data()).toEqual({
|
expect(download.data()).toEqual({
|
||||||
state : 'unstarted',
|
state : 'unstarted',
|
||||||
|
@ -162,9 +171,17 @@ describe "FileDownloadStore", ->
|
||||||
|
|
||||||
describe "_fetch", ->
|
describe "_fetch", ->
|
||||||
it "should call through to startDownload", ->
|
it "should call through to startDownload", ->
|
||||||
spyOn(FileDownloadStore, '_startDownload')
|
spyOn(FileDownloadStore, '_runDownload').andCallFake ->
|
||||||
|
Promise.resolve(@testdownload)
|
||||||
FileDownloadStore._fetch(@testfile)
|
FileDownloadStore._fetch(@testfile)
|
||||||
expect(FileDownloadStore._startDownload).toHaveBeenCalled()
|
expect(FileDownloadStore._runDownload).toHaveBeenCalled()
|
||||||
|
|
||||||
|
it "should fail silently since it's called passively", ->
|
||||||
|
spyOn(FileDownloadStore, '_presentError')
|
||||||
|
spyOn(FileDownloadStore, '_runDownload').andCallFake =>
|
||||||
|
Promise.reject(@testdownload)
|
||||||
|
FileDownloadStore._fetch(@testfile)
|
||||||
|
expect(FileDownloadStore._presentError).not.toHaveBeenCalled()
|
||||||
|
|
||||||
describe "_fetchAndOpen", ->
|
describe "_fetchAndOpen", ->
|
||||||
it "should open the file once it's been downloaded", ->
|
it "should open the file once it's been downloaded", ->
|
||||||
|
@ -172,7 +189,7 @@ describe "FileDownloadStore", ->
|
||||||
download = {targetPath: @savePath}
|
download = {targetPath: @savePath}
|
||||||
downloadResolve = null
|
downloadResolve = null
|
||||||
|
|
||||||
spyOn(FileDownloadStore, '_startDownload').andCallFake =>
|
spyOn(FileDownloadStore, '_runDownload').andCallFake =>
|
||||||
new Promise (resolve, reject) ->
|
new Promise (resolve, reject) ->
|
||||||
downloadResolve = resolve
|
downloadResolve = resolve
|
||||||
|
|
||||||
|
@ -182,17 +199,25 @@ describe "FileDownloadStore", ->
|
||||||
advanceClock(100)
|
advanceClock(100)
|
||||||
expect(shell.openItem).toHaveBeenCalledWith(@savePath)
|
expect(shell.openItem).toHaveBeenCalledWith(@savePath)
|
||||||
|
|
||||||
|
it "should open an error if the download fails", ->
|
||||||
|
spyOn(FileDownloadStore, '_presentError')
|
||||||
|
spyOn(FileDownloadStore, '_runDownload').andCallFake =>
|
||||||
|
Promise.reject(@testdownload)
|
||||||
|
FileDownloadStore._fetchAndOpen(@testfile)
|
||||||
|
advanceClock(1)
|
||||||
|
expect(FileDownloadStore._presentError).toHaveBeenCalled()
|
||||||
|
|
||||||
describe "_fetchAndSave", ->
|
describe "_fetchAndSave", ->
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
@savePath = "/Users/imaginary/.nylas/Downloads/b.png"
|
@savePath = "/Users/imaginary/.nylas/Downloads/b.png"
|
||||||
spyOn(atom, 'showSaveDialog').andCallFake (options, callback) => callback(@savePath)
|
spyOn(atom, 'showSaveDialog').andCallFake (options, callback) => callback(@savePath)
|
||||||
|
|
||||||
it "should open a save dialog and prompt the user to choose a download path", ->
|
it "should open a save dialog and prompt the user to choose a download path", ->
|
||||||
spyOn(FileDownloadStore, '_startDownload').andCallFake =>
|
spyOn(FileDownloadStore, '_runDownload').andCallFake =>
|
||||||
new Promise (resolve, reject) -> # never resolve
|
new Promise (resolve, reject) -> # never resolve
|
||||||
FileDownloadStore._fetchAndSave(@testfile)
|
FileDownloadStore._fetchAndSave(@testfile)
|
||||||
expect(atom.showSaveDialog).toHaveBeenCalled()
|
expect(atom.showSaveDialog).toHaveBeenCalled()
|
||||||
expect(FileDownloadStore._startDownload).toHaveBeenCalledWith(@testfile)
|
expect(FileDownloadStore._runDownload).toHaveBeenCalledWith(@testfile)
|
||||||
|
|
||||||
it "should copy the file to the download path after it's been downloaded and open it after the stream has ended", ->
|
it "should copy the file to the download path after it's been downloaded and open it after the stream has ended", ->
|
||||||
download = {targetPath: @savePath}
|
download = {targetPath: @savePath}
|
||||||
|
@ -202,7 +227,7 @@ describe "FileDownloadStore", ->
|
||||||
on: (eventName, eventCallback) =>
|
on: (eventName, eventCallback) =>
|
||||||
onEndEventCallback = eventCallback
|
onEndEventCallback = eventCallback
|
||||||
|
|
||||||
spyOn(FileDownloadStore, '_startDownload').andCallFake =>
|
spyOn(FileDownloadStore, '_runDownload').andCallFake =>
|
||||||
Promise.resolve(download)
|
Promise.resolve(download)
|
||||||
spyOn(fs, 'createReadStream').andReturn(streamStub)
|
spyOn(fs, 'createReadStream').andReturn(streamStub)
|
||||||
spyOn(fs, 'createWriteStream')
|
spyOn(fs, 'createWriteStream')
|
||||||
|
@ -215,6 +240,14 @@ describe "FileDownloadStore", ->
|
||||||
advanceClock(1)
|
advanceClock(1)
|
||||||
expect(shell.showItemInFolder).toHaveBeenCalledWith(download.targetPath)
|
expect(shell.showItemInFolder).toHaveBeenCalledWith(download.targetPath)
|
||||||
|
|
||||||
|
it "should open an error if the download fails", ->
|
||||||
|
spyOn(FileDownloadStore, '_presentError')
|
||||||
|
spyOn(FileDownloadStore, '_runDownload').andCallFake =>
|
||||||
|
Promise.reject(@testdownload)
|
||||||
|
FileDownloadStore._fetchAndSave(@testfile)
|
||||||
|
advanceClock(1)
|
||||||
|
expect(FileDownloadStore._presentError).toHaveBeenCalled()
|
||||||
|
|
||||||
describe "_abortFetchFile", ->
|
describe "_abortFetchFile", ->
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
@download =
|
@download =
|
||||||
|
|
|
@ -152,7 +152,7 @@ FileDownloadStore = Reflux.createStore
|
||||||
|
|
||||||
# Returns a promise with a Download object, allowing other actions to be
|
# Returns a promise with a Download object, allowing other actions to be
|
||||||
# daisy-chained to the end of the download operation.
|
# daisy-chained to the end of the download operation.
|
||||||
_startDownload: (file) ->
|
_runDownload: (file) ->
|
||||||
@_prepareFolder(file).then =>
|
@_prepareFolder(file).then =>
|
||||||
targetPath = @pathForFile(file)
|
targetPath = @pathForFile(file)
|
||||||
|
|
||||||
|
@ -172,18 +172,16 @@ FileDownloadStore = Reflux.createStore
|
||||||
# Do we actually need to queue and run the download? Queuing a download
|
# Do we actually need to queue and run the download? Queuing a download
|
||||||
# for an already-downloaded file has side-effects, like making the UI
|
# for an already-downloaded file has side-effects, like making the UI
|
||||||
# flicker briefly.
|
# flicker briefly.
|
||||||
@_checkForDownloadedFile(file).then (downloaded) =>
|
@_checkForDownloadedFile(file).then (alreadyHaveFile) =>
|
||||||
if downloaded
|
if alreadyHaveFile
|
||||||
# If we have the file, just resolve with a resolved download representing the file.
|
# If we have the file, just resolve with a resolved download representing the file.
|
||||||
download.promise = Promise.resolve()
|
download.promise = Promise.resolve()
|
||||||
return Promise.resolve(download)
|
return Promise.resolve(download)
|
||||||
else
|
else
|
||||||
cleanup = =>
|
|
||||||
@_cleanupDownload(download)
|
|
||||||
Promise.resolve(download)
|
|
||||||
@_downloads[file.id] = download
|
@_downloads[file.id] = download
|
||||||
@trigger()
|
@trigger()
|
||||||
return download.run().catch(cleanup).then(cleanup)
|
return download.run().finally =>
|
||||||
|
@_cleanupDownload(download)
|
||||||
|
|
||||||
# Returns a promise that resolves with true or false. True if the file has
|
# Returns a promise that resolves with true or false. True if the file has
|
||||||
# been downloaded, false if it should be downloaded.
|
# been downloaded, false if it should be downloaded.
|
||||||
|
@ -203,20 +201,25 @@ FileDownloadStore = Reflux.createStore
|
||||||
mkdirpAsync(targetFolder)
|
mkdirpAsync(targetFolder)
|
||||||
|
|
||||||
_fetch: (file) ->
|
_fetch: (file) ->
|
||||||
@_startDownload(file)
|
@_runDownload(file).catch ->
|
||||||
|
# Passively ignore
|
||||||
|
|
||||||
_fetchAndOpen: (file) ->
|
_fetchAndOpen: (file) ->
|
||||||
@_startDownload(file).then (download) ->
|
@_runDownload(file).then (download) ->
|
||||||
shell.openItem(download.targetPath)
|
shell.openItem(download.targetPath)
|
||||||
|
.catch =>
|
||||||
|
@_presentError(file)
|
||||||
|
|
||||||
_fetchAndSave: (file) ->
|
_fetchAndSave: (file) ->
|
||||||
atom.showSaveDialog @_defaultSavePath(file), (savePath) =>
|
atom.showSaveDialog @_defaultSavePath(file), (savePath) =>
|
||||||
return unless savePath
|
return unless savePath
|
||||||
@_startDownload(file).then (download) ->
|
@_runDownload(file).then (download) ->
|
||||||
stream = fs.createReadStream(download.targetPath)
|
stream = fs.createReadStream(download.targetPath)
|
||||||
stream.pipe(fs.createWriteStream(savePath))
|
stream.pipe(fs.createWriteStream(savePath))
|
||||||
stream.on 'end', ->
|
stream.on 'end', ->
|
||||||
shell.showItemInFolder(savePath)
|
shell.showItemInFolder(savePath)
|
||||||
|
.catch =>
|
||||||
|
@_presentError(file)
|
||||||
|
|
||||||
_abortFetchFile: (file) ->
|
_abortFetchFile: (file) ->
|
||||||
download = @_downloads[file.id]
|
download = @_downloads[file.id]
|
||||||
|
@ -243,5 +246,14 @@ FileDownloadStore = Reflux.createStore
|
||||||
|
|
||||||
path.join(downloadDir, file.displayName())
|
path.join(downloadDir, file.displayName())
|
||||||
|
|
||||||
|
_presentError: (file) ->
|
||||||
|
dialog = require('remote').require('dialog')
|
||||||
|
dialog.showMessageBox
|
||||||
|
type: 'warning'
|
||||||
|
message: "Download Failed"
|
||||||
|
detail: "Unable to download #{file.displayName()}.
|
||||||
|
Check your network connection and try again."
|
||||||
|
buttons: ["OK"]
|
||||||
|
|
||||||
# Expose the Download class for our tests, and possibly for other things someday
|
# Expose the Download class for our tests, and possibly for other things someday
|
||||||
FileDownloadStore.Download = Download
|
FileDownloadStore.Download = Download
|
||||||
|
|
Loading…
Reference in a new issue