feat(database): Save and retrieve arbitrary json blobs to database

This fixes T2233, which was caused by the main window trying to write config.cson very often as initial sync happened, and the parent process trying to observe those changes on disk to watch for the user's API key being removed. Further refactoring would be good but this will fix it.
This commit is contained in:
Ben Gotow 2015-08-06 12:21:24 -07:00
parent 67281430f0
commit d0b7f2b0dd
5 changed files with 51 additions and 52 deletions

View file

@ -158,7 +158,6 @@ class Application
buttons: ['OK']
fs.unlink path.join(configDirPath,'edgehill.db'), (err) =>
@setDatabasePhase('setup')
@config.set("nylas.sync-state", {})
@windowManager.showMainWindow()
# Registers basic application commands, non-idempotent.

View file

@ -1,6 +1,6 @@
_ = require 'underscore'
NylasLongConnection = require './nylas-long-connection'
DatabaseStore = require './stores/database-store'
{Publisher} = require './modules/reflux-coffee'
CoffeeHelpers = require './coffee-helpers'
@ -18,9 +18,13 @@ class NylasSyncWorker
@_terminated = false
@_connection = new NylasLongConnection(api, namespace.id)
@_state = atom.config.get("nylas.sync-state.#{namespace.id}") ? {}
@_state = null
DatabaseStore.findJSONObject("NylasSyncWorker:#{@_namespace.id}").then (json) =>
@_state = json ? {}
for model, modelState of @_state
modelState.busy = false
@resumeFetches()
@
@ -34,6 +38,7 @@ class NylasSyncWorker
@_state
busy: ->
return false unless @_state
for key, state of @_state
if state.busy
return true
@ -51,6 +56,7 @@ class NylasSyncWorker
@
resumeFetches: =>
return unless @_state
@fetchCollection('threads')
@fetchCollection('calendars')
@fetchCollection('contacts')
@ -61,6 +67,7 @@ class NylasSyncWorker
@fetchCollection('folders')
fetchCollection: (model, options = {}) ->
return unless @_state
return if @_state[model]?.complete and not options.force?
return if @_state[model]?.busy
@ -113,7 +120,7 @@ class NylasSyncWorker
writeState: ->
@_writeState ?= _.debounce =>
atom.config.set("nylas.sync-state.#{@_namespace.id}", @_state)
DatabaseStore.persistJSONObject("NylasSyncWorker:#{@_namespace.id}", @_state)
,100
@_writeState()
@trigger()

View file

@ -32,7 +32,7 @@ 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:
- `localPath`: path on disk to keep the cache
- `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
@ -43,35 +43,13 @@ When you create an instance of a JSONCache, you need to provide several settings
###
class JSONCache
@include: CoffeeHelpers.includeModule
@include Publisher
constructor: ({@localPath, @version, @maxAge}) ->
constructor: ({@key, @version, @maxAge}) ->
@_value = null
@readLocal()
detatch: =>
clearInterval(@_interval) if @_interval
value: ->
@_value
reset: ->
fs.unlink @localPath, (err) ->
console.error(err)
@_value = null
readLocal: =>
fs.exists @localPath, (exists) =>
return @refresh() unless exists
fs.readFile @localPath, (err, contents) =>
return @refresh() unless contents and not err
try
json = JSON.parse(contents)
if json.version isnt @version
throw new Error("Outdated schema")
if not json.time
throw new Error("No fetch time present")
DatabaseStore.findJSONObject(@key).then (json) =>
return @refresh() unless json
return @refresh() unless json.version is @version
@_value = json.value
@trigger()
@ -81,17 +59,14 @@ class JSONCache
else
setTimeout(@refresh, @maxAge - age)
catch err
console.error(err)
@reset()
@refresh()
value: ->
@_value
writeLocal: =>
json =
version: @version
time: (new Date).getTime()
value: @_value
fs.writeFile(@localPath, JSON.stringify(json))
reset: ->
DatabaseStore.persistJSONObject(@key, {})
clearInterval(@_interval) if @_interval
@_interval = null
@_value = null
refresh: =>
clearInterval(@_interval) if @_interval
@ -99,7 +74,11 @@ class JSONCache
@refreshValue (newValue) =>
@_value = newValue
@writeLocal()
DatabaseStore.persistJSONObject(@key, {
version: @version
time: (new Date).getTime()
value: @_value
})
@trigger()
refreshValue: (callback) =>
@ -108,6 +87,9 @@ class JSONCache
class RankingsJSONCache extends JSONCache
constructor: ->
super(key: 'RankingsJSONCache', version: 1, maxAge: 60 * 60 * 1000 * 24)
refreshValue: (callback) =>
return unless atom.isMainWindow()
@ -154,11 +136,7 @@ class ContactStore extends NylasStore
@_contactCache = []
@_namespaceId = null
@_rankingsCache = new RankingsJSONCache
localPath: path.join(atom.getConfigDirPath(), 'contact-rankings.json')
maxAge: 60 * 60 * 1000 * 24 # one day
version: 1
@_rankingsCache = new RankingsJSONCache()
@listenTo DatabaseStore, @_onDatabaseChanged
@listenTo NamespaceStore, @_onNamespaceChanged
@listenTo @_rankingsCache, @_sortContactsCacheWithRankings

View file

@ -22,6 +22,10 @@ class DatabaseSetupQueryBuilder
attributes = _.values(klass.attributes)
queries = []
# Add table for storing generic JSON blobs
queries.push("CREATE TABLE IF NOT EXISTS `JSONObject` (key TEXT PRIMARY KEY, data BLOB)")
queries.push("CREATE UNIQUE INDEX IF NOT EXISTS `JSONObject_id` ON `JSONObject` (`key`)")
# Identify attributes of this class that can be matched against. These
# attributes need their own columns in the table
columnAttributes = _.filter attributes, (attr) ->

View file

@ -503,6 +503,17 @@ class DatabaseStore extends NylasStore
})
@_triggerSoon({objectClass: newModel.constructor.name, objects: [oldModel, newModel], type: 'swap'})
persistJSONObject: (key, json) ->
@_query(BEGIN_TRANSACTION)
@_query("REPLACE INTO `JSONObject` (`key`,`data`) VALUES (?,?)", [key, JSON.stringify(json)])
@_query(COMMIT)
findJSONObject: (key) ->
@_query("SELECT `data` FROM `JSONObject` WHERE key = ? LIMIT 1", [key]).then (results) =>
return Promise.resolve(null) unless results[0]
data = JSON.parse(results[0].data)
Promise.resolve(data)
########################################################################
########################### PRIVATE METHODS ############################
########################################################################