diff options
Diffstat (limited to 'raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/plugins')
10 files changed, 668 insertions, 0 deletions
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/plugins/handlers.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/plugins/handlers.go new file mode 100644 index 0000000..33f6564 --- /dev/null +++ b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/plugins/handlers.go @@ -0,0 +1,27 @@ +package plugins + +import ( + "fmt" + "net" + + "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models" +) + +func HandleRequest(conn net.Conn, req models.Request) { + switch req.Method { + case "plugins.list": + HandleList(conn, req) + case "plugins.listInstalled": + HandleListInstalled(conn, req) + case "plugins.install": + HandleInstall(conn, req) + case "plugins.uninstall": + HandleUninstall(conn, req) + case "plugins.update": + HandleUpdate(conn, req) + case "plugins.search": + HandleSearch(conn, req) + default: + models.RespondError(conn, req.ID, fmt.Sprintf("unknown method: %s", req.Method)) + } +} diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/plugins/handlers_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/plugins/handlers_test.go new file mode 100644 index 0000000..70202e3 --- /dev/null +++ b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/plugins/handlers_test.go @@ -0,0 +1,196 @@ +package plugins + +import ( + "encoding/json" + "testing" + + "github.com/AvengeMedia/DankMaterialShell/core/internal/mocks/net" + "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +func TestHandleList(t *testing.T) { + conn := net.NewMockConn(t) + conn.EXPECT().Write(mock.Anything).Return(0, nil).Maybe() + + req := models.Request{ + ID: 123, + Method: "plugins.list", + Params: map[string]any{}, + } + + HandleList(conn, req) +} + +func TestHandleListInstalled(t *testing.T) { + conn := net.NewMockConn(t) + conn.EXPECT().Write(mock.Anything).Return(0, nil).Maybe() + + req := models.Request{ + ID: 123, + Method: "plugins.listInstalled", + Params: map[string]any{}, + } + + HandleListInstalled(conn, req) +} + +func TestHandleInstallMissingName(t *testing.T) { + conn := net.NewMockConn(t) + var written []byte + conn.EXPECT().Write(mock.Anything).RunAndReturn(func(b []byte) (int, error) { + written = b + return len(b), nil + }).Maybe() + + req := models.Request{ + ID: 123, + Method: "plugins.install", + Params: map[string]any{}, + } + + HandleInstall(conn, req) + + var resp models.Response[SuccessResult] + err := json.Unmarshal(written, &resp) + assert.NoError(t, err) + assert.NotEmpty(t, resp.Error) + assert.Contains(t, resp.Error, "missing or invalid 'name' parameter") +} + +func TestHandleInstallInvalidName(t *testing.T) { + conn := net.NewMockConn(t) + var written []byte + conn.EXPECT().Write(mock.Anything).RunAndReturn(func(b []byte) (int, error) { + written = b + return len(b), nil + }).Maybe() + + req := models.Request{ + ID: 123, + Method: "plugins.install", + Params: map[string]any{ + "name": 123, + }, + } + + HandleInstall(conn, req) + + var resp models.Response[SuccessResult] + err := json.Unmarshal(written, &resp) + assert.NoError(t, err) + assert.NotEmpty(t, resp.Error) +} + +func TestHandleUninstallMissingName(t *testing.T) { + conn := net.NewMockConn(t) + var written []byte + conn.EXPECT().Write(mock.Anything).RunAndReturn(func(b []byte) (int, error) { + written = b + return len(b), nil + }).Maybe() + + req := models.Request{ + ID: 123, + Method: "plugins.uninstall", + Params: map[string]any{}, + } + + HandleUninstall(conn, req) + + var resp models.Response[SuccessResult] + err := json.Unmarshal(written, &resp) + assert.NoError(t, err) + assert.NotEmpty(t, resp.Error) +} + +func TestHandleUpdateMissingName(t *testing.T) { + conn := net.NewMockConn(t) + var written []byte + conn.EXPECT().Write(mock.Anything).RunAndReturn(func(b []byte) (int, error) { + written = b + return len(b), nil + }).Maybe() + + req := models.Request{ + ID: 123, + Method: "plugins.update", + Params: map[string]any{}, + } + + HandleUpdate(conn, req) + + var resp models.Response[SuccessResult] + err := json.Unmarshal(written, &resp) + assert.NoError(t, err) + assert.NotEmpty(t, resp.Error) +} + +func TestHandleSearchMissingQuery(t *testing.T) { + conn := net.NewMockConn(t) + var written []byte + conn.EXPECT().Write(mock.Anything).RunAndReturn(func(b []byte) (int, error) { + written = b + return len(b), nil + }).Maybe() + + req := models.Request{ + ID: 123, + Method: "plugins.search", + Params: map[string]any{}, + } + + HandleSearch(conn, req) + + var resp models.Response[[]PluginInfo] + err := json.Unmarshal(written, &resp) + assert.NoError(t, err) + assert.NotEmpty(t, resp.Error) +} + +func TestSortPluginInfoByFirstParty(t *testing.T) { + plugins := []PluginInfo{ + {Name: "third-party", Repo: "https://github.com/other/test"}, + {Name: "first-party", Repo: "https://github.com/AvengeMedia/test"}, + } + + SortPluginInfoByFirstParty(plugins) + + assert.Equal(t, "first-party", plugins[0].Name) + assert.Equal(t, "third-party", plugins[1].Name) +} + +func TestPluginInfoJSON(t *testing.T) { + info := PluginInfo{ + Name: "test", + Description: "test description", + Installed: true, + FirstParty: true, + } + + data, err := json.Marshal(info) + assert.NoError(t, err) + + var unmarshaled PluginInfo + err = json.Unmarshal(data, &unmarshaled) + assert.NoError(t, err) + assert.Equal(t, info.Name, unmarshaled.Name) + assert.Equal(t, info.Installed, unmarshaled.Installed) +} + +func TestSuccessResult(t *testing.T) { + result := SuccessResult{ + Success: true, + Message: "test message", + } + + data, err := json.Marshal(result) + assert.NoError(t, err) + + var unmarshaled SuccessResult + err = json.Unmarshal(data, &unmarshaled) + assert.NoError(t, err) + assert.True(t, unmarshaled.Success) + assert.Equal(t, "test message", unmarshaled.Message) +} diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/plugins/install.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/plugins/install.go new file mode 100644 index 0000000..2c13a0e --- /dev/null +++ b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/plugins/install.go @@ -0,0 +1,69 @@ +package plugins + +import ( + "fmt" + "net" + + "github.com/AvengeMedia/DankMaterialShell/core/internal/plugins" + "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models" +) + +func HandleInstall(conn net.Conn, req models.Request) { + idOrName, ok := models.Get[string](req, "name") + if !ok { + models.RespondError(conn, req.ID, "missing or invalid 'name' parameter") + return + } + + registry, err := plugins.NewRegistry() + if err != nil { + models.RespondError(conn, req.ID, fmt.Sprintf("failed to create registry: %v", err)) + return + } + + pluginList, err := registry.List() + if err != nil { + models.RespondError(conn, req.ID, fmt.Sprintf("failed to list plugins: %v", err)) + return + } + + // First, try to find by ID (preferred method) + var plugin *plugins.Plugin + for _, p := range pluginList { + if p.ID == idOrName { + plugin = &p + break + } + } + + // Fallback to name for backward compatibility + if plugin == nil { + for _, p := range pluginList { + if p.Name == idOrName { + plugin = &p + break + } + } + } + + if plugin == nil { + models.RespondError(conn, req.ID, fmt.Sprintf("plugin not found: %s", idOrName)) + return + } + + manager, err := plugins.NewManager() + if err != nil { + models.RespondError(conn, req.ID, fmt.Sprintf("failed to create manager: %v", err)) + return + } + + if err := manager.Install(*plugin); err != nil { + models.RespondError(conn, req.ID, fmt.Sprintf("failed to install plugin: %v", err)) + return + } + + models.Respond(conn, req.ID, SuccessResult{ + Success: true, + Message: fmt.Sprintf("plugin installed: %s", plugin.Name), + }) +} diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/plugins/list.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/plugins/list.go new file mode 100644 index 0000000..e44d034 --- /dev/null +++ b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/plugins/list.go @@ -0,0 +1,53 @@ +package plugins + +import ( + "fmt" + "net" + "strings" + + "github.com/AvengeMedia/DankMaterialShell/core/internal/plugins" + "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models" +) + +func HandleList(conn net.Conn, req models.Request) { + registry, err := plugins.NewRegistry() + if err != nil { + models.RespondError(conn, req.ID, fmt.Sprintf("failed to create registry: %v", err)) + return + } + + pluginList, err := registry.List() + if err != nil { + models.RespondError(conn, req.ID, fmt.Sprintf("failed to list plugins: %v", err)) + return + } + + manager, err := plugins.NewManager() + if err != nil { + models.RespondError(conn, req.ID, fmt.Sprintf("failed to create manager: %v", err)) + return + } + + result := make([]PluginInfo, len(pluginList)) + for i, p := range pluginList { + installed, _ := manager.IsInstalled(p) + result[i] = PluginInfo{ + ID: p.ID, + Name: p.Name, + Category: p.Category, + Author: p.Author, + Description: p.Description, + Repo: p.Repo, + Path: p.Path, + Capabilities: p.Capabilities, + Compositors: p.Compositors, + Dependencies: p.Dependencies, + Installed: installed, + FirstParty: strings.HasPrefix(p.Repo, "https://github.com/AvengeMedia"), + Featured: p.Featured, + RequiresDMS: p.RequiresDMS, + } + } + + models.Respond(conn, req.ID, result) +} diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/plugins/list_installed.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/plugins/list_installed.go new file mode 100644 index 0000000..b771b0e --- /dev/null +++ b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/plugins/list_installed.go @@ -0,0 +1,77 @@ +package plugins + +import ( + "fmt" + "net" + "strings" + + "github.com/AvengeMedia/DankMaterialShell/core/internal/plugins" + "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models" +) + +func HandleListInstalled(conn net.Conn, req models.Request) { + manager, err := plugins.NewManager() + if err != nil { + models.RespondError(conn, req.ID, fmt.Sprintf("failed to create manager: %v", err)) + return + } + + installedNames, err := manager.ListInstalled() + if err != nil { + models.RespondError(conn, req.ID, fmt.Sprintf("failed to list installed plugins: %v", err)) + return + } + + registry, err := plugins.NewRegistry() + if err != nil { + models.RespondError(conn, req.ID, fmt.Sprintf("failed to create registry: %v", err)) + return + } + + allPlugins, err := registry.List() + if err != nil { + models.RespondError(conn, req.ID, fmt.Sprintf("failed to list plugins: %v", err)) + return + } + + pluginMap := make(map[string]plugins.Plugin) + for _, p := range allPlugins { + pluginMap[p.ID] = p + } + + result := make([]PluginInfo, 0, len(installedNames)) + for _, id := range installedNames { + if plugin, ok := pluginMap[id]; ok { + hasUpdate := false + if hasUpdates, err := manager.HasUpdates(id, plugin); err == nil { + hasUpdate = hasUpdates + } + + result = append(result, PluginInfo{ + ID: plugin.ID, + Name: plugin.Name, + Category: plugin.Category, + Author: plugin.Author, + Description: plugin.Description, + Repo: plugin.Repo, + Path: plugin.Path, + Capabilities: plugin.Capabilities, + Compositors: plugin.Compositors, + Dependencies: plugin.Dependencies, + FirstParty: strings.HasPrefix(plugin.Repo, "https://github.com/AvengeMedia"), + HasUpdate: hasUpdate, + RequiresDMS: plugin.RequiresDMS, + }) + } else { + result = append(result, PluginInfo{ + ID: id, + Name: id, + Note: "not in registry", + }) + } + } + + SortPluginInfoByFirstParty(result) + + models.Respond(conn, req.ID, result) +} diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/plugins/search.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/plugins/search.go new file mode 100644 index 0000000..9dbea6d --- /dev/null +++ b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/plugins/search.go @@ -0,0 +1,74 @@ +package plugins + +import ( + "fmt" + "net" + "strings" + + "github.com/AvengeMedia/DankMaterialShell/core/internal/plugins" + "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models" +) + +func HandleSearch(conn net.Conn, req models.Request) { + query, ok := models.Get[string](req, "query") + if !ok { + models.RespondError(conn, req.ID, "missing or invalid 'query' parameter") + return + } + + registry, err := plugins.NewRegistry() + if err != nil { + models.RespondError(conn, req.ID, fmt.Sprintf("failed to create registry: %v", err)) + return + } + + pluginList, err := registry.List() + if err != nil { + models.RespondError(conn, req.ID, fmt.Sprintf("failed to list plugins: %v", err)) + return + } + + searchResults := plugins.FuzzySearch(query, pluginList) + + if category := models.GetOr(req, "category", ""); category != "" { + searchResults = plugins.FilterByCategory(category, searchResults) + } + + if compositor := models.GetOr(req, "compositor", ""); compositor != "" { + searchResults = plugins.FilterByCompositor(compositor, searchResults) + } + + if capability := models.GetOr(req, "capability", ""); capability != "" { + searchResults = plugins.FilterByCapability(capability, searchResults) + } + + searchResults = plugins.SortByFirstParty(searchResults) + + manager, err := plugins.NewManager() + if err != nil { + models.RespondError(conn, req.ID, fmt.Sprintf("failed to create manager: %v", err)) + return + } + + result := make([]PluginInfo, len(searchResults)) + for i, p := range searchResults { + installed, _ := manager.IsInstalled(p) + result[i] = PluginInfo{ + ID: p.ID, + Name: p.Name, + Category: p.Category, + Author: p.Author, + Description: p.Description, + Repo: p.Repo, + Path: p.Path, + Capabilities: p.Capabilities, + Compositors: p.Compositors, + Dependencies: p.Dependencies, + Installed: installed, + FirstParty: strings.HasPrefix(p.Repo, "https://github.com/AvengeMedia"), + RequiresDMS: p.RequiresDMS, + } + } + + models.Respond(conn, req.ID, result) +} diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/plugins/types.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/plugins/types.go new file mode 100644 index 0000000..8290f72 --- /dev/null +++ b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/plugins/types.go @@ -0,0 +1,25 @@ +package plugins + +type PluginInfo struct { + ID string `json:"id"` + Name string `json:"name"` + Category string `json:"category,omitempty"` + Author string `json:"author,omitempty"` + Description string `json:"description,omitempty"` + Repo string `json:"repo,omitempty"` + Path string `json:"path,omitempty"` + Capabilities []string `json:"capabilities,omitempty"` + Compositors []string `json:"compositors,omitempty"` + Dependencies []string `json:"dependencies,omitempty"` + Installed bool `json:"installed,omitempty"` + FirstParty bool `json:"firstParty,omitempty"` + Featured bool `json:"featured,omitempty"` + Note string `json:"note,omitempty"` + HasUpdate bool `json:"hasUpdate,omitempty"` + RequiresDMS string `json:"requires_dms,omitempty"` +} + +type SuccessResult struct { + Success bool `json:"success"` + Message string `json:"message"` +} diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/plugins/uninstall.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/plugins/uninstall.go new file mode 100644 index 0000000..7a3d698 --- /dev/null +++ b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/plugins/uninstall.go @@ -0,0 +1,66 @@ +package plugins + +import ( + "fmt" + "net" + + "github.com/AvengeMedia/DankMaterialShell/core/internal/plugins" + "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models" +) + +func HandleUninstall(conn net.Conn, req models.Request) { + name, ok := models.Get[string](req, "name") + if !ok { + models.RespondError(conn, req.ID, "missing or invalid 'name' parameter") + return + } + + manager, err := plugins.NewManager() + if err != nil { + models.RespondError(conn, req.ID, fmt.Sprintf("failed to create manager: %v", err)) + return + } + + // First try to find in registry (by name or ID) + registry, err := plugins.NewRegistry() + if err != nil { + models.RespondError(conn, req.ID, fmt.Sprintf("failed to create registry: %v", err)) + return + } + + pluginList, _ := registry.List() + plugin := plugins.FindByIDOrName(name, pluginList) + + // If found in registry, use that + if plugin != nil { + installed, err := manager.IsInstalled(*plugin) + if err != nil { + models.RespondError(conn, req.ID, fmt.Sprintf("failed to check if plugin is installed: %v", err)) + return + } + if !installed { + models.RespondError(conn, req.ID, fmt.Sprintf("plugin not installed: %s", name)) + return + } + if err := manager.Uninstall(*plugin); err != nil { + models.RespondError(conn, req.ID, fmt.Sprintf("failed to uninstall plugin: %v", err)) + return + } + models.Respond(conn, req.ID, SuccessResult{ + Success: true, + Message: fmt.Sprintf("plugin uninstalled: %s", plugin.Name), + }) + return + } + + // Not in registry - try to find and uninstall from installed plugins directly + if err := manager.UninstallByIDOrName(name); err != nil { + models.RespondError(conn, req.ID, fmt.Sprintf("plugin not found: %s", name)) + return + } + + models.Respond(conn, req.ID, SuccessResult{ + Success: true, + Message: fmt.Sprintf("plugin uninstalled: %s", name), + }) +} diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/plugins/update.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/plugins/update.go new file mode 100644 index 0000000..4ea762a --- /dev/null +++ b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/plugins/update.go @@ -0,0 +1,64 @@ +package plugins + +import ( + "fmt" + "net" + + "github.com/AvengeMedia/DankMaterialShell/core/internal/plugins" + "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models" +) + +func HandleUpdate(conn net.Conn, req models.Request) { + name, ok := models.Get[string](req, "name") + if !ok { + models.RespondError(conn, req.ID, "missing or invalid 'name' parameter") + return + } + + manager, err := plugins.NewManager() + if err != nil { + models.RespondError(conn, req.ID, fmt.Sprintf("failed to create manager: %v", err)) + return + } + + registry, err := plugins.NewRegistry() + if err != nil { + models.RespondError(conn, req.ID, fmt.Sprintf("failed to create registry: %v", err)) + return + } + + pluginList, _ := registry.List() + plugin := plugins.FindByIDOrName(name, pluginList) + + if plugin != nil { + installed, err := manager.IsInstalled(*plugin) + if err != nil { + models.RespondError(conn, req.ID, fmt.Sprintf("failed to check if plugin is installed: %v", err)) + return + } + if !installed { + models.RespondError(conn, req.ID, fmt.Sprintf("plugin not installed: %s", name)) + return + } + if err := manager.Update(*plugin); err != nil { + models.RespondError(conn, req.ID, fmt.Sprintf("failed to update plugin: %v", err)) + return + } + models.Respond(conn, req.ID, SuccessResult{ + Success: true, + Message: fmt.Sprintf("plugin updated: %s", plugin.Name), + }) + return + } + + // Not in registry - try to update from installed plugins directly + if err := manager.UpdateByIDOrName(name); err != nil { + models.RespondError(conn, req.ID, fmt.Sprintf("plugin not found: %s", name)) + return + } + + models.Respond(conn, req.ID, SuccessResult{ + Success: true, + Message: fmt.Sprintf("plugin updated: %s", name), + }) +} diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/plugins/utils.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/plugins/utils.go new file mode 100644 index 0000000..8b89f44 --- /dev/null +++ b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/plugins/utils.go @@ -0,0 +1,17 @@ +package plugins + +import ( + "sort" + "strings" +) + +func SortPluginInfoByFirstParty(pluginInfos []PluginInfo) { + sort.SliceStable(pluginInfos, func(i, j int) bool { + isFirstPartyI := strings.HasPrefix(pluginInfos[i].Repo, "https://github.com/AvengeMedia") + isFirstPartyJ := strings.HasPrefix(pluginInfos[j].Repo, "https://github.com/AvengeMedia") + if isFirstPartyI != isFirstPartyJ { + return isFirstPartyI + } + return false + }) +} |