plugin_config.cljs 6.1 KB

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