Mailspring/internal_packages/worker-sync/lib/nylas-sync-worker.coffee
Ben Gotow 9d995ded67 feat(work): Create the "Work" window, move TaskQueue, Nylas sync workers
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
2015-08-27 16:39:40 -07:00

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