diff --git a/core/archiver.go b/core/archiver.go index 21d9f2b78..6d8a4b9fd 100644 --- a/core/archiver.go +++ b/core/archiver.go @@ -14,7 +14,8 @@ import ( ) type Archiver interface { - Zip(ctx context.Context, id string, w io.Writer) error + ZipAlbum(ctx context.Context, id string, w io.Writer) error + ZipArtist(ctx context.Context, id string, w io.Writer) error } func NewArchiver(ds model.DataStore) Archiver { @@ -25,17 +26,33 @@ type archiver struct { ds model.DataStore } -func (a *archiver) Zip(ctx context.Context, id string, out io.Writer) error { - mfs, err := a.loadTracks(ctx, id) +func (a *archiver) ZipAlbum(ctx context.Context, id string, out io.Writer) error { + mfs, err := a.ds.MediaFile(ctx).FindByAlbum(id) if err != nil { - log.Error(ctx, "Error loading media", "id", id, err) + log.Error(ctx, "Error loading mediafiles from album", "id", id, err) return err } + return a.zipTracks(ctx, id, out, mfs) +} + +func (a *archiver) ZipArtist(ctx context.Context, id string, out io.Writer) error { + mfs, err := a.ds.MediaFile(ctx).GetAll(model.QueryOptions{ + Sort: "album", + Filters: squirrel.Eq{"album_artist_id": id}, + }) + if err != nil { + log.Error(ctx, "Error loading mediafiles from artist", "id", id, err) + return err + } + return a.zipTracks(ctx, id, out, mfs) +} + +func (a *archiver) zipTracks(ctx context.Context, id string, out io.Writer, mfs model.MediaFiles) error { z := zip.NewWriter(out) for _, mf := range mfs { _ = a.addFileToZip(ctx, z, mf) } - err = z.Close() + err := z.Close() if err != nil { log.Error(ctx, "Error closing zip file", "id", id, err) } @@ -66,28 +83,3 @@ func (a *archiver) addFileToZip(ctx context.Context, z *zip.Writer, mf model.Med } return nil } - -func (a *archiver) loadTracks(ctx context.Context, id string) (model.MediaFiles, error) { - exist, err := a.ds.Album(ctx).Exists(id) - if err != nil { - return nil, err - } - if exist { - return a.ds.MediaFile(ctx).FindByAlbum(id) - } - exist, err = a.ds.Artist(ctx).Exists(id) - if err != nil { - return nil, err - } - if exist { - return a.ds.MediaFile(ctx).GetAll(model.QueryOptions{ - Sort: "album", - Filters: squirrel.Eq{"album_artist_id": id}, - }) - } - mf, err := a.ds.MediaFile(ctx).Get(id) - if err != nil { - return nil, err - } - return model.MediaFiles{*mf}, nil -} diff --git a/server/subsonic/stream.go b/server/subsonic/stream.go index 874d29a9e..800ce7640 100644 --- a/server/subsonic/stream.go +++ b/server/subsonic/stream.go @@ -1,9 +1,11 @@ package subsonic import ( + "fmt" "io" "net/http" "strconv" + "strings" "github.com/deluan/navidrome/core" "github.com/deluan/navidrome/log" @@ -23,6 +25,7 @@ func NewStreamController(streamer core.MediaStreamer, archiver core.Archiver, ds } func (c *StreamController) Stream(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { + ctx := r.Context() id, err := requiredParamString(r, "id", "id parameter required") if err != nil { return nil, err @@ -31,7 +34,7 @@ func (c *StreamController) Stream(w http.ResponseWriter, r *http.Request) (*resp format := utils.ParamString(r, "format") estimateContentLength := utils.ParamBool(r, "estimateContentLength", false) - stream, err := c.streamer.NewStream(r.Context(), id, format, maxBitRate) + stream, err := c.streamer.NewStream(ctx, id, format, maxBitRate) if err != nil { return nil, err } @@ -56,14 +59,14 @@ func (c *StreamController) Stream(w http.ResponseWriter, r *http.Request) (*resp // if Client requests the estimated content-length, send it if estimateContentLength { length := strconv.Itoa(stream.EstimatedContentLength()) - log.Trace(r.Context(), "Estimated content-length", "contentLength", length) + log.Trace(ctx, "Estimated content-length", "contentLength", length) w.Header().Set("Content-Length", length) } if c, err := io.Copy(w, stream); err != nil { - log.Error(r.Context(), "Error sending transcoded file", "id", id, err) + log.Error(ctx, "Error sending transcoded file", "id", id, err) } else { - log.Trace(r.Context(), "Success sending transcode file", "id", id, "size", c) + log.Trace(ctx, "Success sending transcode file", "id", id, "size", c) } } @@ -71,31 +74,45 @@ func (c *StreamController) Stream(w http.ResponseWriter, r *http.Request) (*resp } func (c *StreamController) Download(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { + ctx := r.Context() id, err := requiredParamString(r, "id", "id parameter required") if err != nil { return nil, err } - isTrack, err := c.ds.MediaFile(r.Context()).Exists(id) + entity, err := getEntityByID(ctx, c.ds, id) if err != nil { return nil, err } - if isTrack { - stream, err := c.streamer.NewStream(r.Context(), id, "raw", 0) + setHeaders := func(name string) { + filename := fmt.Sprintf("attachment; filename=%s.zip", name) + filename = strings.ReplaceAll(filename, ",", "_") + w.Header().Set("Content-Disposition", filename) + w.Header().Set("Content-Type", "application/zip") + } + + switch v := entity.(type) { + case *model.MediaFile: + stream, err := c.streamer.NewStream(ctx, id, "raw", 0) if err != nil { return nil, err } http.ServeContent(w, r, stream.Name(), stream.ModTime(), stream) - } else { - w.Header().Set("Content-Disposition", "attachment; filename=Navidrome-download.zip") - w.Header().Set("Content-Type", "application/zip") - err := c.archiver.Zip(r.Context(), id, w) + return nil, nil + case *model.Album: + setHeaders(v.Name) + err = c.archiver.ZipAlbum(ctx, id, w) + case *model.Artist: + setHeaders(v.Name) + err = c.archiver.ZipArtist(ctx, id, w) + default: + err = model.ErrNotFound + } - if err != nil { - return nil, err - } + if err != nil { + return nil, err } return nil, nil }