Add option to download playlist

This commit is contained in:
Deluan 2020-08-21 13:28:20 -04:00
parent 073e40dc87
commit 8fa5544af7
6 changed files with 56 additions and 13 deletions

View file

@ -16,6 +16,7 @@ import (
type Archiver interface { type Archiver interface {
ZipAlbum(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 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 { func NewArchiver(ds model.DataStore) Archiver {
@ -26,13 +27,15 @@ type archiver struct {
ds model.DataStore 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 { func (a *archiver) ZipAlbum(ctx context.Context, id string, out io.Writer) error {
mfs, err := a.ds.MediaFile(ctx).FindByAlbum(id) mfs, err := a.ds.MediaFile(ctx).FindByAlbum(id)
if err != nil { if err != nil {
log.Error(ctx, "Error loading mediafiles from album", "id", id, err) log.Error(ctx, "Error loading mediafiles from album", "id", id, err)
return err return err
} }
return a.zipTracks(ctx, id, out, mfs) return a.zipTracks(ctx, id, out, mfs, a.createHeader)
} }
func (a *archiver) ZipArtist(ctx context.Context, id string, out io.Writer) error { func (a *archiver) ZipArtist(ctx context.Context, id string, out io.Writer) error {
@ -44,13 +47,22 @@ func (a *archiver) ZipArtist(ctx context.Context, id string, out io.Writer) erro
log.Error(ctx, "Error loading mediafiles from artist", "id", id, err) log.Error(ctx, "Error loading mediafiles from artist", "id", id, err)
return err return err
} }
return a.zipTracks(ctx, id, out, mfs) return a.zipTracks(ctx, id, out, mfs, a.createHeader)
} }
func (a *archiver) zipTracks(ctx context.Context, id string, out io.Writer, mfs model.MediaFiles) error { 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) z := zip.NewWriter(out)
for _, mf := range mfs { for idx, mf := range mfs {
_ = a.addFileToZip(ctx, z, mf) _ = a.addFileToZip(ctx, z, mf, ch(idx, mf))
} }
err := z.Close() err := z.Close()
if err != nil { if err != nil {
@ -59,13 +71,26 @@ func (a *archiver) zipTracks(ctx context.Context, id string, out io.Writer, mfs
return err return err
} }
func (a *archiver) addFileToZip(ctx context.Context, z *zip.Writer, mf model.MediaFile) error { func (a *archiver) createHeader(idx int, mf model.MediaFile) *zip.FileHeader {
_, file := filepath.Split(mf.Path) _, file := filepath.Split(mf.Path)
w, err := z.CreateHeader(&zip.FileHeader{ return &zip.FileHeader{
Name: fmt.Sprintf("%s/%s", mf.Album, file), Name: fmt.Sprintf("%s/%s", mf.Album, file),
Modified: mf.UpdatedAt, Modified: mf.UpdatedAt,
Method: zip.Store, 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, 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 { if err != nil {
log.Error(ctx, "Error creating zip entry", "file", mf.Path, err) log.Error(ctx, "Error creating zip entry", "file", mf.Path, err)
return err return err

View file

@ -269,6 +269,10 @@ func getEntityByID(ctx context.Context, ds model.DataStore, id string) (interfac
if err == nil { if err == nil {
return al, nil return al, nil
} }
pls, err := ds.Playlist(ctx).Get(id)
if err == nil {
return pls, nil
}
mf, err := ds.MediaFile(ctx).Get(id) mf, err := ds.MediaFile(ctx).Get(id)
if err == nil { if err == nil {
return mf, nil return mf, nil

View file

@ -109,6 +109,9 @@ func (c *StreamController) Download(w http.ResponseWriter, r *http.Request) (*re
case *model.Artist: case *model.Artist:
setHeaders(v.Name) setHeaders(v.Name)
err = c.archiver.ZipArtist(ctx, id, w) err = c.archiver.ZipArtist(ctx, id, w)
case *model.Playlist:
setHeaders(v.Name)
err = c.archiver.ZipPlaylist(ctx, id, w)
default: default:
err = model.ErrNotFound err = model.ErrNotFound
} }

View file

@ -1,3 +1,5 @@
import React from 'react'
import { useDispatch } from 'react-redux'
import { import {
Button, Button,
sanitizeListRestProps, sanitizeListRestProps,
@ -7,8 +9,6 @@ import {
import PlayArrowIcon from '@material-ui/icons/PlayArrow' import PlayArrowIcon from '@material-ui/icons/PlayArrow'
import ShuffleIcon from '@material-ui/icons/Shuffle' import ShuffleIcon from '@material-ui/icons/Shuffle'
import CloudDownloadOutlinedIcon from '@material-ui/icons/CloudDownloadOutlined' import CloudDownloadOutlinedIcon from '@material-ui/icons/CloudDownloadOutlined'
import React from 'react'
import { useDispatch } from 'react-redux'
import { playTracks, shuffleTracks } from '../audioplayer' import { playTracks, shuffleTracks } from '../audioplayer'
import subsonic from '../subsonic' import subsonic from '../subsonic'

View file

@ -1,3 +1,5 @@
import React from 'react'
import { useDispatch } from 'react-redux'
import { import {
Button, Button,
sanitizeListRestProps, sanitizeListRestProps,
@ -6,9 +8,9 @@ import {
} from 'react-admin' } from 'react-admin'
import PlayArrowIcon from '@material-ui/icons/PlayArrow' import PlayArrowIcon from '@material-ui/icons/PlayArrow'
import ShuffleIcon from '@material-ui/icons/Shuffle' import ShuffleIcon from '@material-ui/icons/Shuffle'
import React from 'react' import CloudDownloadOutlinedIcon from '@material-ui/icons/CloudDownloadOutlined'
import { useDispatch } from 'react-redux'
import { playTracks, shuffleTracks } from '../audioplayer' import { playTracks, shuffleTracks } from '../audioplayer'
import subsonic from '../subsonic'
const PlaylistActions = ({ const PlaylistActions = ({
className, className,
@ -16,6 +18,7 @@ const PlaylistActions = ({
data, data,
exporter, exporter,
permanentFilter, permanentFilter,
playlistId,
...rest ...rest
}) => { }) => {
const dispatch = useDispatch() const dispatch = useDispatch()
@ -39,6 +42,14 @@ const PlaylistActions = ({
> >
<ShuffleIcon /> <ShuffleIcon />
</Button> </Button>
<Button
onClick={() => {
subsonic.download(playlistId)
}}
label={translate('resources.album.actions.download')}
>
<CloudDownloadOutlinedIcon />
</Button>
</TopToolbar> </TopToolbar>
) )
} }

View file

@ -26,7 +26,7 @@ const PlaylistShow = (props) => {
playlistId={props.id} playlistId={props.id}
readOnly={isReadOnly(record && record.owner)} readOnly={isReadOnly(record && record.owner)}
title={<Title subTitle={record && record.name} />} title={<Title subTitle={record && record.name} />}
actions={<PlaylistActions />} actions={<PlaylistActions playlistId={props.id} />}
filter={{ playlist_id: props.id }} filter={{ playlist_id: props.id }}
resource={'playlistTrack'} resource={'playlistTrack'}
exporter={false} exporter={false}