Added wip plugin store

- Added plugin store snippet
- Added plugin list sync functions
- Work in progress install / uninstall plugin function
This commit is contained in:
Toby Chui
2025-04-22 07:15:30 +08:00
parent 36c2c9a00e
commit 6750c7fe3d
10 changed files with 469 additions and 3 deletions

View File

@@ -249,3 +249,5 @@ func (m *Manager) HandleDisablePlugin(w http.ResponseWriter, r *http.Request) {
utils.SendOK(w)
}
/* Plugin Store */

View File

@@ -82,7 +82,7 @@ func (m *Manager) LoadPluginsFromDisk() error {
m.Log("Loaded plugin: "+thisPlugin.Spec.Name, nil)
// If the plugin was enabled, start it now
fmt.Println("Plugin enabled state", m.GetPluginPreviousEnableState(thisPlugin.Spec.ID))
//fmt.Println("Plugin enabled state", m.GetPluginPreviousEnableState(thisPlugin.Spec.ID))
if m.GetPluginPreviousEnableState(thisPlugin.Spec.ID) {
err = m.StartPlugin(thisPlugin.Spec.ID)
if err != nil {

118
src/mod/plugins/store.go Normal file
View File

@@ -0,0 +1,118 @@
package plugins
import (
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
"time"
"imuslab.com/zoraxy/mod/plugins/zoraxy_plugin"
"imuslab.com/zoraxy/mod/utils"
)
/*
Plugin Store
*/
// See https://github.com/aroz-online/zoraxy-official-plugins/blob/main/directories/index.json for the standard format
type Checksums struct {
LinuxAmd64 string `json:"linux_amd64"`
Linux386 string `json:"linux_386"`
LinuxArm string `json:"linux_arm"`
LinuxArm64 string `json:"linux_arm64"`
LinuxMipsle string `json:"linux_mipsle"`
LinuxRiscv64 string `json:"linux_riscv64"`
WindowsAmd64 string `json:"windows_amd64"`
}
type DownloadablePlugin struct {
IconPath string
PluginIntroSpect zoraxy_plugin.IntroSpect //Plugin introspect information
ChecksumsSHA256 Checksums //Checksums for the plugin binary
DownloadURLs map[string]string //Download URLs for different platforms
}
/* Plugin Store Index List Sync */
//Update the plugin list from the plugin store URLs
func (m *Manager) UpdateDownloadablePluginList() error {
//Get downloadable plugins from each of the plugin store URLS
m.Options.DownloadablePluginCache = []*DownloadablePlugin{}
for _, url := range m.Options.PluginStoreURLs {
pluginList, err := m.getPluginListFromURL(url)
if err != nil {
return fmt.Errorf("failed to get plugin list from %s: %w", url, err)
}
m.Options.DownloadablePluginCache = append(m.Options.DownloadablePluginCache, pluginList...)
}
m.Options.LastSuccPluginSyncTime = time.Now().Unix()
return nil
}
// Get the plugin list from the URL
func (m *Manager) getPluginListFromURL(url string) ([]*DownloadablePlugin, error) {
//Get the plugin list from the URL
resp, err := http.Get(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("failed to get plugin list from %s: %s", url, resp.Status)
}
var pluginList []*DownloadablePlugin
content, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read plugin list from %s: %w", url, err)
}
content = []byte(strings.TrimSpace(string(content)))
err = json.Unmarshal(content, &pluginList)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal plugin list from %s: %w", url, err)
}
return pluginList, nil
}
func (m *Manager) ListDownloadablePlugins() []*DownloadablePlugin {
//List all downloadable plugins
if len(m.Options.DownloadablePluginCache) == 0 {
return []*DownloadablePlugin{}
}
return m.Options.DownloadablePluginCache
}
/*
Handlers for Plugin Store
*/
func (m *Manager) HandleListDownloadablePlugins(w http.ResponseWriter, r *http.Request) {
//List all downloadable plugins
plugins := m.ListDownloadablePlugins()
js, _ := json.Marshal(plugins)
utils.SendJSONResponse(w, string(js))
}
// HandleResyncPluginList is the handler for resyncing the plugin list from the plugin store URLs
func (m *Manager) HandleResyncPluginList(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
//Make sure this function require csrf token
utils.SendErrorResponse(w, "Method not allowed")
return
}
//Resync the plugin list from the plugin store URLs
err := m.UpdateDownloadablePluginList()
if err != nil {
utils.SendErrorResponse(w, "Failed to resync plugin list: "+err.Error())
return
}
utils.SendOK(w)
}

View File

@@ -0,0 +1,52 @@
package plugins
import (
"testing"
)
func TestUpdateDownloadablePluginList(t *testing.T) {
mockManager := &Manager{
Options: &ManagerOptions{
DownloadablePluginCache: []*DownloadablePlugin{},
PluginStoreURLs: []string{},
},
}
//Inject a mock URL for testing
mockManager.Options.PluginStoreURLs = []string{"https://raw.githubusercontent.com/aroz-online/zoraxy-official-plugins/refs/heads/main/directories/index.json"}
err := mockManager.UpdateDownloadablePluginList()
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
if len(mockManager.Options.DownloadablePluginCache) == 0 {
t.Fatalf("expected plugin cache to be updated, but it was empty")
}
if mockManager.Options.LastSuccPluginSyncTime == 0 {
t.Fatalf("expected LastSuccPluginSyncTime to be updated, but it was not")
}
}
func TestGetPluginListFromURL(t *testing.T) {
mockManager := &Manager{
Options: &ManagerOptions{
DownloadablePluginCache: []*DownloadablePlugin{},
PluginStoreURLs: []string{},
},
}
pluginList, err := mockManager.getPluginListFromURL("https://raw.githubusercontent.com/aroz-online/zoraxy-official-plugins/refs/heads/main/directories/index.json")
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
if len(pluginList) == 0 {
t.Fatalf("expected plugin list to be populated, but it was empty")
}
for _, plugin := range pluginList {
t.Logf("Plugin: %+v", plugin)
}
}

View File

@@ -29,10 +29,16 @@ type Plugin struct {
}
type ManagerOptions struct {
/* Plugins */
PluginDir string //The directory where the plugins are stored
PluginGroups map[string][]string //The plugin groups,key is the tag name and the value is an array of plugin IDs
PluginGroupsConfig string //The group / tag configuration file, if set the plugin groups will be loaded from this file
/* Plugin Downloader */
PluginStoreURLs []string //The plugin store URLs, used to download the plugins
DownloadablePluginCache []*DownloadablePlugin //The cache for the downloadable plugins, key is the plugin ID and value is the DownloadablePlugin struct
LastSuccPluginSyncTime int64 //The last sync time for the plugin store URLs, used to check if the plugin store URLs need to be synced again
/* Runtime */
SystemConst *zoraxyPlugin.RuntimeConstantValue //The system constant value
CSRFTokenGen func(*http.Request) string `json:"-"` //The CSRF token generator function