aboutsummaryrefslogtreecommitdiff
path: root/widget/widget.go
blob: 29a8f3a635fafd7ac3d8f907fd2553b09bcc4e45 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
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) 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
}