mirror of
https://github.com/Foundry376/Mailspring.git
synced 2025-01-09 09:38:07 +08:00
041950e014
This should prevent us from flooding the app with contacts (/contacts responds very quickly). Fixes T3475
171 lines
4.5 KiB
CoffeeScript
171 lines
4.5 KiB
CoffeeScript
_ = require 'underscore'
|
|
{Actions, DatabaseStore} = require 'nylas-exports'
|
|
NylasLongConnection = require './nylas-long-connection'
|
|
|
|
PAGE_SIZE = 250
|
|
|
|
# BackoffTimer is a small helper class that wraps setTimeout. It fires the function
|
|
# you provide at a regular interval, but backs off each time you call `backoff`.
|
|
#
|
|
class BackoffTimer
|
|
constructor: (@fn) ->
|
|
@reset()
|
|
|
|
cancel: =>
|
|
clearTimeout(@_timeout) if @_timeout
|
|
@_timeout = null
|
|
|
|
reset: =>
|
|
@cancel()
|
|
@_delay = 20 * 1000
|
|
|
|
backoff: =>
|
|
@_delay = Math.min(@_delay * 1.4, 5 * 1000 * 60) # Cap at 5 minutes
|
|
if not atom.inSpecMode()
|
|
console.log("Backing off after sync failure. Will retry in #{Math.floor(@_delay / 1000)} seconds.")
|
|
|
|
start: =>
|
|
clearTimeout(@_timeout) if @_timeout
|
|
@_timeout = setTimeout =>
|
|
@_timeout = null
|
|
@fn()
|
|
, @_delay
|
|
|
|
|
|
module.exports =
|
|
class NylasSyncWorker
|
|
|
|
constructor: (api, account) ->
|
|
@_api = api
|
|
@_account = account
|
|
|
|
@_terminated = false
|
|
@_connection = new NylasLongConnection(api, account.id)
|
|
@_resumeTimer = new BackoffTimer =>
|
|
# indirection needed so resumeFetches can be spied on
|
|
@resumeFetches()
|
|
|
|
@_unlisten = Actions.retryInitialSync.listen(@_onRetryInitialSync, @)
|
|
|
|
@_state = null
|
|
DatabaseStore.findJSONObject("NylasSyncWorker:#{@_account.id}").then (json) =>
|
|
@_state = json ? {}
|
|
for model, modelState of @_state
|
|
modelState.busy = false
|
|
@resumeFetches()
|
|
|
|
@
|
|
|
|
account: ->
|
|
@_account
|
|
|
|
connection: ->
|
|
@_connection
|
|
|
|
state: ->
|
|
@_state
|
|
|
|
busy: ->
|
|
return false unless @_state
|
|
for key, state of @_state
|
|
if state.busy
|
|
return true
|
|
false
|
|
|
|
start: ->
|
|
@_resumeTimer.start()
|
|
@_connection.start()
|
|
@resumeFetches()
|
|
|
|
cleanup: ->
|
|
@_unlisten?()
|
|
@_resumeTimer.cancel()
|
|
@_connection.end()
|
|
@_terminated = true
|
|
@
|
|
|
|
resumeFetches: =>
|
|
return unless @_state
|
|
|
|
# Stop the timer. If one or more network requests fails during the fetch process
|
|
# we'll backoff and restart the timer.
|
|
@_resumeTimer.cancel()
|
|
|
|
@fetchCollection('threads')
|
|
@fetchCollection('calendars')
|
|
@fetchCollection('contacts')
|
|
@fetchCollection('drafts')
|
|
if @_account.usesLabels()
|
|
@fetchCollection('labels')
|
|
if @_account.usesFolders()
|
|
@fetchCollection('folders')
|
|
|
|
fetchCollection: (model, options = {}) ->
|
|
return unless @_state
|
|
return if @_state[model]?.complete and not options.force?
|
|
return if @_state[model]?.busy
|
|
|
|
@_state[model] =
|
|
complete: false
|
|
error: null
|
|
busy: true
|
|
count: 0
|
|
fetched: 0
|
|
@writeState()
|
|
|
|
@fetchCollectionCount(model)
|
|
@fetchCollectionPage(model, {offset: 0, limit: PAGE_SIZE})
|
|
|
|
fetchCollectionCount: (model) ->
|
|
@_api.makeRequest
|
|
accountId: @_account.id
|
|
path: "/#{model}"
|
|
returnsModel: false
|
|
qs:
|
|
view: 'count'
|
|
success: (response) =>
|
|
return if @_terminated
|
|
@updateTransferState(model, count: response.count)
|
|
error: (err) =>
|
|
return if @_terminated
|
|
@_resumeTimer.backoff()
|
|
@_resumeTimer.start()
|
|
|
|
fetchCollectionPage: (model, params = {}) ->
|
|
requestStartTime = Date.now()
|
|
requestOptions =
|
|
error: (err) =>
|
|
return if @_terminated
|
|
@_resumeTimer.backoff()
|
|
@_resumeTimer.start()
|
|
@updateTransferState(model, {busy: false, complete: false, error: err.toString()})
|
|
success: (json) =>
|
|
return if @_terminated
|
|
lastReceivedIndex = params.offset + json.length
|
|
if json.length is params.limit
|
|
nextParams = _.extend({}, params, {offset: lastReceivedIndex})
|
|
nextDelay = Math.max(0, 1500 - (Date.now() - requestStartTime))
|
|
setTimeout(( => @fetchCollectionPage(model, nextParams)), nextDelay)
|
|
@updateTransferState(model, {fetched: lastReceivedIndex})
|
|
else
|
|
@updateTransferState(model, {fetched: lastReceivedIndex, busy: false, complete: true})
|
|
|
|
if model is 'threads'
|
|
@_api.getThreads(@_account.id, params, requestOptions)
|
|
else
|
|
@_api.getCollection(@_account.id, model, params, requestOptions)
|
|
|
|
updateTransferState: (model, {busy, error, complete, fetched, count}) ->
|
|
@_state[model] = _.defaults({busy, error, complete, fetched, count}, @_state[model])
|
|
@writeState()
|
|
|
|
writeState: ->
|
|
@_writeState ?= _.debounce =>
|
|
DatabaseStore.persistJSONObject("NylasSyncWorker:#{@_account.id}", @_state)
|
|
,100
|
|
@_writeState()
|
|
|
|
_onRetryInitialSync: =>
|
|
@resumeFetches()
|
|
|
|
NylasSyncWorker.BackoffTimer = BackoffTimer
|