Files
SugarDB/echovault/plugin.go
Kelvin Clement Mwinuka 851ca6d11c Added copyright text
2024-05-03 12:13:59 +08:00

183 lines
5.1 KiB
Go

// Copyright 2024 Kelvin Clement Mwinuka
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package echovault
import (
"context"
"errors"
"fmt"
"github.com/echovault/echovault/internal"
"plugin"
"slices"
"strings"
)
// LoadModule loads an external module into EchoVault ar runtime.
//
// Parameters:
//
// `path` - string - The full path to the .so plugin to be loaded.
//
// `args` - ...string - A list of args that will be passed unmodified to the plugins command's
// KeyExtractionFunc and HandlerFunc
func (server *EchoVault) LoadModule(path string, args ...string) error {
p, err := plugin.Open(path)
if err != nil {
return fmt.Errorf("plugin open: %v", err)
}
commandSymbol, err := p.Lookup("Command")
if err != nil {
return err
}
command, ok := commandSymbol.(*string)
if !ok {
return errors.New("command symbol is not a string")
}
categoriesSymbol, err := p.Lookup("Categories")
if err != nil {
return err
}
categories, ok := categoriesSymbol.(*[]string)
if !ok {
return errors.New("categories symbol not a string slice")
}
descriptionSymbol, err := p.Lookup("Description")
if err != nil {
return err
}
description, ok := descriptionSymbol.(*string)
if !ok {
return errors.New("description symbol is no a string")
}
syncSymbol, err := p.Lookup("Sync")
if err != nil {
return err
}
sync, ok := syncSymbol.(*bool)
if !ok {
return errors.New("sync symbol is not a bool")
}
keyExtractionFuncSymbol, err := p.Lookup("KeyExtractionFunc")
if err != nil {
return fmt.Errorf("key extraction func symbol: %v", err)
}
keyExtractionFunc, ok := keyExtractionFuncSymbol.(func(cmd []string, args ...string) ([]string, []string, error))
if !ok {
return errors.New("key extraction function has unexpected signature")
}
handlerFuncSymbol, err := p.Lookup("HandlerFunc")
if err != nil {
return fmt.Errorf("handler func symbol: %v", err)
}
handlerFunc, ok := handlerFuncSymbol.(func(
ctx context.Context,
command []string,
keyExists func(ctx context.Context, key string) bool,
keyLock func(ctx context.Context, key string) (bool, error),
keyUnlock func(ctx context.Context, key string),
keyRLock func(ctx context.Context, key string) (bool, error),
keyRUnlock func(ctx context.Context, key string),
createKeyAndLock func(ctx context.Context, key string) (bool, error),
getValue func(ctx context.Context, key string) interface{},
setValue func(ctx context.Context, key string, value interface{}) error,
args ...string,
) ([]byte, error))
if !ok {
return errors.New("handler function has unexpected signature")
}
server.commandsRWMut.Lock()
defer server.commandsRWMut.Unlock()
server.commands = append(server.commands, internal.Command{
Command: *command,
Module: path,
Categories: func() []string {
// Convert all the categories to lower case for uniformity
cats := make([]string, len(*categories))
for i, cat := range *categories {
cats[i] = strings.ToLower(cat)
}
return cats
}(),
Description: *description,
Sync: *sync,
SubCommands: make([]internal.SubCommand, 0),
KeyExtractionFunc: func(cmd []string) (internal.KeyExtractionFuncResult, error) {
readKeys, writeKeys, err := keyExtractionFunc(cmd, args...)
if err != nil {
return internal.KeyExtractionFuncResult{}, err
}
return internal.KeyExtractionFuncResult{
Channels: make([]string, 0),
ReadKeys: readKeys,
WriteKeys: writeKeys,
}, nil
},
HandlerFunc: func(params internal.HandlerFuncParams) ([]byte, error) {
return handlerFunc(
params.Context,
params.Command,
params.KeyExists,
params.KeyLock,
params.KeyUnlock,
params.KeyRLock,
params.KeyRUnlock,
params.CreateKeyAndLock,
params.GetValue,
params.SetValue,
)
},
})
return nil
}
// UnloadModule unloads the provided module
//
// Parameters:
//
// `module` - string - module name as displayed by the ListModules method.
func (server *EchoVault) UnloadModule(module string) {
server.commandsRWMut.Lock()
defer server.commandsRWMut.Unlock()
server.commands = slices.DeleteFunc(server.commands, func(command internal.Command) bool {
return strings.EqualFold(command.Module, module)
})
}
// ListModules lists the currently loaded modules
//
// Returns: a string slice representing all the currently loaded modules.
func (server *EchoVault) ListModules() []string {
server.commandsRWMut.RLock()
defer server.commandsRWMut.RUnlock()
var modules []string
for _, command := range server.commands {
if !slices.ContainsFunc(modules, func(module string) bool {
return strings.EqualFold(module, command.Module)
}) {
modules = append(modules, strings.ToLower(command.Module))
}
}
return modules
}