/* Copyright © 2023 Philipp Wolfer This file is part of Scotty. Scotty is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Scotty is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Scotty. If not, see . */ package funkwhale import ( "sort" "time" "go.uploadedlobster.com/mbtypes" "go.uploadedlobster.com/scotty/internal/config" "go.uploadedlobster.com/scotty/internal/i18n" "go.uploadedlobster.com/scotty/internal/models" ) const FunkwhaleClientName = "Funkwhale" type FunkwhaleApiBackend struct { client Client username string } func (b *FunkwhaleApiBackend) Name() string { return "funkwhale" } func (b *FunkwhaleApiBackend) Options() []models.BackendOption { return []models.BackendOption{{ Name: "server-url", Label: i18n.Tr("Server URL"), Type: models.String, }, { Name: "username", Label: i18n.Tr("User name"), Type: models.String, }, { Name: "token", Label: i18n.Tr("Access token"), Type: models.Secret, }} } func (b *FunkwhaleApiBackend) FromConfig(config *config.ServiceConfig) models.Backend { b.client = NewClient( config.GetString("server-url"), config.GetString("token"), ) b.username = config.GetString("username") return b } func (b *FunkwhaleApiBackend) ExportListens(oldestTimestamp time.Time, results chan models.ListensResult, progress chan models.Progress) { page := 1 perPage := MaxItemsPerGet defer close(results) // We need to gather the full list of listens in order to sort them listens := make(models.ListensList, 0, 2*perPage) p := models.Progress{Total: int64(perPage)} out: for { result, err := b.client.GetHistoryListenings(b.username, page, perPage) if err != nil { results <- models.ListensResult{Error: err} } count := len(result.Results) if count == 0 { break out } for _, fwListen := range result.Results { listen := fwListen.AsListen() if listen.ListenedAt.Unix() > oldestTimestamp.Unix() { p.Elapsed += 1 listens = append(listens, listen) } else { break out } } if result.Next == "" { // No further results p.Total = p.Elapsed p.Total -= int64(perPage - count) break out } p.Total += int64(perPage) progress <- p page += 1 } sort.Sort(listens) progress <- p.Complete() results <- models.ListensResult{Items: listens} } func (b *FunkwhaleApiBackend) ExportLoves(oldestTimestamp time.Time, results chan models.LovesResult, progress chan models.Progress) { page := 1 perPage := MaxItemsPerGet defer close(results) // We need to gather the full list of listens in order to sort them loves := make(models.LovesList, 0, 2*perPage) p := models.Progress{Total: int64(perPage)} out: for { result, err := b.client.GetFavoriteTracks(page, perPage) if err != nil { progress <- p.Complete() results <- models.LovesResult{Error: err} return } count := len(result.Results) if count == 0 { break out } for _, favorite := range result.Results { love := favorite.AsLove() if love.Created.Unix() > oldestTimestamp.Unix() { p.Elapsed += 1 loves = append(loves, love) } else { break out } } if result.Next == "" { // No further results break out } p.Total += int64(perPage) progress <- p page += 1 } sort.Sort(loves) progress <- p.Complete() results <- models.LovesResult{Items: loves} } func (l Listening) AsListen() models.Listen { listen := models.Listen{ UserName: l.User.UserName, Track: l.Track.AsTrack(), } listenedAt, err := time.Parse(time.RFC3339, l.CreationDate) if err == nil { listen.ListenedAt = listenedAt } return listen } func (f FavoriteTrack) AsLove() models.Love { track := f.Track.AsTrack() love := models.Love{ UserName: f.User.UserName, RecordingMBID: track.RecordingMBID, Track: track, } created, err := time.Parse(time.RFC3339, f.CreationDate) if err == nil { love.Created = created } return love } func (t Track) AsTrack() models.Track { track := models.Track{ TrackName: t.Title, ReleaseName: t.Album.Title, ArtistNames: []string{t.Artist.Name}, TrackNumber: t.Position, DiscNumber: t.DiscNumber, RecordingMBID: t.RecordingMBID, ReleaseMBID: t.Album.ReleaseMBID, ArtistMBIDs: []mbtypes.MBID{t.Artist.ArtistMBID}, Tags: t.Tags, AdditionalInfo: map[string]any{ "media_player": FunkwhaleClientName, }, } if len(t.Uploads) > 0 { track.Duration = time.Duration(t.Uploads[0].Duration * int(time.Second)) } return track }