浏览代码

improve(plugin): basic impls of file storage api for specific plugin context

charlie 4 年之前
父节点
当前提交
661bb064f7

+ 8 - 2
libs/src/LSPlugin.caller.ts

@@ -220,7 +220,11 @@ class LSPluginCaller extends EventEmitter {
 
         this._call = async (...args: any) => {
           // parent all will get message before handshaked
-          await refChild.call(LSPMSGFn(pl.id), { type: args[0], payload: args[1] || {} })
+          await refChild.call(LSPMSGFn(pl.id), {
+            type: args[0], payload: Object.assign(args[1] || {}, {
+              $$pid: pl.id
+            })
+          })
         }
 
         this._callUserModel = async (type, payload: any) => {
@@ -263,7 +267,9 @@ class LSPluginCaller extends EventEmitter {
 
         // TODO: support sync call
         // @ts-ignore Call in same thread
-        this._pluginLocal?.emit(type, payload)
+        this._pluginLocal?.emit(type, Object.assign(payload, {
+          $$pid: pl.id
+        }))
 
         return actor?.promise
       }

+ 3 - 3
libs/src/LSPlugin.core.ts

@@ -7,7 +7,7 @@ import {
   setupInjectedUI,
   deferred,
   invokeHostExportedApi,
-  isObject, withFileProtocol
+  isObject, withFileProtocol, IS_DEV, getSDKPathRoot
 } from './helpers'
 import * as pluginHelpers from './helpers'
 import Debug from 'debug'
@@ -477,7 +477,7 @@ class PluginLocal
 
     if (!entry.endsWith('.js')) return
 
-    let sdkPath = await invokeHostExportedApi('_callApplication', 'getAppPath')
+    let sdkPathRoot = await getSDKPathRoot()
     let entryPath = await invokeHostExportedApi(
       'write_user_tmp_file',
       `${this._id}_index.html`,
@@ -486,7 +486,7 @@ class PluginLocal
   <head>
     <meta charset="UTF-8">
     <title>logseq plugin entry</title>
-    <script src="${sdkPath}/js/lsplugin.user.js"></script>
+    <script src="${sdkPathRoot}/lsplugin.user.js"></script>
   </head>
   <body>
   <div id="app"></div>

+ 3 - 0
libs/src/LSPlugin.ts

@@ -1,6 +1,7 @@
 import EventEmitter from 'eventemitter3'
 import * as CSS from 'csstype'
 import { LSPluginCaller } from './LSPlugin.caller'
+import { LSPluginFileStorage } from './modules/LSPlugin.Storage'
 
 export type PluginLocalIdentity = string
 
@@ -494,4 +495,6 @@ export interface ILSPluginUser extends EventEmitter<LSPluginUserEvents> {
   App: IAppProxy & Record<string, any>
   Editor: IEditorProxy & Record<string, any>
   DB: IDBProxy
+
+  FileStorage: LSPluginFileStorage
 }

+ 11 - 1
libs/src/LSPlugin.user.ts

@@ -16,6 +16,7 @@ import Debug from 'debug'
 import * as CSS from 'csstype'
 import { snakeCase } from 'snake-case'
 import EventEmitter from 'eventemitter3'
+import { LSPluginFileStorage } from './modules/LSPlugin.Storage'
 
 declare global {
   interface Window {
@@ -154,7 +155,7 @@ const editor: Partial<IEditorProxy> = {
       return false
     }
 
-    const key = + '_' + this.baseInfo.id
+    const key = +'_' + this.baseInfo.id
     const label = tag
     const type = 'block-context-menu-item'
 
@@ -192,6 +193,8 @@ export class LSPluginUser extends EventEmitter<LSPluginUserEvents> implements IL
    */
   private _ui = new Map<number, uiState>()
 
+  private _fileStorage: LSPluginFileStorage
+
   /**
    * handler of before unload plugin
    * @private
@@ -226,6 +229,9 @@ export class LSPluginUser extends EventEmitter<LSPluginUserEvents> implements IL
         actor?.reject(e)
       }
     })
+
+    // modules
+    this._fileStorage = new LSPluginFileStorage(this)
   }
 
   async ready (
@@ -400,6 +406,10 @@ export class LSPluginUser extends EventEmitter<LSPluginUserEvents> implements IL
   get DB (): IDBProxy {
     return this._makeUserProxy(db)
   }
+
+  get FileStorage (): LSPluginFileStorage {
+    return this._fileStorage
+  }
 }
 
 export * from './LSPlugin'

+ 26 - 0
libs/src/helpers.ts

@@ -1,6 +1,7 @@
 import { StyleString, UIOptions } from './LSPlugin'
 import { PluginLocal } from './LSPlugin.core'
 import { snakeCase } from 'snake-case'
+import * as path from 'path'
 
 interface IObject {
   [key: string]: any;
@@ -13,6 +14,31 @@ declare global {
   }
 }
 
+export const IS_DEV = process.env.NODE_ENV === 'development'
+
+let _appPathRoot
+
+export async function getAppPathRoot () {
+  if (_appPathRoot) {
+    return _appPathRoot
+  }
+
+  return (_appPathRoot =
+      await invokeHostExportedApi('_callApplication', 'getAppPath')
+  )
+}
+
+export async function getSDKPathRoot () {
+  if (IS_DEV) {
+    // TODO: cache in preference file
+    return localStorage.getItem('LSP_DEV_SDK_ROOT') || 'http://localhost:8080'
+  }
+
+  const appPathRoot = await getAppPathRoot()
+
+  return path.join(appPathRoot, 'js')
+}
+
 export function isObject (item: any) {
   return (item === Object(item) && !Array.isArray(item))
 }

+ 87 - 0
libs/src/modules/LSPlugin.Storage.ts

@@ -0,0 +1,87 @@
+import { LSPluginUser } from '../LSPlugin.user'
+
+export interface IAsyncStorage {
+  getItem (key: string): Promise<string | undefined>
+
+  setItem (key: string, value: string): Promise<void>
+
+  removeItem (key: string): Promise<void>
+
+  hasItem (key: string): Promise<boolean>
+
+  clear (): Promise<void>
+}
+
+/**
+ * A storage based on local files under specific context
+ */
+class LSPluginFileStorage implements IAsyncStorage {
+  /**
+   * @param ctx
+   */
+  constructor (
+    private ctx: LSPluginUser
+  ) {}
+
+  /**
+   * plugin id
+   */
+  get ctxId () {
+    return this.ctx.baseInfo.id
+  }
+
+  /**
+   * @param key A string as file name that support nested directory
+   * @param value Storage value
+   */
+  setItem (key: string, value: string): Promise<void> {
+    return this.ctx.caller.callAsync(`api:call`, {
+      method: 'write-plugin-storage-file',
+      args: [this.ctxId, key, value]
+    })
+  }
+
+  /**
+   * @param key
+   */
+  getItem (key: string): Promise<string | undefined> {
+    return this.ctx.caller.callAsync(`api:call`, {
+      method: 'read-plugin-storage-file',
+      args: [this.ctxId, key]
+    })
+  }
+
+  /**
+   * @param key
+   */
+  removeItem (key: string): Promise<void> {
+    return this.ctx.caller.call(`api:call`, {
+      method: 'unlink-plugin-storage-file',
+      args: [this.ctxId, key]
+    })
+  }
+
+  /**
+   * Clears the storage
+   */
+  clear (): Promise<void> {
+    return this.ctx.caller.call(`api:call`, {
+      method: 'clear-plugin-storage-files',
+      args: [this.ctxId]
+    })
+  }
+
+  /**
+   * @param key
+   */
+  hasItem (key: string): Promise<boolean> {
+    return this.ctx.caller.callAsync(`api:call`, {
+      method: 'exist-plugin-storage-file',
+      args: [this.ctxId, key]
+    })
+  }
+}
+
+export {
+  LSPluginFileStorage
+}

+ 6 - 2
libs/webpack.config.js

@@ -1,4 +1,5 @@
 const path = require('path')
+const webpack = require('webpack')
 const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
 
 module.exports = {
@@ -16,11 +17,14 @@ module.exports = {
     extensions: ['.tsx', '.ts', '.js'],
   },
   plugins: [
+    new webpack.ProvidePlugin({
+      process: 'process/browser',
+    }),
     // new BundleAnalyzerPlugin()
   ],
   output: {
-    library: "LSPluginEntry",
-    libraryTarget: "umd",
+    library: 'LSPluginEntry',
+    libraryTarget: 'umd',
     filename: 'lsplugin.user.js',
     path: path.resolve(__dirname, 'dist')
   },

+ 6 - 0
src/electron/electron/handler.cljs

@@ -18,6 +18,12 @@
 (defmethod handle :mkdir [_window [_ dir]]
   (fs/mkdirSync dir))
 
+(defmethod handle :mkdir-recur [_window [_ dir]]
+  (fs/mkdirSync dir #js {:recursive true}))
+
+(defmethod handle :rmdir-recur [_window [_ dir]]
+  (fs/rmdirSync dir #js {:recursive true}))
+
 ;; {encoding: 'utf8', withFileTypes: true}
 (defn- readdir
   [dir]

+ 4 - 0
src/main/frontend/fs.cljs

@@ -43,6 +43,10 @@
   [dir]
   (protocol/mkdir! (get-fs dir) dir))
 
+(defn mkdir-recur!
+  [dir]
+  (protocol/mkdir-recur! (get-fs dir) dir))
+
 (defn readdir
   [dir]
   (protocol/readdir (get-fs dir) dir))

+ 3 - 1
src/main/frontend/fs/node.cljs

@@ -65,12 +65,14 @@
   protocol/Fs
   (mkdir! [this dir]
     (ipc/ipc "mkdir" dir))
+  (mkdir-recur! [this dir]
+    (ipc/ipc "mkdir-recur" dir))
   (readdir [this dir]                   ; recursive
     (ipc/ipc "readdir" dir))
   (unlink! [this path _opts]
     (ipc/ipc "unlink" path))
   (rmdir! [this dir]
-    nil)
+    (ipc/ipc "rmdir-recur" dir))
   (read-file [this dir path _options]
     (let [path (concat-path dir path)]
       (ipc/ipc "readFile" path)))

+ 1 - 0
src/main/frontend/fs/protocol.cljs

@@ -2,6 +2,7 @@
 
 (defprotocol Fs
   (mkdir! [this dir])
+  (mkdir-recur! [this dir])
   (readdir [this dir])
   (unlink! [this path opts])
   (rmdir! [this dir])

+ 76 - 8
src/main/logseq/api.cljs

@@ -93,16 +93,84 @@
           path (util/node-path.join path "package.json")]
       (fs/write-file! repo "" path (js/JSON.stringify data nil 2) {:skip-mtime? true}))))
 
+(defn ^:private write_dotdir_file!
+  [file content sub-root]
+  (p/let [repo ""
+          path (plugin-handler/get-ls-dotdir-root)
+          path (util/node-path.join path sub-root)
+          exist? (fs/file-exists? path "")
+          _ (when-not exist? (fs/mkdir-recur! path))
+          user-path (util/node-path.join path file)
+          sub-dir? (string/starts-with? user-path path)
+          _ (if-not sub-dir? (do (log/info :debug user-path) (throw "write file denied")))
+          user-path-root (util/node-path.dirname user-path)
+          exist? (fs/file-exists? user-path-root "")
+          _ (when-not exist? (fs/mkdir-recur! user-path-root))
+          _ (fs/write-file! repo "" user-path content {:skip-mtime? true})]
+    user-path))
+
+(defn ^:private read_dotdir_file
+  [file sub-root]
+  (p/let [repo ""
+          path (plugin-handler/get-ls-dotdir-root)
+          path (util/node-path.join path sub-root)
+          user-path (util/node-path.join path file)
+          sub-dir? (string/starts-with? user-path path)
+          _ (if-not sub-dir? (do (log/info :debug user-path) (throw "read file denied")))
+          exist? (fs/file-exists? "" user-path)
+          _ (when-not exist? (do (log/info :debug user-path) (throw "file not existed")))
+          content (fs/read-file "" user-path)]
+    content))
+
+(defn ^:private unlink_dotdir_file!
+  [file sub-root]
+  (p/let [repo ""
+          path (plugin-handler/get-ls-dotdir-root)
+          path (util/node-path.join path sub-root)
+          user-path (util/node-path.join path file)
+          sub-dir? (string/starts-with? user-path path)
+          _ (if-not sub-dir? (do (log/info :debug user-path) (throw "access file denied")))
+          exist? (fs/file-exists? "" user-path)
+          _ (when-not exist? (do (log/info :debug user-path) (throw "file not existed")))
+          _ (fs/unlink! user-path {})]))
+
 (def ^:export write_user_tmp_file
   (fn [file content]
-    (p/let [repo ""
-            path (plugin-handler/get-ls-dotdir-root)
-            path (util/node-path.join path "tmp")
-            exist? (fs/file-exists? path "")
-            _ (when-not exist? (fs/mkdir! path))
-            path (util/node-path.join path file)
-            _ (fs/write-file! repo "" path content {:skip-mtime? true})]
-      path)))
+    (write_dotdir_file! file content "tmp")))
+
+(def ^:export write_plugin_storage_file
+  (fn [plugin-id file content]
+    (write_dotdir_file!
+     file content
+     (let [plugin-id (util/node-path.basename plugin-id)]
+       (util/node-path.join "storages" plugin-id)))))
+
+(def ^:export read_plugin_storage_file
+  (fn [plugin-id file]
+    (let [plugin-id (util/node-path.basename plugin-id)]
+      (read_dotdir_file
+       file (util/node-path.join "storages" plugin-id)))))
+
+(def ^:export unlink_plugin_storage_file
+  (fn [plugin-id file]
+    (let [plugin-id (util/node-path.basename plugin-id)]
+      (unlink_dotdir_file!
+       file (util/node-path.join "storages" plugin-id)))))
+
+(def ^:export exist_plugin_storage_file
+  (fn [plugin-id file]
+    (p/let [root (plugin-handler/get-ls-dotdir-root)
+            plugin-id (util/node-path.basename plugin-id)
+            exist? (fs/file-exists?
+                    (util/node-path.join root "storages" plugin-id)
+                    file)]
+      exist?)))
+
+(def ^:export clear_plugin_storage_files
+  (fn [plugin-id]
+    (p/let [root (plugin-handler/get-ls-dotdir-root)
+            plugin-id (util/node-path.basename plugin-id)]
+      (fs/rmdir! (util/node-path.join root  "storages" plugin-id)))))
 
 (def ^:export load_user_preferences
   (fn []