mirror of
https://github.com/Foundry376/Mailspring.git
synced 2025-09-10 22:54:45 +08:00
Summary: Fixes T3566 Fixes T3567 Test Plan: new tests Reviewers: dillon, bengotow Reviewed By: bengotow Maniphest Tasks: T3566, T3567 Differential Revision: https://phab.nylas.com/D2025
162 lines
5.6 KiB
CoffeeScript
162 lines
5.6 KiB
CoffeeScript
fs = require 'fs'
|
|
_ = require 'underscore'
|
|
crypto = require 'crypto'
|
|
pathUtils = require 'path'
|
|
Task = require './task'
|
|
{APIError} = require '../errors'
|
|
File = require '../models/file'
|
|
Message = require '../models/message'
|
|
Actions = require '../actions'
|
|
AccountStore = require '../stores/account-store'
|
|
DatabaseStore = require '../stores/database-store'
|
|
{isTempId} = require '../models/utils'
|
|
NylasAPI = require '../nylas-api'
|
|
Utils = require '../models/utils'
|
|
|
|
UploadCounter = 0
|
|
|
|
class FileUploadTask extends Task
|
|
|
|
constructor: (@filePath, @messageClientId) ->
|
|
super
|
|
@_startDate = Date.now()
|
|
@_startId = UploadCounter
|
|
UploadCounter += 1
|
|
|
|
@progress = null # The progress checking timer.
|
|
|
|
performLocal: ->
|
|
return Promise.reject(new Error("Must pass an absolute path to upload")) unless @filePath?.length
|
|
return Promise.reject(new Error("Must be attached to a messageClientId")) unless isTempId(@messageClientId)
|
|
Actions.uploadStateChanged @_uploadData("pending")
|
|
Promise.resolve()
|
|
|
|
performRemote: ->
|
|
Actions.uploadStateChanged @_uploadData("started")
|
|
|
|
DatabaseStore.findBy(Message, {clientId: @messageClientId}).then (draft) =>
|
|
return Promise.resolve(Task.Status.Finished) unless draft
|
|
@_accountId = draft.accountId
|
|
|
|
@_makeRequest()
|
|
.then @_performRemoteParseFile
|
|
.then @_performRemoteAttachFile
|
|
.then (file) =>
|
|
Actions.uploadStateChanged @_uploadData("completed")
|
|
Actions.fileUploaded(file: file, uploadData: @_uploadData("completed"))
|
|
return Promise.resolve(Task.Status.Finished)
|
|
.catch APIError, (err) =>
|
|
if err.statusCode in NylasAPI.PermanentErrorCodes
|
|
msg = "There was a problem uploading this file. Please try again later."
|
|
Actions.uploadStateChanged(@_uploadData("failed"))
|
|
Actions.postNotification({message: msg, type: "error"})
|
|
return Promise.resolve(Task.Status.Finished)
|
|
else if err.statusCode is NylasAPI.CancelledErrorCode
|
|
Actions.uploadStateChanged(@_uploadData("aborted"))
|
|
Actions.fileAborted(@_uploadData("aborted"))
|
|
return Promise.resolve(Task.Status.Finished)
|
|
else
|
|
return Promise.resolve(Task.Status.Retry)
|
|
|
|
_makeRequest: =>
|
|
started = (req) =>
|
|
@req = req
|
|
@progress = setInterval =>
|
|
Actions.uploadStateChanged(@_uploadData("progress"))
|
|
, 250
|
|
|
|
cleanup = =>
|
|
clearInterval(@progress)
|
|
@req = null
|
|
|
|
NylasAPI.makeRequest
|
|
path: "/files"
|
|
accountId: @_accountId
|
|
method: "POST"
|
|
json: false
|
|
formData: @_formData()
|
|
started: started
|
|
.finally(cleanup)
|
|
|
|
_performRemoteParseFile: (rawResponseString) =>
|
|
# The Nylas API returns the file json wrapped in an array.
|
|
# Since we requested `json:false` the response will come back as
|
|
# a raw string.
|
|
json = JSON.parse(rawResponseString)
|
|
file = (new File).fromJSON(json[0])
|
|
Promise.resolve(file)
|
|
|
|
_performRemoteAttachFile: (file) =>
|
|
# The minute we know what file is associated with the upload, we need
|
|
# to fire an Action to notify a popout window's FileUploadStore that
|
|
# these two objects are linked. We unfortunately can't wait until
|
|
# `_attachFileToDraft` resolves, because that will resolve after the
|
|
# DB transaction is completed AND all of the callbacks have fired.
|
|
# Unfortunately in the callback chain is a render method which means
|
|
# that the upload will be left on the page for a split second before
|
|
# we know the file has been uploaded.
|
|
#
|
|
# Associating the upload with the file ahead of time can let the
|
|
# Composer know which ones to ignore when de-duping the upload/file
|
|
# listing.
|
|
Actions.linkFileToUpload(file: file, uploadData: @_uploadData("completed"))
|
|
|
|
|
|
DraftStore = require '../stores/draft-store'
|
|
# We have a `DatabaseStore.atomically` block surrounding the object
|
|
# right before we persist changes
|
|
DraftStore.sessionForClientId(@messageClientId).then (session) =>
|
|
files = _.clone(session.draft().files) ? []
|
|
files.push(file)
|
|
session.changes.add({files})
|
|
session.changes.commit().then ->
|
|
return file
|
|
|
|
cancel: ->
|
|
super
|
|
|
|
# Note: When you call cancel, we stop the request, which causes
|
|
# NylasAPI.makeRequest to reject with an error.
|
|
return unless @req
|
|
@req.abort()
|
|
|
|
# Helper Methods
|
|
|
|
_formData: ->
|
|
file: # Must be named `file` as per the Nylas API spec
|
|
value: fs.createReadStream(@filePath)
|
|
options:
|
|
filename: @_uploadData().fileName
|
|
|
|
# returns:
|
|
# messageClientId - The clientId of the message (draft) we're uploading to
|
|
# filePath - The full absolute local system file path
|
|
# fileSize - The size in bytes
|
|
# fileName - The basename of the file
|
|
# bytesUploaded - Current number of bytes uploaded
|
|
# state - one of "pending" "started" "progress" "completed" "aborted" "failed"
|
|
_uploadData: (state) ->
|
|
@_memoUploadData ?=
|
|
uploadTaskId: @id
|
|
startDate: @_startDate
|
|
startId: @_startId
|
|
messageClientId: @messageClientId
|
|
filePath: @filePath
|
|
fileSize: @_getFileSize(@filePath)
|
|
fileName: pathUtils.basename(@filePath)
|
|
@_memoUploadData.bytesUploaded = @_getBytesUploaded()
|
|
@_memoUploadData.state = state if state?
|
|
return _.extend({}, @_memoUploadData)
|
|
|
|
_getFileSize: (path) ->
|
|
fs.statSync(path)["size"]
|
|
|
|
_getBytesUploaded: ->
|
|
# https://github.com/request/request/issues/941
|
|
# http://stackoverflow.com/questions/12098713/upload-progress-request
|
|
@req?.req?.connection?._bytesDispatched ? 0
|
|
|
|
_accountId: ->
|
|
AccountStore.current()?.id
|
|
|
|
module.exports = FileUploadTask
|