plugin_config.cljs 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. (ns frontend.handler.plugin-config
  2. "This system component encapsulates the global plugin.edn and depends on the
  3. global-config component. This component is only enabled? if both the
  4. global-config and plugin components are enabled. plugin.edn is automatically updated
  5. when a plugin is installed, updated or removed"
  6. (:require [borkdude.rewrite-edn :as rewrite]
  7. [cljs-bean.core :as bean]
  8. [clojure.edn :as edn]
  9. [clojure.pprint :as pprint]
  10. [clojure.set :as set]
  11. [frontend.fs :as fs]
  12. [frontend.handler.common.plugin :as plugin-common-handler]
  13. [frontend.handler.global-config :as global-config-handler]
  14. [frontend.handler.notification :as notification]
  15. [frontend.schema.handler.plugin-config :as plugin-config-schema]
  16. [frontend.state :as state]
  17. [frontend.util :as util]
  18. [lambdaisland.glogi :as log]
  19. [logseq.common.path :as path]
  20. [malli.core :as m]
  21. [malli.error :as me]
  22. [promesa.core :as p]))
  23. (defn plugin-config-path
  24. "Full path to plugins.edn"
  25. []
  26. (path/path-join (global-config-handler/global-config-dir) "plugins.edn"))
  27. (def common-plugin-keys
  28. "Vec of plugin keys to store in plugins.edn and to compare with installed-plugins state"
  29. (->> plugin-config-schema/Plugin rest (mapv first)))
  30. (defn add-or-update-plugin
  31. "Adds or updates a plugin from plugin.edn"
  32. [{:keys [id] :as plugin}]
  33. (p/let [content (fs/read-file nil (plugin-config-path))
  34. updated-content (-> content
  35. rewrite/parse-string
  36. (rewrite/assoc (keyword id) (select-keys plugin common-plugin-keys))
  37. str)]
  38. ;; fs protocols require repo and dir when they aren't necessary. For this component,
  39. ;; neither is needed so these are blank and nil respectively
  40. (fs/write-plain-text-file! "" nil (plugin-config-path) updated-content {:skip-compare? true})))
  41. (defn remove-plugin
  42. "Removes a plugin from plugin.edn"
  43. [plugin-id]
  44. (p/let [content (fs/read-file "" (plugin-config-path))
  45. updated-content (-> content rewrite/parse-string (rewrite/dissoc (keyword plugin-id)) str)]
  46. (fs/write-plain-text-file! "" nil (plugin-config-path) updated-content {:skip-compare? true})))
  47. (defn- create-plugin-config-file-if-not-exists
  48. []
  49. (let [content (-> (:plugin/installed-plugins @state/state)
  50. (update-vals #(select-keys % common-plugin-keys))
  51. pprint/pprint
  52. with-out-str)]
  53. (fs/create-if-not-exists "" nil (plugin-config-path) content)))
  54. (defn- determine-plugins-to-change
  55. "Given installed plugins state and plugins from plugins.edn,
  56. returns map of plugins to install and uninstall"
  57. [installed-plugins edn-plugins]
  58. (let [installed-plugins-set (->> installed-plugins
  59. vals
  60. (map #(-> (select-keys % common-plugin-keys)
  61. (assoc :id (keyword (:id %)))))
  62. set)
  63. edn-plugins-set (->> edn-plugins
  64. (map (fn [[k v]] (assoc v :id k)))
  65. set)]
  66. (if (= installed-plugins-set edn-plugins-set)
  67. {}
  68. {:install (mapv #(assoc % :plugin-action "install")
  69. (set/difference edn-plugins-set installed-plugins-set))
  70. :uninstall (vec (set/difference installed-plugins-set edn-plugins-set))})))
  71. (defn open-install-plugin-from-github-modal
  72. []
  73. (state/pub-event! [:go/install-plugin-from-github]))
  74. (defn open-replace-plugins-modal
  75. []
  76. (p/catch
  77. (p/let [edn-plugins* (fs/read-file nil (plugin-config-path))
  78. edn-plugins (edn/read-string edn-plugins*)]
  79. (if-let [errors (->> edn-plugins (m/explain plugin-config-schema/Plugins-edn) me/humanize)]
  80. (do
  81. (notification/show! "Invalid plugins.edn provided. See javascript console for specific errors"
  82. :error)
  83. (log/error :plugin-edn-errors errors)
  84. (println "Invalid plugins.edn, errors: " errors))
  85. (let [plugins-to-change (determine-plugins-to-change
  86. (:plugin/installed-plugins @state/state)
  87. edn-plugins)]
  88. (state/pub-event! [:go/plugins-from-file plugins-to-change]))))
  89. (fn [e]
  90. (if (= :reader-exception (:type (ex-data e)))
  91. (notification/show! "Malformed plugins.edn provided. Please check the file has correct edn syntax."
  92. :error)
  93. (log/error :unexpected-error e)))))
  94. (defn replace-plugins
  95. "Replaces current plugins given plugins to install and uninstall"
  96. [plugins]
  97. (log/info :uninstall-plugins (:uninstall plugins))
  98. (doseq [plugin (:uninstall plugins)]
  99. (plugin-common-handler/unregister-plugin (name (:id plugin))))
  100. (log/info :install-plugins (:install plugins))
  101. (doseq [plugin (:install plugins)]
  102. (plugin-common-handler/install-marketplace-plugin!
  103. ;; Add :name so that install notifications are readable
  104. (assoc plugin :name (name (:id plugin))))))
  105. (defn setup-install-listener!
  106. "Sets up a listener for the lsp-installed event to update plugins.edn"
  107. []
  108. (let [channel (name :lsp-updates)
  109. listener (fn listener [_ e]
  110. (when-let [{:keys [status payload only-check]} (bean/->clj e)]
  111. (when (and (= status "completed") (not only-check))
  112. (let [{:keys [theme effect]} payload]
  113. (add-or-update-plugin
  114. (assoc payload
  115. :version (:installed-version payload)
  116. :effect (boolean effect)
  117. ;; Manual installation doesn't have theme field but
  118. ;; plugin.edn requires this field
  119. :theme (boolean theme)))))))]
  120. (when (util/electron?)
  121. (js/window.apis.addListener channel listener))
  122. ;;teardown
  123. (fn []
  124. (when (util/electron?)
  125. (js/window.apis.removeListener channel listener)))))
  126. (defn start
  127. "This component has just one responsibility on start, to create a plugins.edn
  128. if none exists"
  129. []
  130. (create-plugin-config-file-if-not-exists))