db_worker.cljs 34 KB

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