| 
					
				 | 
			
			
				@@ -0,0 +1,138 @@ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+(ns electron.db 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  "SQLite db" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  (:require ["path" :as node-path] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            ["fs-extra" :as fs] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            ["better-sqlite3" :as sqlite3] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            [clojure.string :as string] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            ["electron" :refer [app]] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            [electron.logger :as logger] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            [medley.core :as medley] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            [electron.utils :as utils] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            [cljs-bean.core :as bean])) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+;; use built-in blocks to represent db schema, config, custom css, custom js, etc. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+(defonce databases (atom nil)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+(defn close! 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  [] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  (when @databases 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    (doseq [[_ database] @databases] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      (.close database)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    (reset! databases nil))) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+(defn sanitize-db-name 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  [db-name] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  (-> db-name 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      (string/replace "/" "_") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      (string/replace "\\" "_") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      (string/replace ":" "_"))) ;; windows 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+(defn get-db 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  [repo] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  (get @databases (sanitize-db-name repo))) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+(declare delete-db!) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+(defn prepare 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  [^object db sql db-name] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  (when db 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    (try 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      (.prepare db sql) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      (catch :default e 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        (logger/error (str "SQLite prepare failed: " e ": " db-name)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        (throw e))))) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+(defn create-blocks-table! 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  [db db-name] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  (let [stmt (prepare db "CREATE TABLE IF NOT EXISTS blocks ( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        id INTEGER PRIMARY KEY, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        page INTEGER, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        name TEXT, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        uuid TEXT NOT NULL, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        content TEXT, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        serialized_edn TEXT, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        journal_day INTEGER, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        core_data INTEGER, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        created_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        updated_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        )" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                      db-name)] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    (.run ^object stmt))) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+;; ~/logseq 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+(defn get-graphs-dir 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  [] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  (let [path (.getPath ^object app "home")] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    (node-path/join path "logseq" "graphs"))) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+(defn ensure-graphs-dir! 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  [] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  (fs/ensureDirSync (get-graphs-dir))) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+(defn get-db-full-path 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  [db-name] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  (let [db-name (sanitize-db-name db-name) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        dir (get-graphs-dir)] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    [db-name (node-path/join dir db-name)])) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+(defn open-db! 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  [db-name] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  (let [[db-sanitized-name db-full-path] (get-db-full-path db-name)] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    (try (let [db (sqlite3 db-full-path nil)] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+           (create-blocks-table! db db-name) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+           (swap! databases assoc db-sanitized-name db)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+         (catch :default e 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+           (logger/error (str e ": " db-name)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+           ;; (fs/unlinkSync db-full-path) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+           )))) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+(defn- clj-list->sql 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  "Turn clojure list into SQL list 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   '(1 2 3 4) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   -> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   \"('1','2','3','4')\"" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  [ids] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  (str "(" (->> (map (fn [id] (str "'" id "'")) ids) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                (string/join ", ")) ")")) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+(defn upsert-blocks! 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  [repo blocks] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  (if-let [db (get-db repo)] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    (let [insert (prepare db "INSERT INTO blocks (id, page, name, uuid, content, serialized_edn, journal_day, core_data, created_at, updated_at) VALUES (@id, @page, @name, @uuid, @content, @serialized_edn, @journal_day, @core_data, @created_at, @updated_at) ON CONFLICT (id) DO UPDATE SET (page, name, uuid, content, serialized_edn, journal_day, core_data, created_at, updated_at) = (@page, @name, @uuid, @content, @serialized_edn, @journal_day, @core_data, @created_at, @updated_at)" repo) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          insert-many (.transaction ^object db 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                                    (fn [blocks] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                                      (doseq [block blocks] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                                        (.run ^object insert block))))] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      (insert-many blocks)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    (do 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      (open-db! repo) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      (upsert-blocks! repo blocks)))) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+(defn delete-blocks! 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  [repo ids] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  (when-let [db (get-db repo)] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    (let [sql (str "DELETE from blocks WHERE id IN " (clj-list->sql ids)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          stmt (prepare db sql repo)] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      (.run ^object stmt)))) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+;; Initial data: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+;; All pages and block ids 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+;; latest 3 journals 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+;; core data such as config, custom css/js 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+;; current page, sidebar blocks 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+(defn get-initial-data! 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  [repo] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  (when-let [db (get-db repo)] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    (let [sql "select * from blocks" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          stmt (prepare db sql repo)] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      (.all ^object stmt)))) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+(defn get-all-data 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  [repo] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  (when-let [db (get-db repo)] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    (let [sql "select * from blocks" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          stmt (prepare db sql repo)] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      (.all ^object stmt)))) 
			 |