Skip to content

Commit

Permalink
Enhance Python host setup
Browse files Browse the repository at this point in the history
  • Loading branch information
qianlifeng committed Dec 6, 2024
1 parent a21dd8d commit 358ac01
Show file tree
Hide file tree
Showing 26 changed files with 935 additions and 41 deletions.
1 change: 1 addition & 0 deletions Wox.Plugin.Host.Python/.python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.10
3 changes: 3 additions & 0 deletions Wox.Plugin.Host.Python/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Wox.Plugin.Host.Python

Python host for Wox plugins.
15 changes: 13 additions & 2 deletions Wox.Plugin.Host.Python/jsonrpc.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import json
import importlib.util
from os import path
import sys
from typing import Any, Dict, Optional
import uuid
import zipimport
import websockets
import logger
from wox_plugin import (
Expand Down Expand Up @@ -49,16 +51,25 @@ async def load_plugin(ctx: Context, request: Dict[str, Any]) -> None:
entry = request["Params"]["Entry"]
plugin_id = request["PluginId"]
plugin_name = request["PluginName"]

await logger.info(ctx["Values"]["traceId"], f"<{plugin_name}> load plugin, directory: {plugin_directory}, entry: {entry}")

try:
# Add plugin directory to Python path
if plugin_directory not in sys.path:
sys.path.append(plugin_directory)

deps_dir = path.join(plugin_directory, "dependencies")
if path.exists(deps_dir) and deps_dir not in sys.path:
sys.path.append(deps_dir)

# Combine plugin directory and entry file to get full path
full_entry_path = path.join(plugin_directory, entry)

# Import the plugin module
spec = importlib.util.spec_from_file_location("plugin", entry)
spec = importlib.util.spec_from_file_location("plugin", full_entry_path)
if spec is None or spec.loader is None:
raise ImportError(f"Could not load plugin from {entry}")
raise ImportError(f"Could not load plugin from {full_entry_path}")

module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
Expand Down
11 changes: 11 additions & 0 deletions Wox.Plugin.Host.Python/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[project]
name = "wox-plugin-host"
version = "0.0.1"
description = "Python host for Wox plugins"
readme = "README.md"
requires-python = ">=3.10"
dependencies = [
"wox-plugin>=0.1.0",
"loguru",
"websockets",
]
4 changes: 2 additions & 2 deletions Wox.Plugin.Host.Python/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
websockets==12.0
loguru==0.7.2
wox-plugin==0.0.1
websockets==14.1
wox-plugin==0.1.0
118 changes: 118 additions & 0 deletions Wox.Plugin.Host.Python/uv.lock

Large diffs are not rendered by default.

9 changes: 9 additions & 0 deletions Wox.Plugin.Nodejs/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,15 @@ export interface QueryEnv {
* Active window title when user query
*/
ActiveWindowTitle: string

/**
* Active window pid when user query, 0 if not available
*/
ActiveWindowPid: number

// active browser url when user query
// Only available when active window is browser and https://github.com/Wox-launcher/Wox.Chrome.Extension is installed
ActiveBrowserUrl: string
}

export interface Query {
Expand Down
1 change: 1 addition & 0 deletions Wox.Plugin.Python/.python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.12
2 changes: 2 additions & 0 deletions Wox.Plugin.Python/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
publish:
python publish.py patch
5 changes: 5 additions & 0 deletions Wox.Plugin.Python/publish.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,5 +90,10 @@ def main():
print("Package can be installed with:")
print(f"pip install wox-plugin=={new_version}")

# remove build directory
if run_command("rm -rf dist wox_plugin_python.egg-info"):
print("Error: Failed to remove build directory")
sys.exit(1)

if __name__ == "__main__":
main()
1 change: 0 additions & 1 deletion Wox.Plugin.Python/publish.sh

This file was deleted.

12 changes: 12 additions & 0 deletions Wox.Plugin.Python/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[project]
name = "wox-plugin"
version = "0.1.0"
description = "Python types for Wox plugins"
readme = "README.md"
requires-python = ">=3.12"

[project.optional-dependencies]
dev = [
"build>=1.2.2.post1",
"twine>=6.0.1",
]
2 changes: 1 addition & 1 deletion Wox.Plugin.Python/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

setup(
name="wox-plugin",
version="0.0.1",
version="0.0.18",
description="All Python plugins for Wox should use types in this package",
long_description=open("README.md").read(),
long_description_content_type="text/markdown",
Expand Down
439 changes: 439 additions & 0 deletions Wox.Plugin.Python/uv.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion Wox.Plugin.Python/wox_plugin/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,4 @@
PluginInitParams,
)

__version__ = "0.0.82"
__version__ = "0.0.18"
18 changes: 18 additions & 0 deletions Wox.Plugin.Python/wox_plugin/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
class Context(TypedDict):
Values: Dict[str, str]

# get traceId from context
def get_trace_id(self) -> str:
return self["Values"]["traceId"]

def new_context() -> Context:
return {"Values": {"traceId": str(uuid.uuid4())}}

Expand All @@ -33,7 +37,21 @@ class Selection:
# Query Environment
@dataclass
class QueryEnv:
"""
Active window title when user query
"""
ActiveWindowTitle: str

"""
Active window pid when user query, 0 if not available
"""
ActiveWindowPid: int

"""
active browser url when user query
Only available when active window is browser and https://github.com/Wox-launcher/Wox.Chrome.Extension is installed
"""
ActiveBrowserUrl: str

# Query
class QueryType(str, Enum):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -557,6 +557,9 @@ class WoxLauncherController extends GetxController {
responseWoxWebsocketRequest(msg, true, null);
} else if (msg.method == "GetCurrentQuery") {
responseWoxWebsocketRequest(msg, true, currentQuery.value.toJson());
} else if (msg.method == "IsVisible") {
var isVisible = await windowManager.isVisible();
responseWoxWebsocketRequest(msg, true, isVisible);
}
}

Expand Down
4 changes: 2 additions & 2 deletions Wox.UI.Flutter/wox/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -958,10 +958,10 @@ packages:
dependency: transitive
description:
name: vm_service
sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d"
sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc
url: "https://pub.dev"
source: hosted
version: "14.2.5"
version: "14.2.4"
web:
dependency: transitive
description:
Expand Down
17 changes: 5 additions & 12 deletions Wox/plugin/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import (
"wox/setting/definition"
"wox/share"
"wox/util"
"wox/util/notifier"

"github.com/disintegration/imaging"
"github.com/samber/lo"
Expand Down Expand Up @@ -64,17 +63,11 @@ func (a *APIImpl) ShowApp(ctx context.Context) {
}

func (a *APIImpl) Notify(ctx context.Context, message string) {
// translate message
message = a.GetTranslation(ctx, message)

if GetPluginManager().GetUI().IsPluginQuery(ctx, a.pluginInstance.Metadata.Id) {
GetPluginManager().GetUI().ShowToolbarMsg(ctx, share.ToolbarMsg{
Text: message,
DisplaySeconds: 3,
})
} else {
notifier.Notify(message)
}
GetPluginManager().GetUI().Notify(ctx, share.NotifyMsg{
PluginId: a.pluginInstance.Metadata.Id,
Text: a.GetTranslation(ctx, message),
DisplaySeconds: 3,
})
}

func (a *APIImpl) Log(ctx context.Context, level LogLevel, msg string) {
Expand Down
4 changes: 4 additions & 0 deletions Wox/plugin/runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,7 @@ func IsSupportedRuntime(runtime string) bool {
runtimeUpper := strings.ToUpper(runtime)
return runtimeUpper == string(PLUGIN_RUNTIME_PYTHON) || runtimeUpper == string(PLUGIN_RUNTIME_NODEJS) || runtimeUpper == string(PLUGIN_RUNTIME_GO)
}

func ConvertToRuntime(runtime string) Runtime {
return Runtime(strings.ToUpper(runtime))
}
113 changes: 109 additions & 4 deletions Wox/plugin/store.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
package plugin

import (
"archive/zip"
"context"
"encoding/json"
"fmt"
"io"
"os"
"path"
"strings"
"sync"
"time"
"wox/util"
Expand Down Expand Up @@ -127,11 +128,9 @@ func (s *Store) GetStorePluginManifest(ctx context.Context, store storeManifest)
return nil, unmarshalErr
}

var finalStorePluginManifests []StorePluginManifest
for i := range storePluginManifests {
if IsSupportedRuntime(string(storePluginManifests[i].Runtime)) {
storePluginManifests[i].Runtime = Runtime(strings.ToUpper(string(storePluginManifests[i].Runtime)))
finalStorePluginManifests = append(finalStorePluginManifests, storePluginManifests[i])
storePluginManifests[i].Runtime = ConvertToRuntime(string(storePluginManifests[i].Runtime))
}
}

Expand Down Expand Up @@ -250,6 +249,112 @@ func (s *Store) Install(ctx context.Context, manifest StorePluginManifest) error
return nil
}

func (s *Store) ParsePluginManifestFromLocal(ctx context.Context, filePath string) (Metadata, error) {
reader, err := zip.OpenReader(filePath)
if err != nil {
return Metadata{}, fmt.Errorf("failed to open wox plugin file: %s", err.Error())
}
defer reader.Close()

var pluginMetadata Metadata
for _, file := range reader.File {
if file.Name != "plugin.json" {
continue
}

rc, err := file.Open()
if err != nil {
return Metadata{}, fmt.Errorf("failed to read plugin.json: %s", err.Error())
}
defer rc.Close()

bytes, err := io.ReadAll(rc)
if err != nil {
return Metadata{}, fmt.Errorf("failed to read plugin.json content: %s", err.Error())
}

err = json.Unmarshal(bytes, &pluginMetadata)
if err != nil {
return Metadata{}, fmt.Errorf("failed to parse plugin.json: %s", err.Error())
}

break
}

if pluginMetadata.Id == "" {
return Metadata{}, fmt.Errorf("plugin.json not found or invalid")
}

return pluginMetadata, nil
}

func (s *Store) InstallFromLocal(ctx context.Context, filePath string) error {
pluginMetadata, err := s.ParsePluginManifestFromLocal(ctx, filePath)
if err != nil {
return err
}

// check if plugin's runtime is started
if !GetPluginManager().IsHostStarted(ctx, ConvertToRuntime(pluginMetadata.Runtime)) {
logger.Error(ctx, fmt.Sprintf("%s runtime is not started, please start first", pluginMetadata.Runtime))
return fmt.Errorf("%s runtime is not started, please start first", pluginMetadata.Runtime)
}

// check if installed newer version
installedPlugin, exist := lo.Find(GetPluginManager().GetPluginInstances(), func(item *Instance) bool {
return item.Metadata.Id == pluginMetadata.Id
})
if exist {
logger.Info(ctx, fmt.Sprintf("found this plugin has installed %s(%s)", installedPlugin.Metadata.Name, installedPlugin.Metadata.Version))
installedVersion, installedErr := semver.NewVersion(installedPlugin.Metadata.Version)
currentVersion, currentErr := semver.NewVersion(pluginMetadata.Version)
if installedErr == nil && currentErr == nil {
if installedVersion.GreaterThan(currentVersion) {
logger.Info(ctx, fmt.Sprintf("skip %s(%s) from %s store, because it's already installed(%s)", pluginMetadata.Name, pluginMetadata.Version, pluginMetadata.Name, installedPlugin.Metadata.Version))
return fmt.Errorf("skip %s(%s) from %s store, because it's already installed(%s)", pluginMetadata.Name, pluginMetadata.Version, pluginMetadata.Name, installedPlugin.Metadata.Version)
}
}

uninstallErr := s.Uninstall(ctx, installedPlugin)
if uninstallErr != nil {
logger.Error(ctx, fmt.Sprintf("failed to uninstall plugin %s(%s): %s", installedPlugin.Metadata.Name, installedPlugin.Metadata.Version, uninstallErr.Error()))
return fmt.Errorf("failed to uninstall plugin %s(%s): %s", installedPlugin.Metadata.Name, installedPlugin.Metadata.Version, uninstallErr.Error())
}
}

pluginDirectory := path.Join(util.GetLocation().GetPluginDirectory(), fmt.Sprintf("%s_%s@%s", pluginMetadata.Id, pluginMetadata.Name, pluginMetadata.Version))
directoryErr := util.GetLocation().EnsureDirectoryExist(pluginDirectory)
if directoryErr != nil {
logger.Error(ctx, fmt.Sprintf("failed to create plugin directory %s: %s", pluginDirectory, directoryErr.Error()))
return fmt.Errorf("failed to create plugin directory %s: %s", pluginDirectory, directoryErr.Error())
}

//unzip plugin
logger.Info(ctx, fmt.Sprintf("start to unzip plugin %s(%s)", pluginMetadata.Name, pluginMetadata.Version))
unzipErr := util.Unzip(filePath, pluginDirectory)
if unzipErr != nil {
logger.Error(ctx, fmt.Sprintf("failed to unzip plugin %s(%s): %s", pluginMetadata.Name, pluginMetadata.Version, unzipErr.Error()))
return fmt.Errorf("failed to unzip plugin %s(%s): %s", pluginMetadata.Name, pluginMetadata.Version, unzipErr.Error())
}

//load plugin
logger.Info(ctx, fmt.Sprintf("start to load plugin %s(%s)", pluginMetadata.Name, pluginMetadata.Version))
loadErr := GetPluginManager().LoadPlugin(ctx, pluginDirectory)
if loadErr != nil {
logger.Error(ctx, fmt.Sprintf("failed to load plugin %s(%s): %s", pluginMetadata.Name, pluginMetadata.Version, loadErr.Error()))

// remove plugin directory
removeErr := os.RemoveAll(pluginDirectory)
if removeErr != nil {
logger.Error(ctx, fmt.Sprintf("failed to remove plugin directory %s: %s", pluginDirectory, removeErr.Error()))
}

return fmt.Errorf("failed to load plugin %s(%s): %s", pluginMetadata.Name, pluginMetadata.Version, loadErr.Error())
}

return nil
}

func (s *Store) Uninstall(ctx context.Context, plugin *Instance) error {
logger.Info(ctx, fmt.Sprintf("start to uninstall plugin %s(%s)", plugin.Metadata.Name, plugin.Metadata.Version))

Expand Down
Loading

0 comments on commit 358ac01

Please sign in to comment.