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 }