mirror of
https://github.com/navidrome/navidrome.git
synced 2024-09-11 20:12:54 +02:00
114 lines
3 KiB
Go
114 lines
3 KiB
Go
package core
|
|
|
|
import (
|
|
"archive/zip"
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"github.com/Masterminds/squirrel"
|
|
"github.com/navidrome/navidrome/log"
|
|
"github.com/navidrome/navidrome/model"
|
|
)
|
|
|
|
type Archiver interface {
|
|
ZipAlbum(ctx context.Context, id string, w io.Writer) error
|
|
ZipArtist(ctx context.Context, id string, w io.Writer) error
|
|
ZipPlaylist(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
|
|
}
|
|
|
|
type createHeader func(idx int, mf model.MediaFile) *zip.FileHeader
|
|
|
|
func (a *archiver) ZipAlbum(ctx context.Context, id string, out io.Writer) error {
|
|
mfs, err := a.ds.MediaFile(ctx).GetAll(model.QueryOptions{
|
|
Filters: squirrel.Eq{"album_id": id},
|
|
Sort: "album",
|
|
})
|
|
if err != nil {
|
|
log.Error(ctx, "Error loading mediafiles from album", "id", id, err)
|
|
return err
|
|
}
|
|
return a.zipTracks(ctx, id, out, mfs, a.createHeader)
|
|
}
|
|
|
|
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, a.createHeader)
|
|
}
|
|
|
|
func (a *archiver) ZipPlaylist(ctx context.Context, id string, out io.Writer) error {
|
|
pls, err := a.ds.Playlist(ctx).Get(id)
|
|
if err != nil {
|
|
log.Error(ctx, "Error loading mediafiles from playlist", "id", id, err)
|
|
return err
|
|
}
|
|
return a.zipTracks(ctx, id, out, pls.Tracks, a.createPlaylistHeader)
|
|
}
|
|
|
|
func (a *archiver) zipTracks(ctx context.Context, id string, out io.Writer, mfs model.MediaFiles, ch createHeader) error {
|
|
z := zip.NewWriter(out)
|
|
for idx, mf := range mfs {
|
|
_ = a.addFileToZip(ctx, z, mf, ch(idx, mf))
|
|
}
|
|
err := z.Close()
|
|
if err != nil {
|
|
log.Error(ctx, "Error closing zip file", "id", id, err)
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (a *archiver) createHeader(idx int, mf model.MediaFile) *zip.FileHeader {
|
|
_, file := filepath.Split(mf.Path)
|
|
return &zip.FileHeader{
|
|
Name: fmt.Sprintf("%s/%s", mf.Album, file),
|
|
Modified: mf.UpdatedAt,
|
|
Method: zip.Store,
|
|
}
|
|
}
|
|
|
|
func (a *archiver) createPlaylistHeader(idx int, mf model.MediaFile) *zip.FileHeader {
|
|
_, file := filepath.Split(mf.Path)
|
|
return &zip.FileHeader{
|
|
Name: fmt.Sprintf("%d - %s - %s", idx+1, mf.AlbumArtist, file),
|
|
Modified: mf.UpdatedAt,
|
|
Method: zip.Store,
|
|
}
|
|
}
|
|
|
|
func (a *archiver) addFileToZip(ctx context.Context, z *zip.Writer, mf model.MediaFile, zh *zip.FileHeader) error {
|
|
w, err := z.CreateHeader(zh)
|
|
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
|
|
}
|