mirror of
https://github.com/zero-peak/ZeroOmega.git
synced 2025-01-22 15:08:12 -05:00
Disable profile syncing if quota exceeded in sync storage. Fix #272.
This commit is contained in:
parent
718c569fc8
commit
2f6e9dc280
@ -107,9 +107,10 @@ class ChromeOptions extends OmegaTarget.Options
|
||||
config['rules'] = rules
|
||||
return config
|
||||
|
||||
_proxyChangeWatchers: []
|
||||
_proxyChangeWatchers: null
|
||||
_proxyChangeListener: null
|
||||
watchProxyChange: (callback) ->
|
||||
@_proxyChangeWatchers = []
|
||||
if not @_proxyChangeListener?
|
||||
@_proxyChangeListener = (details) =>
|
||||
for watcher in @_proxyChangeWatchers
|
||||
|
@ -4,6 +4,7 @@ Promise = OmegaTarget.Promise
|
||||
|
||||
module.exports = class ProxyAuth
|
||||
constructor: (options) ->
|
||||
@_requests = {}
|
||||
@options = options
|
||||
|
||||
listening: false
|
||||
@ -64,7 +65,7 @@ module.exports = class ProxyAuth
|
||||
|
||||
_proxies: {}
|
||||
_fallbacks: []
|
||||
_requests: {}
|
||||
_requests: null
|
||||
authHandler: (details) ->
|
||||
return {} unless details.isProxy
|
||||
req = @_requests[details.requestId]
|
||||
|
@ -1,9 +1,10 @@
|
||||
class ChromeTabs
|
||||
_dirtyTabs: {}
|
||||
_defaultAction: null
|
||||
_badgeTab: null
|
||||
|
||||
constructor: (@actionForUrl) -> return
|
||||
constructor: (@actionForUrl) ->
|
||||
@_dirtyTabs = {}
|
||||
return
|
||||
|
||||
ignoreError: ->
|
||||
chrome.runtime.lastError
|
||||
|
@ -16,15 +16,13 @@ class Options
|
||||
# All the options, in a map from key to value.
|
||||
# @type OmegaOptions
|
||||
###
|
||||
_options: {}
|
||||
_options: null
|
||||
_storage: null
|
||||
_state: null
|
||||
_currentProfileName: null
|
||||
_revertToProfileName: null
|
||||
_watchingProfiles: {}
|
||||
_tempProfile: null
|
||||
_tempProfileRules: {}
|
||||
_tempProfileRulesByProfile: {}
|
||||
fallbackProfileName: 'system'
|
||||
_isSystem: false
|
||||
debugStr: 'Options'
|
||||
@ -56,6 +54,9 @@ class Options
|
||||
return value
|
||||
|
||||
constructor: (options, @_storage, @_state, @log, @sync) ->
|
||||
@_options = {}
|
||||
@_tempProfileRules = {}
|
||||
@_tempProfileRulesByProfile = {}
|
||||
@_storage ?= Storage()
|
||||
@_state ?= Storage()
|
||||
@log ?= Log
|
||||
|
@ -12,7 +12,6 @@ class OptionsSync
|
||||
_timeout: null
|
||||
_bucket: null
|
||||
_waiting: false
|
||||
_pending: {}
|
||||
|
||||
###*
|
||||
# The debounce timeout (ms) for requestPush scheduling. See requestPush.
|
||||
@ -33,6 +32,7 @@ class OptionsSync
|
||||
storage: null
|
||||
|
||||
constructor: (@storage, @_bucket) ->
|
||||
@_pending = {}
|
||||
@_bucket ?= new TokenBucket(10, 10, 'minute', null)
|
||||
@_bucket.clear ?= =>
|
||||
@_bucket.tryRemoveTokens(@_bucket.content)
|
||||
@ -50,9 +50,10 @@ class OptionsSync
|
||||
###*
|
||||
# Merge newVal and oldVal of a given key. The default implementation choose
|
||||
# between newVal and oldVal based on the following rules:
|
||||
# 1. Choose oldVal if it has a revision newer than or equal to that of newVal.
|
||||
# 2. Choose oldVal if it deeply equals newVal.
|
||||
# 3. Otherwise, choose newVal.
|
||||
# 1. Choose oldVal if syncOptions is 'disabled' in either oldVal or newVal.
|
||||
# 2. Choose oldVal if it has a revision newer than or equal to that of newVal.
|
||||
# 3. Choose oldVal if it deeply equals newVal.
|
||||
# 4. Otherwise, choose newVal.
|
||||
#
|
||||
# @param {string} key The key of the item
|
||||
# @param {} newVal The new value
|
||||
@ -66,6 +67,8 @@ class OptionsSync
|
||||
)
|
||||
return (key, newVal, oldVal) ->
|
||||
return oldVal if newVal == oldVal
|
||||
if oldVal?.syncOptions == 'disabled' or newVal?.syncOptions == 'disabled'
|
||||
return oldVal
|
||||
if oldVal?.revision? and newVal?.revision?
|
||||
result = Revision.compare(oldVal.revision, newVal.revision)
|
||||
return oldVal if result >= 0
|
||||
@ -87,7 +90,7 @@ class OptionsSync
|
||||
###
|
||||
requestPush: (changes) ->
|
||||
clearTimeout(@_timeout) if @_timeout?
|
||||
for key, value of changes
|
||||
for own key, value of changes
|
||||
if typeof value != 'undefined'
|
||||
value = @transformValue(value, key)
|
||||
continue if typeof value == 'undefined'
|
||||
@ -128,7 +131,7 @@ class OptionsSync
|
||||
return Promise.reject('bucket')
|
||||
).catch (e) =>
|
||||
# Re-submit the changes for syncing, but with lower priority.
|
||||
for key, value of set
|
||||
for own key, value of set
|
||||
if not (key of @_pending)
|
||||
@_pending[key] = value
|
||||
for key in remove
|
||||
@ -144,8 +147,19 @@ class OptionsSync
|
||||
@requestPush({})
|
||||
return
|
||||
else if e instanceof Storage.QuotaExceededError
|
||||
# TODO(catus): Remove profiles that are too large and retry.
|
||||
@_pending = {}
|
||||
# For now, we just disable syncing for all changed profiles.
|
||||
# TODO(catus): Remove the largest profile each time and retry.
|
||||
valuesAffected = 0
|
||||
for own key, value of set
|
||||
if key[0] == '+' and value.syncOptions != 'disabled'
|
||||
value.syncOptions = 'disabled'
|
||||
value.syncError = {reason: 'quotaPerItem'}
|
||||
valuesAffected++
|
||||
if valuesAffected > 0
|
||||
@requestPush({})
|
||||
else
|
||||
@_pending = {}
|
||||
return
|
||||
else
|
||||
Promise.reject(e)
|
||||
|
||||
@ -162,8 +176,8 @@ class OptionsSync
|
||||
###
|
||||
copyTo: (local) ->
|
||||
Promise.join local.get(null), @storage.get(null), (base, changes) =>
|
||||
for key of base when not (key of changes)
|
||||
if key[0] == '+'
|
||||
for own key of base when not (key of changes)
|
||||
if key[0] == '+' and not base[key]?.syncOptions == 'disabled'
|
||||
changes[key] = undefined
|
||||
local.apply(
|
||||
changes: changes
|
||||
@ -192,7 +206,7 @@ class OptionsSync
|
||||
local.apply(operations)
|
||||
|
||||
@storage.watch null, (changes) =>
|
||||
for key, value of changes
|
||||
for own key, value of changes
|
||||
pull[key] = value
|
||||
return if pullScheduled?
|
||||
pullScheduled = setTimeout(doPull, @pullThrottle)
|
||||
|
@ -35,31 +35,23 @@ class Storage
|
||||
# @param {?{}} args Extra arguments
|
||||
# @param {Object.<string, {}>?} args.base The original items in the storage.
|
||||
# @param {function(key, newVal, oldVal)} args.merge A function that merges
|
||||
# the newVal and oldVal. oldVal is only provided if args.base is present.
|
||||
# the newVal and oldVal. oldVal is provided only if args.base is present.
|
||||
# Otherwise it will be equal to newVal (i.e. merge(key, newVal, newVal)).
|
||||
# @returns {WriteOperations} The operations that should be performed.
|
||||
###
|
||||
@operationsForChanges: (changes, {base, merge} = {}) ->
|
||||
set = {}
|
||||
remove = []
|
||||
for key, newVal of changes
|
||||
if not base?
|
||||
newVal = if merge then merge(key, newVal) else newVal
|
||||
if typeof newVal == 'undefined'
|
||||
oldVal = if base? then base[key] else newVal
|
||||
if merge
|
||||
newVal = merge(key, newVal, oldVal)
|
||||
continue if base? and newVal == oldVal
|
||||
if typeof newVal == 'undefined'
|
||||
if typeof oldVal != 'undefined' or not base?
|
||||
remove.push(key)
|
||||
else
|
||||
set[key] = newVal
|
||||
else
|
||||
oldVal = base[key]
|
||||
if typeof newVal == 'undefined'
|
||||
if typeof oldVal != 'undefined'
|
||||
remove.push(key)
|
||||
else if newVal != oldVal
|
||||
if merge
|
||||
newVal = merge(key, newVal, oldVal)
|
||||
if newVal != oldVal
|
||||
set[key] = newVal
|
||||
else
|
||||
set[key] = newVal
|
||||
set[key] = newVal
|
||||
return {set: set, remove: remove}
|
||||
|
||||
###*
|
||||
|
@ -7,6 +7,7 @@ describe 'OptionsSync', ->
|
||||
OptionsSync = require '../src/options_sync'
|
||||
Storage = require '../src/storage'
|
||||
Log = require '../src/log'
|
||||
Promise = require 'bluebird'
|
||||
|
||||
before ->
|
||||
# Silence storage and sync logging.
|
||||
@ -36,6 +37,14 @@ describe 'OptionsSync', ->
|
||||
newVal = {revision: '2'}
|
||||
oldVal = {revision: '1'}
|
||||
sync.merge('example', newVal, oldVal).should.equal(newVal)
|
||||
it 'should use oldVal when sync is disabled in newVal', ->
|
||||
newVal = {revision: '2', is: 'newVal', syncOptions: 'disabled'}
|
||||
oldVal = {revision: '1', is: 'oldVal'}
|
||||
sync.merge('example', newVal, oldVal).should.equal(oldVal)
|
||||
it 'should use oldVal when sync is disabled in oldVal', ->
|
||||
newVal = {revision: '2', is: 'newVal'}
|
||||
oldVal = {revision: '1', is: 'oldVal', syncOptions: 'disabled'}
|
||||
sync.merge('example', newVal, oldVal).should.equal(oldVal)
|
||||
it 'should favor oldVal when revisions are equal', ->
|
||||
newVal = {revision: '1', is: 'newVal'}
|
||||
oldVal = {revision: '1', is: 'oldVal'}
|
||||
@ -101,6 +110,30 @@ describe 'OptionsSync', ->
|
||||
sync.requestPush({e: 1})
|
||||
sync.requestPush({e: undefined})
|
||||
|
||||
it 'should disable syncing for the profiles if quota is exceeded', (done) ->
|
||||
options = {'+a': {is: 'a', oversized: true}, b: {is: 'b'}}
|
||||
|
||||
storage = new Storage()
|
||||
storage.set = (changes) ->
|
||||
for key, value of changes
|
||||
if value.oversized
|
||||
err = new Storage.QuotaExceededError()
|
||||
err.perItem = true
|
||||
return Promise.reject(err)
|
||||
storage.set.should.have.been.calledTwice
|
||||
storage.set.should.have.been.calledWith(options)
|
||||
storage.set.should.have.been.calledWith({b: {is: 'b'}})
|
||||
options['+a'].syncOptions.should.equal('disabled')
|
||||
options['+a'].syncError.reason.should.equal('quotaPerItem')
|
||||
done()
|
||||
Promise.resolve()
|
||||
|
||||
sinon.spy(storage, 'set')
|
||||
|
||||
sync = new OptionsSync(storage, unlimited)
|
||||
sync.debounce = 0
|
||||
sync.requestPush(options)
|
||||
|
||||
describe '#copyTo', ->
|
||||
it 'should fetch all items from remote storage', (done) ->
|
||||
remote = new Storage()
|
||||
|
Loading…
Reference in New Issue
Block a user