mirror of
https://github.com/Foundry376/Mailspring.git
synced 2024-09-23 08:46:07 +08:00
1a576d92dc
Summary: Move sync workers and Edgehill token checks to work window Move the task queue and database setup to the work window Move ContactStore background refresh to work window Store the task queue in the database WIP The TaskQueue now puts tasks in the database instead of in a file, which also means it can be observed Move all delta sync and initial sync to a package, make NylasSyncStore which exposes read-only sync state DraftStore no longer reads task status. Once you set the "sending" bit on a draft, it never gets unset. But that's fine actually. If your package lists windowTypes, you *only* get loaded in those windowTypes. If you specify no windowTypes, you get loaded in the root window. This means that onboarding, worker-ui, worker-sync, etc. no longer get loaded into the main window ActivitySidebar has a special little store that observes the task queue since it's no longer in the window Move "toggle component regions" / "toggle react remote" to the Developer menu Move sync worker specs, update draft store specs to not rely on TaskQueue at all Test Plan: Run existing tests, all pass Reviewers: dillon, evan Reviewed By: evan Differential Revision: https://phab.nylas.com/D1936
164 lines
4.2 KiB
CoffeeScript
164 lines
4.2 KiB
CoffeeScript
_ = require 'underscore'
|
|
{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()
|
|
|
|
@_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: ->
|
|
@_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 = {}) ->
|
|
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})
|
|
@fetchCollectionPage(model, nextParams)
|
|
@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()
|
|
|
|
NylasSyncWorker.BackoffTimer = BackoffTimer
|