Support downloading full album and artist discography through Subsonic API

This commit is contained in:
Deluan 2020-08-04 12:34:40 -04:00
parent f745b8d223
commit 2c370cae28
7 changed files with 124 additions and 11 deletions

View file

@ -52,8 +52,9 @@ func CreateSubsonicAPIRouter() (*subsonic.Router, error) {
transcoderTranscoder := transcoder.New()
transcodingCache := core.NewTranscodingCache()
mediaStreamer := core.NewMediaStreamer(dataStore, transcoderTranscoder, transcodingCache)
archiver := core.NewArchiver(dataStore)
players := engine.NewPlayers(dataStore)
router := subsonic.New(browser, artwork, listGenerator, users, playlists, scrobbler, search, mediaStreamer, players, dataStore)
router := subsonic.New(browser, artwork, listGenerator, users, playlists, scrobbler, search, mediaStreamer, archiver, players, dataStore)
return router, nil
}

89
core/archiver.go Normal file
View file

@ -0,0 +1,89 @@
package core
import (
"archive/zip"
"context"
"fmt"
"io"
"os"
"path/filepath"
"github.com/Masterminds/squirrel"
"github.com/deluan/navidrome/log"
"github.com/deluan/navidrome/model"
)
type Archiver interface {
Zip(ctx context.Context, id string, w io.Writer) error
}
func NewArchiver(ds model.DataStore) Archiver {
return &archiver{ds: ds}
}
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)
if err != nil {
log.Error(ctx, "Error loading media", "id", id, err)
return err
}
z := zip.NewWriter(out)
for _, mf := range mfs {
_ = a.addFileToZip(ctx, z, mf)
}
err = z.Close()
if err != nil {
log.Error(ctx, "Error closing zip file", "id", id, err)
}
return err
}
func (a *archiver) addFileToZip(ctx context.Context, z *zip.Writer, mf model.MediaFile) error {
_, file := filepath.Split(mf.Path)
w, err := z.Create(fmt.Sprintf("%s/%s", mf.Album, file))
if err != nil {
log.Error(ctx, "Error creating zip entry", "file", mf.Path, err)
return err
}
f, err := os.Open(mf.Path)
defer func() { _ = f.Close() }()
if err != nil {
log.Error(ctx, "Error opening file for zipping", "file", mf.Path, err)
return err
}
_, err = io.Copy(w, f)
if err != nil {
log.Error(ctx, "Error zipping file", "file", mf.Path, err)
return err
}
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
}

View file

@ -10,5 +10,6 @@ var Set = wire.NewSet(
NewMediaStreamer,
NewTranscodingCache,
NewImageCache,
NewArchiver,
transcoder.New,
)

View file

@ -31,6 +31,7 @@ type Router struct {
Search engine.Search
Users engine.Users
Streamer core.MediaStreamer
Archiver core.Archiver
Players engine.Players
DataStore model.DataStore
@ -39,10 +40,10 @@ type Router struct {
func New(browser engine.Browser, artwork core.Artwork, listGenerator engine.ListGenerator, users engine.Users,
playlists engine.Playlists, scrobbler engine.Scrobbler, search engine.Search,
streamer core.MediaStreamer, players engine.Players, ds model.DataStore) *Router {
streamer core.MediaStreamer, archiver core.Archiver, players engine.Players, ds model.DataStore) *Router {
r := &Router{Browser: browser, Artwork: artwork, ListGenerator: listGenerator, Playlists: playlists,
Scrobbler: scrobbler, Search: search, Users: users, Streamer: streamer, Players: players,
DataStore: ds}
Scrobbler: scrobbler, Search: search, Users: users, Streamer: streamer, Archiver: archiver,
Players: players, DataStore: ds}
r.mux = r.routes()
return r
}

View file

@ -7,16 +7,19 @@ import (
"github.com/deluan/navidrome/core"
"github.com/deluan/navidrome/log"
"github.com/deluan/navidrome/model"
"github.com/deluan/navidrome/server/subsonic/responses"
"github.com/deluan/navidrome/utils"
)
type StreamController struct {
streamer core.MediaStreamer
archiver core.Archiver
ds model.DataStore
}
func NewStreamController(streamer core.MediaStreamer) *StreamController {
return &StreamController{streamer: streamer}
func NewStreamController(streamer core.MediaStreamer, archiver core.Archiver, ds model.DataStore) *StreamController {
return &StreamController{streamer: streamer, archiver: archiver, ds: ds}
}
func (c *StreamController) Stream(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) {
@ -73,11 +76,25 @@ func (c *StreamController) Download(w http.ResponseWriter, r *http.Request) (*re
return nil, err
}
stream, err := c.streamer.NewStream(r.Context(), id, "raw", 0)
isTrack, err := c.ds.MediaFile(r.Context()).Exists(id)
if err != nil {
return nil, err
}
http.ServeContent(w, r, stream.Name(), stream.ModTime(), stream)
if isTrack {
stream, err := c.streamer.NewStream(r.Context(), id, "raw", 0)
if err != nil {
return nil, err
}
http.ServeContent(w, r, stream.Name(), stream.ModTime(), stream)
} else {
w.Header().Set("Content-Type", "application/zip")
err := c.archiver.Zip(r.Context(), id, w)
if err != nil {
return nil, err
}
}
return nil, nil
}

View file

@ -60,7 +60,9 @@ func initMediaRetrievalController(router *Router) *MediaRetrievalController {
func initStreamController(router *Router) *StreamController {
mediaStreamer := router.Streamer
streamController := NewStreamController(mediaStreamer)
archiver := router.Archiver
dataStore := router.DataStore
streamController := NewStreamController(mediaStreamer, archiver, dataStore)
return streamController
}
@ -82,5 +84,6 @@ var allProviders = wire.NewSet(
NewUsersController,
NewMediaRetrievalController,
NewStreamController,
NewBookmarksController, wire.FieldsOf(new(*Router), "Browser", "Artwork", "ListGenerator", "Playlists", "Scrobbler", "Search", "Streamer", "DataStore"),
NewBookmarksController, wire.FieldsOf(new(*Router), "Browser", "Artwork", "ListGenerator", "Playlists", "Scrobbler",
"Search", "Streamer", "Archiver", "DataStore"),
)

View file

@ -17,7 +17,8 @@ var allProviders = wire.NewSet(
NewMediaRetrievalController,
NewStreamController,
NewBookmarksController,
wire.FieldsOf(new(*Router), "Browser", "Artwork", "ListGenerator", "Playlists", "Scrobbler", "Search", "Streamer", "DataStore"),
wire.FieldsOf(new(*Router), "Browser", "Artwork", "ListGenerator", "Playlists", "Scrobbler",
"Search", "Streamer", "Archiver", "DataStore"),
)
func initSystemController(router *Router) *SystemController {