mirror of
https://github.com/Foundry376/Mailspring.git
synced 2024-09-22 00:06:06 +08:00
fix(contacts): move contact rank fetching to sync workers, refactor
Summary: Fixes bug where contact ranking was not being fetched, and refactors the refreshing of contact ranks. Moves periodic refreshing of the database-stored ranks to the sync workers so it occurs in the background, once per account. Refactors JSON cache code accordingly. Test Plan: manual Reviewers: evan, bengotow Reviewed By: bengotow Differential Revision: https://phab.nylas.com/D2137
This commit is contained in:
parent
fd69d1af9c
commit
d3d450105e
|
@ -0,0 +1,30 @@
|
|||
|
||||
RefreshingJSONCache = require './refreshing-json-cache'
|
||||
{NylasAPI} = require 'nylas-exports'
|
||||
|
||||
# Stores contact rankings
|
||||
class ContactRankingsCache extends RefreshingJSONCache
|
||||
constructor: (accountId) ->
|
||||
@_accountId = accountId
|
||||
super({
|
||||
key: "ContactRankingsFor#{accountId}",
|
||||
version: 1,
|
||||
refreshInterval: 60 * 60 * 1000 * 24 # one day
|
||||
})
|
||||
|
||||
fetchData: (callback) =>
|
||||
NylasAPI.makeRequest
|
||||
accountId: @_accountId
|
||||
path: "/contacts/rankings"
|
||||
returnsModel: false
|
||||
.then (json) =>
|
||||
# Convert rankings into the format needed for quick lookup
|
||||
rankings = {}
|
||||
for [email, rank] in json
|
||||
rankings[email.toLowerCase()] = rank
|
||||
callback(rankings)
|
||||
.catch (err) =>
|
||||
console.warn("Request for Contact Rankings failed for account #{@_accountId}. #{err}")
|
||||
|
||||
|
||||
module.exports = ContactRankingsCache
|
|
@ -1,6 +1,7 @@
|
|||
_ = require 'underscore'
|
||||
{Actions, DatabaseStore} = require 'nylas-exports'
|
||||
NylasLongConnection = require './nylas-long-connection'
|
||||
ContactRankingsCache = require './contact-rankings-cache'
|
||||
|
||||
INITIAL_PAGE_SIZE = 30
|
||||
MAX_PAGE_SIZE = 250
|
||||
|
@ -42,6 +43,7 @@ class NylasSyncWorker
|
|||
|
||||
@_terminated = false
|
||||
@_connection = new NylasLongConnection(api, account.id)
|
||||
@_refreshingCaches = [new ContactRankingsCache(account.id)]
|
||||
@_resumeTimer = new BackoffTimer =>
|
||||
# indirection needed so resumeFetches can be spied on
|
||||
@resumeFetches()
|
||||
|
@ -76,12 +78,14 @@ class NylasSyncWorker
|
|||
start: ->
|
||||
@_resumeTimer.start()
|
||||
@_connection.start()
|
||||
@_refreshingCaches.map (c) -> c.start()
|
||||
@resumeFetches()
|
||||
|
||||
cleanup: ->
|
||||
@_unlisten?()
|
||||
@_resumeTimer.cancel()
|
||||
@_connection.end()
|
||||
@_refreshingCaches.map (c) -> c.end()
|
||||
@_terminated = true
|
||||
@
|
||||
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
_ = require 'underscore'
|
||||
{NylasStore, DatabaseStore} = require 'nylas-exports'
|
||||
|
||||
|
||||
class RefreshingJSONCache
|
||||
|
||||
constructor: ({@key, @version, @refreshInterval}) ->
|
||||
@_timeoutId = null
|
||||
|
||||
start: ->
|
||||
# Clear any scheduled actions
|
||||
@end()
|
||||
|
||||
# Look up existing data from db
|
||||
DatabaseStore.findJSONObject(@key).then (json) =>
|
||||
|
||||
# Refresh immediately if json is missing or version is outdated. Otherwise,
|
||||
# compute next refresh time and schedule
|
||||
timeUntilRefresh = 0
|
||||
if json? and json.version is @version
|
||||
timeUntilRefresh = Math.max(0, @refreshInterval - (Date.now() - json.time))
|
||||
|
||||
@_timeoutId = setTimeout(@refresh, timeUntilRefresh)
|
||||
|
||||
reset: ->
|
||||
# Clear db value, turn off any scheduled actions
|
||||
DatabaseStore.persistJSONObject(@key, {})
|
||||
@end()
|
||||
|
||||
end: ->
|
||||
# Turn off any scheduled actions
|
||||
clearInterval(@_timeoutId) if @_timeoutId
|
||||
@_timeoutId = null
|
||||
|
||||
refresh: =>
|
||||
# Set up next refresh call
|
||||
clearTimeout(@_timeoutId) if @_timeoutId
|
||||
@_timeoutId = setTimeout(@refresh, @refreshInterval)
|
||||
|
||||
# Call fetch data function, save it to the database
|
||||
@fetchData (newValue) =>
|
||||
DatabaseStore.persistJSONObject(@key, {
|
||||
version: @version
|
||||
time: Date.now()
|
||||
value: newValue
|
||||
})
|
||||
|
||||
fetchData: (callback) =>
|
||||
throw new Error("Subclasses should override this method.")
|
||||
|
||||
|
||||
|
||||
module.exports = RefreshingJSONCache
|
39
src/flux/stores/contact-ranking-store.coffee
Normal file
39
src/flux/stores/contact-ranking-store.coffee
Normal file
|
@ -0,0 +1,39 @@
|
|||
|
||||
NylasStore = require 'nylas-store'
|
||||
DatabaseStore = require './database-store'
|
||||
AccountStore = require './account-store'
|
||||
|
||||
class ContactRankingStore extends NylasStore
|
||||
|
||||
constructor: ->
|
||||
@listenTo DatabaseStore, @_onDatabaseChanged
|
||||
@listenTo AccountStore, @_onAccountChanged
|
||||
@_value = null
|
||||
@_accountId = null
|
||||
@_refresh()
|
||||
|
||||
_onDatabaseChanged: (change) =>
|
||||
if change.objectClass is 'JSONObject' and change.objects[0].key is "ContactRankingsFor#{@_accountId}"
|
||||
@_value = change.objects[0].json.value
|
||||
@trigger()
|
||||
|
||||
_onAccountChanged: =>
|
||||
@_refresh()
|
||||
@reset()
|
||||
@trigger()
|
||||
|
||||
value: ->
|
||||
@_value
|
||||
|
||||
reset: ->
|
||||
@_value = null
|
||||
|
||||
_refresh: =>
|
||||
return if @_accountId is AccountStore.current()?.id
|
||||
@_accountId = AccountStore.current()?.id
|
||||
DatabaseStore.findJSONObject("ContactRankingsFor#{@_accountId}").then (json) =>
|
||||
@_value = if json? then json.value else null
|
||||
@trigger()
|
||||
|
||||
|
||||
module.exports = new ContactRankingStore()
|
|
@ -8,112 +8,11 @@ NylasStore = require 'nylas-store'
|
|||
RegExpUtils = require '../../regexp-utils'
|
||||
DatabaseStore = require './database-store'
|
||||
AccountStore = require './account-store'
|
||||
ContactRankingStore = require './contact-ranking-store'
|
||||
_ = require 'underscore'
|
||||
|
||||
NylasAPI = require '../nylas-api'
|
||||
{Listener, Publisher} = require '../modules/reflux-coffee'
|
||||
CoffeeHelpers = require '../coffee-helpers'
|
||||
|
||||
WindowBridge = require '../../window-bridge'
|
||||
|
||||
###
|
||||
The JSONCache class exposes a simple API for maintaining a local cache of data
|
||||
in a JSON file that needs to be refreshed periodically. Using JSONCache is a good
|
||||
idea because it handles a file errors and JSON parsing errors gracefully.
|
||||
|
||||
To use the JSONCache class, subclass it and implement `refreshValue`, which
|
||||
should compute a new JSON value and return it via the callback:
|
||||
|
||||
```
|
||||
refreshValue: (callback) ->
|
||||
NylasAPI.makeRequest(...).then (values) ->
|
||||
callback(values)
|
||||
```
|
||||
|
||||
If you do not wish to refresh the value, do not call the callback.
|
||||
|
||||
When you create an instance of a JSONCache, you need to provide several settings:
|
||||
|
||||
- `key`: A unique key identifying this object.
|
||||
|
||||
- `version`: a version number. If the local cache has a different version number
|
||||
it will be thrown out. Useful if you want to change the format of the data
|
||||
stored in the cache.
|
||||
|
||||
- `maxAge`: the maximum age of the local cache before it should be refreshed.
|
||||
|
||||
###
|
||||
class JSONCache
|
||||
@include: CoffeeHelpers.includeModule
|
||||
@include Publisher
|
||||
|
||||
constructor: ({@key, @version, @maxAge}) ->
|
||||
@_value = null
|
||||
DatabaseStore.findJSONObject(@key).then (json) =>
|
||||
return @refresh() unless json
|
||||
return @refresh() unless json.version is @version
|
||||
@_value = json.value
|
||||
@trigger()
|
||||
|
||||
age = (new Date).getTime() - json.time
|
||||
if age > @maxAge
|
||||
@refresh()
|
||||
else
|
||||
setTimeout(@refresh, @maxAge - age)
|
||||
|
||||
value: ->
|
||||
@_value
|
||||
|
||||
reset: ->
|
||||
DatabaseStore.persistJSONObject(@key, {})
|
||||
clearInterval(@_interval) if @_interval
|
||||
@_interval = null
|
||||
@_value = null
|
||||
|
||||
refresh: =>
|
||||
clearInterval(@_interval) if @_interval
|
||||
@_interval = setInterval(@refresh, @maxAge)
|
||||
|
||||
@refreshValue (newValue) =>
|
||||
@_value = newValue
|
||||
DatabaseStore.persistJSONObject(@key, {
|
||||
version: @version
|
||||
time: (new Date).getTime()
|
||||
value: @_value
|
||||
})
|
||||
@trigger()
|
||||
|
||||
refreshValue: (callback) =>
|
||||
throw new Error("Subclasses should override this method.")
|
||||
|
||||
|
||||
class RankingsJSONCache extends JSONCache
|
||||
|
||||
constructor: ->
|
||||
super(key: 'RankingsJSONCache', version: 1, maxAge: 60 * 60 * 1000 * 24)
|
||||
|
||||
refreshValue: (callback) =>
|
||||
return unless atom.isMainWindow()
|
||||
|
||||
accountId = AccountStore.current()?.id
|
||||
return unless accountId
|
||||
|
||||
NylasAPI.makeRequest
|
||||
accountId: accountId
|
||||
path: "/contacts/rankings"
|
||||
returnsModel: false
|
||||
.then (json) =>
|
||||
# Check that the current account is still the one we requested data for
|
||||
return unless accountId is AccountStore.current()?.id
|
||||
# Convert rankings into the format needed for quick lookup
|
||||
rankings = {}
|
||||
for [email, rank] in json
|
||||
rankings[email.toLowerCase()] = rank
|
||||
callback(rankings)
|
||||
.catch (err) =>
|
||||
console.warn("Request for Contact Rankings failed. #{err}")
|
||||
|
||||
|
||||
###
|
||||
Public: ContactStore maintains an in-memory cache of the user's address
|
||||
book, making it easy to build autocompletion functionality and resolve
|
||||
|
@ -141,10 +40,9 @@ class ContactStore extends NylasStore
|
|||
@_contactCache = []
|
||||
@_accountId = null
|
||||
|
||||
@_rankingsCache = new RankingsJSONCache()
|
||||
@listenTo DatabaseStore, @_onDatabaseChanged
|
||||
@listenTo AccountStore, @_onAccountChanged
|
||||
@listenTo @_rankingsCache, @_sortContactsCacheWithRankings
|
||||
@listenTo ContactRankingStore, @_sortContactsCacheWithRankings
|
||||
|
||||
@_accountId = AccountStore.current()?.id
|
||||
@_refreshCache()
|
||||
|
@ -276,7 +174,7 @@ class ContactStore extends NylasStore
|
|||
_refreshCache: _.debounce(ContactStore::__refreshCache, 100)
|
||||
|
||||
_sortContactsCacheWithRankings: =>
|
||||
rankings = @_rankingsCache.value()
|
||||
rankings = ContactRankingStore.value()
|
||||
return unless rankings
|
||||
@_contactCache = _.sortBy @_contactCache, (contact) =>
|
||||
- (rankings[contact.email.toLowerCase()] ? 0) / 1
|
||||
|
@ -287,7 +185,7 @@ class ContactStore extends NylasStore
|
|||
|
||||
_resetCache: =>
|
||||
@_contactCache = []
|
||||
@_rankingsCache.reset()
|
||||
ContactRankingStore.reset()
|
||||
@trigger(@)
|
||||
|
||||
_onAccountChanged: =>
|
||||
|
@ -295,7 +193,6 @@ class ContactStore extends NylasStore
|
|||
@_accountId = AccountStore.current()?.id
|
||||
|
||||
if @_accountId
|
||||
@_rankingsCache.refresh()
|
||||
@_refreshCache()
|
||||
else
|
||||
@_resetCache()
|
||||
|
|
Loading…
Reference in a new issue