db_worker.cljs 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931
  1. (ns frontend.worker.db-worker
  2. "Worker used for browser DB implementation"
  3. (:require ["@logseq/sqlite-wasm" :default sqlite3InitModule]
  4. ["comlink" :as Comlink]
  5. [cljs-bean.core :as bean]
  6. [clojure.edn :as edn]
  7. [clojure.string :as string]
  8. [datascript.core :as d]
  9. [datascript.storage :refer [IStorage] :as storage]
  10. [frontend.common.file.core :as common-file]
  11. [frontend.worker.crypt :as worker-crypt]
  12. [frontend.worker.db-listener :as db-listener]
  13. [frontend.worker.db-metadata :as worker-db-metadata]
  14. [frontend.worker.db.migrate :as db-migrate]
  15. [frontend.worker.device :as worker-device]
  16. [frontend.worker.export :as worker-export]
  17. [frontend.worker.file :as file]
  18. [frontend.worker.handler.page :as worker-page]
  19. [frontend.worker.handler.page.file-based.rename :as file-worker-page-rename]
  20. [frontend.worker.rtc.asset-db-listener]
  21. [frontend.worker.rtc.client-op :as client-op]
  22. [frontend.worker.rtc.core :as rtc-core]
  23. [frontend.worker.rtc.db-listener]
  24. [frontend.worker.search :as search]
  25. [frontend.worker.state :as worker-state] ;; [frontend.worker.undo-redo :as undo-redo]
  26. [frontend.worker.undo-redo2 :as undo-redo]
  27. [frontend.worker.util :as worker-util]
  28. [goog.object :as gobj]
  29. [logseq.common.config :as common-config]
  30. [logseq.common.util :as common-util]
  31. [logseq.db :as ldb]
  32. [logseq.db.frontend.order :as db-order]
  33. [logseq.db.frontend.schema :as db-schema]
  34. [logseq.db.sqlite.common-db :as sqlite-common-db]
  35. [logseq.db.sqlite.create-graph :as sqlite-create-graph]
  36. [logseq.db.sqlite.util :as sqlite-util]
  37. [logseq.outliner.op :as outliner-op]
  38. [me.tonsky.persistent-sorted-set :as set :refer [BTSet]]
  39. [promesa.core :as p]
  40. [shadow.cljs.modern :refer [defclass]]))
  41. (defonce *sqlite worker-state/*sqlite)
  42. (defonce *sqlite-conns worker-state/*sqlite-conns)
  43. (defonce *datascript-conns worker-state/*datascript-conns)
  44. (defonce *client-ops-conns worker-state/*client-ops-conns)
  45. (defonce *opfs-pools worker-state/*opfs-pools)
  46. (defonce *publishing? (atom false))
  47. (defn- check-worker-scope!
  48. []
  49. (when (or (gobj/get js/self "React")
  50. (gobj/get js/self "module$react"))
  51. (throw (js/Error. "[db-worker] React is forbidden in worker scope!"))))
  52. (defn- <get-opfs-pool
  53. [graph]
  54. (when-not @*publishing?
  55. (or (worker-state/get-opfs-pool graph)
  56. (p/let [^js pool (.installOpfsSAHPoolVfs @*sqlite #js {:name (worker-util/get-pool-name graph)
  57. :initialCapacity 20})]
  58. (swap! *opfs-pools assoc graph pool)
  59. pool))))
  60. (defn- init-sqlite-module!
  61. []
  62. (when-not @*sqlite
  63. (p/let [href (.. js/location -href)
  64. electron? (string/includes? href "electron=true")
  65. publishing? (string/includes? href "publishing=true")
  66. _ (reset! *publishing? publishing?)
  67. base-url (str js/self.location.protocol "//" js/self.location.host)
  68. sqlite-wasm-url (if electron?
  69. (js/URL. "sqlite3.wasm" (.. js/location -href))
  70. (str base-url (string/replace js/self.location.pathname "db-worker.js" "")))
  71. sqlite (sqlite3InitModule (clj->js {:url sqlite-wasm-url
  72. :print js/console.log
  73. :printErr js/console.error}))]
  74. (reset! *sqlite sqlite)
  75. nil)))
  76. (def repo-path "/db.sqlite")
  77. (defn- <export-db-file
  78. [repo]
  79. (p/let [^js pool (<get-opfs-pool repo)]
  80. (when pool
  81. (.exportFile ^js pool repo-path))))
  82. (defn- <import-db
  83. [^js pool data]
  84. (.importDb ^js pool repo-path data))
  85. (defn- get-all-datoms-from-sqlite-db
  86. [db]
  87. (some->> (.exec db #js {:sql "select * from kvs"
  88. :rowMode "array"})
  89. bean/->clj
  90. (mapcat
  91. (fn [[_addr content _addresses]]
  92. (let [content' (sqlite-util/transit-read content)
  93. datoms (when (map? content')
  94. (:keys content'))]
  95. datoms)))
  96. distinct
  97. (map (fn [[e a v t]]
  98. (d/datom e a v t)))))
  99. (defn- rebuild-db-from-datoms!
  100. "Persistent-sorted-set has been broken, used addresses can't be found"
  101. [datascript-conn sqlite-db]
  102. (let [datoms (get-all-datoms-from-sqlite-db sqlite-db)
  103. db (d/init-db [] db-schema/schema-for-db-based-graph
  104. {:storage (storage/storage @datascript-conn)})
  105. db (d/db-with db
  106. (map (fn [d]
  107. [:db/add (:e d) (:a d) (:v d) (:t d)]) datoms))]
  108. (prn :debug :rebuild-db-from-datoms :datoms-count (count datoms))
  109. ;; export db first
  110. (worker-util/post-message :notification ["The SQLite db will be exported to avoid any data-loss." :warning false])
  111. (worker-util/post-message :export-current-db [])
  112. (.exec sqlite-db #js {:sql "delete from kvs"})
  113. (d/reset-conn! datascript-conn db)))
  114. (comment
  115. (defn- gc-kvs-table!
  116. [^Object db]
  117. (let [schema (some->> (.exec db #js {:sql "select content from kvs where addr = 0"
  118. :rowMode "array"})
  119. bean/->clj
  120. ffirst
  121. sqlite-util/transit-read)
  122. result (->> (.exec db #js {:sql "select addr, addresses from kvs"
  123. :rowMode "array"})
  124. bean/->clj
  125. (map (fn [[addr addresses]]
  126. [addr (bean/->clj (js/JSON.parse addresses))])))
  127. used-addresses (set (concat (mapcat second result)
  128. [0 1 (:eavt schema) (:avet schema) (:aevt schema)]))
  129. unused-addresses (set/difference (set (map first result)) used-addresses)]
  130. (when unused-addresses
  131. (prn :debug :db-gc :unused-addresses unused-addresses)
  132. (.transaction db (fn [tx]
  133. (doseq [addr unused-addresses]
  134. (.exec tx #js {:sql "Delete from kvs where addr = ?"
  135. :bind #js [addr]}))))))))
  136. (defn upsert-addr-content!
  137. "Upsert addr+data-seq. Update sqlite-cli/upsert-addr-content! when making changes"
  138. [repo data delete-addrs & {:keys [client-ops-db?] :or {client-ops-db? false}}]
  139. (let [^Object db (worker-state/get-sqlite-conn repo (if client-ops-db? :client-ops :db))]
  140. (assert (some? db) "sqlite db not exists")
  141. (.transaction db (fn [tx]
  142. (doseq [item data]
  143. (.exec tx #js {:sql "INSERT INTO kvs (addr, content, addresses) values ($addr, $content, $addresses) on conflict(addr) do update set content = $content, addresses = $addresses"
  144. :bind item}))))
  145. (when (seq delete-addrs)
  146. (.transaction db (fn [tx]
  147. ;; (prn :debug :delete-addrs delete-addrs)
  148. (doseq [addr delete-addrs]
  149. (.exec tx #js {:sql "Delete from kvs WHERE addr = ? AND NOT EXISTS (SELECT 1 FROM json_each(addresses) WHERE value = ?);"
  150. :bind #js [addr]})))))))
  151. (defn restore-data-from-addr
  152. "Update sqlite-cli/restore-data-from-addr when making changes"
  153. [repo addr & {:keys [client-ops-db?] :or {client-ops-db? false}}]
  154. (let [^Object db (worker-state/get-sqlite-conn repo (if client-ops-db? :client-ops :db))]
  155. (assert (some? db) "sqlite db not exists")
  156. (when-let [result (-> (.exec db #js {:sql "select content, addresses from kvs where addr = ?"
  157. :bind #js [addr]
  158. :rowMode "array"})
  159. first)]
  160. (let [[content addresses] (bean/->clj result)
  161. addresses (when addresses
  162. (js/JSON.parse addresses))
  163. data (sqlite-util/transit-read content)]
  164. (if (and addresses (map? data))
  165. (assoc data :addresses addresses)
  166. data)))))
  167. (defn new-sqlite-storage
  168. "Update sqlite-cli/new-sqlite-storage when making changes"
  169. [repo _opts]
  170. (reify IStorage
  171. (-store [_ addr+data-seq delete-addrs]
  172. (let [used-addrs (set (mapcat
  173. (fn [[addr data]]
  174. (cons addr
  175. (when (map? data)
  176. (:addresses data))))
  177. addr+data-seq))
  178. delete-addrs (remove used-addrs delete-addrs)
  179. data (map
  180. (fn [[addr data]]
  181. (let [data' (if (map? data) (dissoc data :addresses) data)
  182. addresses (when (map? data)
  183. (when-let [addresses (:addresses data)]
  184. (js/JSON.stringify (bean/->js addresses))))]
  185. #js {:$addr addr
  186. :$content (sqlite-util/transit-write data')
  187. :$addresses addresses}))
  188. addr+data-seq)]
  189. (upsert-addr-content! repo data delete-addrs)))
  190. (-restore [_ addr]
  191. (restore-data-from-addr repo addr))))
  192. (defn new-sqlite-client-ops-storage
  193. [repo]
  194. (reify IStorage
  195. (-store [_ addr+data-seq delete-addrs]
  196. (let [used-addrs (set (mapcat
  197. (fn [[addr data]]
  198. (cons addr
  199. (when (map? data)
  200. (:addresses data))))
  201. addr+data-seq))
  202. delete-addrs (remove used-addrs delete-addrs)
  203. data (map
  204. (fn [[addr data]]
  205. (let [data' (if (map? data) (dissoc data :addresses) data)
  206. addresses (when (map? data)
  207. (when-let [addresses (:addresses data)]
  208. (js/JSON.stringify (bean/->js addresses))))]
  209. #js {:$addr addr
  210. :$content (sqlite-util/transit-write data')
  211. :$addresses addresses}))
  212. addr+data-seq)]
  213. (upsert-addr-content! repo data delete-addrs :client-ops-db? true)))
  214. (-restore [_ addr]
  215. (restore-data-from-addr repo addr :client-ops-db? true))))
  216. (defn- close-db-aux!
  217. [repo ^Object db ^Object search ^Object client-ops]
  218. (swap! *sqlite-conns dissoc repo)
  219. (swap! *datascript-conns dissoc repo)
  220. (swap! *client-ops-conns dissoc repo)
  221. (when db (.close db))
  222. (when search (.close search))
  223. (when client-ops (.close client-ops))
  224. (when-let [^js pool (worker-state/get-opfs-pool repo)]
  225. (.releaseAccessHandles pool))
  226. (swap! *opfs-pools dissoc repo))
  227. (defn- close-other-dbs!
  228. [repo]
  229. (doseq [[r {:keys [db search client-ops]}] @*sqlite-conns]
  230. (when-not (= repo r)
  231. (close-db-aux! r db search client-ops))))
  232. (defn close-db!
  233. [repo]
  234. (let [{:keys [db search client-ops]} (get @*sqlite-conns repo)]
  235. (close-db-aux! repo db search client-ops)))
  236. (defn reset-db!
  237. [repo db-transit-str]
  238. (when-let [conn (get @*datascript-conns repo)]
  239. (let [new-db (ldb/read-transit-str db-transit-str)
  240. new-db' (update new-db :eavt (fn [^BTSet s]
  241. (set! (.-storage s) (.-storage (:eavt @conn)))
  242. s))]
  243. (d/reset-conn! conn new-db' {:reset-conn! true})
  244. (d/reset-schema! conn (:schema new-db))
  245. nil)))
  246. (defn- get-dbs
  247. [repo]
  248. (if @*publishing?
  249. (p/let [^object DB (.-DB ^object (.-oo1 ^object @*sqlite))
  250. db (new DB "/db.sqlite" "c")
  251. search-db (new DB "/search-db.sqlite" "c")]
  252. [db search-db nil])
  253. (p/let [^js pool (<get-opfs-pool repo)
  254. capacity (.getCapacity pool)
  255. _ (when (zero? capacity) ; file handle already releases since pool will be initialized only once
  256. (.acquireAccessHandles pool))
  257. db (new (.-OpfsSAHPoolDb pool) repo-path)
  258. search-db (new (.-OpfsSAHPoolDb pool) (str "search" repo-path))
  259. client-ops-db (new (.-OpfsSAHPoolDb pool) (str "client-ops-" repo-path))]
  260. [db search-db client-ops-db])))
  261. (defn- enable-sqlite-wal-mode!
  262. [^Object db]
  263. (.exec db "PRAGMA locking_mode=exclusive")
  264. (.exec db "PRAGMA journal_mode=WAL"))
  265. (defn- create-or-open-db!
  266. [repo {:keys [config import-type]}]
  267. (when-not (worker-state/get-sqlite-conn repo)
  268. (p/let [[db search-db client-ops-db :as dbs] (get-dbs repo)
  269. storage (new-sqlite-storage repo {})
  270. client-ops-storage (when-not @*publishing? (new-sqlite-client-ops-storage repo))
  271. db-based? (sqlite-util/db-based-graph? repo)]
  272. (swap! *sqlite-conns assoc repo {:db db
  273. :search search-db
  274. :client-ops client-ops-db})
  275. (doseq [db' dbs]
  276. (enable-sqlite-wal-mode! db'))
  277. (sqlite-common-db/create-kvs-table! db)
  278. (when-not @*publishing? (sqlite-common-db/create-kvs-table! client-ops-db))
  279. (db-migrate/migrate-sqlite-db db)
  280. (when-not @*publishing? (db-migrate/migrate-sqlite-db client-ops-db))
  281. (search/create-tables-and-triggers! search-db)
  282. (let [schema (sqlite-util/get-schema repo)
  283. conn (sqlite-common-db/get-storage-conn storage schema)
  284. client-ops-conn (when-not @*publishing? (sqlite-common-db/get-storage-conn client-ops-storage client-op/schema-in-db))
  285. initial-data-exists? (and (d/entity @conn :logseq.class/Root)
  286. (= "db" (:kv/value (d/entity @conn :logseq.kv/db-type))))]
  287. (swap! *datascript-conns assoc repo conn)
  288. (swap! *client-ops-conns assoc repo client-ops-conn)
  289. (when (and db-based? (not initial-data-exists?))
  290. (let [config (or config "")
  291. initial-data (sqlite-create-graph/build-db-initial-data config
  292. (when import-type {:import-type import-type}))]
  293. (d/transact! conn initial-data {:initial-db? true})))
  294. (when-not db-based?
  295. (try
  296. (when-not (ldb/page-exists? @conn common-config/views-page-name #{:logseq.class/Page})
  297. (ldb/transact! conn (sqlite-create-graph/build-initial-views)))
  298. (catch :default _e)))
  299. ;; (gc-kvs-table! db)
  300. (try
  301. (db-migrate/migrate conn search-db)
  302. (catch :default _e
  303. (when db-based?
  304. (rebuild-db-from-datoms! conn db))))
  305. (db-listener/listen-db-changes! repo (get @*datascript-conns repo))))))
  306. (defn- iter->vec [iter']
  307. (when iter'
  308. (p/loop [acc []]
  309. (p/let [elem (.next iter')]
  310. (if (.-done elem)
  311. acc
  312. (p/recur (conj acc (.-value elem))))))))
  313. (comment
  314. (defn- <list-all-files
  315. []
  316. (let [dir? #(= (.-kind %) "directory")]
  317. (p/let [^js root (.getDirectory js/navigator.storage)]
  318. (p/loop [result []
  319. dirs [root]]
  320. (if (empty? dirs)
  321. result
  322. (p/let [dir (first dirs)
  323. result (conj result dir)
  324. values-iter (when (dir? dir) (.values dir))
  325. values (when values-iter (iter->vec values-iter))
  326. current-dir-dirs (filter dir? values)
  327. result (concat result values)
  328. dirs (concat
  329. current-dir-dirs
  330. (rest dirs))]
  331. (p/recur result dirs))))))))
  332. (defn- <list-all-dbs
  333. []
  334. (let [dir? #(= (.-kind %) "directory")]
  335. (p/let [^js root (.getDirectory js/navigator.storage)
  336. values-iter (when (dir? root) (.values root))
  337. values (when values-iter (iter->vec values-iter))
  338. current-dir-dirs (filter dir? values)
  339. db-dirs (filter (fn [file]
  340. (string/starts-with? (.-name file) ".logseq-pool-"))
  341. current-dir-dirs)]
  342. (prn :debug
  343. :db-dirs (map #(.-name %) db-dirs)
  344. :all-dirs (map #(.-name %) current-dir-dirs))
  345. (p/all (map (fn [dir]
  346. (p/let [graph-name (-> (.-name dir)
  347. (string/replace-first ".logseq-pool-" "")
  348. ;; TODO: DRY
  349. (string/replace "+3A+" ":")
  350. (string/replace "++" "/"))
  351. metadata-file-handle (.getFileHandle dir "metadata.edn" #js {:create true})
  352. metadata-file (.getFile metadata-file-handle)
  353. metadata (.text metadata-file)]
  354. {:name graph-name
  355. :metadata (edn/read-string metadata)})) db-dirs)))))
  356. (defn- <db-exists?
  357. [graph]
  358. (->
  359. (p/let [^js root (.getDirectory js/navigator.storage)
  360. _dir-handle (.getDirectoryHandle root (str "." (worker-util/get-pool-name graph)))]
  361. true)
  362. (p/catch
  363. (fn [_e] ; not found
  364. false))))
  365. (defn- remove-vfs!
  366. [^js pool]
  367. (when pool
  368. (.removeVfs ^js pool)))
  369. (defn- get-search-db
  370. [repo]
  371. (worker-state/get-sqlite-conn repo :search))
  372. (defn- with-write-transit-str
  373. [p]
  374. (p/chain p
  375. (fn [result]
  376. (let [result (when-not (= result @worker-state/*state) result)]
  377. (ldb/write-transit-str result)))))
  378. #_:clj-kondo/ignore
  379. (defclass DBWorker
  380. (extends js/Object)
  381. (constructor
  382. [this]
  383. (super))
  384. Object
  385. (getVersion
  386. [_this]
  387. (when-let [sqlite @*sqlite]
  388. (.-version sqlite)))
  389. (init
  390. [_this rtc-ws-url]
  391. (reset! worker-state/*rtc-ws-url rtc-ws-url)
  392. (init-sqlite-module!))
  393. (storeMetadata
  394. [_this repo metadata-str]
  395. (worker-db-metadata/<store repo metadata-str))
  396. (listDB
  397. [_this]
  398. (p/let [dbs (<list-all-dbs)]
  399. (bean/->js dbs)))
  400. (createOrOpenDB
  401. [_this repo opts-str]
  402. (let [{:keys [close-other-db?] :or {close-other-db? true} :as opts} (ldb/read-transit-str opts-str)]
  403. (p/do!
  404. (when close-other-db?
  405. (close-other-dbs! repo))
  406. (create-or-open-db! repo (dissoc opts :close-other-db?)))))
  407. (getMaxTx
  408. [_this repo]
  409. (when-let [conn (worker-state/get-datascript-conn repo)]
  410. (:max-tx @conn)))
  411. (q [_this repo inputs-str]
  412. "Datascript q"
  413. (when-let [conn (worker-state/get-datascript-conn repo)]
  414. (let [inputs (ldb/read-transit-str inputs-str)
  415. result (apply d/q (first inputs) @conn (rest inputs))]
  416. (ldb/write-transit-str result))))
  417. (pull
  418. [_this repo selector-str id-str]
  419. (when-let [conn (worker-state/get-datascript-conn repo)]
  420. (let [selector (ldb/read-transit-str selector-str)
  421. id (ldb/read-transit-str id-str)
  422. eid (if (and (vector? id) (= :block/name (first id)))
  423. (:db/id (ldb/get-page @conn (second id)))
  424. id)
  425. result (some->> eid
  426. (d/pull @conn selector)
  427. (sqlite-common-db/with-parent @conn))]
  428. (ldb/write-transit-str result))))
  429. (pull-many
  430. [_this repo selector-str ids-str]
  431. (when-let [conn (worker-state/get-datascript-conn repo)]
  432. (let [selector (ldb/read-transit-str selector-str)
  433. ids (ldb/read-transit-str ids-str)
  434. result (d/pull-many @conn selector ids)]
  435. (ldb/write-transit-str result))))
  436. (get-right-sibling
  437. [_this repo db-id]
  438. (when-let [conn (worker-state/get-datascript-conn repo)]
  439. (let [result (ldb/get-right-sibling (d/entity @conn db-id))]
  440. (ldb/write-transit-str result))))
  441. (get-block-and-children
  442. [_this repo id opts]
  443. (when-let [conn (worker-state/get-datascript-conn repo)]
  444. (let [id (if (and (string? id) (common-util/uuid-string? id)) (uuid id) id)]
  445. (ldb/write-transit-str (sqlite-common-db/get-block-and-children @conn id (ldb/read-transit-str opts))))))
  446. (get-block-refs
  447. [_this repo id]
  448. (when-let [conn (worker-state/get-datascript-conn repo)]
  449. (ldb/write-transit-str (ldb/get-block-refs @conn id))))
  450. (get-block-refs-count
  451. [_this repo id]
  452. (when-let [conn (worker-state/get-datascript-conn repo)]
  453. (ldb/get-block-refs-count @conn id)))
  454. (get-block-parents
  455. [_this repo id depth]
  456. (when-let [conn (worker-state/get-datascript-conn repo)]
  457. (let [block-id (:block/uuid (d/entity @conn id))
  458. parents (->> (ldb/get-block-parents @conn block-id {:depth (or depth 3)})
  459. (map (fn [b] (d/pull @conn '[*] (:db/id b)))))]
  460. (ldb/write-transit-str parents))))
  461. (get-page-unlinked-refs
  462. [_this repo page-id search-result-eids-str]
  463. (when-let [conn (worker-state/get-datascript-conn repo)]
  464. (let [search-result-eids (ldb/read-transit-str search-result-eids-str)]
  465. (ldb/write-transit-str (ldb/get-page-unlinked-refs @conn page-id search-result-eids)))))
  466. (set-context
  467. [_this context]
  468. (let [context (if (string? context)
  469. (ldb/read-transit-str context)
  470. context)]
  471. (when context (worker-state/update-context! context))
  472. nil))
  473. (transact
  474. [_this repo tx-data tx-meta context]
  475. (when repo (worker-state/set-db-latest-tx-time! repo))
  476. (when-let [conn (worker-state/get-datascript-conn repo)]
  477. (try
  478. (let [tx-data' (if (string? tx-data)
  479. (ldb/read-transit-str tx-data)
  480. tx-data)
  481. tx-meta (if (string? tx-meta)
  482. (ldb/read-transit-str tx-meta)
  483. tx-meta)
  484. tx-data' (if (contains? #{:insert-blocks} (:outliner-op tx-meta))
  485. (map (fn [m]
  486. (if (and (map? m) (nil? (:block/order m)))
  487. (assoc m :block/order (db-order/gen-key nil))
  488. m)) tx-data')
  489. tx-data')
  490. context (if (string? context)
  491. (ldb/read-transit-str context)
  492. context)
  493. _ (when context (worker-state/set-context! context))
  494. tx-meta' (cond-> tx-meta
  495. (and (not (:whiteboard/transact? tx-meta))
  496. (not (:rtc-download-graph? tx-meta))) ; delay writes to the disk
  497. (assoc :skip-store? true)
  498. true
  499. (dissoc :insert-blocks?))]
  500. (when-not (and (:create-today-journal? tx-meta)
  501. (:today-journal-name tx-meta)
  502. (seq tx-data')
  503. (ldb/get-page @conn (:today-journal-name tx-meta))) ; today journal created already
  504. ;; (prn :debug :transact :tx-data tx-data' :tx-meta tx-meta')
  505. (worker-util/profile "Worker db transact"
  506. (ldb/transact! conn tx-data' tx-meta')))
  507. nil)
  508. (catch :default e
  509. (prn :debug :error)
  510. (let [tx-data (if (string? tx-data)
  511. (ldb/read-transit-str tx-data)
  512. tx-data)]
  513. (js/console.error e)
  514. (prn :debug :tx-data @conn tx-data))))))
  515. (getInitialData
  516. [_this repo]
  517. (when-let [conn (worker-state/get-datascript-conn repo)]
  518. (ldb/write-transit-str (sqlite-common-db/get-initial-data @conn))))
  519. (get-page-refs-count
  520. [_this repo]
  521. (when-let [conn (worker-state/get-datascript-conn repo)]
  522. (ldb/write-transit-str (sqlite-common-db/get-page->refs-count @conn))))
  523. (fetch-all-pages
  524. [_this repo exclude-page-ids-str]
  525. ;; (when-let [conn (worker-state/get-datascript-conn repo)]
  526. ;; (async/go
  527. ;; (let [all-pages (sqlite-common-db/get-all-pages @conn (ldb/read-transit-str exclude-page-ids-str))
  528. ;; partitioned-data (map-indexed (fn [idx p] [idx p]) (partition-all 2000 all-pages))]
  529. ;; (doseq [[idx tx-data] partitioned-data]
  530. ;; (worker-util/post-message :sync-db-changes {:repo repo
  531. ;; :tx-data tx-data
  532. ;; :tx-meta {:initial-pages? true
  533. ;; :end? (= idx (dec (count partitioned-data)))}})
  534. ;; (async/<! (async/timeout 100)))))
  535. ;; nil)
  536. )
  537. (closeDB
  538. [_this repo]
  539. (close-db! repo))
  540. (resetDB
  541. [_this repo db-transit]
  542. (reset-db! repo db-transit))
  543. (unsafeUnlinkDB
  544. [_this repo]
  545. (p/let [pool (<get-opfs-pool repo)
  546. _ (close-db! repo)
  547. result (remove-vfs! pool)]
  548. nil))
  549. (releaseAccessHandles
  550. [_this repo]
  551. (when-let [^js pool (worker-state/get-opfs-pool repo)]
  552. (.releaseAccessHandles pool)))
  553. (dbExists
  554. [_this repo]
  555. (<db-exists? repo))
  556. (exportDB
  557. [_this repo]
  558. (when-let [^js db (worker-state/get-sqlite-conn repo :db)]
  559. (.exec db "PRAGMA wal_checkpoint(2)"))
  560. (<export-db-file repo))
  561. (importDb
  562. [this repo data]
  563. (when-not (string/blank? repo)
  564. (p/let [pool (<get-opfs-pool repo)]
  565. (<import-db pool data))))
  566. ;; Search
  567. (search-blocks
  568. [this repo q option]
  569. (p/let [search-db (get-search-db repo)
  570. conn (worker-state/get-datascript-conn repo)
  571. result (search/search-blocks repo conn search-db q (bean/->clj option))]
  572. (ldb/write-transit-str result)))
  573. (search-upsert-blocks
  574. [this repo blocks]
  575. (p/let [db (get-search-db repo)]
  576. (search/upsert-blocks! db blocks)
  577. nil))
  578. (search-delete-blocks
  579. [this repo ids]
  580. (p/let [db (get-search-db repo)]
  581. (search/delete-blocks! db ids)
  582. nil))
  583. (search-truncate-tables
  584. [this repo]
  585. (p/let [db (get-search-db repo)]
  586. (search/truncate-table! db)
  587. nil))
  588. (search-build-blocks-indice
  589. [this repo]
  590. (when-let [conn (worker-state/get-datascript-conn repo)]
  591. (search/build-blocks-indice repo @conn)))
  592. (search-build-pages-indice
  593. [this repo]
  594. nil)
  595. (apply-outliner-ops
  596. [this repo ops-str opts-str]
  597. (when-let [conn (worker-state/get-datascript-conn repo)]
  598. (try
  599. (worker-util/profile
  600. "apply outliner ops"
  601. (let [ops (ldb/read-transit-str ops-str)
  602. opts (ldb/read-transit-str opts-str)
  603. result (outliner-op/apply-ops! repo conn ops (worker-state/get-date-formatter repo) opts)]
  604. (ldb/write-transit-str result)))
  605. (catch :default e
  606. (let [data (ex-data e)
  607. {:keys [type payload]} (when (map? data) data)]
  608. (case type
  609. :notification
  610. (worker-util/post-message type [(:message payload) (:type payload)])
  611. (throw e)))))))
  612. (file-writes-finished?
  613. [this repo]
  614. (let [conn (worker-state/get-datascript-conn repo)
  615. writes @file/*writes]
  616. ;; Clean pages that have been deleted
  617. (when conn
  618. (swap! file/*writes (fn [writes]
  619. (->> writes
  620. (remove (fn [[_ pid]] (d/entity @conn pid)))
  621. (into {})))))
  622. (if (empty? writes)
  623. true
  624. (do
  625. (prn "Unfinished file writes:" @file/*writes)
  626. false))))
  627. (page-file-saved
  628. [this request-id page-id]
  629. (file/dissoc-request! request-id)
  630. nil)
  631. (sync-app-state
  632. [this new-state-str]
  633. (let [new-state (ldb/read-transit-str new-state-str)]
  634. (worker-state/set-new-state! new-state)
  635. nil))
  636. (sync-ui-state
  637. [_this repo state-str]
  638. (undo-redo/record-ui-state! repo state-str)
  639. nil)
  640. ;; Export
  641. (block->content
  642. [this repo block-uuid-str tree->file-opts context]
  643. (assert (common-util/uuid-string? block-uuid-str))
  644. (let [block-uuid (uuid block-uuid-str)]
  645. (when-let [conn (worker-state/get-datascript-conn repo)]
  646. (common-file/block->content repo @conn block-uuid
  647. (ldb/read-transit-str tree->file-opts)
  648. (ldb/read-transit-str context)))))
  649. (get-debug-datoms
  650. [this repo]
  651. (when-let [db (worker-state/get-sqlite-conn repo)]
  652. (ldb/write-transit-str (worker-export/get-debug-datoms db))))
  653. (get-all-pages
  654. [this repo]
  655. (when-let [conn (worker-state/get-datascript-conn repo)]
  656. (ldb/write-transit-str (worker-export/get-all-pages repo @conn))))
  657. (get-all-page->content
  658. [this repo]
  659. (when-let [conn (worker-state/get-datascript-conn repo)]
  660. (ldb/write-transit-str (worker-export/get-all-page->content repo @conn))))
  661. ;; RTC
  662. (rtc-start
  663. [this repo token]
  664. (with-write-transit-str
  665. (js/Promise. (rtc-core/new-task--rtc-start repo token))))
  666. (rtc-stop
  667. [this]
  668. (rtc-core/rtc-stop))
  669. (rtc-toggle-auto-push
  670. [this]
  671. (rtc-core/rtc-toggle-auto-push))
  672. (rtc-toggle-remote-profile
  673. [this]
  674. (rtc-core/rtc-toggle-remote-profile))
  675. (rtc-grant-graph-access
  676. [this token graph-uuid target-user-uuids-str target-user-emails-str]
  677. (let [target-user-uuids (ldb/read-transit-str target-user-uuids-str)
  678. target-user-emails (ldb/read-transit-str target-user-emails-str)]
  679. (with-write-transit-str
  680. (js/Promise.
  681. (rtc-core/new-task--grant-access-to-others token graph-uuid
  682. :target-user-uuids target-user-uuids
  683. :target-user-emails target-user-emails)))))
  684. (rtc-get-graphs
  685. [this token]
  686. (with-write-transit-str
  687. (js/Promise. (rtc-core/new-task--get-graphs token))))
  688. (rtc-delete-graph
  689. [this token graph-uuid]
  690. (with-write-transit-str
  691. (js/Promise. (rtc-core/new-task--delete-graph token graph-uuid))))
  692. (rtc-get-users-info
  693. [this token graph-uuid]
  694. (with-write-transit-str
  695. (js/Promise. (rtc-core/new-task--get-users-info token graph-uuid))))
  696. (rtc-get-block-content-versions
  697. [this token graph-uuid block-uuid]
  698. (with-write-transit-str
  699. (js/Promise. (rtc-core/new-task--get-block-content-versions token graph-uuid block-uuid))))
  700. (rtc-get-debug-state
  701. [this]
  702. (with-write-transit-str
  703. (js/Promise. (rtc-core/new-task--get-debug-state))))
  704. (rtc-async-upload-graph
  705. [this repo token remote-graph-name]
  706. (with-write-transit-str
  707. (js/Promise. (rtc-core/new-task--upload-graph token repo remote-graph-name))))
  708. ;; ================================================================
  709. (rtc-request-download-graph
  710. [this token graph-uuid]
  711. (with-write-transit-str
  712. (js/Promise. (rtc-core/new-task--request-download-graph token graph-uuid))))
  713. (rtc-wait-download-graph-info-ready
  714. [this token download-info-uuid graph-uuid timeout-ms]
  715. (with-write-transit-str
  716. (js/Promise.
  717. (rtc-core/new-task--wait-download-info-ready token download-info-uuid graph-uuid timeout-ms))))
  718. (rtc-download-graph-from-s3
  719. [this graph-uuid graph-name s3-url]
  720. (with-write-transit-str
  721. (js/Promise. (rtc-core/new-task--download-graph-from-s3 graph-uuid graph-name s3-url))))
  722. (rtc-download-info-list
  723. [this token graph-uuid]
  724. (with-write-transit-str
  725. (js/Promise. (rtc-core/new-task--download-info-list token graph-uuid))))
  726. (rtc-snapshot-graph
  727. [this token graph-uuid]
  728. (with-write-transit-str
  729. (js/Promise. (rtc-core/new-task--snapshot-graph token graph-uuid))))
  730. (rtc-snapshot-list
  731. [this token graph-uuid]
  732. (with-write-transit-str
  733. (js/Promise. (rtc-core/new-task--snapshot-list token graph-uuid))))
  734. (rtc-get-graph-keys
  735. [this repo]
  736. (with-write-transit-str
  737. (worker-crypt/get-graph-keys-jwk repo)))
  738. (rtc-sync-current-graph-encrypted-aes-key
  739. [this token device-uuids-transit-str]
  740. (with-write-transit-str
  741. (js/Promise.
  742. (worker-device/new-task--sync-current-graph-encrypted-aes-key
  743. token device-uuids-transit-str))))
  744. (device-list-devices
  745. [this token]
  746. (with-write-transit-str
  747. (js/Promise. (worker-device/new-task--list-devices token))))
  748. (device-remove-device-public-key
  749. [this token device-uuid key-name]
  750. (with-write-transit-str
  751. (js/Promise. (worker-device/new-task--remove-device-public-key token device-uuid key-name))))
  752. (device-remove-device
  753. [this token device-uuid]
  754. (with-write-transit-str
  755. (js/Promise. (worker-device/new-task--remove-device token device-uuid))))
  756. (undo
  757. [_this repo _page-block-uuid-str]
  758. (when-let [conn (worker-state/get-datascript-conn repo)]
  759. (ldb/write-transit-str (undo-redo/undo repo conn))))
  760. (redo
  761. [_this repo _page-block-uuid-str]
  762. (when-let [conn (worker-state/get-datascript-conn repo)]
  763. (ldb/write-transit-str (undo-redo/redo repo conn))))
  764. (record-editor-info
  765. [_this repo _page-block-uuid-str editor-info-str]
  766. (undo-redo/record-editor-info! repo (ldb/read-transit-str editor-info-str))
  767. nil)
  768. (dangerousRemoveAllDbs
  769. [this repo]
  770. (p/let [dbs (.listDB this)]
  771. (p/all (map #(.unsafeUnlinkDB this (:name %)) dbs)))))
  772. (defn- rename-page!
  773. [repo conn page-uuid new-name]
  774. (let [config (worker-state/get-config repo)
  775. f (if (sqlite-util/db-based-graph? repo)
  776. (throw (ex-info "Rename page is a file graph only operation" {}))
  777. file-worker-page-rename/rename!)]
  778. (f repo conn config page-uuid new-name)))
  779. (defn- delete-page!
  780. [repo conn page-uuid]
  781. (let [error-handler (fn [{:keys [msg]}]
  782. (worker-util/post-message :notification
  783. [[:div [:p msg]] :error]))]
  784. (worker-page/delete! repo conn page-uuid {:error-handler error-handler})))
  785. (defn- create-page!
  786. [repo conn title options]
  787. (let [config (worker-state/get-config repo)]
  788. (worker-page/create! repo conn config title options)))
  789. (defn- outliner-register-op-handlers!
  790. []
  791. (outliner-op/register-op-handlers!
  792. {:create-page (fn [repo conn [title options]]
  793. (create-page! repo conn title options))
  794. :rename-page (fn [repo conn [page-uuid new-name]]
  795. (rename-page! repo conn page-uuid new-name))
  796. :delete-page (fn [repo conn [page-uuid]]
  797. (delete-page! repo conn page-uuid))}))
  798. (defn init
  799. "web worker entry"
  800. []
  801. (check-worker-scope!)
  802. (let [^js obj (DBWorker.)]
  803. (outliner-register-op-handlers!)
  804. (worker-state/set-worker-object! obj)
  805. (file/<ratelimit-file-writes!)
  806. (js/setInterval #(.postMessage js/self "keepAliveResponse") (* 1000 25))
  807. (Comlink/expose obj)
  808. (reset! worker-state/*main-thread (Comlink/wrap js/self))))
  809. (comment
  810. (defn <remove-all-files!
  811. "!! Dangerous: use it only for development."
  812. []
  813. (p/let [all-files (<list-all-files)
  814. files (filter #(= (.-kind %) "file") all-files)
  815. dirs (filter #(= (.-kind %) "directory") all-files)
  816. _ (p/all (map (fn [file] (.remove file)) files))]
  817. (p/all (map (fn [dir] (.remove dir)) dirs)))))