|
4 | 4 | [app.shared.specs :as specs] |
5 | 5 | ["better-sqlite3" :as sqlite] |
6 | 6 | [clojure.string :as str] |
| 7 | + [clojure.data :as data] |
7 | 8 | ["electron" :refer [app]] |
8 | 9 | ["fs" :as fs] |
9 | 10 | ["path" :as path])) |
|
12 | 13 | (def db-path (.join path (.getPath app "userData") db-name)) |
13 | 14 | (def db (sqlite. db-path)) |
14 | 15 |
|
15 | | -(defn wipe! |
16 | | - "Wipes the database and relaunches the application." |
17 | | - [] |
18 | | - (.exec db "DROP TABLE words; |
19 | | - DROP TABLE articles; |
20 | | - DROP TABLE phrases; |
21 | | - DROP TABLE settings;" #(println %)) |
22 | | - (.relaunch app) |
23 | | - (.quit app)) |
24 | 16 |
|
25 | 17 | ;; (defn remove-db-file! [] |
26 | 18 | ;; (.unlink fs db-path #(when % (prn "Failed to delete db file") %))) |
|
73 | 65 | word_ids TEXT, |
74 | 66 | language TEXT, |
75 | 67 | date_created INTEGER, |
76 | | - last_opened INTEGER |
| 68 | + last_opened INTEGER, |
| 69 | + current_page INTEGER DEFAULT 0 |
77 | 70 | ); |
78 | 71 |
|
79 | 72 | CREATE TABLE IF NOT EXISTS settings ( |
|
110 | 103 | js->clj |
111 | 104 | (get "version"))) |
112 | 105 |
|
113 | | -;; -- DB calls ----------------------------------------------------------------- |
| 106 | + |
| 107 | +(defn read-sample-file |
| 108 | + [name] |
| 109 | + (-> (.readFileSync ^:export fs (.join path js/__dirname ".." "test/sample_texts/" name) "utf8"))) |
| 110 | + |
| 111 | + |
| 112 | + |
| 113 | +;; -- DB: Settings ------------------------------------------------------------- |
| 114 | +;; Settings are handled in JSON. For better or worse: ¯\_(ツ)_/¯ |
| 115 | +;; All settings updates/inserts need to be jsonified. |
| 116 | + |
| 117 | + |
| 118 | +(defn- settings->json |
| 119 | + [s] |
| 120 | + (-> s clj->js js/JSON.stringify)) |
| 121 | + |
| 122 | +(defn- settings->edn |
| 123 | + [json-from-db] |
| 124 | + (-> json-from-db js/JSON.parse js->clj)) |
| 125 | + |
| 126 | +(defn settings-get |
| 127 | + [] |
| 128 | + (let [res (sql {:op :get :stmt "SELECT user FROM settings"})] |
| 129 | + (-> res :user settings->edn))) |
| 130 | + |
| 131 | +(defn settings-hook! |
| 132 | + "since we batch update settings as json (for better or worse)) we need to |
| 133 | + sometimes handle side-effectful things, so we diff the new settings |
| 134 | + against the old, and run 'hooks' based on what changed." |
| 135 | + [new-settings-from-fe] |
| 136 | + (let [old-settings (settings-get) |
| 137 | + ;; normalize new-settings to look like it came from the db before we diff it. |
| 138 | + new-settings (-> new-settings-from-fe settings->json settings->edn) |
| 139 | + [old-diff new-diff both] (data/diff new-settings old-settings)] |
| 140 | + (cond |
| 141 | + (contains? new-diff "page-size") |
| 142 | + (sql {:op :run :params [] :stmt "UPDATE articles SET current_page = 0"}) |
| 143 | + |
| 144 | + :else nil |
| 145 | + ))) |
| 146 | + |
| 147 | +(defn settings-update |
| 148 | + [settings] |
| 149 | + (settings-hook! settings) |
| 150 | + (sql {:op :run |
| 151 | + :stmt "UPDATE settings SET user = ? WHERE settings_id = 1" |
| 152 | + :params [(settings->json settings)]})) |
| 153 | + |
| 154 | +(defn settings-init |
| 155 | + "If there are no rows in the settings table, initialize it." |
| 156 | + [] |
| 157 | + (let [default-settings (settings->json (specs/make-default-settings trunk-version)) |
| 158 | + existing-settings (settings-get)] |
| 159 | + (when-not existing-settings |
| 160 | + (sql {:op :run |
| 161 | + :stmt "INSERT INTO settings(user) VALUES (?)" |
| 162 | + :params [default-settings]})))) |
| 163 | + |
| 164 | +;; -- DB: Articles ------------------------------------------------------------- |
114 | 165 |
|
115 | 166 | (defn article-get-by-id |
116 | 167 | [{:keys [article_id language]}] |
|
131 | 182 | :op :all})) |
132 | 183 |
|
133 | 184 | (defn article-update-last-opened |
134 | | - [{:keys [article_id]}] |
135 | | - (sql {:stmt "UPDATE articles SET last_opened = ? WHERE article_ID = ?" |
136 | | - :op :run |
137 | | - :params [(js/Date.now) article_id]})) |
| 185 | + [{:keys [article_id current_page]}] |
| 186 | + (let [current_page (if (< current_page 0) 0 current_page)] |
| 187 | + (sql {:stmt "UPDATE articles SET last_opened = ?, current_page = ? WHERE article_ID = ?" |
| 188 | + :op :run |
| 189 | + :params [(js/Date.now) current_page article_id]}))) |
138 | 190 |
|
139 | 191 | (defn article-attach-words |
140 | 192 | "When given an article, it fetches the word data for each word from the DB and |
141 | 193 | attaches it back to the article." |
142 | 194 | [article] |
143 | | - (let [word-ids (get article :word_ids) |
| 195 | + (let [page-size (get (settings-get) "page-size" 1000) ; aka 'limit' |
| 196 | + curr-page (get article :current_page 0) |
| 197 | + word-ids (get article :word_ids) |
144 | 198 | words-ids-vec (u/split-delimited-article word-ids) |
| 199 | + total-pages (/ (count words-ids-vec) page-size) |
| 200 | + ;; paginate the words we return. |
| 201 | + words-ids-slice (u/paginate-vector words-ids-vec page-size curr-page) |
145 | 202 | words-out (atom [])] |
146 | | - (doseq [word-id words-ids-vec] |
| 203 | + (doseq [word-id words-ids-slice] |
147 | 204 | (let [res (sql {:stmt "SELECT * FROM words WHERE id = ?" |
148 | 205 | :params [word-id] |
149 | 206 | :op :get})] |
150 | 207 | (swap! words-out conj res))) |
151 | | - (assoc article :word-data @words-out))) |
| 208 | + (assoc article :word-data @words-out :total-pages (js/Math.ceil total-pages))) |
| 209 | + ) |
152 | 210 |
|
153 | 211 | (defn article-insert |
154 | 212 | "Creates a new article. Requirements: |
|
286 | 344 | (assoc article :word-data @new-word-data)))) |
287 | 345 |
|
288 | 346 |
|
289 | | -;; -- DB: Settings ------------------------------------------------------------- |
290 | | -;; All settings updates/inserts need to be jsonified. |
291 | | -(defn- settings->json |
292 | | - [s] |
293 | | - (-> s clj->js js/JSON.stringify)) |
294 | 347 |
|
295 | | -(defn- settings->edn |
296 | | - [json-from-db] |
297 | | - (-> json-from-db js/JSON.parse js->clj)) |
| 348 | +;; ---------------------------------------------------------------------------------------- |
| 349 | +;; Seed fns |
298 | 350 |
|
299 | | -(defn settings-get |
| 351 | + |
| 352 | +(defn seed-article |
300 | 353 | [] |
301 | | - (let [res (sql {:op :get :stmt "SELECT user FROM settings"})] |
302 | | - (-> res :user settings->edn))) |
| 354 | + (let [ |
| 355 | + data {:article (read-sample-file "fr_compte2.txt") :title "Compte, Ch 2", :source "..", :language "fr"} |
| 356 | + _ (words-insert data) |
| 357 | + word-ids-str (words-get-ids-for-article data) |
| 358 | + inserted-article (article-insert (merge data {:word_ids word-ids-str}))]) |
| 359 | + ) |
303 | 360 |
|
304 | | -(defn settings-update |
305 | | - [settings] |
306 | | - (sql {:op :run |
307 | | - :stmt "UPDATE settings SET user = ? WHERE settings_id = 1" |
308 | | - :params [(settings->json settings)]})) |
| 361 | +(defn run-seeds |
309 | 362 |
|
310 | | -(defn settings-init |
311 | | - "If there are no rows in the settings table, initialize it." |
312 | 363 | [] |
313 | | - (let [default-settings (settings->json (specs/make-default-settings trunk-version)) |
314 | | - existing-settings (settings-get)] |
315 | | - (when-not existing-settings |
316 | | - (sql {:op :run |
317 | | - :stmt "INSERT INTO settings(user) VALUES (?)" |
318 | | - :params [default-settings]})))) |
| 364 | + (let [articles (sql {:op :get :stmt "SELECT * FROM articles" :params []}) |
| 365 | + words (sql {:op :get :stmt "SELECT * FROM words" :params []}) |
| 366 | + no-content-yet (and (= (count articles) 0) |
| 367 | + (= (count words) 0))] |
| 368 | + (when no-content-yet |
| 369 | + (seed-article)))) |
| 370 | + |
| 371 | + |
| 372 | +;; Wipe / Init ----------------------------------------------------------------------------- |
| 373 | + |
| 374 | +(defn wipe! |
| 375 | + "Wipes the database and relaunches the application." |
| 376 | + [] |
| 377 | + (.exec db "DROP TABLE words; |
| 378 | + DROP TABLE articles; |
| 379 | + DROP TABLE phrases; |
| 380 | + DROP TABLE settings;" #(println %)) |
| 381 | + (.relaunch app) |
| 382 | + (.quit app) |
| 383 | + ) |
319 | 384 |
|
320 | | -;; ----------------------------------------------------------------------------- |
321 | 385 |
|
322 | 386 | (defn init |
323 | 387 | [] |
324 | 388 | (.exec db db-seed |
325 | 389 | (fn [err] |
326 | 390 | (when err |
327 | 391 | (throw (js/Error. (str "Failed db" err)))))) |
328 | | - (settings-init)) |
| 392 | + (settings-init) |
| 393 | + (when u/debug? (run-seeds)) |
| 394 | + ) |
329 | 395 |
|
330 | 396 | ;; FIXME: when do I run "db.close()"? |
0 commit comments