| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283 |
- class app.DB
- NAME = 'docs'
- constructor: ->
- @useIndexedDB = @useIndexedDB()
- @appVersion = @appVersion()
- @callbacks = []
- db: (fn) ->
- return fn() unless @useIndexedDB
- @callbacks.push(fn) if fn
- return if @open
- try
- @open = true
- req = indexedDB.open(NAME, @schemaVersion())
- req.onsuccess = @onOpenSuccess
- req.onerror = @onOpenError
- req.onupgradeneeded = @onUpgradeNeeded
- catch
- @onOpenError()
- return
- onOpenSuccess: (event) =>
- try
- db = event.target.result
- unless @checkedBuggyIDB
- @idbTransaction(db, stores: ['docs', app.docs.all()[0].slug], mode: 'readwrite').abort() # https://bugs.webkit.org/show_bug.cgi?id=136937
- @checkedBuggyIDB = true
- catch
- try db.close()
- @reason = 'apple'
- @onOpenError()
- return
- @runCallbacks(db)
- @open = false
- db.close()
- return
- onOpenError: (event) =>
- event?.preventDefault()
- @open = false
- if event?.target?.error?.name is 'QuotaExceededError'
- @reset()
- @db()
- app.onQuotaExceeded()
- else
- @useIndexedDB = false
- @reason or= 'cant_open'
- @runCallbacks()
- return
- runCallbacks: (db) ->
- fn(db) while fn = @callbacks.shift()
- return
- onUpgradeNeeded: (event) ->
- return unless db = event.target.result
- objectStoreNames = $.makeArray(db.objectStoreNames)
- unless $.arrayDelete(objectStoreNames, 'docs')
- db.createObjectStore('docs')
- for doc in app.docs.all() when not $.arrayDelete(objectStoreNames, doc.slug)
- try db.createObjectStore(doc.slug)
- for name in objectStoreNames
- try db.deleteObjectStore(name)
- return
- store: (doc, data, onSuccess, onError) ->
- @db (db) =>
- unless db
- onError()
- return
- txn = @idbTransaction db, stores: ['docs', doc.slug], mode: 'readwrite', ignoreError: false
- txn.oncomplete = =>
- @cachedDocs?[doc.slug] = doc.mtime
- onSuccess()
- return
- txn.onerror = (event) ->
- event.preventDefault()
- onError(event)
- return
- store = txn.objectStore(doc.slug)
- store.clear()
- store.add(content, path) for path, content of data
- store = txn.objectStore('docs')
- store.put(doc.mtime, doc.slug)
- return
- return
- unstore: (doc, onSuccess, onError) ->
- @db (db) =>
- unless db
- onError()
- return
- txn = @idbTransaction db, stores: ['docs', doc.slug], mode: 'readwrite', ignoreError: false
- txn.oncomplete = =>
- delete @cachedDocs?[doc.slug]
- onSuccess()
- return
- txn.onerror = (event) ->
- event.preventDefault()
- onError(event)
- return
- store = txn.objectStore(doc.slug)
- store.clear()
- store = txn.objectStore('docs')
- store.delete(doc.slug)
- return
- return
- version: (doc, fn) ->
- if (version = @cachedVersion(doc))?
- fn(version)
- return
- @db (db) =>
- unless db
- fn(false)
- return
- txn = @idbTransaction db, stores: ['docs'], mode: 'readonly'
- store = txn.objectStore('docs')
- req = store.get(doc.slug)
- req.onsuccess = ->
- fn(req.result)
- return
- req.onerror = (event) ->
- event.preventDefault()
- fn(false)
- return
- return
- return
- cachedVersion: (doc) ->
- return unless @cachedDocs
- @cachedDocs[doc.slug] or false
- versions: (docs, fn) ->
- if versions = @cachedVersions(docs)
- fn(versions)
- return
- @db (db) =>
- unless db
- fn(false)
- return
- txn = @idbTransaction db, stores: ['docs'], mode: 'readonly'
- txn.oncomplete = ->
- fn(result)
- return
- store = txn.objectStore('docs')
- result = {}
- docs.forEach (doc) ->
- req = store.get(doc.slug)
- req.onsuccess = ->
- result[doc.slug] = req.result
- return
- req.onerror = (event) ->
- event.preventDefault()
- result[doc.slug] = false
- return
- return
- return
- cachedVersions: (docs) ->
- return unless @cachedDocs
- result = {}
- result[doc.slug] = @cachedVersion(doc) for doc in docs
- result
- load: (entry, onSuccess, onError) ->
- if @shouldLoadWithIDB(entry)
- onError = @loadWithXHR.bind(@, entry, onSuccess, onError)
- @loadWithIDB entry, onSuccess, onError
- else
- @loadWithXHR entry, onSuccess, onError
- loadWithXHR: (entry, onSuccess, onError) ->
- ajax
- url: entry.fileUrl()
- dataType: 'html'
- success: onSuccess
- error: onError
- loadWithIDB: (entry, onSuccess, onError) ->
- @db (db) =>
- unless db
- onError()
- return
- unless db.objectStoreNames.contains(entry.doc.slug)
- onError()
- @loadDocsCache(db)
- return
- txn = @idbTransaction db, stores: [entry.doc.slug], mode: 'readonly'
- store = txn.objectStore(entry.doc.slug)
- req = store.get(entry.dbPath())
- req.onsuccess = ->
- if req.result then onSuccess(req.result) else onError()
- return
- req.onerror = (event) ->
- event.preventDefault()
- onError()
- return
- @loadDocsCache(db)
- return
- loadDocsCache: (db) ->
- return if @cachedDocs
- @cachedDocs = {}
- txn = @idbTransaction db, stores: ['docs'], mode: 'readonly'
- store = txn.objectStore('docs')
- req = store.openCursor()
- req.onsuccess = (event) =>
- return unless cursor = event.target.result
- @cachedDocs[cursor.key] = cursor.value
- cursor.continue()
- return
- req.onerror = (event) ->
- event.preventDefault()
- return
- return
- shouldLoadWithIDB: (entry) ->
- @useIndexedDB and (not @cachedDocs or @cachedDocs[entry.doc.slug])
- idbTransaction: (db, options) ->
- txn = db.transaction(options.stores, options.mode)
- unless options.ignoreError is false
- txn.onerror = (event) ->
- event.preventDefault()
- return
- unless options.ignoreAbort is false
- txn.onabort = (event) ->
- event.preventDefault()
- return
- txn
- reset: ->
- try indexedDB?.deleteDatabase(NAME) catch
- return
- useIndexedDB: ->
- try
- if !app.isSingleDoc() and window.indexedDB
- true
- else
- @reason = 'not_supported'
- false
- catch
- false
- migrate: ->
- app.settings.set('schema', @userVersion() + 1)
- return
- schemaVersion: ->
- @appVersion * 10 + @userVersion()
- userVersion: ->
- app.settings.get('schema')
- appVersion: ->
- if app.config.env is 'production' then app.config.version else Math.floor(Date.now() / 1000)
|