refactor: remove disabled watcher feature
This commit is contained in:
@@ -1,16 +0,0 @@
|
|||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NOTE: The watch command is disabled due to database concurrency issues.
|
|
||||||
// To re-enable, uncomment RootCmd.AddCommand(watchCmd) in init().
|
|
||||||
var watchCmd = &cobra.Command{
|
|
||||||
Use: "watch",
|
|
||||||
Short: "Watch playlists and auto-download new tracks",
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
// RootCmd.AddCommand(watchCmd)
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/mathismqn/godeez/internal/store"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
var watchAddCmd = &cobra.Command{
|
|
||||||
Use: "add <playlist_id>",
|
|
||||||
Short: "Add a playlist to the watch list",
|
|
||||||
Args: cobra.ExactArgs(1),
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
id := args[0]
|
|
||||||
|
|
||||||
ok, err := store.IsWatched(id)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if ok {
|
|
||||||
fmt.Printf("Playlist %s is already being watched\n", id)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
playlist := &store.WatchedPlaylist{
|
|
||||||
ID: id,
|
|
||||||
Quality: strings.ToLower(opts.Quality),
|
|
||||||
BPM: opts.BPM,
|
|
||||||
Timeout: opts.Timeout,
|
|
||||||
}
|
|
||||||
if err := playlist.Save(); err != nil {
|
|
||||||
return fmt.Errorf("failed to add playlist %s to watch list: %w", id, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("Playlist %s added to watch list\n", id)
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
watchCmd.AddCommand(watchAddCmd)
|
|
||||||
|
|
||||||
watchAddCmd.Flags().StringVarP(&opts.Quality, "quality", "q", "mp3_320", "download quality [mp3_128, mp3_320, flac]")
|
|
||||||
watchAddCmd.Flags().DurationVarP(&opts.Timeout, "timeout", "t", 2*time.Minute, "timeout for each download (e.g. 10s, 1m, 2m30s)")
|
|
||||||
watchAddCmd.Flags().BoolVar(&opts.BPM, "bpm", false, "fetch BPM/key and add to file tags")
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"text/tabwriter"
|
|
||||||
|
|
||||||
"github.com/mathismqn/godeez/internal/store"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
var watchListCmd = &cobra.Command{
|
|
||||||
Use: "list",
|
|
||||||
Short: "List watched playlists",
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
playlists, err := store.ListWatchedPlaylists()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to list watched playlists: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(playlists) == 0 {
|
|
||||||
fmt.Println("No watched playlists.")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
|
|
||||||
fmt.Fprintln(w, "ID\tQuality\tFetch BPM\tTimeout")
|
|
||||||
fmt.Fprintln(w, "---\t-------\t----------\t-------")
|
|
||||||
|
|
||||||
for _, playlist := range playlists {
|
|
||||||
fmt.Fprintf(w, "%s\t%s\t%t\t%s\n", playlist.ID, playlist.Quality, playlist.BPM, playlist.Timeout)
|
|
||||||
}
|
|
||||||
w.Flush()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
watchCmd.AddCommand(watchListCmd)
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/mathismqn/godeez/internal/store"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
var watchRemoveCmd = &cobra.Command{
|
|
||||||
Use: "remove <playlist_id>",
|
|
||||||
Short: "Remove a playlist from the watch list",
|
|
||||||
Args: cobra.ExactArgs(1),
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
id := args[0]
|
|
||||||
ok, err := store.IsWatched(id)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("playlist %s is not being watched", id)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := store.RemoveWatchedPlaylist(id); err != nil {
|
|
||||||
return fmt.Errorf("failed to remove playlist %s from watch list: %w", id, err)
|
|
||||||
}
|
|
||||||
fmt.Printf("Playlist %s removed from watch list\n", id)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
watchCmd.AddCommand(watchRemoveCmd)
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/mathismqn/godeez/internal/config"
|
|
||||||
"github.com/mathismqn/godeez/internal/watcher"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
var watchRunCmd = &cobra.Command{
|
|
||||||
Use: "run",
|
|
||||||
Short: "Start the background playlist watcher",
|
|
||||||
Hidden: true,
|
|
||||||
PreRun: func(cmd *cobra.Command, args []string) {
|
|
||||||
appConfig, err := config.New("")
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
cmd.SetContext(context.WithValue(cmd.Context(), appConfigKey, appConfig))
|
|
||||||
},
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
appConfig, _ := cmd.Context().Value(appConfigKey).(*config.Config)
|
|
||||||
watcher.New(appConfig).Run(cmd.Context(), opts)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
watchCmd.AddCommand(watchRunCmd)
|
|
||||||
}
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
package store
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
bolt "go.etcd.io/bbolt"
|
|
||||||
)
|
|
||||||
|
|
||||||
type WatchedPlaylist struct {
|
|
||||||
ID string `json:"id"`
|
|
||||||
Quality string `json:"quality"`
|
|
||||||
BPM bool `json:"bpm"`
|
|
||||||
Timeout time.Duration `json:"timeout"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var watchedBucket = []byte("watched")
|
|
||||||
|
|
||||||
func ListWatchedPlaylists() ([]*WatchedPlaylist, error) {
|
|
||||||
var playlists []*WatchedPlaylist
|
|
||||||
if err := db.View(func(tx *bolt.Tx) error {
|
|
||||||
b := tx.Bucket(watchedBucket)
|
|
||||||
if b == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return b.ForEach(func(k, v []byte) error {
|
|
||||||
var p WatchedPlaylist
|
|
||||||
if err := json.Unmarshal(v, &p); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
playlists = append(playlists, &p)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return playlists, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *WatchedPlaylist) Save() error {
|
|
||||||
return db.Update(func(tx *bolt.Tx) error {
|
|
||||||
b, err := tx.CreateBucketIfNotExists(watchedBucket)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := json.Marshal(p)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return b.Put([]byte(p.ID), data)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func RemoveWatchedPlaylist(playlistID string) error {
|
|
||||||
return db.Update(func(tx *bolt.Tx) error {
|
|
||||||
b := tx.Bucket(watchedBucket)
|
|
||||||
if b == nil {
|
|
||||||
return fmt.Errorf("bucket not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
return b.Delete([]byte(playlistID))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func IsWatched(playlistID string) (bool, error) {
|
|
||||||
var found bool
|
|
||||||
err := db.View(func(tx *bolt.Tx) error {
|
|
||||||
b := tx.Bucket(watchedBucket)
|
|
||||||
if b == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
found = b.Get([]byte(playlistID)) != nil
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
return found, err
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
package watcher
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// EnsureAutostart installs the watcher as a system autostart service.
|
|
||||||
// Currently disabled: installAutostart is not called due to DB concurrency issues.
|
|
||||||
func EnsureAutostart(homeDir string) error {
|
|
||||||
if isAutostartInstalled(homeDir) || isTemporaryExecutable() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// return installAutostart(homeDir)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func isAutostartInstalled(homeDir string) bool {
|
|
||||||
if runtime.GOOS != "darwin" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
path := filepath.Join(homeDir, "Library", "LaunchAgents", "com.godeez.watch.plist")
|
|
||||||
_, err := os.Stat(path)
|
|
||||||
return err == nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func isTemporaryExecutable() bool {
|
|
||||||
exe, err := os.Executable()
|
|
||||||
if err != nil {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return strings.Contains(exe, "go-build")
|
|
||||||
}
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
//go:build darwin
|
|
||||||
|
|
||||||
package watcher
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
|
||||||
)
|
|
||||||
|
|
||||||
func installAutostart(homeDir string) error {
|
|
||||||
exe, err := os.Executable()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
exe, err = filepath.EvalSymlinks(exe)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
plist := fmt.Sprintf(`<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
|
|
||||||
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict>
|
|
||||||
<key>Label</key>
|
|
||||||
<string>com.godeez.watch</string>
|
|
||||||
<key>ProgramArguments</key>
|
|
||||||
<array>
|
|
||||||
<string>%s</string>
|
|
||||||
<string>watch</string>
|
|
||||||
<string>run</string>
|
|
||||||
</array>
|
|
||||||
<key>RunAtLoad</key>
|
|
||||||
<true/>
|
|
||||||
<key>KeepAlive</key>
|
|
||||||
<true/>
|
|
||||||
</dict>
|
|
||||||
</plist>`, exe)
|
|
||||||
|
|
||||||
path := filepath.Join(homeDir, "Library", "LaunchAgents", "com.godeez.watch.plist")
|
|
||||||
if err := os.WriteFile(path, []byte(plist), 0644); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return exec.Command("launchctl", "load", path).Run()
|
|
||||||
}
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
package watcher
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/mathismqn/godeez/internal/config"
|
|
||||||
"github.com/mathismqn/godeez/internal/downloader"
|
|
||||||
"github.com/mathismqn/godeez/internal/logger"
|
|
||||||
"github.com/mathismqn/godeez/internal/store"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Watcher struct {
|
|
||||||
appConfig *config.Config
|
|
||||||
logger *logger.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
func New(appConfig *config.Config) *Watcher {
|
|
||||||
logFile := filepath.Join(appConfig.HomeDir, ".godeez", "watcher.log")
|
|
||||||
file, err := os.OpenFile(logFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Failed to open log file: %v\n", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
l := logger.New(log.New(file, "", log.LstdFlags))
|
|
||||||
|
|
||||||
return &Watcher{
|
|
||||||
appConfig: appConfig,
|
|
||||||
logger: l,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *Watcher) Run(ctx context.Context, opts downloader.Options) {
|
|
||||||
w.logger.Infof("Starting watcher...")
|
|
||||||
|
|
||||||
for {
|
|
||||||
playlists, err := store.ListWatchedPlaylists()
|
|
||||||
if err != nil {
|
|
||||||
w.logger.Errorf("Failed to list watched playlists: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, playlist := range playlists {
|
|
||||||
dl := downloader.New(w.appConfig, "playlist")
|
|
||||||
dl.Logger = w.logger
|
|
||||||
if err := dl.Run(ctx, opts, playlist.ID); err != nil {
|
|
||||||
if errors.Is(err, context.Canceled) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
w.logger.Errorf("Playlist %s: %v", playlist.ID, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return
|
|
||||||
case <-time.After(15 * time.Minute):
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user