fix(sqlite): graph list and unlinking for in-browser graph

pull/10050/head
Andelf 2023-08-16 18:31:02 +08:00
parent bcd3e79730
commit fd0ccf2add
9 changed files with 170 additions and 66 deletions

View File

@ -8,14 +8,13 @@ import * as Comlink from 'comlink';
let sqlite3;
let dbMap = {};
const idbName = "logseq-VFS";
class AsyncLock {
constructor() {
this.disable = (_) => { }
this.promise = Promise.resolve()
}
enable() {
this.promise = new Promise(resolve => this.disable = resolve)
}
@ -42,7 +41,56 @@ const SQLiteDB = {
inc() {
return 233;
},
async listDB() {
return new Promise((resolve, reject) => {
const request = indexedDB.open(idbName);
request.onerror = reject;
request.onsuccess = (ev) => {
const db = ev.target.result;
const transaction = db.transaction(["blocks"], "readonly");
const objectStore = transaction.objectStore("blocks");
const request = objectStore.getAllKeys();
request.onsuccess = () => {
db.close();
const dbNames = [];
for (const key of request.result) {
if (dbNames.findIndex(dbName => dbName === key[0]) === -1) {
dbNames.push(key[0]);
}
}
resolve(dbNames.map(dbName => dbName.replace(/^\//, "")));
};
request.onerror = reject;
};
});
},
async unsafeUnlinkDB(dbName) {
const dbKey = "/" + dbName;
console.log("[worker] deleting", dbName);
return new Promise((resolve, reject) => {
const request = indexedDB.open(idbName);
request.onerror = reject;
request.onsuccess = (ev) => {
const db = ev.target.result;
const transaction = db.transaction(["blocks"], "readwrite");
const objectStore = transaction.objectStore("blocks");
const request = objectStore.openCursor();
request.onsuccess = (event) => {
const cursor = event.target.result;
if (cursor) {
if (cursor.key[0] === (dbKey)) {
cursor.delete();
}
cursor.continue();
} else {
db.close();
resolve();
}
};
request.onerror = reject;
};
});
},
async init() {
console.log("[worker] calling init");
const module = await SQLiteModuleFactory();
@ -59,9 +107,12 @@ const SQLiteDB = {
// :db-new
async newDB(dbName) {
console.log("[worker] SQLite newDB", dbName);
const db = await sqlite3.open_v2(dbName ?? 'demo');
let db = dbMap[dbName];
if (db) {
return db;
}
db = await sqlite3.open_v2(dbName ?? 'demo');
// create-blocks-table!
const str = sqlite3.str_new(db, `CREATE TABLE IF NOT EXISTS blocks (
uuid TEXT PRIMARY KEY NOT NULL,
type INTEGER NOT NULL,

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -50,7 +50,8 @@
"Graph list in `All graphs` page"
[repos]
(for [{:keys [url remote? GraphUUID GraphName] :as repo} repos
:let [only-cloud? (and remote? (nil? url))]]
:let [only-cloud? (and remote? (nil? url))
db-based? (config/db-based-graph? url)]]
[:div.flex.justify-between.mb-4.items-center {:key (or url GraphUUID)}
(normalized-graph-label repo #(if only-cloud?
(state/pub-event! [:graph/pull-down-remote-graph repo])
@ -59,35 +60,53 @@
[:div.controls
[:div.flex.flex-row.items-center
(ui/tippy {:html [:div.text-sm.max-w-xs
(if only-cloud?
(cond
only-cloud?
"Deletes this remote graph. Note this can't be recovered."
db-based?
"Unsafe delete this DB-based graph. Note this can't be recovered."
:else
"Removes Logseq's access to the local file path of your graph. It won't remove your local files.")]
:class "tippy-hover"
:interactive true}
[:a.text-gray-400.ml-4.font-medium.text-sm.whitespace-nowrap
{:on-click (fn []
(if only-cloud?
(let [confirm-fn
(fn []
(ui/make-confirm-modal
{:title [:div
{:style {:max-width 700}}
(str "Are you sure to permanently delete the graph \"" GraphName "\" from our server?")]
:sub-title [:div.small.mt-1
"Notice that we can't recover this graph after being deleted. Make sure you have backups before deleting it."]
:on-confirm (fn [_ {:keys [close-fn]}]
(close-fn)
(state/set-state! [:file-sync/remote-graphs :loading] true)
(go (<! (file-sync/<delete-graph GraphUUID))
(state/delete-repo! repo)
(state/delete-remote-graph! repo)
(state/set-state! [:file-sync/remote-graphs :loading] false)))}))]
(state/set-modal! (confirm-fn)))
(let [current-repo (state/get-current-repo)]
(repo-handler/remove-repo! repo)
(state/pub-event! [:graph/unlinked repo current-repo]))))}
(if only-cloud? "Remove" "Unlink")])]]]))
(let [has-prompt? (or only-cloud? db-based?)
prompt-str (cond only-cloud?
(str "Are you sure to permanently delete the graph \"" GraphName "\" from our server?")
db-based?
(str "Are you sure to permanently delete the graph \"" url "\" from Logseq?")
:else
"")
unlink-or-remote-fn (fn []
(let [current-repo (state/get-current-repo)]
(repo-handler/remove-repo! repo)
(state/pub-event! [:graph/unlinked repo current-repo])))
action-confirm-fn (if only-cloud?
(fn []
(state/set-state! [:file-sync/remote-graphs :loading] true)
(go (<! (file-sync/<delete-graph GraphUUID))
(state/delete-repo! repo)
(state/delete-remote-graph! repo)
(state/set-state! [:file-sync/remote-graphs :loading] false)))
unlink-or-remote-fn)
confirm-fn
(fn []
(ui/make-confirm-modal
{:title [:div
{:style {:max-width 700}}
prompt-str]
:sub-title [:div.small.mt-1
"Notice that we can't recover this graph after being deleted. Make sure you have backups before deleting it."]
:on-confirm (fn [_ {:keys [close-fn]}]
(close-fn)
(action-confirm-fn))}))]
(if has-prompt?
(state/set-modal! (confirm-fn))
(unlink-or-remote-fn))))}
(if (or db-based? only-cloud?) "Remove" "Unlink")])]]]))
(rum/defc repos < rum/reactive
[]

View File

@ -5,7 +5,8 @@
[frontend.config :as config]
[electron.ipc :as ipc]
[frontend.db.conn :as db-conn]
[promesa.core :as p]))
[promesa.core :as p]
[frontend.persist-db :as persist-db]))
(defn get-all-graphs
[]
@ -15,7 +16,9 @@
;; backward compatibility (release <= 0.5.4)
result (if (seq result) result (idb/get-nfs-dbs))]
(distinct result))
(idb/get-nfs-dbs)))
(p/let [repos (idb/get-nfs-dbs)
db-repos (persist-db/<list-db)]
(concat repos db-repos))))
(defn get-serialized-graph
[graph-name]
@ -40,6 +43,7 @@
[graph]
(let [key (db-conn/datascript-db graph)
db-based? (config/db-based-graph? graph)]
(persist-db/<unsafe-delete graph)
(if (util/electron?)
(ipc/ipc "deleteGraph" graph key db-based?)
(idb/remove-item! key))))

View File

@ -1,27 +1,31 @@
(ns frontend.persist-db
"Backend of DB based graph"
(:require [frontend.persist-db.browser :as browser]
[frontend.persist-db.node :as node]
[frontend.persist-db.protocol :as protocol]
[frontend.util :as util]
[promesa.core :as p]))
"Backend of DB based graph"
(:require [frontend.persist-db.browser :as browser]
[frontend.persist-db.node :as node]
[frontend.persist-db.protocol :as protocol]
[frontend.util :as util]
[promesa.core :as p]))
(defonce electron-ipc-sqlite-db (node/->ElectronIPC))
(defonce electron-ipc-sqlite-db (node/->ElectronIPC))
(defonce opfs-db (browser/->InBrowser))
(defonce opfs-db (browser/->InBrowser))
(defn- get-impl
"Get the actual implementation of PersistentDB"
[]
(cond
(util/electron?)
electron-ipc-sqlite-db
(defn- get-impl
"Get the actual implementation of PersistentDB"
[]
(cond
(util/electron?)
electron-ipc-sqlite-db
:else
opfs-db))
:else
opfs-db))
(defn <list-db []
(protocol/<list-db (get-impl)))
(defn <unsafe-delete [repo]
(protocol/<unsafe-delete (get-impl) repo))
(defn <new [repo]
(protocol/<new (get-impl) repo))

View File

@ -15,22 +15,31 @@
(defn- ensure-sqlite-init []
(if (nil? @*worker)
(js/Promise. (fn [resolve _reject]
(let [worker (js/SharedWorker. "/static/js/ls-wa-sqlite/persist-db-worker.js")
(let [worker (try
(js/SharedWorker. "/static/js/ls-wa-sqlite/persist-db-worker.js")
(catch js/Error e
(js/console.error "worker error", e)
nil))
_ (reset! *worker worker)
^js sqlite (Comlink/wrap (.-port worker))
_ (reset! *sqlite sqlite)]
(p/do!
(.init ^js sqlite)
(resolve @*sqlite)
;; start the upsert loop
(go-loop []
(let [[repo ret-ch deleted-uuids upsert-blocks] (<! db-upsert-chan)
delete-rc (when (seq deleted-uuids)
(<! (p->c (.deleteBlocks sqlite repo (clj->js deleted-uuids)))))
upsert-rc (<! (p->c (.upsertBlocks sqlite repo (clj->js upsert-blocks))))]
(async/put! ret-ch [delete-rc upsert-rc])
(prn :db-upsert-chan :delete delete-rc :upsert upsert-rc))
(recur))))))
(-> (.init sqlite)
(p/then (fn []
(js/console.log "sqlite init done")
(resolve @*sqlite)))
(p/then (fn []
(go-loop []
(let [[repo ret-ch deleted-uuids upsert-blocks] (<! db-upsert-chan)
delete-rc (when (seq deleted-uuids)
(<! (p->c (.deleteBlocks sqlite repo (clj->js deleted-uuids)))))
upsert-rc (<! (p->c (.upsertBlocks sqlite repo (clj->js upsert-blocks))))]
(async/put! ret-ch [delete-rc upsert-rc])
(prn :db-upsert-chan :delete delete-rc :upsert upsert-rc))
(recur))
(prn ::done))
)
(p/catch (fn [e]
(js/console.error "init error", e)))))))
(p/resolved @*sqlite)))
(defn- type-of-block
@ -77,10 +86,18 @@
(defrecord InBrowser []
protocol/PersistentDB
(<new [_this repo]
(prn ::repo repo)
(prn ::new-repo repo)
(p/let [^js sqlite (ensure-sqlite-init)]
(.newDB sqlite repo)))
(<list-db [_this]
(p/let [^js sqlite (ensure-sqlite-init)
rc (.newDB ^js sqlite repo)]
(js/console.log "new db created rc=" rc)))
dbs (.listDB sqlite)]
(js/console.log "list DBs:" dbs)
dbs))
(<unsafe-delete [_this repo]
(p/let [^js sqlite (ensure-sqlite-init)]
(.unsafeUnlinkDB sqlite repo)))
(<transact-data [_this repo upsert-blocks deleted-uuids]
(go

View File

@ -1,13 +1,20 @@
(ns frontend.persist-db.node
"Electron ipc based persistent db"
(:require [electron.ipc :as ipc]
[frontend.persist-db.protocol :as protocol]))
[frontend.persist-db.protocol :as protocol]
[promesa.core :as p]))
(defrecord ElectronIPC []
protocol/PersistentDB
(<new [_this repo]
(prn ::new repo)
(ipc/ipc :db-new repo))
(<list-db [_this]
(js/console.warn "TODO: list-db for electron not implemented")
[])
(<unsafe-delete [_this _repo]
(js/console.warn "TODO: delete")
(p/resolved nil))
(<transact-data [_this repo added-blocks deleted-block-uuids]
;; (prn ::transact-data repo added-blocks deleted-block-uuids)
(ipc/ipc :db-transact-data repo

View File

@ -4,7 +4,9 @@
;; TODO: exporting, importing support
(defprotocol PersistentDB
(<list-db [this])
(<new [this repo])
(<unsafe-delete [this repo])
(<transact-data [this repo added-blocks deleted-block-uuids]
"Transact data to db