Unified · Split
| @@ -1,587 +1,559 @@ | |||
| 1 | // TODO: This file was created by bulk-decaffeinate. | 1 | app.DB = class DB { |
| 2 | // Sanity-check the conversion and remove this comment. | 2 | static NAME = "docs"; |
| 3 | /* | 3 | static VERSION = 15; |
| 4 | * decaffeinate suggestions: | 4 | |
| 5 | * DS101: Remove unnecessary use of Array.from | 5 | constructor() { |
| 6 | * DS102: Remove unnecessary code created because of implicit returns | 6 | this.versionMultipler = $.isIE() ? 1e5 : 1e9; |
| 7 | * DS205: Consider reworking code to avoid use of IIFEs | 7 | this.useIndexedDB = this.useIndexedDB(); |
| 8 | * DS206: Consider reworking classes to avoid initClass | 8 | this.callbacks = []; |
| 9 | * DS207: Consider shorter variations of null checks | 9 | } |
| 10 | * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md | 10 | |
| 11 | */ | 11 | db(fn) { |
| 12 | (function () { | 12 | if (!this.useIndexedDB) { |
| 13 | let NAME = undefined; | 13 | return fn(); |
| 14 | let VERSION = undefined; | 14 | } |
| 15 | app.DB = class DB { | 15 | if (fn) { |
| 16 | static initClass() { | 16 | this.callbacks.push(fn); |
| 17 | NAME = "docs"; | 17 | } |
| 18 | VERSION = 15; | 18 | if (this.open) { |
| 19 | return; | ||
| 19 | } | 20 | } |
| 20 | 21 | ||
| 21 | constructor() { | 22 | try { |
| 22 | this.versionMultipler = $.isIE() ? 1e5 : 1e9; | 23 | this.open = true; |
| 23 | this.useIndexedDB = this.useIndexedDB(); | 24 | const req = indexedDB.open( |
| 24 | this.callbacks = []; | 25 | DB.NAME, |
| 26 | DB.VERSION * this.versionMultipler + this.userVersion(), | ||
| 27 | ); | ||
| 28 | req.onsuccess = (event) => this.onOpenSuccess(event); | ||
| 29 | req.onerror = (event) => this.onOpenError(event); | ||
| 30 | req.onupgradeneeded = (event) => this.onUpgradeNeeded(event); | ||
| 31 | } catch (error) { | ||
| 32 | this.fail("exception", error); | ||
| 25 | } | 33 | } |
| 34 | } | ||
| 26 | 35 | ||
| 27 | db(fn) { | 36 | onOpenSuccess(event) { |
| 28 | if (!this.useIndexedDB) { | 37 | let error; |
| 29 | return fn(); | 38 | const db = event.target.result; |
| 30 | } | ||
| 31 | if (fn) { | ||
| 32 | this.callbacks.push(fn); | ||
| 33 | } | ||
| 34 | if (this.open) { | ||
| 35 | return; | ||
| 36 | } | ||
| 37 | 39 | ||
| 40 | if (db.objectStoreNames.length === 0) { | ||
| 38 | try { | 41 | try { |
| 39 | this.open = true; | 42 | db.close(); |
| 40 | const req = indexedDB.open( | 43 | } catch (error1) {} |
| 41 | NAME, | 44 | this.open = false; |
| 42 | VERSION * this.versionMultipler + this.userVersion(), | 45 | this.fail("empty"); |
| 43 | ); | 46 | } else if ((error = this.buggyIDB(db))) { |
| 44 | req.onsuccess = (event) => this.onOpenSuccess(event); | 47 | try { |
| 45 | req.onerror = (event) => this.onOpenError(event); | 48 | db.close(); |
| 46 | req.onupgradeneeded = (event) => this.onUpgradeNeeded(event); | 49 | } catch (error2) {} |
| 47 | } catch (error) { | 50 | this.open = false; |
| 48 | this.fail("exception", error); | 51 | this.fail("buggy", error); |
| 49 | } | 52 | } else { |
| 53 | this.runCallbacks(db); | ||
| 54 | this.open = false; | ||
| 55 | db.close(); | ||
| 50 | } | 56 | } |
| 57 | } | ||
| 58 | |||
| 59 | onOpenError(event) { | ||
| 60 | event.preventDefault(); | ||
| 61 | this.open = false; | ||
| 62 | const { error } = event.target; | ||
| 63 | |||
| 64 | switch (error.name) { | ||
| 65 | case "QuotaExceededError": | ||
| 66 | this.onQuotaExceededError(); | ||
| 67 | break; | ||
| 68 | case "VersionError": | ||
| 69 | this.onVersionError(); | ||
| 70 | break; | ||
| 71 | case "InvalidStateError": | ||
| 72 | this.fail("private_mode"); | ||
| 73 | break; | ||
| 74 | default: | ||
| 75 | this.fail("cant_open", error); | ||
| 76 | } | ||
| 77 | } | ||
| 51 | 78 | ||
| 52 | onOpenSuccess(event) { | 79 | fail(reason, error) { |
| 53 | let error; | 80 | this.cachedDocs = null; |
| 54 | const db = event.target.result; | 81 | this.useIndexedDB = false; |
| 55 | 82 | if (!this.reason) { | |
| 56 | if (db.objectStoreNames.length === 0) { | 83 | this.reason = reason; |
| 57 | try { | 84 | } |
| 58 | db.close(); | 85 | if (!this.error) { |
| 59 | } catch (error1) {} | 86 | this.error = error; |
| 60 | this.open = false; | 87 | } |
| 61 | this.fail("empty"); | 88 | if (error) { |
| 62 | } else if ((error = this.buggyIDB(db))) { | 89 | if (typeof console.error === "function") { |
| 63 | try { | 90 | console.error("IDB error", error); |
| 64 | db.close(); | ||
| 65 | } catch (error2) {} | ||
| 66 | this.open = false; | ||
| 67 | this.fail("buggy", error); | ||
| 68 | } else { | ||
| 69 | this.runCallbacks(db); | ||
| 70 | this.open = false; | ||
| 71 | db.close(); | ||
| 72 | } | 91 | } |
| 73 | } | 92 | } |
| 74 | 93 | this.runCallbacks(); | |
| 75 | onOpenError(event) { | 94 | if (error && reason === "cant_open") { |
| 95 | Raven.captureMessage(`${error.name}: ${error.message}`, { | ||
| 96 | level: "warning", | ||
| 97 | fingerprint: [error.name], | ||
| 98 | }); | ||
| 99 | } | ||
| 100 | } | ||
| 101 | |||
| 102 | onQuotaExceededError() { | ||
| 103 | this.reset(); | ||
| 104 | this.db(); | ||
| 105 | app.onQuotaExceeded(); | ||
| 106 | Raven.captureMessage("QuotaExceededError", { level: "warning" }); | ||
| 107 | } | ||
| 108 | |||
| 109 | onVersionError() { | ||
| 110 | const req = indexedDB.open(DB.NAME); | ||
| 111 | req.onsuccess = (event) => { | ||
| 112 | return this.handleVersionMismatch(event.target.result.version); | ||
| 113 | }; | ||
| 114 | req.onerror = function (event) { | ||
| 76 | event.preventDefault(); | 115 | event.preventDefault(); |
| 77 | this.open = false; | 116 | return this.fail("cant_open", error); |
| 78 | const { error } = event.target; | 117 | }; |
| 79 | 118 | } | |
| 80 | switch (error.name) { | 119 | |
| 81 | case "QuotaExceededError": | 120 | handleVersionMismatch(actualVersion) { |
| 82 | this.onQuotaExceededError(); | 121 | if (Math.floor(actualVersion / this.versionMultipler) !== DB.VERSION) { |
| 83 | break; | 122 | this.fail("version"); |
| 84 | case "VersionError": | 123 | } else { |
| 85 | this.onVersionError(); | 124 | this.setUserVersion(actualVersion - DB.VERSION * this.versionMultipler); |
| 86 | break; | 125 | this.db(); |
| 87 | case "InvalidStateError": | ||
| 88 | this.fail("private_mode"); | ||
| 89 | break; | ||
| 90 | default: | ||
| 91 | this.fail("cant_open", error); | ||
| 92 | } | ||
| 93 | } | 126 | } |
| 127 | } | ||
| 94 | 128 | ||
| 95 | fail(reason, error) { | 129 | buggyIDB(db) { |
| 96 | this.cachedDocs = null; | 130 | if (this.checkedBuggyIDB) { |
| 97 | this.useIndexedDB = false; | 131 | return; |
| 98 | if (!this.reason) { | ||
| 99 | this.reason = reason; | ||
| 100 | } | ||
| 101 | if (!this.error) { | ||
| 102 | this.error = error; | ||
| 103 | } | ||
| 104 | if (error) { | ||
| 105 | if (typeof console.error === "function") { | ||
| 106 | console.error("IDB error", error); | ||
| 107 | } | ||
| 108 | } | ||
| 109 | this.runCallbacks(); | ||
| 110 | if (error && reason === "cant_open") { | ||
| 111 | Raven.captureMessage(`${error.name}: ${error.message}`, { | ||
| 112 | level: "warning", | ||
| 113 | fingerprint: [error.name], | ||
| 114 | }); | ||
| 115 | } | ||
| 116 | } | 132 | } |
| 117 | 133 | this.checkedBuggyIDB = true; | |
| 118 | onQuotaExceededError() { | 134 | try { |
| 119 | this.reset(); | 135 | this.idbTransaction(db, { |
| 120 | this.db(); | 136 | stores: $.makeArray(db.objectStoreNames).slice(0, 2), |
| 121 | app.onQuotaExceeded(); | 137 | mode: "readwrite", |
| 122 | Raven.captureMessage("QuotaExceededError", { level: "warning" }); | 138 | }).abort(); // https://bugs.webkit.org/show_bug.cgi?id=136937 |
| 139 | return; | ||
| 140 | } catch (error) { | ||
| 141 | return error; | ||
| 123 | } | 142 | } |
| 143 | } | ||
| 124 | 144 | ||
| 125 | onVersionError() { | 145 | runCallbacks(db) { |
| 126 | const req = indexedDB.open(NAME); | 146 | let fn; |
| 127 | req.onsuccess = (event) => { | 147 | while ((fn = this.callbacks.shift())) { |
| 128 | return this.handleVersionMismatch(event.target.result.version); | 148 | fn(db); |
| 129 | }; | ||
| 130 | req.onerror = function (event) { | ||
| 131 | event.preventDefault(); | ||
| 132 | return this.fail("cant_open", error); | ||
| 133 | }; | ||
| 134 | } | 149 | } |
| 150 | } | ||
| 135 | 151 | ||
| 136 | handleVersionMismatch(actualVersion) { | 152 | onUpgradeNeeded(event) { |
| 137 | if (Math.floor(actualVersion / this.versionMultipler) !== VERSION) { | 153 | let db; |
| 138 | this.fail("version"); | 154 | if (!(db = event.target.result)) { |
| 139 | } else { | 155 | return; |
| 140 | this.setUserVersion(actualVersion - VERSION * this.versionMultipler); | ||
| 141 | this.db(); | ||
| 142 | } | ||
| 143 | } | 156 | } |
| 144 | 157 | ||
| 145 | buggyIDB(db) { | 158 | const objectStoreNames = $.makeArray(db.objectStoreNames); |
| 146 | if (this.checkedBuggyIDB) { | 159 | |
| 147 | return; | 160 | if (!$.arrayDelete(objectStoreNames, "docs")) { |
| 148 | } | ||
| 149 | this.checkedBuggyIDB = true; | ||
| 150 | try { | 161 | try { |
| 151 | this.idbTransaction(db, { | 162 | db.createObjectStore("docs"); |
| 152 | stores: $.makeArray(db.objectStoreNames).slice(0, 2), | 163 | } catch (error) {} |
| 153 | mode: "readwrite", | ||
| 154 | }).abort(); // https://bugs.webkit.org/show_bug.cgi?id=136937 | ||
| 155 | return; | ||
| 156 | } catch (error) { | ||
| 157 | return error; | ||
| 158 | } | ||
| 159 | } | 164 | } |
| 160 | 165 | ||
| 161 | runCallbacks(db) { | 166 | for (var doc of app.docs.all()) { |
| 162 | let fn; | 167 | if (!$.arrayDelete(objectStoreNames, doc.slug)) { |
| 163 | while ((fn = this.callbacks.shift())) { | 168 | try { |
| 164 | fn(db); | 169 | db.createObjectStore(doc.slug); |
| 170 | } catch (error1) {} | ||
| 165 | } | 171 | } |
| 166 | } | 172 | } |
| 167 | 173 | ||
| 168 | onUpgradeNeeded(event) { | 174 | for (var name of objectStoreNames) { |
| 169 | let db; | 175 | try { |
| 170 | if (!(db = event.target.result)) { | 176 | db.deleteObjectStore(name); |
| 177 | } catch (error2) {} | ||
| 178 | } | ||
| 179 | } | ||
| 180 | |||
| 181 | store(doc, data, onSuccess, onError, _retry) { | ||
| 182 | if (_retry == null) { | ||
| 183 | _retry = true; | ||
| 184 | } | ||
| 185 | this.db((db) => { | ||
| 186 | if (!db) { | ||
| 187 | onError(); | ||
| 171 | return; | 188 | return; |
| 172 | } | 189 | } |
| 173 | 190 | ||
| 174 | const objectStoreNames = $.makeArray(db.objectStoreNames); | 191 | const txn = this.idbTransaction(db, { |
| 192 | stores: ["docs", doc.slug], | ||
| 193 | mode: "readwrite", | ||
| 194 | ignoreError: false, | ||
| 195 | }); | ||
| 196 | txn.oncomplete = () => { | ||
| 197 | if (this.cachedDocs != null) { | ||
| 198 | this.cachedDocs[doc.slug] = doc.mtime; | ||
| 199 | } | ||
| 200 | onSuccess(); | ||
| 201 | }; | ||
| 202 | txn.onerror = (event) => { | ||
| 203 | event.preventDefault(); | ||
| 204 | if (txn.error?.name === "NotFoundError" && _retry) { | ||
| 205 | this.migrate(); | ||
| 206 | setTimeout(() => { | ||
| 207 | return this.store(doc, data, onSuccess, onError, false); | ||
| 208 | }, 0); | ||
| 209 | } else { | ||
| 210 | onError(event); | ||
| 211 | } | ||
| 212 | }; | ||
| 175 | 213 | ||
| 176 | if (!$.arrayDelete(objectStoreNames, "docs")) { | 214 | let store = txn.objectStore(doc.slug); |
| 177 | try { | 215 | store.clear(); |
| 178 | db.createObjectStore("docs"); | 216 | for (var path in data) { |
| 179 | } catch (error) {} | 217 | var content = data[path]; |
| 218 | store.add(content, path); | ||
| 180 | } | 219 | } |
| 181 | 220 | ||
| 182 | for (var doc of Array.from(app.docs.all())) { | 221 | store = txn.objectStore("docs"); |
| 183 | if (!$.arrayDelete(objectStoreNames, doc.slug)) { | 222 | store.put(doc.mtime, doc.slug); |
| 184 | try { | 223 | }); |
| 185 | db.createObjectStore(doc.slug); | 224 | } |
| 186 | } catch (error1) {} | ||
| 187 | } | ||
| 188 | } | ||
| 189 | 225 | ||
| 190 | for (var name of Array.from(objectStoreNames)) { | 226 | unstore(doc, onSuccess, onError, _retry) { |
| 191 | try { | 227 | if (_retry == null) { |
| 192 | db.deleteObjectStore(name); | 228 | _retry = true; |
| 193 | } catch (error2) {} | ||
| 194 | } | ||
| 195 | } | 229 | } |
| 196 | 230 | this.db((db) => { | |
| 197 | store(doc, data, onSuccess, onError, _retry) { | 231 | if (!db) { |
| 198 | if (_retry == null) { | 232 | onError(); |
| 199 | _retry = true; | 233 | return; |
| 200 | } | 234 | } |
| 201 | this.db((db) => { | 235 | |
| 202 | if (!db) { | 236 | const txn = this.idbTransaction(db, { |
| 203 | onError(); | 237 | stores: ["docs", doc.slug], |
| 204 | return; | 238 | mode: "readwrite", |
| 239 | ignoreError: false, | ||
| 240 | }); | ||
| 241 | txn.oncomplete = () => { | ||
| 242 | if (this.cachedDocs != null) { | ||
| 243 | delete this.cachedDocs[doc.slug]; | ||
| 244 | } | ||
| 245 | onSuccess(); | ||
| 246 | }; | ||
| 247 | txn.onerror = function (event) { | ||
| 248 | event.preventDefault(); | ||
| 249 | if (txn.error?.name === "NotFoundError" && _retry) { | ||
| 250 | this.migrate(); | ||
| 251 | setTimeout(() => { | ||
| 252 | return this.unstore(doc, onSuccess, onError, false); | ||
| 253 | }, 0); | ||
| 254 | } else { | ||
| 255 | onError(event); | ||
| 205 | } | 256 | } |
| 257 | }; | ||
| 206 | 258 | ||
| 207 | const txn = this.idbTransaction(db, { | 259 | let store = txn.objectStore("docs"); |
| 208 | stores: ["docs", doc.slug], | 260 | store.delete(doc.slug); |
| 209 | mode: "readwrite", | ||
| 210 | ignoreError: false, | ||
| 211 | }); | ||
| 212 | txn.oncomplete = () => { | ||
| 213 | if (this.cachedDocs != null) { | ||
| 214 | this.cachedDocs[doc.slug] = doc.mtime; | ||
| 215 | } | ||
| 216 | onSuccess(); | ||
| 217 | }; | ||
| 218 | txn.onerror = (event) => { | ||
| 219 | event.preventDefault(); | ||
| 220 | if ( | ||
| 221 | (txn.error != null ? txn.error.name : undefined) === | ||
| 222 | "NotFoundError" && | ||
| 223 | _retry | ||
| 224 | ) { | ||
| 225 | this.migrate(); | ||
| 226 | setTimeout(() => { | ||
| 227 | return this.store(doc, data, onSuccess, onError, false); | ||
| 228 | }, 0); | ||
| 229 | } else { | ||
| 230 | onError(event); | ||
| 231 | } | ||
| 232 | }; | ||
| 233 | 261 | ||
| 234 | let store = txn.objectStore(doc.slug); | 262 | store = txn.objectStore(doc.slug); |
| 235 | store.clear(); | 263 | store.clear(); |
| 236 | for (var path in data) { | 264 | }); |
| 237 | var content = data[path]; | 265 | } |
| 238 | store.add(content, path); | ||
| 239 | } | ||
| 240 | 266 | ||
| 241 | store = txn.objectStore("docs"); | 267 | version(doc, fn) { |
| 242 | store.put(doc.mtime, doc.slug); | 268 | let version = this.cachedVersion(doc); |
| 243 | }); | 269 | if (version != null) { |
| 270 | fn(version); | ||
| 271 | return; | ||
| 244 | } | 272 | } |
| 245 | 273 | ||
| 246 | unstore(doc, onSuccess, onError, _retry) { | 274 | this.db((db) => { |
| 247 | if (_retry == null) { | 275 | if (!db) { |
| 248 | _retry = true; | 276 | fn(false); |
| 277 | return; | ||
| 249 | } | 278 | } |
| 250 | this.db((db) => { | ||
| 251 | if (!db) { | ||
| 252 | onError(); | ||
| 253 | return; | ||
| 254 | } | ||
| 255 | 279 | ||
| 256 | const txn = this.idbTransaction(db, { | 280 | const txn = this.idbTransaction(db, { |
| 257 | stores: ["docs", doc.slug], | 281 | stores: ["docs"], |
| 258 | mode: "readwrite", | 282 | mode: "readonly", |
| 259 | ignoreError: false, | 283 | }); |
| 260 | }); | 284 | const store = txn.objectStore("docs"); |
| 261 | txn.oncomplete = () => { | ||
| 262 | if (this.cachedDocs != null) { | ||
| 263 | delete this.cachedDocs[doc.slug]; | ||
| 264 | } | ||
| 265 | onSuccess(); | ||
| 266 | }; | ||
| 267 | txn.onerror = function (event) { | ||
| 268 | event.preventDefault(); | ||
| 269 | if ( | ||
| 270 | (txn.error != null ? txn.error.name : undefined) === | ||
| 271 | "NotFoundError" && | ||
| 272 | _retry | ||
| 273 | ) { | ||
| 274 | this.migrate(); | ||
| 275 | setTimeout(() => { | ||
| 276 | return this.unstore(doc, onSuccess, onError, false); | ||
| 277 | }, 0); | ||
| 278 | } else { | ||
| 279 | onError(event); | ||
| 280 | } | ||
| 281 | }; | ||
| 282 | 285 | ||
| 283 | let store = txn.objectStore("docs"); | 286 | const req = store.get(doc.slug); |
| 284 | store.delete(doc.slug); | 287 | req.onsuccess = function () { |
| 288 | fn(req.result); | ||
| 289 | }; | ||
| 290 | req.onerror = function (event) { | ||
| 291 | event.preventDefault(); | ||
| 292 | fn(false); | ||
| 293 | }; | ||
| 294 | }); | ||
| 295 | } | ||
| 285 | 296 | ||
| 286 | store = txn.objectStore(doc.slug); | 297 | cachedVersion(doc) { |
| 287 | store.clear(); | 298 | if (!this.cachedDocs) { |
| 288 | }); | 299 | return; |
| 300 | } | ||
| 301 | return this.cachedDocs[doc.slug] || false; | ||
| 302 | } | ||
| 303 | |||
| 304 | versions(docs, fn) { | ||
| 305 | let versions = this.cachedVersions(docs); | ||
| 306 | if (versions) { | ||
| 307 | fn(versions); | ||
| 308 | return; | ||
| 289 | } | 309 | } |
| 290 | 310 | ||
| 291 | version(doc, fn) { | 311 | return this.db((db) => { |
| 292 | let version; | 312 | if (!db) { |
| 293 | if ((version = this.cachedVersion(doc)) != null) { | 313 | fn(false); |
| 294 | fn(version); | ||
| 295 | return; | 314 | return; |
| 296 | } | 315 | } |
| 297 | 316 | ||
| 298 | this.db((db) => { | 317 | const txn = this.idbTransaction(db, { |
| 299 | if (!db) { | 318 | stores: ["docs"], |
| 300 | fn(false); | 319 | mode: "readonly", |
| 301 | return; | 320 | }); |
| 302 | } | 321 | txn.oncomplete = function () { |
| 303 | 322 | fn(result); | |
| 304 | const txn = this.idbTransaction(db, { | 323 | }; |
| 305 | stores: ["docs"], | 324 | const store = txn.objectStore("docs"); |
| 306 | mode: "readonly", | 325 | var result = {}; |
| 307 | }); | ||
| 308 | const store = txn.objectStore("docs"); | ||
| 309 | 326 | ||
| 327 | docs.forEach(function (doc) { | ||
| 310 | const req = store.get(doc.slug); | 328 | const req = store.get(doc.slug); |
| 311 | req.onsuccess = function () { | 329 | req.onsuccess = function () { |
| 312 | fn(req.result); | 330 | result[doc.slug] = req.result; |
| 313 | }; | 331 | }; |
| 314 | req.onerror = function (event) { | 332 | req.onerror = function (event) { |
| 315 | event.preventDefault(); | 333 | event.preventDefault(); |
| 316 | fn(false); | 334 | result[doc.slug] = false; |
| 317 | }; | 335 | }; |
| 318 | }); | 336 | }); |
| 319 | } | 337 | }); |
| 338 | } | ||
| 320 | 339 | ||
| 321 | cachedVersion(doc) { | 340 | cachedVersions(docs) { |
| 322 | if (!this.cachedDocs) { | 341 | if (!this.cachedDocs) { |
| 323 | return; | 342 | return; |
| 324 | } | ||
| 325 | return this.cachedDocs[doc.slug] || false; | ||
| 326 | } | 343 | } |
| 327 | 344 | const result = {}; | |
| 328 | versions(docs, fn) { | 345 | for (var doc of docs) { |
| 329 | let versions; | 346 | result[doc.slug] = this.cachedVersion(doc); |
| 330 | if ((versions = this.cachedVersions(docs))) { | ||
| 331 | fn(versions); | ||
| 332 | return; | ||
| 333 | } | ||
| 334 | |||
| 335 | return this.db((db) => { | ||
| 336 | if (!db) { | ||
| 337 | fn(false); | ||
| 338 | return; | ||
| 339 | } | ||
| 340 | |||
| 341 | const txn = this.idbTransaction(db, { | ||
| 342 | stores: ["docs"], | ||
| 343 | mode: "readonly", | ||
| 344 | }); | ||
| 345 | txn.oncomplete = function () { | ||
| 346 | fn(result); | ||
| 347 | }; | ||
| 348 | const store = txn.objectStore("docs"); | ||
| 349 | var result = {}; | ||
| 350 | |||
| 351 | docs.forEach(function (doc) { | ||
| 352 | const req = store.get(doc.slug); | ||
| 353 | req.onsuccess = function () { | ||
| 354 | result[doc.slug] = req.result; | ||
| 355 | }; | ||
| 356 | req.onerror = function (event) { | ||
| 357 | event.preventDefault(); | ||
| 358 | result[doc.slug] = false; | ||
| 359 | }; | ||
| 360 | }); | ||
| 361 | }); | ||
| 362 | } | 347 | } |
| 348 | return result; | ||
| 349 | } | ||
| 363 | 350 | ||
| 364 | cachedVersions(docs) { | 351 | load(entry, onSuccess, onError) { |
| 365 | if (!this.cachedDocs) { | 352 | if (this.shouldLoadWithIDB(entry)) { |
| 366 | return; | 353 | return this.loadWithIDB(entry, onSuccess, () => |
| 367 | } | 354 | this.loadWithXHR(entry, onSuccess, onError), |
| 368 | const result = {}; | 355 | ); |
| 369 | for (var doc of Array.from(docs)) { | 356 | } else { |
| 370 | result[doc.slug] = this.cachedVersion(doc); | 357 | return this.loadWithXHR(entry, onSuccess, onError); |
| 371 | } | ||
| 372 | return result; | ||
| 373 | } | 358 | } |
| 374 | 359 | } | |
| 375 | load(entry, onSuccess, onError) { | 360 | |
| 376 | if (this.shouldLoadWithIDB(entry)) { | 361 | loadWithXHR(entry, onSuccess, onError) { |
| 377 | return this.loadWithIDB(entry, onSuccess, () => this.loadWithXHR(entry, onSuccess, onError)); | 362 | return ajax({ |
| 378 | } else { | 363 | url: entry.fileUrl(), |
| 379 | return this.loadWithXHR(entry, onSuccess, onError); | 364 | dataType: "html", |
| 365 | success: onSuccess, | ||
| 366 | error: onError, | ||
| 367 | }); | ||
| 368 | } | ||
| 369 | |||
| 370 | loadWithIDB(entry, onSuccess, onError) { | ||
| 371 | return this.db((db) => { | ||
| 372 | if (!db) { | ||
| 373 | onError(); | ||
| 374 | return; | ||
| 380 | } | 375 | } |
| 381 | } | ||
| 382 | 376 | ||
| 383 | loadWithXHR(entry, onSuccess, onError) { | 377 | if (!db.objectStoreNames.contains(entry.doc.slug)) { |
| 384 | return ajax({ | 378 | onError(); |
| 385 | url: entry.fileUrl(), | ||
| 386 | dataType: "html", | ||
| 387 | success: onSuccess, | ||
| 388 | error: onError, | ||
| 389 | }); | ||
| 390 | } | ||
| 391 | |||
| 392 | loadWithIDB(entry, onSuccess, onError) { | ||
| 393 | return this.db((db) => { | ||
| 394 | if (!db) { | ||
| 395 | onError(); | ||
| 396 | return; | ||
| 397 | } | ||
| 398 | |||
| 399 | if (!db.objectStoreNames.contains(entry.doc.slug)) { | ||
| 400 | onError(); | ||
| 401 | this.loadDocsCache(db); | ||
| 402 | return; | ||
| 403 | } | ||
| 404 | |||
| 405 | const txn = this.idbTransaction(db, { | ||
| 406 | stores: [entry.doc.slug], | ||
| 407 | mode: "readonly", | ||
| 408 | }); | ||
| 409 | const store = txn.objectStore(entry.doc.slug); | ||
| 410 | |||
| 411 | const req = store.get(entry.dbPath()); | ||
| 412 | req.onsuccess = function () { | ||
| 413 | if (req.result) { | ||
| 414 | onSuccess(req.result); | ||
| 415 | } else { | ||
| 416 | onError(); | ||
| 417 | } | ||
| 418 | }; | ||
| 419 | req.onerror = function (event) { | ||
| 420 | event.preventDefault(); | ||
| 421 | onError(); | ||
| 422 | }; | ||
| 423 | this.loadDocsCache(db); | 379 | this.loadDocsCache(db); |
| 424 | }); | ||
| 425 | } | ||
| 426 | |||
| 427 | loadDocsCache(db) { | ||
| 428 | if (this.cachedDocs) { | ||
| 429 | return; | 380 | return; |
| 430 | } | 381 | } |
| 431 | this.cachedDocs = {}; | ||
| 432 | 382 | ||
| 433 | const txn = this.idbTransaction(db, { | 383 | const txn = this.idbTransaction(db, { |
| 434 | stores: ["docs"], | 384 | stores: [entry.doc.slug], |
| 435 | mode: "readonly", | 385 | mode: "readonly", |
| 436 | }); | 386 | }); |
| 437 | txn.oncomplete = () => { | 387 | const store = txn.objectStore(entry.doc.slug); |
| 438 | setTimeout(() => this.checkForCorruptedDocs(), 50); | ||
| 439 | }; | ||
| 440 | 388 | ||
| 441 | const req = txn.objectStore("docs").openCursor(); | 389 | const req = store.get(entry.dbPath()); |
| 442 | req.onsuccess = (event) => { | 390 | req.onsuccess = function () { |
| 443 | let cursor; | 391 | if (req.result) { |
| 444 | if (!(cursor = event.target.result)) { | 392 | onSuccess(req.result); |
| 445 | return; | 393 | } else { |
| 394 | onError(); | ||
| 446 | } | 395 | } |
| 447 | this.cachedDocs[cursor.key] = cursor.value; | ||
| 448 | cursor.continue(); | ||
| 449 | }; | 396 | }; |
| 450 | req.onerror = function (event) { | 397 | req.onerror = function (event) { |
| 451 | event.preventDefault(); | 398 | event.preventDefault(); |
| 399 | onError(); | ||
| 452 | }; | 400 | }; |
| 453 | } | 401 | this.loadDocsCache(db); |
| 402 | }); | ||
| 403 | } | ||
| 454 | 404 | ||
| 455 | checkForCorruptedDocs() { | 405 | loadDocsCache(db) { |
| 456 | this.db((db) => { | 406 | if (this.cachedDocs) { |
| 457 | let slug; | 407 | return; |
| 458 | this.corruptedDocs = []; | 408 | } |
| 459 | const docs = (() => { | 409 | this.cachedDocs = {}; |
| 460 | const result = []; | 410 | |
| 461 | for (var key in this.cachedDocs) { | 411 | const txn = this.idbTransaction(db, { |
| 462 | var value = this.cachedDocs[key]; | 412 | stores: ["docs"], |
| 463 | if (value) { | 413 | mode: "readonly", |
| 464 | result.push(key); | 414 | }); |
| 465 | } | 415 | txn.oncomplete = () => { |
| 416 | setTimeout(() => this.checkForCorruptedDocs(), 50); | ||
| 417 | }; | ||
| 418 | |||
| 419 | const req = txn.objectStore("docs").openCursor(); | ||
| 420 | req.onsuccess = (event) => { | ||
| 421 | let cursor; | ||
| 422 | if (!(cursor = event.target.result)) { | ||
| 423 | return; | ||
| 424 | } | ||
| 425 | this.cachedDocs[cursor.key] = cursor.value; | ||
| 426 | cursor.continue(); | ||
| 427 | }; | ||
| 428 | req.onerror = function (event) { | ||
| 429 | event.preventDefault(); | ||
| 430 | }; | ||
| 431 | } | ||
| 432 | |||
| 433 | checkForCorruptedDocs() { | ||
| 434 | this.db((db) => { | ||
| 435 | let slug; | ||
| 436 | this.corruptedDocs = []; | ||
| 437 | const docs = (() => { | ||
| 438 | const result = []; | ||
| 439 | for (var key in this.cachedDocs) { | ||
| 440 | var value = this.cachedDocs[key]; | ||
| 441 | if (value) { | ||
| 442 | result.push(key); | ||
| 466 | } | 443 | } |
| 467 | return result; | ||
| 468 | })(); | ||
| 469 | if (docs.length === 0) { | ||
| 470 | return; | ||
| 471 | } | 444 | } |
| 445 | return result; | ||
| 446 | })(); | ||
| 447 | if (docs.length === 0) { | ||
| 448 | return; | ||
| 449 | } | ||
| 472 | 450 | ||
| 473 | for (slug of Array.from(docs)) { | 451 | for (slug of docs) { |
| 474 | if (!app.docs.findBy("slug", slug)) { | 452 | if (!app.docs.findBy("slug", slug)) { |
| 475 | this.corruptedDocs.push(slug); | 453 | this.corruptedDocs.push(slug); |
| 476 | } | ||
| 477 | } | 454 | } |
| 455 | } | ||
| 478 | 456 | ||
| 479 | for (slug of Array.from(this.corruptedDocs)) { | 457 | for (slug of this.corruptedDocs) { |
| 480 | $.arrayDelete(docs, slug); | 458 | $.arrayDelete(docs, slug); |
| 481 | } | 459 | } |
| 460 | |||
| 461 | if (docs.length === 0) { | ||
| 462 | setTimeout(() => this.deleteCorruptedDocs(), 0); | ||
| 463 | return; | ||
| 464 | } | ||
| 482 | 465 | ||
| 483 | if (docs.length === 0) { | 466 | const txn = this.idbTransaction(db, { |
| 467 | stores: docs, | ||
| 468 | mode: "readonly", | ||
| 469 | ignoreError: false, | ||
| 470 | }); | ||
| 471 | txn.oncomplete = () => { | ||
| 472 | if (this.corruptedDocs.length > 0) { | ||
| 484 | setTimeout(() => this.deleteCorruptedDocs(), 0); | 473 | setTimeout(() => this.deleteCorruptedDocs(), 0); |
| 485 | return; | ||
| 486 | } | 474 | } |
| 475 | }; | ||
| 487 | 476 | ||
| 488 | const txn = this.idbTransaction(db, { | 477 | for (var doc of docs) { |
| 489 | stores: docs, | 478 | txn.objectStore(doc).get("index").onsuccess = (event) => { |
| 490 | mode: "readonly", | 479 | if (!event.target.result) { |
| 491 | ignoreError: false, | 480 | this.corruptedDocs.push(event.target.source.name); |
| 492 | }); | ||
| 493 | txn.oncomplete = () => { | ||
| 494 | if (this.corruptedDocs.length > 0) { | ||
| 495 | setTimeout(() => this.deleteCorruptedDocs(), 0); | ||
| 496 | } | 481 | } |
| 497 | }; | 482 | }; |
| 483 | } | ||
| 484 | }); | ||
| 485 | } | ||
| 498 | 486 | ||
| 499 | for (var doc of Array.from(docs)) { | 487 | deleteCorruptedDocs() { |
| 500 | txn.objectStore(doc).get("index").onsuccess = (event) => { | 488 | this.db((db) => { |
| 501 | if (!event.target.result) { | 489 | let doc; |
| 502 | this.corruptedDocs.push(event.target.source.name); | 490 | const txn = this.idbTransaction(db, { |
| 503 | } | 491 | stores: ["docs"], |
| 504 | }; | 492 | mode: "readwrite", |
| 505 | } | 493 | ignoreError: false, |
| 506 | }); | ||
| 507 | } | ||
| 508 | |||
| 509 | deleteCorruptedDocs() { | ||
| 510 | this.db((db) => { | ||
| 511 | let doc; | ||
| 512 | const txn = this.idbTransaction(db, { | ||
| 513 | stores: ["docs"], | ||
| 514 | mode: "readwrite", | ||
| 515 | ignoreError: false, | ||
| 516 | }); | ||
| 517 | const store = txn.objectStore("docs"); | ||
| 518 | while ((doc = this.corruptedDocs.pop())) { | ||
| 519 | this.cachedDocs[doc] = false; | ||
| 520 | store.delete(doc); | ||
| 521 | } | ||
| 522 | }); | ||
| 523 | Raven.captureMessage("corruptedDocs", { | ||
| 524 | level: "info", | ||
| 525 | extra: { docs: this.corruptedDocs.join(",") }, | ||
| 526 | }); | 494 | }); |
| 527 | } | 495 | const store = txn.objectStore("docs"); |
| 528 | 496 | while ((doc = this.corruptedDocs.pop())) { | |
| 529 | shouldLoadWithIDB(entry) { | 497 | this.cachedDocs[doc] = false; |
| 530 | return ( | 498 | store.delete(doc); |
| 531 | this.useIndexedDB && | ||
| 532 | (!this.cachedDocs || this.cachedDocs[entry.doc.slug]) | ||
| 533 | ); | ||
| 534 | } | ||
| 535 | |||
| 536 | idbTransaction(db, options) { | ||
| 537 | app.lastIDBTransaction = [options.stores, options.mode]; | ||
| 538 | const txn = db.transaction(options.stores, options.mode); | ||
| 539 | if (options.ignoreError !== false) { | ||
| 540 | txn.onerror = function (event) { | ||
| 541 | event.preventDefault(); | ||
| 542 | }; | ||
| 543 | } | 499 | } |
| 544 | if (options.ignoreAbort !== false) { | 500 | }); |
| 545 | txn.onabort = function (event) { | 501 | Raven.captureMessage("corruptedDocs", { |
| 546 | event.preventDefault(); | 502 | level: "info", |
| 547 | }; | 503 | extra: { docs: this.corruptedDocs.join(",") }, |
| 548 | } | 504 | }); |
| 549 | return txn; | 505 | } |
| 506 | |||
| 507 | shouldLoadWithIDB(entry) { | ||
| 508 | return ( | ||
| 509 | this.useIndexedDB && (!this.cachedDocs || this.cachedDocs[entry.doc.slug]) | ||
| 510 | ); | ||
| 511 | } | ||
| 512 | |||
| 513 | idbTransaction(db, options) { | ||
| 514 | app.lastIDBTransaction = [options.stores, options.mode]; | ||
| 515 | const txn = db.transaction(options.stores, options.mode); | ||
| 516 | if (options.ignoreError !== false) { | ||
| 517 | txn.onerror = function (event) { | ||
| 518 | event.preventDefault(); | ||
| 519 | }; | ||
| 550 | } | 520 | } |
| 551 | 521 | if (options.ignoreAbort !== false) { | |
| 552 | reset() { | 522 | txn.onabort = function (event) { |
| 553 | try { | 523 | event.preventDefault(); |
| 554 | if (typeof indexedDB !== "undefined" && indexedDB !== null) { | 524 | }; |
| 555 | indexedDB.deleteDatabase(NAME); | ||
| 556 | } | ||
| 557 | } catch (error) {} | ||
| 558 | } | 525 | } |
| 559 | 526 | return txn; | |
| 560 | useIndexedDB() { | 527 | } |
| 561 | try { | 528 | |
| 562 | if (!app.isSingleDoc() && window.indexedDB) { | 529 | reset() { |
| 563 | return true; | 530 | try { |
| 564 | } else { | 531 | indexedDB?.deleteDatabase(DB.NAME); |
| 565 | this.reason = "not_supported"; | 532 | } catch (error) {} |
| 566 | return false; | 533 | } |
| 567 | } | 534 | |
| 568 | } catch (error) { | 535 | useIndexedDB() { |
| 536 | try { | ||
| 537 | if (!app.isSingleDoc() && window.indexedDB) { | ||
| 538 | return true; | ||
| 539 | } else { | ||
| 540 | this.reason = "not_supported"; | ||
| 569 | return false; | 541 | return false; |
| 570 | } | 542 | } |
| 543 | } catch (error) { | ||
| 544 | return false; | ||
| 571 | } | 545 | } |
| 546 | } | ||
| 572 | 547 | ||
| 573 | migrate() { | 548 | migrate() { |
| 574 | app.settings.set("schema", this.userVersion() + 1); | 549 | app.settings.set("schema", this.userVersion() + 1); |
| 575 | } | 550 | } |
| 576 | 551 | ||
| 577 | setUserVersion(version) { | 552 | setUserVersion(version) { |
| 578 | app.settings.set("schema", version); | 553 | app.settings.set("schema", version); |
| 579 | } | 554 | } |
| 580 | 555 | ||
| 581 | userVersion() { | 556 | userVersion() { |
| 582 | return app.settings.get("schema"); | 557 | return app.settings.get("schema"); |
| 583 | } | 558 | } |
| 584 | }; | 559 | }; |
| 585 | app.DB.initClass(); | ||
| 586 | return app.DB; | ||
| 587 | })(); | ||