aboutsummaryrefslogtreecommitdiffsponsor
path: root/internal/service/service.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/service/service.go')
-rw-r--r--internal/service/service.go195
1 files changed, 195 insertions, 0 deletions
diff --git a/internal/service/service.go b/internal/service/service.go
new file mode 100644
index 0000000..c34bcd3
--- /dev/null
+++ b/internal/service/service.go
@@ -0,0 +1,195 @@
+package service
+
+import (
+ "encoding/json"
+ "errors"
+ "fmt"
+ "math/rand"
+ "net/url"
+ "os"
+ "strconv"
+ "strings"
+ "sync"
+ "time"
+
+ "git.server.ky/slackcoder/mirror/internal/github"
+)
+
+type Mirror struct {
+ Method string `json:"method"`
+ From *url.URL `json:"-"`
+ To *url.URL `json:"-"`
+ Description string `json:"description,omitempty"`
+}
+
+func (m *Mirror) Equal(arg *Mirror) bool {
+ return m.Method == arg.Method && m.From.String() == arg.From.String() && m.To.String() == arg.To.String()
+}
+
+func (m *Mirror) String() string {
+ buf, err := json.Marshal(m)
+ if err != nil {
+ panic(err)
+ }
+ return string(buf)
+}
+
+type Service struct {
+ cfg *Config
+
+ mirrorsLock sync.Mutex
+ mirrors []*Mirror
+ schedule []time.Time
+
+ stopCh chan struct{}
+}
+
+func NewService(cfg *Config) (*Service, error) {
+ cfg.Apply(DefaultConfig)
+
+ srv := &Service{
+ cfg: cfg,
+ mirrors: nil,
+ stopCh: make(chan struct{}),
+ }
+
+ for _, m := range cfg.Mirrors {
+ err := srv.AddMirror(m)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ return srv, nil
+}
+
+func (s *Service) scheduleNextRun(arg *Mirror) {
+ // Be polite.
+ period := s.cfg.MaxInterval.Duration - s.cfg.MinInterval.Duration
+ at := time.Now().Add(s.cfg.MinInterval.Duration + time.Duration(rand.Intn(int(period))))
+
+ for i, m := range s.mirrors {
+ if m.Equal(arg) {
+ s.schedule[i] = at
+ }
+ }
+}
+
+func (s *Service) AddMirror(arg *Mirror) error {
+ defer s.mirrorsLock.Unlock()
+ s.mirrorsLock.Lock()
+
+ for _, m := range s.mirrors {
+ if m.Equal(arg) {
+ return errors.New("must be unique")
+ }
+ }
+
+ s.schedule = append(s.schedule, time.Time{})
+ s.mirrors = append(s.mirrors, arg)
+ s.scheduleNextRun(arg)
+
+ return nil
+}
+
+func (s *Service) scheduled(yield func(*Mirror) bool) {
+ defer s.mirrorsLock.Unlock()
+ s.mirrorsLock.Lock()
+
+ now := time.Now()
+ for i, m := range s.mirrors {
+ if s.schedule[i].After(now) {
+ continue
+ }
+
+ if !yield(m) {
+ break
+ }
+ }
+}
+
+func (s *Service) Mirror(arg *Mirror) error {
+ if arg.From.Path == "" || arg.To.Path == "" || arg.Method == "" {
+ return fmt.Errorf("badly formatted mirror '%s'", arg.String())
+ }
+
+ var err error
+
+ switch arg.Method {
+ case "git":
+ err = MirrorGit(arg.To, arg.From, arg.Description)
+ case "github-assets":
+ client := github.NewClient()
+ err = client.MirrorAssets(arg.To, arg.From)
+ case "rsync":
+ err = Rsync(arg.To, arg.From)
+ default:
+ err = fmt.Errorf("unknown method '%s'", arg.Method)
+ }
+
+ if err != nil {
+ return fmt.Errorf("could not clone from '%s': %w", arg.From, err)
+ }
+
+ return nil
+}
+
+func (s *Service) RemoveMirror(arg *Mirror) error {
+ defer s.mirrorsLock.Unlock()
+ s.mirrorsLock.Lock()
+
+ for i, m := range s.mirrors {
+ if m.Equal(arg) {
+ s.mirrors[i] = s.mirrors[len(s.mirrors)-1]
+ s.mirrors = s.mirrors[:len(s.mirrors)-1]
+
+ s.schedule[i] = s.schedule[len(s.schedule)-1]
+ s.schedule = s.schedule[:len(s.schedule)-1]
+
+ return nil
+ }
+ }
+
+ return errors.New("not found")
+}
+
+func (s *Service) Log(str string) {
+ escaped := []rune(strconv.Quote(strings.TrimSpace(str)))
+ escaped = escaped[1 : len(escaped)-1]
+
+ fmt.Fprintf(os.Stderr, "%s: %s\n", time.Now().Format(time.RFC822Z), string(escaped))
+}
+
+func (s *Service) Run() error {
+ wakeup := time.NewTicker(time.Second)
+
+mainLoop:
+ for {
+ select {
+ case <-wakeup.C:
+ case <-s.stopCh:
+ break mainLoop
+ }
+
+ s.scheduled(func(m *Mirror) bool {
+ err := s.Mirror(m)
+ if err != nil {
+ s.Log(err.Error())
+ }
+
+ s.scheduleNextRun(m)
+
+ return true
+ })
+ }
+
+ return nil
+}
+
+func (s *Service) Stop() {
+ select {
+ case <-s.stopCh:
+ default:
+ close(s.stopCh)
+ }
+}