Refactor Options constructor into loadOptions and init methods.

This commit is contained in:
FelisCatus 2015-01-22 22:39:55 +08:00
parent a145d4f6a5
commit 29bebd458c
3 changed files with 153 additions and 46 deletions

View File

@ -136,5 +136,7 @@ angular.module('omegaTarget', []).factory 'omegaTarget', ($q) ->
return d.promise
openManage: ->
chrome.tabs.create url: 'chrome://extensions/?id=' + chrome.runtime.id
setOptionsSync: (enabled, args) ->
callBackground('setOptionsSync', enabled, args)
return omegaTarget

View File

@ -237,13 +237,13 @@ class ChromeOptions extends OmegaTarget.Options
else
Promise.reject()
getOldOptions = getOldOptions.catch =>
getOldOptions = getOldOptions.catch ->
if options?['config']
Promise.resolve options
else if localStorage['config']
Promise.resolve localStorage
else
Promise.reject new @NoOptionsError()
Promise.reject new OmegaTarget.Options.NoOptionsError()
getOldOptions.then (oldOptions) =>
i18n = {

View File

@ -55,48 +55,99 @@ class Options
value = profile
return value
constructor: (@_options, @_storage, @_state, @log, @sync) ->
constructor: (options, @_storage, @_state, @log, @sync) ->
@_storage ?= Storage()
@_state ?= Storage()
@log ?= Log
if @_options?
@ready = Promise.resolve(@_options)
if not options?
@init()
else
@ready = if @sync?.enabled then Promise.resolve() else @_storage.get(null)
@ready = @ready.then (options) =>
return options if not @sync?
if options?['schemaVersion']
@_state.get({'syncOptions': ''}).then ({syncOptions}) =>
return if syncOptions
@_state.set({'syncOptions': 'conflict'})
@sync.storage.get('schemaVersion').then({schemaVersion}) =>
@_state.set({'syncOptions': 'pristine'}) if not schemaVersion
return options
@ready = @_storage.remove().then(=>
@_storage.set(options)
).then =>
@init()
###*
# Attempt to load options from local and remote storage.
# @param {?{}} args Extra arguments
# @param {number=3} args.retry Number of retries before giving up.
# @returns {Promise<OmegaOptions>} The loaded options
###
loadOptions: ({retry} = {}) ->
retry ?= 3
@_syncWatchStop?()
@_syncWatchStop = null
@_watchStop?()
@_watchStop = null
loadRaw = if options? then Promise.resolve(options) else
if not @sync?.enabled
if not @sync?
@_state.set({'syncOptions': 'unsupported'})
@_storage.get(null)
else
@_state.set({'syncOptions': 'sync'})
@sync.watchAndPull(@_storage)
@_syncWatchStop = @sync.watchAndPull(@_storage)
@sync.copyTo(@_storage).then =>
@_storage.get(null)
@ready = @ready.then((options) =>
@upgrade(options).then(([options, changes]) =>
modified = {}
removed = []
for own key, value of changes
if typeof value == 'undefined'
removed.push(value)
else
modified[key] = value
@_storage.set(modified).then(=>
@_storage.remove(removed)
).return(options)
).catch (ex) =>
if not ex instanceof NoOptionsError
@log.error(ex.stack)
@reset().tap =>
@_state.set({'firstRun': 'new', 'web.switchGuide': 'showOnFirstUse'})
).then((options) =>
@optionsLoaded = loadRaw.then((options) =>
@upgrade(options)
).then(([options, changes]) =>
@_storage.apply(changes: changes).return(options)
).tap((options) =>
@_options = options
@_watch()
).then(=>
@_watchStop = @_watch()
# Try to set syncOptions to some value if not initialized.
@_state.get({'syncOptions': ''}).then ({syncOptions}) =>
return if syncOptions
@_state.set({'syncOptions': 'conflict'})
@sync.storage.get('schemaVersion').then ({schemaVersion}) =>
@_state.set({'syncOptions': 'pristine'}) if not schemaVersion
).catch (e) =>
return Promise.reject(e) unless retry > 0
getFallbackOptions = Promise.resolve().then =>
if e instanceof NoOptionsError
@_state.get({
'firstRun': 'new'
'web.switchGuide': 'showOnFirstUse'
}).then (items) => @_state.set(items)
return null unless @sync?
# Try to fetch options from sync storage.
return @sync.storage.get(null).then (options) =>
if not options['schemaVersion']
@_state.set({'syncOptions': 'pristine'})
return null
else
@_state.set({'syncOptions': 'sync'})
@sync.enabled = true
@log.log('Options#loadOptions::fromSync', options)
options
else
@log.error(e.stack)
# Some serious error happened when loading options. Disable syncing
# and use fallback options.
@_state.remove(['syncOptions'])
return null
getFallbackOptions.then (options) =>
options ?= @parseOptions(@getDefaultOptions())
if @sync?
prevEnabled = @sync.enabled
@sync.enabled = false
@_storage.remove().then(=>
@_storage.set(options)
).then =>
@sync.enabled = prevEnabled if @sync?
@loadOptions({retry: retry - 1})
###*
# Attempt to initialize (or reinitialize) options.
# @returns {Promise<OmegaOptions>} A promise that is fulfilled on ready.
###
init: ->
@ready = @loadOptions().then(=>
if @_options['-startupProfileName']
@applyProfile(@_options['-startupProfileName'])
else
@ -125,6 +176,8 @@ class Options
if @_options['-downloadInterval'] > 0
@updateProfile()
return @ready
toString: -> "<Options>"
###*
@ -173,14 +226,11 @@ class Options
Promise.reject new Error("Invalid schemaVerion #{version}!")
###*
# Reset the options to the given options or initial options.
# @param {?OmegaOptions} options The options to set. Defaults to initial.
# @returns {Promise<OmegaOptions>} The options just applied
# Parse options in various formats (including JSON & base64).
# @param {OmegaOptions|string} options The options to parse
# @returns {Promise<OmegaOptions>} The parsed options.
###
reset: (options) ->
@log.method('Options#reset', this, arguments)
if not options
options = @getDefaultOptions()
parseOptions: (options) ->
if typeof options == 'string'
if options[0] != '{'
try
@ -190,11 +240,26 @@ class Options
options = null
options = try JSON.parse(options)
if not options
return Promise.reject new Error('Invalid options!')
@upgrade(options).then ([opt]) =>
return throw new Error('Invalid options!')
return options
###*
# Reset the options to the given options or initial options.
# @param {?OmegaOptions} options The options to set. Defaults to initial.
# @returns {Promise<OmegaOptions>} The options just applied
###
reset: (options) ->
@log.method('Options#reset', this, arguments)
options ?= getDefaultOptions()
@upgrade(@parseOptions(options)).then ([opt]) =>
# Disable syncing when resetting to avoid affecting sync storage.
@sync.enabled = false if @sync?
@_state.remove(['syncOptions'])
@_storage.remove().then(=>
@_storage.set(opt)
).then -> opt
).then =>
@init()
###*
# Called on the first initialization of options.
@ -856,4 +921,44 @@ class Options
@currentProfileChanged('external')
return
###*
# Switch options syncing on and off.
# @param {boolean} enabled Whether to enable syncing
# @param {?{}} args Extra arguments
# @param {boolean=false} args.force If true, overwrite options when conflict
# @returns {Promise} A promise which is fulfilled when the syncing is switched
###
setOptionsSync: (enabled, args) ->
@log.method('Options#setOptionsSync', this, arguments)
if not @sync?
return Promise.reject(new Error('Options syncing is unsupported.'))
@_state.get({'syncOptions': ''}).then ({syncOptions}) =>
if not enabled
if syncOptions == 'sync'
@_state.set({'syncOptions': 'conflict'})
@sync.enabled = false
@_syncWatchStop?()
@_syncWatchStop = null
return
if syncOptions == 'conflict'
if not args?.force
return Promise.reject(new Error(
'Syncing not enabled due to conflict. Retry with force to overwrite
local options and enable syncing.'))
return if syncOptions == 'sync'
@_state.set({'syncOptions': 'sync'}).then =>
if syncOptions == 'conflict'
# Try to re-init options from sync.
@sync.enabled = false
@_storage.remove().then =>
@sync.enabled = true
@init()
else
@sync.enabled = true
@_syncWatchStop?()
@sync.requestPush(@_options)
@_syncWatchStop = @sync.watchAndPull(@_storage)
return
module.exports = Options