From 6daef9e834b45773ab6e09097b5ab9cb7f4649dc Mon Sep 17 00:00:00 2001 From: Agustin Krapovickas Date: Tue, 31 Dec 2024 04:07:52 -0300 Subject: [PATCH] feat: logging --- .gitignore | 1 + cmd/cli/main.go | 17 ++++- cmd/gui/main.go | 17 ++++- deezer/client.go | 38 +++++++--- deezer/client_test.go | 9 +-- go.mod | 28 ++++---- go.sum | 60 ++++++++-------- internal/logs/logger.go | 153 ++++++++++++++++++++++++++++++++++++++++ playlists/manager.go | 4 +- spotify/client.go | 4 +- spotify/client_test.go | 2 +- 11 files changed, 264 insertions(+), 69 deletions(-) create mode 100644 internal/logs/logger.go diff --git a/.gitignore b/.gitignore index 528c2cf..f59779e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .env bin/ fyne-cross/ +*.log diff --git a/cmd/cli/main.go b/cmd/cli/main.go index 47d55a9..6498eef 100644 --- a/cmd/cli/main.go +++ b/cmd/cli/main.go @@ -10,15 +10,19 @@ import ( "path/filepath" "strings" + "fyne.io/fyne/v2" "github.com/agukrapo/go-http-client/client" "github.com/agukrapo/playlist-creator/deezer" "github.com/agukrapo/playlist-creator/internal/env" + "github.com/agukrapo/playlist-creator/internal/logs" "github.com/agukrapo/playlist-creator/internal/random" "github.com/agukrapo/playlist-creator/internal/results" "github.com/agukrapo/playlist-creator/playlists" "github.com/agukrapo/playlist-creator/spotify" ) +const appTitle = "playlist-creator-cli" + func main() { if err := run(); err != nil { _, _ = fmt.Fprintf(os.Stderr, "Error: %v\n", err) @@ -38,7 +42,14 @@ func run() error { cancel() }() - manager, err := buildManager() + logFile, err := logs.NewFile(appTitle) + if err != nil { + fyne.LogError("logs.NewFile", err) + os.Exit(1) + } + defer logFile.Close() + + manager, err := buildManager(logs.New(logFile)) if err != nil { return err } @@ -84,7 +95,7 @@ func run() error { return nil } -func buildManager() (*playlists.Manager, error) { +func buildManager(log *logs.Logger) (*playlists.Manager, error) { if len(os.Args) < 2 { return nil, errors.New("target argument missing") } @@ -102,7 +113,7 @@ func buildManager() (*playlists.Manager, error) { if err != nil { return nil, err } - target = deezer.New(client.New(), cookie) + target = deezer.New(client.New(), cookie, log) default: return nil, fmt.Errorf("unknown target %s", os.Args[1]) } diff --git a/cmd/gui/main.go b/cmd/gui/main.go index f2ab9e2..0f75d21 100644 --- a/cmd/gui/main.go +++ b/cmd/gui/main.go @@ -17,6 +17,7 @@ import ( "github.com/agukrapo/go-http-client/client" "github.com/agukrapo/playlist-creator/deezer" "github.com/agukrapo/playlist-creator/internal/env" + "github.com/agukrapo/playlist-creator/internal/logs" "github.com/agukrapo/playlist-creator/internal/random" "github.com/agukrapo/playlist-creator/internal/results" "github.com/agukrapo/playlist-creator/playlists" @@ -30,7 +31,14 @@ func main() { fyne.LogError("env.Lookup", err) } - app := newApplication(cookie) + logFile, err := logs.NewFile(appTitle) + if err != nil { + fyne.LogError("logs.NewFile", err) + os.Exit(1) + } + defer logFile.Close() + + app := newApplication(cookie, logs.New(logFile)) app.ShowAndRun() } @@ -41,9 +49,11 @@ type application struct { dialogs chan dialoger cookie string + + log *logs.Logger } -func newApplication(cookie string) *application { +func newApplication(cookie string, log *logs.Logger) *application { out := fyneapp.New() version := out.Metadata().Custom["version"] @@ -58,6 +68,7 @@ func newApplication(cookie string) *application { window: w, dialogs: make(chan dialoger), cookie: cookie, + log: log, } } @@ -102,7 +113,7 @@ func (a *application) renderForm() { a.working() - target := deezer.New(client.New(), arl.Text) + target := deezer.New(client.New(), arl.Text, a.log) a.renderResults(target, name.Text, lines(songs.Text)) } diff --git a/deezer/client.go b/deezer/client.go index 0241de7..d3f73d2 100644 --- a/deezer/client.go +++ b/deezer/client.go @@ -11,6 +11,7 @@ import ( "strings" "github.com/agukrapo/go-http-client/requests" + "github.com/agukrapo/playlist-creator/internal/logs" "github.com/agukrapo/playlist-creator/playlists" ) @@ -26,13 +27,16 @@ type Client struct { tokenizer func(ctx context.Context) (string, cookieJar, error) arl string + + log *logs.Logger } -func New(httpClient doer, arl string) *Client { +func New(httpClient doer, arl string, log *logs.Logger) *Client { out := &Client{ httpClient: httpClient, apiURL: "https://www.deezer.com/ajax/gw-light.php", arl: arl, + log: log, } out.tokenizer = out.token @@ -55,11 +59,14 @@ type userResponse struct { CheckForm string `json:"checkForm"` } -func (c *Client) token(ctx context.Context) (string, cookieJar, error) { +func (c *Client) token(ctx context.Context) (token string, cookies cookieJar, err error) { + tr := c.log.Trace("deezer.token").Begins() + defer func() { tr.Ends(err, logs.Var("token", token), logs.Var("cookies", cookies)) }() + arl := newJar(&http.Cookie{Name: "arl", Value: c.arl}) var out userResponse - cookies, err := c.send(ctx, "", "deezer.getUserData", arl, nil, &out) + cookies, err = c.send(ctx, tr, "", "deezer.getUserData", arl, nil, &out) if err != nil { return "", nil, err } @@ -115,7 +122,10 @@ func (sr searchResponse) tracks() []playlists.Track { return out } -func (c *Client) SearchTrack(ctx context.Context, query string) ([]playlists.Track, error) { +func (c *Client) SearchTracks(ctx context.Context, query string) (tracks []playlists.Track, err error) { + tr := c.log.Trace("deezer.SearchTracks").Begins(logs.Var("query", query)) + defer func() { tr.Ends(err, logs.Var("tracks", tracks)) }() + token, cookies, err := c.tokenizer(ctx) if err != nil { return nil, err @@ -124,14 +134,17 @@ func (c *Client) SearchTrack(ctx context.Context, query string) ([]playlists.Tra in := map[string]string{"query": query} var out searchResponse - if _, err := c.send(ctx, token, "deezer.pageSearch", cookies, in, &out); err != nil { + if _, err := c.send(ctx, tr, token, "deezer.pageSearch", cookies, in, &out); err != nil { return nil, err } return out.tracks(), nil } -func (c *Client) CreatePlaylist(ctx context.Context, title string) (string, error) { +func (c *Client) CreatePlaylist(ctx context.Context, title string) (id string, err error) { + tr := c.log.Trace("deezer.CreatePlaylist").Begins(logs.Var("title", title)) + defer func() { tr.Ends(err, logs.Var("id", id)) }() + token, cookies, err := c.tokenizer(ctx) if err != nil { return "", err @@ -140,7 +153,7 @@ func (c *Client) CreatePlaylist(ctx context.Context, title string) (string, erro in := map[string]any{"title": title} var out json.Number - if _, err := c.send(ctx, token, "playlist.create", cookies, in, &out); err != nil { + if _, err := c.send(ctx, tr, token, "playlist.create", cookies, in, &out); err != nil { return "", err } @@ -151,7 +164,10 @@ func (c *Client) CreatePlaylist(ctx context.Context, title string) (string, erro return out.String(), nil } -func (c *Client) PopulatePlaylist(ctx context.Context, playlist string, tracks []string) error { +func (c *Client) PopulatePlaylist(ctx context.Context, playlist string, tracks []string) (err error) { + tr := c.log.Trace("deezer.PopulatePlaylist").Begins(logs.Var("playlist", playlist), logs.Var("tracks", tracks)) + defer func() { tr.Ends(err) }() + token, cookies, err := c.tokenizer(ctx) if err != nil { return err @@ -168,7 +184,7 @@ func (c *Client) PopulatePlaylist(ctx context.Context, playlist string, tracks [ } var out bool - if _, err := c.send(ctx, token, "playlist.addSongs", cookies, in, &out); err != nil { + if _, err := c.send(ctx, tr, token, "playlist.addSongs", cookies, in, &out); err != nil { return err } @@ -228,7 +244,7 @@ func newJar(cookies ...*http.Cookie) cookieJar { return out } -func (c *Client) send(ctx context.Context, token, method string, cookies cookieJar, in, out any) (cookieJar, error) { +func (c *Client) send(ctx context.Context, trace *logs.Trace, token, method string, cookies cookieJar, in, out any) (cookieJar, error) { req, err := requests.New(c.apiURL).Post().JSON(in).Build(ctx) if err != nil { return nil, err @@ -257,6 +273,8 @@ func (c *Client) send(ctx context.Context, token, method string, cookies cookieJ return nil, err } + trace.Dump(raw) + if len(raw) == 0 { return nil, fmt.Errorf("%s: empty response", method) } diff --git a/deezer/client_test.go b/deezer/client_test.go index 998811c..e6c46fd 100644 --- a/deezer/client_test.go +++ b/deezer/client_test.go @@ -8,6 +8,7 @@ import ( "net/http/httptest" "testing" + "github.com/agukrapo/playlist-creator/internal/logs" "github.com/agukrapo/playlist-creator/internal/tests" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -44,7 +45,7 @@ func TestClient_token(t *testing.T) { })) defer svr.Close() - client := New(http.DefaultClient, "_ARL") + client := New(http.DefaultClient, "_ARL", logs.New(nil)) client.apiURL = svr.URL token, cookies, err := client.token(context.Background()) @@ -89,13 +90,13 @@ func TestClient_SearchTrack(t *testing.T) { })) defer svr.Close() - client := New(http.DefaultClient, "_ARL") + client := New(http.DefaultClient, "_ARL", logs.New(nil)) client.apiURL = svr.URL client.tokenizer = func(context.Context) (string, cookieJar, error) { return "_TOKEN", newJar(&http.Cookie{Name: "arl", Value: "_ARL"}), nil } - matches, err := client.SearchTrack(context.Background(), "_QUERY") + matches, err := client.SearchTracks(context.Background(), "_QUERY") require.NoError(t, err) assert.Len(t, matches, test.expectedMatchesLength) @@ -140,7 +141,7 @@ func TestClient_PopulatePlaylist(t *testing.T) { })) defer svr.Close() - client := New(http.DefaultClient, "_ARL") + client := New(http.DefaultClient, "_ARL", logs.New(nil)) client.apiURL = svr.URL client.tokenizer = func(context.Context) (string, cookieJar, error) { return "_TOKEN", newJar(&http.Cookie{Name: "arl", Value: "_ARL"}), nil diff --git a/go.mod b/go.mod index 3fd4136..590bff8 100644 --- a/go.mod +++ b/go.mod @@ -3,11 +3,11 @@ module github.com/agukrapo/playlist-creator go 1.23 require ( - fyne.io/fyne/v2 v2.5.2 - github.com/agukrapo/go-http-client v1.3.0 + fyne.io/fyne/v2 v2.5.3 + github.com/agukrapo/go-http-client v1.3.1 github.com/joho/godotenv v1.5.1 - github.com/stretchr/testify v1.9.0 - golang.org/x/sync v0.8.0 + github.com/stretchr/testify v1.10.0 + golang.org/x/sync v0.10.0 ) require ( @@ -15,28 +15,28 @@ require ( github.com/BurntSushi/toml v1.4.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/fredbi/uri v1.1.0 // indirect - github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/fsnotify/fsnotify v1.8.0 // indirect github.com/fyne-io/gl-js v0.0.0-20230506162202-1fdaa286a934 // indirect - github.com/fyne-io/glfw-js v0.0.0-20240101223322-6e1efdc71b7a // indirect + github.com/fyne-io/glfw-js v0.0.0-20241126112943-313d8a0fe1d0 // indirect github.com/fyne-io/image v0.0.0-20240417123036-dc0ee9e7c964 // indirect github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71 // indirect github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a // indirect github.com/go-text/render v0.2.0 // indirect - github.com/go-text/typesetting v0.2.0 // indirect + github.com/go-text/typesetting v0.2.1 // indirect github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/gopherjs/gopherjs v1.17.2 // indirect - github.com/jeandeaual/go-locale v0.0.0-20240223122105-ce5225dcaa49 // indirect + github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08 // indirect github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25 // indirect github.com/nicksnyder/go-i18n/v2 v2.4.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/rymdport/portal v0.2.6 // indirect + github.com/rymdport/portal v0.3.0 // indirect github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c // indirect github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef // indirect github.com/yuin/goldmark v1.7.8 // indirect - golang.org/x/image v0.21.0 // indirect - golang.org/x/mobile v0.0.0-20241016134751-7ff83004ec2c // indirect - golang.org/x/net v0.30.0 // indirect - golang.org/x/sys v0.26.0 // indirect - golang.org/x/text v0.19.0 // indirect + golang.org/x/image v0.23.0 // indirect + golang.org/x/mobile v0.0.0-20241213221354-a87c1cf6cf46 // indirect + golang.org/x/net v0.33.0 // indirect + golang.org/x/sys v0.28.0 // indirect + golang.org/x/text v0.21.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index c402b35..b91a7bf 100644 --- a/go.sum +++ b/go.sum @@ -37,16 +37,16 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -fyne.io/fyne/v2 v2.5.2 h1:eSyGTmSkv10yAdAeHpDet6u2KkKxOGFc14kQu81We7Q= -fyne.io/fyne/v2 v2.5.2/go.mod h1:26gqPDvtaxHeyct+C0BBjuGd2zwAJlPkUGSBrb+d7Ug= +fyne.io/fyne/v2 v2.5.3 h1:k6LjZx6EzRZhClsuzy6vucLZBstdH2USDGHSGWq8ly8= +fyne.io/fyne/v2 v2.5.3/go.mod h1:0GOXKqyvNwk3DLmsFu9v0oYM0ZcD1ysGnlHCerKoAmo= fyne.io/systray v1.11.0 h1:D9HISlxSkx+jHSniMBR6fCFOUjk1x/OOOJLa9lJYAKg= fyne.io/systray v1.11.0/go.mod h1:RVwqP9nYMo7h5zViCBHri2FgjXF7H2cub7MAq4NSoLs= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/agukrapo/go-http-client v1.3.0 h1:kfzeBNfrXOmtBCeMQ49H5Usu9pQL6BgWS223WjFR5Dc= -github.com/agukrapo/go-http-client v1.3.0/go.mod h1:9cRK0EC3V48toCcGjmpCqU5TMAk1GFNBoiQpJ8yzS20= +github.com/agukrapo/go-http-client v1.3.1 h1:sTGPUCZjjEjlIx1JCaIgRg6T2E4xLI8K1/aB9cKtv4k= +github.com/agukrapo/go-http-client v1.3.1/go.mod h1:9cRK0EC3V48toCcGjmpCqU5TMAk1GFNBoiQpJ8yzS20= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= @@ -80,12 +80,12 @@ github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNu github.com/fredbi/uri v1.1.0 h1:OqLpTXtyRg9ABReqvDGdJPqZUxs8cyBDOMXBbskCaB8= github.com/fredbi/uri v1.1.0/go.mod h1:aYTUoAXBOq7BLfVJ8GnKmfcuURosB1xyHDIfWeC/iW4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= -github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= +github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fyne-io/gl-js v0.0.0-20230506162202-1fdaa286a934 h1:dZC5aKobSN07hf71oMivxUmAofFja5GrfPK2rBlttX4= github.com/fyne-io/gl-js v0.0.0-20230506162202-1fdaa286a934/go.mod h1:d4clgH0/GrRwWjRzJJQXxT/h1TyuNSfF/X64zb/3Ggg= -github.com/fyne-io/glfw-js v0.0.0-20240101223322-6e1efdc71b7a h1:ybgRdYvAHTn93HW79bLiBiJwVL4jVeyGQRZMgImoeWs= -github.com/fyne-io/glfw-js v0.0.0-20240101223322-6e1efdc71b7a/go.mod h1:gsGA2dotD4v0SR6PmPCYvS9JuOeMwAtmfvDE7mbYXMY= +github.com/fyne-io/glfw-js v0.0.0-20241126112943-313d8a0fe1d0 h1:/1YRWFv9bAWkoo3SuxpFfzpXH0D/bQnTjNXyF4ih7Os= +github.com/fyne-io/glfw-js v0.0.0-20241126112943-313d8a0fe1d0/go.mod h1:gsGA2dotD4v0SR6PmPCYvS9JuOeMwAtmfvDE7mbYXMY= github.com/fyne-io/image v0.0.0-20240417123036-dc0ee9e7c964 h1:0pTELtjlVAVGSazfwRNcqTVzqmkWb1GsNozCmmZfdZA= github.com/fyne-io/image v0.0.0-20240417123036-dc0ee9e7c964/go.mod h1:J9Uunu842kOcTjzQj4Eq8XIDmF55szvT1PTS1cUb1UE= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -99,10 +99,10 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a h1:vxnBhFDDT+ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-text/render v0.2.0 h1:LBYoTmp5jYiJ4NPqDc2pz17MLmA3wHw1dZSVGcOdeAc= github.com/go-text/render v0.2.0/go.mod h1:CkiqfukRGKJA5vZZISkjSYrcdtgKQWRa2HIzvwNN5SU= -github.com/go-text/typesetting v0.2.0 h1:fbzsgbmk04KiWtE+c3ZD4W2nmCRzBqrqQOvYlwAOdho= -github.com/go-text/typesetting v0.2.0/go.mod h1:2+owI/sxa73XA581LAzVuEBZ3WEEV2pXeDswCH/3i1I= -github.com/go-text/typesetting-utils v0.0.0-20240317173224-1986cbe96c66 h1:GUrm65PQPlhFSKjLPGOZNPNxLCybjzjYBzjfoBGaDUY= -github.com/go-text/typesetting-utils v0.0.0-20240317173224-1986cbe96c66/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o= +github.com/go-text/typesetting v0.2.1 h1:x0jMOGyO3d1qFAPI0j4GSsh7M0Q3Ypjzr4+CEVg82V8= +github.com/go-text/typesetting v0.2.1/go.mod h1:mTOxEwasOFpAMBjEQDhdWRckoLLeI/+qrQeBCTGEt6M= +github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066 h1:qCuYC+94v2xrb1PoS4NIDe7DGYtLnU2wWiQe9a1B1c0= +github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= @@ -200,8 +200,8 @@ github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/J github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/jeandeaual/go-locale v0.0.0-20240223122105-ce5225dcaa49 h1:Po+wkNdMmN+Zj1tDsJQy7mJlPlwGNQd9JZoPjObagf8= -github.com/jeandeaual/go-locale v0.0.0-20240223122105-ce5225dcaa49/go.mod h1:YiutDnxPRLk5DLUFj6Rw4pRBBURZY07GFr54NdV9mQg= +github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08 h1:wMeVzrPO3mfHIWLZtDcSaGAe2I4PW9B/P5nMkRSwCAc= +github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08/go.mod h1:ZDXo8KHryOWSIqnsb/CiDq7hQUYryCgdVnxbj8tDG7o= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -252,8 +252,8 @@ github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6L github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/rymdport/portal v0.2.6 h1:HWmU3gORu7vWcpr7VSwUS2Xx1HtJXVcUuTqEZcMEsIg= -github.com/rymdport/portal v0.2.6/go.mod h1:kFF4jslnJ8pD5uCi17brj/ODlfIidOxlgUDTO5ncnC4= +github.com/rymdport/portal v0.3.0 h1:QRHcwKwx3kY5JTQcsVhmhC3TGqGQb9LFghVNUy8AdB8= +github.com/rymdport/portal v0.3.0/go.mod h1:kFF4jslnJ8pD5uCi17brj/ODlfIidOxlgUDTO5ncnC4= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= @@ -278,8 +278,8 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -323,8 +323,8 @@ golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EH golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.21.0 h1:c5qV36ajHpdj4Qi0GnE0jUc/yuo33OLFaa0d+crTD5s= -golang.org/x/image v0.21.0/go.mod h1:vUbsLavqK/W303ZroQQVKQ+Af3Yl6Uz1Ppu5J/cLz78= +golang.org/x/image v0.23.0 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68= +golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -340,8 +340,8 @@ golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPI golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mobile v0.0.0-20211207041440-4e6c2922fdee/go.mod h1:pe2sM7Uk+2Su1y7u/6Z8KJ24D7lepUjFZbhFOrmDfuQ= -golang.org/x/mobile v0.0.0-20241016134751-7ff83004ec2c h1:zuNS/LWsEpPTLfrmBkis6Xofw3nieAqB4hYLn8+uswk= -golang.org/x/mobile v0.0.0-20241016134751-7ff83004ec2c/go.mod h1:snk1Mn2ZpdKCt90JPEsDh4sL3ReK520U2t0d7RHBnSU= +golang.org/x/mobile v0.0.0-20241213221354-a87c1cf6cf46 h1:E+R1qmJL8cmWTyWXBHVtmqRxr7FdiTwntffsba1F1Tg= +golang.org/x/mobile v0.0.0-20241213221354-a87c1cf6cf46/go.mod h1:Sf9LBimL0mWKEdgAjRmJ6iu7Z34osHQTK/devqFbM2I= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= @@ -388,8 +388,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= -golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -413,8 +413,8 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -460,8 +460,8 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= -golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -471,8 +471,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= -golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= diff --git a/internal/logs/logger.go b/internal/logs/logger.go new file mode 100644 index 0000000..d56da99 --- /dev/null +++ b/internal/logs/logger.go @@ -0,0 +1,153 @@ +package logs + +import ( + "fmt" + "os" + "path/filepath" + "strings" + "sync" + "sync/atomic" + "time" +) + +type Logger struct { + target writer + + c chan string + + traceID atomic.Uint32 +} + +type writer interface { + Writeln(line string) error +} + +type nopWriter struct{} + +func (nopWriter) Writeln(_ string) error { + return nil +} + +func New(target writer) *Logger { + if target == nil { + target = nopWriter{} + } + + out := &Logger{ + target: target, + c: make(chan string), + } + + go out.start() + + return out +} + +func (l *Logger) start() { + for { + str, ok := <-l.c + if !ok { + return + } + + fmt.Println(str) + + if err := l.target.Writeln(str); err != nil { + fmt.Printf("Logger: %v\n", err) + } + } +} + +type Trace struct { + id string + c chan string + start time.Time +} + +func (l *Logger) Trace(name string) *Trace { + l.traceID.Add(1) + + return &Trace{ + id: fmt.Sprintf("%03d %s", l.traceID.Load(), name), + c: l.c, + } +} + +func Var(name string, value any) string { + switch v := value.(type) { + case string, fmt.Stringer: + return fmt.Sprintf("%s=%q", name, v) + default: + return fmt.Sprintf("%s=%v", name, value) + } +} + +func (t *Trace) Begins(vars ...string) *Trace { + t.start = time.Now() + + str := t.id + " started" + defer func() { + t.c <- str + }() + + if len(vars) == 0 { + return t + } + + str = fmt.Sprintf("%s [%s]", str, strings.Join(vars, ", ")) + + return t +} + +func (t *Trace) Ends(err error, vars ...string) { + str := t.id + " finished in " + time.Since(t.start).String() + defer func() { + t.c <- str + }() + + var v string + if len(vars) != 0 { + v = v + strings.Join(vars, ", ") + ", " + } + + var e string + if err != nil { + e = err.Error() + } + + str = fmt.Sprintf("%s [%serr=%q]", str, v, e) +} + +func (t *Trace) Dump(bytes []byte) { + t.c <- fmt.Sprintf("%s dump\n%s", t.id, bytes) +} + +type File struct { + f *os.File + mu sync.Mutex +} + +func NewFile(name string) (*File, error) { + f, err := os.OpenFile(filepath.Clean(name)+".log", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o600) + if err != nil { + return nil, err + } + + return &File{f: f}, nil +} + +func (f *File) Writeln(line string) error { + f.mu.Lock() + defer f.mu.Unlock() + + ln := fmt.Sprintf("%s %s\n", time.Now().Format("15:04:05.000"), line) + _, err := f.f.WriteString(ln) + + return err +} + +func (f *File) Close() { + if err := f.f.Close(); err != nil { + fmt.Printf("Close: %v\n", err) + } +} diff --git a/playlists/manager.go b/playlists/manager.go index 65df957..9303d70 100644 --- a/playlists/manager.go +++ b/playlists/manager.go @@ -18,7 +18,7 @@ type Track struct { type Target interface { Name() string Setup(ctx context.Context) error - SearchTrack(ctx context.Context, query string) (matches []Track, err error) + SearchTracks(ctx context.Context, query string) (matches []Track, err error) CreatePlaylist(ctx context.Context, name string) (playlistID string, err error) PopulatePlaylist(ctx context.Context, playlistID string, tracks []string) error } @@ -49,7 +49,7 @@ func (m *Manager) Gather(ctx context.Context, songs []string, fn Callback) error for i, song := range songs { g.Go(func() error { - matches, err := m.target.SearchTrack(ctx, song) + matches, err := m.target.SearchTracks(ctx, song) if err != nil { return fmt.Errorf("%s: searching track %q: %w", m.target.Name(), song, err) } diff --git a/spotify/client.go b/spotify/client.go index 7946b01..ae24b36 100644 --- a/spotify/client.go +++ b/spotify/client.go @@ -100,8 +100,8 @@ func (sr searchResponse) tracks() []playlists.Track { return out } -// SearchTrack searches for the given query and retrieves the matches. -func (c *Client) SearchTrack(ctx context.Context, query string) ([]playlists.Track, error) { +// SearchTracks searches for the given query and retrieves the matches. +func (c *Client) SearchTracks(ctx context.Context, query string) ([]playlists.Track, error) { u := c.baseURL + "/v1/search?type=track&q=" + url.QueryEscape(query) req, err := requests.New(u).Headers(c.headers()).Build(ctx) diff --git a/spotify/client_test.go b/spotify/client_test.go index 11180bc..b6e1a19 100644 --- a/spotify/client_test.go +++ b/spotify/client_test.go @@ -108,7 +108,7 @@ func TestClient_SearchTrack(t *testing.T) { httpClient: http.DefaultClient, } - matches, err := client.SearchTrack(context.Background(), "query") + matches, err := client.SearchTracks(context.Background(), "query") require.Equal(t, test.expectedError, tests.AsString(err)) if test.expectedError != "" {