82 lines
1.9 KiB
Go
82 lines
1.9 KiB
Go
package syncer
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"log"
|
|
"os/exec"
|
|
"path/filepath"
|
|
|
|
"mediawatcher/internal/classifier"
|
|
"mediawatcher/internal/config"
|
|
)
|
|
|
|
type Syncer struct {
|
|
cfg *config.SyncConfig
|
|
}
|
|
|
|
func New(cfg *config.SyncConfig) *Syncer {
|
|
if cfg == nil || !cfg.Enabled {
|
|
return nil
|
|
}
|
|
return &Syncer{cfg: cfg}
|
|
}
|
|
|
|
func (s *Syncer) Sync(res classifier.Result) {
|
|
if s == nil || !s.cfg.Enabled {
|
|
return
|
|
}
|
|
|
|
for _, t := range s.cfg.Targets {
|
|
if err := s.syncToTarget(t, res); err != nil {
|
|
log.Printf("[sync] error syncing to %s: %v", t.Host, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *Syncer) syncToTarget(t config.RsyncTarget, res classifier.Result) error {
|
|
if t.Host == "" || t.DestBase == "" {
|
|
return fmt.Errorf("invalid target config: host and dest_base required")
|
|
}
|
|
|
|
user := t.User
|
|
if user == "" {
|
|
user = "media"
|
|
}
|
|
port := t.Port
|
|
if port == 0 {
|
|
port = 22
|
|
}
|
|
|
|
// Recreate relative dest under DestBase
|
|
rel, err := filepath.Rel(res.DestDir, filepath.Join(res.DestDir, res.DestName))
|
|
if err != nil {
|
|
rel = res.DestName
|
|
}
|
|
|
|
remoteDir := t.DestBase
|
|
remote := fmt.Sprintf("%s@%s:%s/", user, t.Host, remoteDir)
|
|
|
|
args := []string{"-avh"}
|
|
if port != 22 {
|
|
args = append(args, "-e", fmt.Sprintf("ssh -p %d", port))
|
|
}
|
|
args = append(args, t.ExtraArgs...)
|
|
args = append(args, filepath.Join(res.DestDir, res.DestName), remote)
|
|
|
|
log.Printf("[sync] rsync %v", args)
|
|
|
|
cmd := exec.Command(s.cfg.RsyncBinary, args...)
|
|
var stdout, stderr bytes.Buffer
|
|
cmd.Stdout = &stdout
|
|
cmd.Stderr = &stderr
|
|
|
|
if err := cmd.Run(); err != nil {
|
|
log.Printf("[sync] stdout: %s", stdout.String())
|
|
log.Printf("[sync] stderr: %s", stderr.String())
|
|
return fmt.Errorf("rsync failed: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|