mirror of
https://github.com/logseq/logseq.git
synced 2026-05-16 08:52:20 +00:00
140 lines
5.9 KiB
Clojure
140 lines
5.9 KiB
Clojure
(ns frontend.handler.plugin-config
|
|
"This system component encapsulates the global plugin.edn and depends on the
|
|
global-config component. This component is only enabled? if both the
|
|
global-config and plugin components are enabled. plugins.edn is automatically updated
|
|
when a plugin is installed, updated or removed"
|
|
(:require [borkdude.rewrite-edn :as rewrite]
|
|
[cljs-bean.core :as bean]
|
|
[clojure.edn :as edn]
|
|
[clojure.pprint :as pprint]
|
|
[clojure.set :as set]
|
|
[frontend.fs :as fs]
|
|
[frontend.context.i18n :refer [t]]
|
|
[frontend.handler.common.plugin :as plugin-common-handler]
|
|
[frontend.handler.global-config :as global-config-handler]
|
|
[frontend.handler.notification :as notification]
|
|
[frontend.schema.handler.plugin-config :as plugin-config-schema]
|
|
[frontend.state :as state]
|
|
[frontend.util :as util]
|
|
[lambdaisland.glogi :as log]
|
|
[logseq.common.path :as path]
|
|
[malli.core :as m]
|
|
[malli.error :as me]
|
|
[promesa.core :as p]))
|
|
|
|
(defn plugin-config-path
|
|
"Full path to plugins.edn"
|
|
[]
|
|
(path/path-join (global-config-handler/global-config-dir) "plugins.edn"))
|
|
|
|
(def common-plugin-keys
|
|
"Vec of plugin keys to store in plugins.edn and to compare with installed-plugins state"
|
|
(->> plugin-config-schema/Plugin rest (mapv first)))
|
|
|
|
(defn add-or-update-plugin
|
|
"Adds or updates a plugin from plugins.edn"
|
|
[{:keys [id] :as plugin}]
|
|
(p/let [content (fs/read-file nil (plugin-config-path))
|
|
updated-content (-> content
|
|
rewrite/parse-string
|
|
(rewrite/assoc (keyword id) (select-keys plugin common-plugin-keys))
|
|
str)]
|
|
(fs/write-file! (plugin-config-path) updated-content)))
|
|
|
|
(defn remove-plugin
|
|
"Removes a plugin from plugins.edn"
|
|
[plugin-id]
|
|
(p/let [content (fs/read-file nil (plugin-config-path))
|
|
updated-content (-> content rewrite/parse-string (rewrite/dissoc (keyword plugin-id)) str)]
|
|
(fs/write-file! (plugin-config-path) updated-content)))
|
|
|
|
(defn- create-plugin-config-file-if-not-exists
|
|
[]
|
|
(let [content (-> (:plugin/installed-plugins @state/state)
|
|
(update-vals #(select-keys % common-plugin-keys))
|
|
pprint/pprint
|
|
with-out-str)]
|
|
(fs/create-if-not-exists (state/get-current-repo) nil (plugin-config-path) content)))
|
|
|
|
(defn- determine-plugins-to-change
|
|
"Given installed plugins state and plugins from plugins.edn,
|
|
returns map of plugins to install and uninstall"
|
|
[installed-plugins edn-plugins]
|
|
(let [installed-plugins-set (->> installed-plugins
|
|
vals
|
|
(map #(-> (select-keys % common-plugin-keys)
|
|
(assoc :id (keyword (:id %)))))
|
|
set)
|
|
edn-plugins-set (->> edn-plugins
|
|
(map (fn [[k v]] (assoc v :id k)))
|
|
set)]
|
|
(if (= installed-plugins-set edn-plugins-set)
|
|
{}
|
|
{:install (mapv #(assoc % :plugin-action "install")
|
|
(set/difference edn-plugins-set installed-plugins-set))
|
|
:uninstall (vec (set/difference installed-plugins-set edn-plugins-set))})))
|
|
|
|
(defn open-install-plugin-from-github-modal
|
|
[]
|
|
(state/pub-event! [:go/install-plugin-from-github]))
|
|
|
|
(defn open-replace-plugins-modal
|
|
[]
|
|
(p/catch
|
|
(p/let [edn-plugins* (fs/read-file nil (plugin-config-path))
|
|
edn-plugins (edn/read-string edn-plugins*)]
|
|
(if-let [errors (->> edn-plugins (m/explain plugin-config-schema/Plugins-edn) me/humanize)]
|
|
(do
|
|
(notification/show! (t :plugin/invalid-plugins-edn)
|
|
:error)
|
|
(log/error :plugin-edn-errors errors))
|
|
(let [plugins-to-change (determine-plugins-to-change
|
|
(:plugin/installed-plugins @state/state)
|
|
edn-plugins)]
|
|
(state/pub-event! [:go/plugins-from-file plugins-to-change]))))
|
|
(fn [e]
|
|
(if (= :reader-exception (:type (ex-data e)))
|
|
(notification/show! (t :plugin/malformed-plugins-edn)
|
|
:error)
|
|
(log/error :unexpected-error e)))))
|
|
|
|
(defn replace-plugins
|
|
"Replaces current plugins given plugins to install and uninstall"
|
|
[plugins]
|
|
(log/info :uninstall-plugins (:uninstall plugins))
|
|
(doseq [plugin (:uninstall plugins)]
|
|
(plugin-common-handler/unregister-plugin (name (:id plugin))))
|
|
(log/info :install-plugins (:install plugins))
|
|
(doseq [plugin (:install plugins)]
|
|
(plugin-common-handler/install-marketplace-plugin!
|
|
;; Add :name so that install notifications are readable
|
|
(assoc plugin :name (name (:id plugin))))))
|
|
|
|
(defn setup-install-listener!
|
|
"Sets up a listener for the lsp-installed event to update plugins.edn"
|
|
[]
|
|
(let [channel (name :lsp-updates)
|
|
listener (fn listener [_ e]
|
|
(when-let [{:keys [status payload only-check]} (bean/->clj e)]
|
|
(when (and (= status "completed") (not only-check))
|
|
(let [{:keys [theme effect]} payload]
|
|
(add-or-update-plugin
|
|
(assoc payload
|
|
:version (:installed-version payload)
|
|
:effect (boolean effect)
|
|
;; Manual installation doesn't have theme field but
|
|
;; plugin.edn requires this field
|
|
:theme (boolean theme)))))))]
|
|
(when (util/electron?)
|
|
(js/window.apis.addListener channel listener))
|
|
;;teardown
|
|
(fn []
|
|
(when (util/electron?)
|
|
(js/window.apis.removeListener channel listener)))))
|
|
|
|
(defn start
|
|
"This component has just one responsibility on start, to create a plugins.edn
|
|
if none exists"
|
|
[]
|
|
(create-plugin-config-file-if-not-exists))
|