You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
devdocs/assets/javascripts/app/db.js

593 lines
14 KiB

// TODO: This file was created by bulk-decaffeinate.
// Sanity-check the conversion and remove this comment.
/*
* decaffeinate suggestions:
* DS101: Remove unnecessary use of Array.from
* DS102: Remove unnecessary code created because of implicit returns
* DS205: Consider reworking code to avoid use of IIFEs
* DS206: Consider reworking classes to avoid initClass
* DS207: Consider shorter variations of null checks
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md
*/
1 year ago
(function () {
let NAME = undefined;
let VERSION = undefined;
const Cls = (app.DB = class DB {
static initClass() {
1 year ago
NAME = "docs";
VERSION = 15;
}
constructor() {
this.onOpenSuccess = this.onOpenSuccess.bind(this);
this.onOpenError = this.onOpenError.bind(this);
this.checkForCorruptedDocs = this.checkForCorruptedDocs.bind(this);
this.deleteCorruptedDocs = this.deleteCorruptedDocs.bind(this);
this.versionMultipler = $.isIE() ? 1e5 : 1e9;
this.useIndexedDB = this.useIndexedDB();
this.callbacks = [];
}
db(fn) {
1 year ago
if (!this.useIndexedDB) {
return fn();
}
if (fn) {
this.callbacks.push(fn);
}
if (this.open) {
return;
}
try {
this.open = true;
1 year ago
const req = indexedDB.open(
NAME,
VERSION * this.versionMultipler + this.userVersion(),
);
req.onsuccess = this.onOpenSuccess;
req.onerror = this.onOpenError;
req.onupgradeneeded = this.onUpgradeNeeded;
} catch (error) {
1 year ago
this.fail("exception", error);
}
}
onOpenSuccess(event) {
let error;
const db = event.target.result;
if (db.objectStoreNames.length === 0) {
1 year ago
try {
db.close();
} catch (error1) {}
this.open = false;
1 year ago
this.fail("empty");
} else if ((error = this.buggyIDB(db))) {
try {
db.close();
} catch (error2) {}
this.open = false;
1 year ago
this.fail("buggy", error);
} else {
this.runCallbacks(db);
this.open = false;
db.close();
}
}
onOpenError(event) {
event.preventDefault();
this.open = false;
1 year ago
const { error } = event.target;
switch (error.name) {
1 year ago
case "QuotaExceededError":
this.onQuotaExceededError();
break;
1 year ago
case "VersionError":
this.onVersionError();
break;
1 year ago
case "InvalidStateError":
this.fail("private_mode");
break;
default:
1 year ago
this.fail("cant_open", error);
}
}
fail(reason, error) {
this.cachedDocs = null;
this.useIndexedDB = false;
1 year ago
if (!this.reason) {
this.reason = reason;
}
if (!this.error) {
this.error = error;
}
if (error) {
if (typeof console.error === "function") {
console.error("IDB error", error);
}
}
this.runCallbacks();
1 year ago
if (error && reason === "cant_open") {
Raven.captureMessage(`${error.name}: ${error.message}`, {
level: "warning",
fingerprint: [error.name],
});
}
}
onQuotaExceededError() {
this.reset();
this.db();
app.onQuotaExceeded();
1 year ago
Raven.captureMessage("QuotaExceededError", { level: "warning" });
}
onVersionError() {
const req = indexedDB.open(NAME);
1 year ago
req.onsuccess = (event) => {
return this.handleVersionMismatch(event.target.result.version);
};
1 year ago
req.onerror = function (event) {
event.preventDefault();
1 year ago
return this.fail("cant_open", error);
};
}
handleVersionMismatch(actualVersion) {
if (Math.floor(actualVersion / this.versionMultipler) !== VERSION) {
1 year ago
this.fail("version");
} else {
1 year ago
this.setUserVersion(actualVersion - VERSION * this.versionMultipler);
this.db();
}
}
buggyIDB(db) {
1 year ago
if (this.checkedBuggyIDB) {
return;
}
this.checkedBuggyIDB = true;
try {
1 year ago
this.idbTransaction(db, {
stores: $.makeArray(db.objectStoreNames).slice(0, 2),
mode: "readwrite",
}).abort(); // https://bugs.webkit.org/show_bug.cgi?id=136937
return;
} catch (error) {
return error;
}
}
runCallbacks(db) {
let fn;
1 year ago
while ((fn = this.callbacks.shift())) {
fn(db);
}
}
onUpgradeNeeded(event) {
let db;
1 year ago
if (!(db = event.target.result)) {
return;
}
const objectStoreNames = $.makeArray(db.objectStoreNames);
1 year ago
if (!$.arrayDelete(objectStoreNames, "docs")) {
try {
db.createObjectStore("docs");
} catch (error) {}
}
for (var doc of Array.from(app.docs.all())) {
if (!$.arrayDelete(objectStoreNames, doc.slug)) {
1 year ago
try {
db.createObjectStore(doc.slug);
} catch (error1) {}
}
}
for (var name of Array.from(objectStoreNames)) {
1 year ago
try {
db.deleteObjectStore(name);
} catch (error2) {}
}
}
store(doc, data, onSuccess, onError, _retry) {
1 year ago
if (_retry == null) {
_retry = true;
}
this.db((db) => {
if (!db) {
onError();
return;
}
1 year ago
const txn = this.idbTransaction(db, {
stores: ["docs", doc.slug],
mode: "readwrite",
ignoreError: false,
});
txn.oncomplete = () => {
if (this.cachedDocs != null) {
this.cachedDocs[doc.slug] = doc.mtime;
}
onSuccess();
};
1 year ago
txn.onerror = (event) => {
event.preventDefault();
1 year ago
if (
(txn.error != null ? txn.error.name : undefined) ===
"NotFoundError" &&
_retry
) {
this.migrate();
setTimeout(() => {
return this.store(doc, data, onSuccess, onError, false);
1 year ago
}, 0);
} else {
onError(event);
}
};
let store = txn.objectStore(doc.slug);
store.clear();
1 year ago
for (var path in data) {
var content = data[path];
store.add(content, path);
}
1 year ago
store = txn.objectStore("docs");
store.put(doc.mtime, doc.slug);
});
}
unstore(doc, onSuccess, onError, _retry) {
1 year ago
if (_retry == null) {
_retry = true;
}
this.db((db) => {
if (!db) {
onError();
return;
}
1 year ago
const txn = this.idbTransaction(db, {
stores: ["docs", doc.slug],
mode: "readwrite",
ignoreError: false,
});
txn.oncomplete = () => {
if (this.cachedDocs != null) {
delete this.cachedDocs[doc.slug];
}
onSuccess();
};
1 year ago
txn.onerror = function (event) {
event.preventDefault();
1 year ago
if (
(txn.error != null ? txn.error.name : undefined) ===
"NotFoundError" &&
_retry
) {
this.migrate();
setTimeout(() => {
return this.unstore(doc, onSuccess, onError, false);
1 year ago
}, 0);
} else {
onError(event);
}
};
1 year ago
let store = txn.objectStore("docs");
store.delete(doc.slug);
store = txn.objectStore(doc.slug);
store.clear();
});
}
version(doc, fn) {
let version;
if ((version = this.cachedVersion(doc)) != null) {
fn(version);
return;
}
1 year ago
this.db((db) => {
if (!db) {
fn(false);
return;
}
1 year ago
const txn = this.idbTransaction(db, {
stores: ["docs"],
mode: "readonly",
});
const store = txn.objectStore("docs");
const req = store.get(doc.slug);
1 year ago
req.onsuccess = function () {
fn(req.result);
};
1 year ago
req.onerror = function (event) {
event.preventDefault();
fn(false);
};
});
}
cachedVersion(doc) {
1 year ago
if (!this.cachedDocs) {
return;
}
return this.cachedDocs[doc.slug] || false;
}
versions(docs, fn) {
let versions;
1 year ago
if ((versions = this.cachedVersions(docs))) {
fn(versions);
return;
}
1 year ago
return this.db((db) => {
if (!db) {
fn(false);
return;
}
1 year ago
const txn = this.idbTransaction(db, {
stores: ["docs"],
mode: "readonly",
});
txn.oncomplete = function () {
fn(result);
};
1 year ago
const store = txn.objectStore("docs");
var result = {};
1 year ago
docs.forEach(function (doc) {
const req = store.get(doc.slug);
1 year ago
req.onsuccess = function () {
result[doc.slug] = req.result;
};
1 year ago
req.onerror = function (event) {
event.preventDefault();
result[doc.slug] = false;
};
});
});
}
cachedVersions(docs) {
1 year ago
if (!this.cachedDocs) {
return;
}
const result = {};
1 year ago
for (var doc of Array.from(docs)) {
result[doc.slug] = this.cachedVersion(doc);
}
return result;
}
load(entry, onSuccess, onError) {
if (this.shouldLoadWithIDB(entry)) {
onError = this.loadWithXHR.bind(this, entry, onSuccess, onError);
return this.loadWithIDB(entry, onSuccess, onError);
} else {
return this.loadWithXHR(entry, onSuccess, onError);
}
}
loadWithXHR(entry, onSuccess, onError) {
return ajax({
url: entry.fileUrl(),
1 year ago
dataType: "html",
success: onSuccess,
1 year ago
error: onError,
});
}
loadWithIDB(entry, onSuccess, onError) {
1 year ago
return this.db((db) => {
if (!db) {
onError();
return;
}
if (!db.objectStoreNames.contains(entry.doc.slug)) {
onError();
this.loadDocsCache(db);
return;
}
1 year ago
const txn = this.idbTransaction(db, {
stores: [entry.doc.slug],
mode: "readonly",
});
const store = txn.objectStore(entry.doc.slug);
const req = store.get(entry.dbPath());
1 year ago
req.onsuccess = function () {
if (req.result) {
onSuccess(req.result);
} else {
onError();
}
};
1 year ago
req.onerror = function (event) {
event.preventDefault();
onError();
};
this.loadDocsCache(db);
});
}
loadDocsCache(db) {
1 year ago
if (this.cachedDocs) {
return;
}
this.cachedDocs = {};
1 year ago
const txn = this.idbTransaction(db, {
stores: ["docs"],
mode: "readonly",
});
txn.oncomplete = () => {
setTimeout(this.checkForCorruptedDocs, 50);
};
1 year ago
const req = txn.objectStore("docs").openCursor();
req.onsuccess = (event) => {
let cursor;
1 year ago
if (!(cursor = event.target.result)) {
return;
}
this.cachedDocs[cursor.key] = cursor.value;
cursor.continue();
};
1 year ago
req.onerror = function (event) {
event.preventDefault();
};
}
checkForCorruptedDocs() {
1 year ago
this.db((db) => {
let slug;
this.corruptedDocs = [];
1 year ago
const docs = (() => {
const result = [];
for (var key in this.cachedDocs) {
var value = this.cachedDocs[key];
if (value) {
result.push(key);
}
}
return result;
1 year ago
})();
if (docs.length === 0) {
return;
}
for (slug of Array.from(docs)) {
1 year ago
if (!app.docs.findBy("slug", slug)) {
this.corruptedDocs.push(slug);
}
}
for (slug of Array.from(this.corruptedDocs)) {
$.arrayDelete(docs, slug);
}
if (docs.length === 0) {
setTimeout(this.deleteCorruptedDocs, 0);
return;
}
1 year ago
const txn = this.idbTransaction(db, {
stores: docs,
mode: "readonly",
ignoreError: false,
});
txn.oncomplete = () => {
1 year ago
if (this.corruptedDocs.length > 0) {
setTimeout(this.deleteCorruptedDocs, 0);
}
};
for (var doc of Array.from(docs)) {
1 year ago
txn.objectStore(doc).get("index").onsuccess = (event) => {
if (!event.target.result) {
this.corruptedDocs.push(event.target.source.name);
}
};
}
});
}
deleteCorruptedDocs() {
1 year ago
this.db((db) => {
let doc;
1 year ago
const txn = this.idbTransaction(db, {
stores: ["docs"],
mode: "readwrite",
ignoreError: false,
});
const store = txn.objectStore("docs");
while ((doc = this.corruptedDocs.pop())) {
this.cachedDocs[doc] = false;
store.delete(doc);
}
});
1 year ago
Raven.captureMessage("corruptedDocs", {
level: "info",
extra: { docs: this.corruptedDocs.join(",") },
});
}
shouldLoadWithIDB(entry) {
1 year ago
return (
this.useIndexedDB &&
(!this.cachedDocs || this.cachedDocs[entry.doc.slug])
);
}
idbTransaction(db, options) {
app.lastIDBTransaction = [options.stores, options.mode];
const txn = db.transaction(options.stores, options.mode);
if (options.ignoreError !== false) {
1 year ago
txn.onerror = function (event) {
event.preventDefault();
};
}
if (options.ignoreAbort !== false) {
1 year ago
txn.onabort = function (event) {
event.preventDefault();
};
}
return txn;
}
reset() {
1 year ago
try {
if (typeof indexedDB !== "undefined" && indexedDB !== null) {
indexedDB.deleteDatabase(NAME);
}
} catch (error) {}
}
useIndexedDB() {
try {
if (!app.isSingleDoc() && window.indexedDB) {
return true;
} else {
1 year ago
this.reason = "not_supported";
return false;
}
} catch (error) {
return false;
}
}
migrate() {
1 year ago
app.settings.set("schema", this.userVersion() + 1);
}
setUserVersion(version) {
1 year ago
app.settings.set("schema", version);
}
userVersion() {
1 year ago
return app.settings.get("schema");
}
});
Cls.initClass();
return Cls;
})();