From aa29a00b88107401d0d06e0af3875586e70b1c0c Mon Sep 17 00:00:00 2001 From: Junyi Du Date: Tue, 12 Apr 2022 20:18:31 +0800 Subject: [PATCH] feature: logseq protocol; refactor persistGraph --- src/electron/electron/core.cljs | 20 +++++-- src/electron/electron/fs_watcher.cljs | 1 + src/electron/electron/handler.cljs | 81 ++++++++++++++++++--------- src/electron/electron/state.cljs | 5 +- src/electron/electron/utils.cljs | 10 ++++ src/main/electron/listener.cljs | 10 +++- src/main/frontend/handler/repo.cljs | 19 ++----- 7 files changed, 98 insertions(+), 48 deletions(-) diff --git a/src/electron/electron/core.cljs b/src/electron/electron/core.cljs index 51c160b60..c598bbb5c 100644 --- a/src/electron/electron/core.cljs +++ b/src/electron/electron/core.cljs @@ -44,10 +44,22 @@ [win url] (.info logger "open-url" (str {:url url})) - (let [parsed-url (js/URL. url)] - (when (and (= (str LSP_SCHEME ":") (.-protocol parsed-url)) - (= "auth-callback" (.-host parsed-url))) - (send-to-renderer win "loginCallback" (.get (.-searchParams parsed-url) "code"))))) + (let [parsed-url (js/URL. url) + url-protocol (.-protocol parsed-url) + url-host (.-host parsed-url)] + (when (= (str LSP_SCHEME ":") url-protocol) + (cond + (= "auth-callback" url-host) + (send-to-renderer win "loginCallback" (.get (.-searchParams parsed-url) "code")) + + (= "local" url-host) + (let [[graph page block-id] (utils/get-URL-decoded-params parsed-url ["graph" "page" "block-id"]) + graph-name (handler/get-graph-name graph)] + (if graph-name + (utils/send-to-renderer win "openNewWindowOfGraph" graph-name) + (utils/send-to-renderer "notification" {:type "error" + :payload (str "Cannot match graph name " graph + " to any linked graph.")}))))))) (defn setup-interceptor! [^js app] (.setAsDefaultProtocolClient app LSP_SCHEME) diff --git a/src/electron/electron/fs_watcher.cljs b/src/electron/electron/fs_watcher.cljs index 2efbba384..443aef69e 100644 --- a/src/electron/electron/fs_watcher.cljs +++ b/src/electron/electron/fs_watcher.cljs @@ -36,6 +36,7 @@ :stat (fs/statSync path)})) (defn watch-dir! + "Watch a directory if no such file watcher exists" [_win dir] (when (and (fs/existsSync dir) (not (get @*file-watcher dir))) diff --git a/src/electron/electron/handler.cljs b/src/electron/electron/handler.cljs index 02babbcd2..72d340a89 100644 --- a/src/electron/electron/handler.cljs +++ b/src/electron/electron/handler.cljs @@ -209,6 +209,7 @@ dir)) (defn- get-graphs + "Returns all graph names in the cache directory (strating with `logseq_local_`)" [] (let [dir (get-graphs-dir)] (->> (readdir dir) @@ -216,6 +217,15 @@ (map #(path/basename % ".transit")) (map graph-name->path)))) +(defn get-graph-name + "Given a graph's name, returns the graph's fullname. + E.g., given `cat`, returns `logseq_local_/cat` + Returns `nil` if no such graph exists." + [graph] + ;; FIXME: path-normalize for electron? + (->> (get-graphs) + (some #(when (string/ends-with? % (str "/" graph)) %)))) + (defmethod handle :getGraphs [_window [_]] (get-graphs)) @@ -326,18 +336,22 @@ (defn close-watcher-when-orphaned! "When it's the last window for the directory, close the watcher." - [window dir] - (when (not (win/graph-has-other-windows? window dir)) - (watcher/close-watcher! dir))) + [window graph-path] + (when (not (win/graph-has-other-windows? window graph-path)) + (watcher/close-watcher! graph-path))) -(defmethod handle :setCurrentGraph [^js win [_ path]] - (let [path (when path (utils/get-graph-dir path)) - old-path (state/get-window-graph-path win)] - (when old-path (close-watcher-when-orphaned! win old-path)) - (swap! state/state assoc :graph/current path) - (swap! state/state assoc-in [:window/graph win] path) +(defn set-current-graph! + [window graph-path] + (let [old-path (state/get-window-graph-path window)] + (when old-path (close-watcher-when-orphaned! window old-path)) + (swap! state/state assoc :graph/current graph-path) + (swap! state/state assoc-in [:window/graph window] graph-path) nil)) +(defmethod handle :setCurrentGraph [^js window [_ graph-name]] + (when graph-name + (set-current-graph! window (utils/get-graph-dir graph-name)))) + (defmethod handle :runGit [_ [_ args]] (when (seq args) (git/raw! args))) @@ -375,26 +389,30 @@ (defmethod handle :graphHasMultipleWindows [^js _win [_ graph]] (let [dir (utils/get-graph-dir graph) windows (win/get-graph-all-windows dir)] - ;; windows (filter #(.isVisible %) windows) ;; for mac .hide windows. such windows should also included (> (count windows) 1))) (defmethod handle :addDirWatcher [^js window [_ dir]] ;; receive dir path (not repo / graph) from frontend ;; Windows on same dir share the same watcher ;; Only close file watcher when: - ;; 1. there is no one window on the same dir (TODO: check this on a window is closed) + ;; 1. there is no one window on the same dir ;; 2. reset file watcher to resend `add` event on window refreshing (when dir ;; adding dir watcher when the window has watcher already - must be cmd + r refreshing - ;; TODO: handle duplicated adding dir watcher when multiple windows + ;; maintain only one file watcher when multiple windows on the same dir (close-watcher-when-orphaned! window dir) (watcher/watch-dir! window dir))) -(defmethod handle :openNewWindow [_window [_]] +(defn open-new-window! + [] (let [win (win/create-main-window)] (win/on-close-actions! win close-watcher-when-orphaned!) (win/setup-window-listeners! win) - nil)) + win)) + +(defmethod handle :openNewWindow [_window [_]] + (open-new-window!) + nil) (defmethod handle :searchVersionChanged? [^js _win [_ graph]] @@ -443,21 +461,28 @@ (defmethod handle :default [args] (println "Error: no ipc handler for: " (bean/->js args))) -(defmethod handle :persistGraph [^js win [_ graph]] - ;; call a window holds the specific graph to persist - (let [dir (utils/get-graph-dir graph) - windows (win/get-graph-all-windows dir) - ;; windows (filter #(.isVisible %) windows) ;; for mac .hide windows. such windows should also included - tar-graph-win (first windows)] - (if tar-graph-win - (utils/send-to-renderer tar-graph-win "persistGraph" graph) - (utils/send-to-renderer win "persistGraphDone" graph)))) ;; if no such graph, skip directly +(defn broadcast-persist-graph! + "Sends persist graph event to the renderer contains the target graph. + Returns a promise." + [graph-name] + (p/create (fn [resolve _reject] + (let [graph-path (utils/get-graph-dir graph-name) + windows (win/get-graph-all-windows graph-path) + tar-graph-win (first windows)] + (if tar-graph-win + ;; if no such graph, skip directly + (do (state/set-state! :window/once-persist-done #(resolve nil)) + (utils/send-to-renderer tar-graph-win "persistGraph" graph-name)) + (resolve nil)))))) -(defmethod handle :persistGraphDone [^js _win [_ graph]] - ;; when graph is persisted, broadcast it to all windows - (let [windows (win/get-all-windows)] - (doseq [window windows] - (utils/send-to-renderer window "persistGraphDone" graph)))) +(defmethod handle :broadcastPersistGraph [^js _win [_ graph-name]] + (broadcast-persist-graph! graph-name)) + +(defmethod handle :broadcastPersistGraphDone [^js _win [_]] + ;; main process -> renderer doesn't support promise, so we use a global var to store the callback + (when-let [f (:window/once-persist-done @state/state)] + (f) + (state/set-state! :window/once-persist-done nil))) (defn set-ipc-handler! [window] (let [main-channel "main"] diff --git a/src/electron/electron/state.cljs b/src/electron/electron/state.cljs index 19fad2846..682d5cafb 100644 --- a/src/electron/electron/state.cljs +++ b/src/electron/electron/state.cljs @@ -14,7 +14,10 @@ :graph/current nil ;; window -> current graph - :window/graph {}})) + :window/graph {} + + ;; job to do when persistGraph is done on renderer + :window/once-persist-done nil})) (defn set-state! [path value] diff --git a/src/electron/electron/utils.cljs b/src/electron/electron/utils.cljs index 1ac17177e..6399aa8d2 100644 --- a/src/electron/electron/utils.cljs +++ b/src/electron/electron/utils.cljs @@ -123,3 +123,13 @@ (defn get-graph-dir [graph-name] (string/replace graph-name "logseq_local_" "")) + +(defn get-URL-decoded-params + "Get decoded URL parameters from parsed js/URL. + `nil` for non-existing keys." + [^js parsed-url keys] + (let [params (.-searchParams parsed-url)] + (map (fn [key] + (when-let [value (.get params key)] + (js/decodeURI value))) + keys))) diff --git a/src/main/electron/listener.cljs b/src/main/electron/listener.cljs index f0f8593a2..a8403ac52 100644 --- a/src/main/electron/listener.cljs +++ b/src/main/electron/listener.cljs @@ -78,12 +78,13 @@ (js/window.apis.on "persistGraph" ;; electron is requesting window for persisting a graph in it's db + ;; fire back "broadcastPersistGraphDone" on done (fn [data] (let [repo (bean/->clj data) before-f #(notification/show! (ui/loading (t :graph/persist)) :warning) - after-f #(ipc/ipc "persistGraphDone" repo) + after-f #(ipc/ipc "broadcastPersistGraphDone") error-f (fn [] (after-f) (notification/show! @@ -96,7 +97,12 @@ (js/window.apis.on "loginCallback" (fn [code] - (user/login-callback code)))) + (user/login-callback code))) + + (js/window.apis.on "openNewWindowOfGraph" + ;; Handle open new window in renderer, until the destination graph doesn't rely on setting local storage + (fn [repo] + (ui-handler/open-new-window! nil repo)))) (defn listen! [] diff --git a/src/main/frontend/handler/repo.cljs b/src/main/frontend/handler/repo.cljs index f19e9cda0..4e68f7946 100644 --- a/src/main/frontend/handler/repo.cljs +++ b/src/main/frontend/handler/repo.cljs @@ -670,17 +670,10 @@ "Only works for electron Call backend to handle persisting a specific db on other window Skip persisting if no other windows is open (controlled by electron) - step 1. [In HERE] a window --persistGraph-----> electron - step 2. electron --persistGraph-----> window holds the graph - step 3. window w/ graph --persistGraphDone-> electron - step 4. [In HERE] electron --persistGraphDone-> all windows" + step 1. [In HERE] a window ---broadcastPersistGraph----> electron + step 2. electron ---------persistGraph-------> window holds the graph + step 3. window w/ graph --broadcastPersistGraphDone-> electron + step 4. [In HERE] a window <---broadcastPersistGraph---- electron" [graph] - (p/create (fn [resolve _] - (js/window.apis.on "persistGraphDone" - #(let [repo (bean/->clj %)] - (prn "received persistGraphDone" repo) - (when (= graph repo) - ;; js/window.apis.once doesn't work - (js/window.apis.removeAllListeners "persistGraphDone") - (resolve repo)))) - (ipc/ipc "persistGraph" graph)))) + (p/let [_ (ipc/ipc "broadcastPersistGraph" graph)] ;; invoke for chaining promise + nil))