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