Browse Source

Graph parser adds page metadata to a fresh db

Gabriel Horner 3 years ago
parent
commit
6713d1230f
1 changed files with 253 additions and 0 deletions
  1. 253 0
      src/main/frontend/handler/graph_parser.cljs

+ 253 - 0
src/main/frontend/handler/graph_parser.cljs

@@ -0,0 +1,253 @@
+(ns frontend.handler.graph-parser
+  (:require [clojure.string :as string]
+            [clojure.set :as set]
+            [clojure.edn :as edn]
+            [datascript.core :as d]
+            ["fs" :as fs]
+            ["child_process" :as child-process]
+            [frontend.db-schema :as db-schema]
+            ;; Disable for now since kondo can't pick it up
+            ; #?(:org.babashka/nbb [nbb.core :as nbb])
+            [nbb.core :as nbb]))
+
+;; Helpers
+;; =======
+
+(defn- fline [and-form] (:line (meta and-form)))
+
+(defmacro -spy [line name expr]
+  `(let [result# ~expr
+         line-str# ~(str "Line " line ":")]
+     ;; Subject to elision:
+     ;; (log* ~config ~level ~name "=>" result#) ; CLJ-865
+     (apply println line-str# [~name "=>" result#])
+
+     ;; NOT subject to elision:
+     result#))
+
+(defmacro spy
+  "Modified version of taoensso.timbre/spy"
+  [expr] `(-spy ~(fline &form) '~expr ~expr))
+
+(defn slurp
+  "Like clojure.core/slurp"
+  [file]
+  (str (fs/readFileSync file)))
+
+(defn sh
+  "Run shell cmd synchronously and print to inherited streams by default. Aims
+  to be similar to babashka.tasks/shell"
+  [cmd opts]
+  (child-process/spawnSync (first cmd)
+                           (clj->js (rest cmd))
+                           (clj->js (merge {:stdio "inherit"} opts))))
+
+;; From other namespace
+;; =====
+(defn get-all-pages
+  [db]
+  (d/q
+   '[:find [(pull ?page [*]) ...]
+     :where
+     [?page :block/name]]
+   db))
+
+(defn get-file
+  ([db path]
+   (when-let [conn db]
+     (:file/content (d/entity conn [:file/path path])))))
+
+(defn safe-read-string
+  [content error-message-or-handler]
+  (try
+    ;; reader/read-string
+    (edn/read-string content)
+    (catch :default e
+      (js/console.error e)
+      (if (fn? error-message-or-handler)
+        (error-message-or-handler e)
+        (println error-message-or-handler))
+      {})))
+
+#_(defn alter-file
+  [repo path content {:keys [reset? re-render-root? from-disk? skip-compare? new-graph?]
+                      :or {reset? true
+                           re-render-root? false
+                           from-disk? false
+                           skip-compare? false}}]
+  (let [original-content (get-file repo path)
+        write-file! (if from-disk?
+                      #(p/resolved nil)
+                      #(fs/write-file! repo (config/get-repo-dir repo) path content
+                                       (assoc (when original-content {:old-content original-content})
+                                              :skip-compare? skip-compare?)))]
+    (if reset?
+      (do
+        (when-let [page-id (db/get-file-page-id path)]
+          (db/transact! repo
+            [[:db/retract page-id :block/alias]
+             [:db/retract page-id :block/tags]]))
+        (reset-file! repo path content new-graph?))
+      (db/set-file-content! repo path content))
+    (util/p-handle (write-file!)
+                   (fn [_]
+                     (when (= path (config/get-config-path repo))
+                       (restore-config! repo true))
+                     (when (= path (config/get-custom-css-path repo))
+                       (ui-handler/add-style-if-exists!))
+                     (when re-render-root? (ui-handler/re-render-root!)))
+                   (fn [error]
+                     (println "Write file failed, path: " path ", content: " content)
+                     (log/error :write/failed error)))))
+
+(defn normalize
+  [format]
+  (case (keyword format)
+    :md :markdown
+    :asciidoc :adoc
+    ;; default
+    (keyword format)))
+(defn get-format
+  [file]
+  (when file
+    (normalize (keyword (string/lower-case (last (string/split file #"\.")))))))
+
+;; Main handler.repo parse fns
+;; ====
+(defonce local-db-prefix "logseq_local_")
+
+(defn get-local-dir
+  [s]
+  (string/replace s local-db-prefix ""))
+
+(defn get-repo-dir
+  [repo-url]
+  (cond
+    true
+    #_(and (util/electron?) (local-db? repo-url))
+    (get-local-dir repo-url)
+
+    #_(and (mobile-util/is-native-platform?) (local-db? repo-url))
+    #_(let [dir (get-local-dir repo-url)]
+      (if (string/starts-with? dir "file:")
+        dir
+        (str "file:///" (string/replace dir #"^/+" ""))))
+
+    :else
+    (str "/"
+         (->> (take-last 2 (string/split repo-url #"/"))
+              (string/join "_")))))
+
+(defn path-normalize
+  "Normalize file path (for reading paths from FS, not required by writting)"
+  [s]
+  (.normalize s "NFC"))
+
+(defn get-file-path
+  "Normalization happens here"
+  [repo-url relative-path]
+  (when (and repo-url relative-path)
+    (let [path (cond
+                 true
+                 #_(and (util/electron?) (local-db? repo-url))
+                 (let [dir (get-repo-dir repo-url)]
+                   (if (string/starts-with? relative-path dir)
+                     relative-path
+                     (str dir "/"
+                          (string/replace relative-path #"^/" ""))))
+
+                 (= "/" (first relative-path))
+                 (subs relative-path 1)
+
+                 :else
+                 relative-path)]
+      (path-normalize path))))
+
+(def app-name "logseq")
+(def pages-metadata-file "pages-metadata.edn")
+
+(defn get-pages-metadata-path
+  [repo]
+  (when repo
+    (get-file-path repo (str app-name "/" pages-metadata-file))))
+
+(defn- load-pages-metadata!
+  "force?: if set true, skip the metadata timestamp range check"
+  [conn repo _file-paths files _force?]
+  (try
+    (let [file (get-pages-metadata-path repo)]
+      (when-let [content (some #(when (= (:file/path %) file) (:file/content %)) files)]
+        (let [metadata (safe-read-string content "Parsing pages metadata file failed: ")
+              ;; pages (get-all-pages repo)
+              ;; pages (zipmap (map :block/name pages) pages)
+              metadata (->> metadata
+                            #_(filter (fn [{:block/keys [name created-at updated-at]}]
+                                        (when-let [page (get pages name)]
+                                          (and
+                                           (>= updated-at created-at) ;; metadata validation
+                                           (or force? ;; when force is true, shortcut timestamp range check
+                                               (and (or (nil? (:block/created-at page))
+                                                        (>= created-at (:block/created-at page)))
+                                                    (or (nil? (:block/updated-at page))
+                                                        (>= updated-at (:block/created-at page)))))
+                                           (or ;; persistent metadata is the gold standard
+                                               (not= created-at (:block/created-at page))
+                                               (not= updated-at (:block/created-at page)))))))
+                            (remove nil?))]
+          (when (seq metadata)
+            (d/transact! conn metadata)))))
+    (catch js/Error e
+      (prn e)
+      #_(log/error :exception e))))
+
+(defonce mldoc-support-formats
+  #{:org :markdown :md})
+
+(defn parse
+  "From parse-files-and-create-default-files-inner!"
+  [conn repo-url files file-paths]
+  (let [support-files (filter
+                       (fn [file]
+                         (let [format (get-format (:file/path file))]
+                           (contains? (set/union #{:edn :css} mldoc-support-formats) format)))
+                       files)
+        _support-files (sort-by :file/path support-files)]
+    ;; TODO: Add file content
+    #_(doseq [file support-files]
+        (alter-file repo-url
+                    (:file/path file)
+                    (:file/content file)
+                    {:new-graph? (:new-graph? opts)
+                     :re-render-root? false
+                     :from-disk? true
+                     :metadata metadata}))
+    (load-pages-metadata! conn repo-url file-paths files true)))
+
+
+(defn db-start
+  ;; TODO: Add frontend.db.default/built-in-pages
+  []
+  (let [db-conn (d/create-conn db-schema/schema)]
+    (d/transact! db-conn [{:schema/version db-schema/version}])
+    db-conn))
+
+(defn -main
+  [args]
+  (let [conn (db-start)
+        repo-dir (or (first args)
+                     (throw (ex-info "Directory required" {})))
+        files (->> (str (.-stdout (sh ["git" "ls-files"]
+                                      {:cwd repo-dir :stdio nil})))
+                   string/split-lines
+                   (filter #(re-find #"^(pages|assets|journals|logseq)" %))
+                   (map #(str repo-dir "/" %)))
+        file-maps (mapv #(hash-map :file/path % :file/content (slurp %)) files)]
+    (parse conn
+           (str "logseq_local_" repo-dir)
+           file-maps
+           files)
+    (prn (d/q '[:find ?n :where [?b :block/name ?n]]
+              @conn))))
+
+(when (= nbb/*file* (:file (meta #'-main)))
+  (-main *command-line-args*))