package widget import ( "bytes" "context" "encoding/json" "errors" "fmt" "io" "now/widget/api" "os" "os/exec" "strings" "time" ) const Version uint = 1 var ( ErrUnexceptedType = errors.New("unexcepted type") ) type Widget struct { api.Hello Path string `json:"-"` } func FromJson(data []byte) (*Widget, error) { var widget Widget return &widget, json.Unmarshal(data, &widget) } func Load(path string) ([]*Widget, error) { path = strings.TrimSuffix(path, "/") dir, err := os.ReadDir(path) if err != nil { if !errors.Is(err, os.ErrNotExist) { return nil, err } return nil, os.Mkdir(path, 0755) } return readPluginDir(dir, path+"/") } func readPluginDir(dir []os.DirEntry, path string) ([]*Widget, error) { if !strings.HasSuffix(path, "/") { path += "/" } ctx, cancel := context.WithCancel(context.Background()) defer cancel() var widgets []*Widget for _, e := range dir { p := path + e.Name() if e.IsDir() { d, err := os.ReadDir(p) if err != nil { return nil, err } ws, err := readPluginDir(d, p) if err != nil { return nil, err } widgets = append(widgets, ws...) } w := &Widget{ Path: p, } c := make(chan any) ctx, cancel := context.WithTimeout(ctx, 5*time.Second) go w.Request(ctx, api.MethodHello, c) select { case <-ctx.Done(): cancel() return nil, ctx.Err() case out := <-c: cancel() err, ok := out.(error) if ok { return nil, err } b, ok := out.([]byte) if !ok { return nil, errors.Join(ErrUnexceptedType, fmt.Errorf("got %T instead of []byte", out)) } if err = json.Unmarshal(b, w); err != nil { return nil, err } } widgets = append(widgets, w) } return widgets, nil } func (w *Widget) FetchCredits(ctx context.Context) (*api.Credits, error) { c := make(chan any) go w.Request(ctx, api.MethodCredits, c) select { case <-ctx.Done(): return nil, ctx.Err() case out := <-c: err, ok := out.(error) if ok { return nil, err } b, ok := out.([]byte) if !ok { return nil, errors.Join(ErrUnexceptedType, fmt.Errorf("got %T instead of []byte", out)) } var credits api.Credits return &credits, json.Unmarshal(b, &credits) } } func (w *Widget) Request(_ context.Context, m api.Method, c chan<- any, args ...string) { args = append([]string{string(m)}, args...) cmd := exec.Command(fmt.Sprintf("./plugins/%s", w.Path), args...) if cmd.Err != nil { c <- cmd.Err return } wb := new(bytes.Buffer) cmd.Stdout = wb cmd.Stderr = wb if err := cmd.Run(); err != nil { c <- err return } b, err := io.ReadAll(wb) if err != nil { c <- err return } c <- b }