aboutsummaryrefslogtreecommitdiffstats
path: root/background_scripts/sync.coffee
blob: 40e0c5c36e521831b63b1715a990ed9c48178397 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
#
# * Sync.set() and Sync.clear() propagate local changes to chrome.storage.sync.
# * Sync.listener() listens for changes to chrome.storage.sync and propagates those
#   changes to localStorage and into vimium's internal state.
# * Sync.pull() polls chrome.storage.sync at startup, similarly propagating
#   changes to localStorage and into vimium's internal state.
#
# Changes are propagated into vimium's state using the same mechanism
# (Settings.doPostUpdateHook) that is used when options are changed on
# the options page.
#
# The effect is best-effort synchronization of vimium options/settings between
# chrome/vimium instances.
#
# NOTE:
#   Values handled within this module are ALWAYS already JSON.stringifed, so
#   they're always non-empty strings.
#

root = exports ? window
root.Sync = Sync =

  debug: true
  storage: chrome.storage.sync
  doNotSync: [ "settingsVersion", "previousVersion" ]

  init: ->
    chrome.storage.onChanged.addListener (changes, area) -> Sync.listener changes, area
    @pull()

  # Asynchronous fetch from synced storage, called only at startup.
  pull: ->
    @storage.get null, (items) ->
      if chrome.runtime.lastError is undefined
        for own key, value of items
          @storeAndPropagate key, value
      else
        @log "chrome sync callback for Sync.pull() indicates error"
        @log chrome.runtime.lastError

  # Asynchronous message from synced storage.
  listener: (changes, area) ->
    for own key, change of changes
      @storeAndPropagate key, change.newValue
  
  # Only ever called from asynchronous synced-storage callbacks (pull and listener).
  storeAndPropagate: (key, value) ->
    # Value must be JSON.stringifed or undefined.
    if not @checkHaveStringOrUndefined value
      return
    # Ignore, we're not accepting this key.
    if not @isSyncKey key
       @log "ignoring: #{key}"
       return
    # Ignore, it's unchanged
    if localStorage[key] is value
       @log "unchanged: #{key}"
       return

    # Ok: accept, store and propagate this update.
    defaultValue = Settings.defaults[key]
    defaultValueJSON = JSON.stringify(defaultValue)

    if value && value != defaultValueJSON
      # Key/value has been changed to non-default value at remote instance.
      @log "update: #{key}=#{value}"
      localStorage[key] = value
      Settings.doPostUpdateHook key, JSON.parse(value)
    else
      # Key has been reset to default value at remote instance.
      @log "clear: #{key}"
      delete localStorage[key]
      Settings.doPostUpdateHook key, defaultValue

  # Only called synchronously from within vimium, never on a callback.
  # No need to propagate updates into the rest of vimium.
  set: (key, value) ->
    # value has already been JSON.stringifed
    if not @checkHaveString value
      return
    #
    if @isSyncKey key
      @storage.set @mkKeyValue(key,value), ->
        if chrome.runtime.lastError
          @log "chrome sync callback for Sync.set() indicates error: " + key
          @log chrome.runtime.lastError
      @log "set scheduled: #{key}=#{value}"

  # Only called synchronously from within vimium, never on a callback.
  clear: (key) ->
    if @isSyncKey key
      @storage.remove key, ->
        if chrome.runtime.lastError
          @log "chrome sync callback for Sync.clear() indicates error: " + key
          @log chrome.runtime.lastError

  # Should we synchronize this key?
  isSyncKey: (key) ->
    key not in @doNotSync

  # There has to be a more elegant way to do this!
  mkKeyValue: (key, value) ->
    obj = {}
    obj[key] = value
    obj

  # Debugging messages.
  # Disable debugginf by setting root.Sync.debug to anything falsy.
  # Enabled for the time being (18/4/14) -- smblott.
  log: (msg) ->
    console.log "Sync: #{msg}" if @debug

  checkHaveString: (thing) ->
    if typeof(thing) != "string" or not thing
      @log "Sync: Yikes! this should be a non-empty string: #{typeof(thing)} #{thing}"
      return false
    return true

  checkHaveStringOrUndefined: (thing) ->
    if ( typeof(thing) != "string" and typeof(thing) != "undefined" ) or ( typeof(thing) == "string" and not thing )
      @log "Sync: Yikes! this should be a non-empty string or undefined: #{typeof(thing)} #{thing}"
      return false
    return true
  
Sync.init()