diff --git a/db/migration/20201012210022_add_artist_playlist_size.go b/db/migration/20201012210022_add_artist_playlist_size.go
new file mode 100644
index 000000000..5dd67609d
--- /dev/null
+++ b/db/migration/20201012210022_add_artist_playlist_size.go
@@ -0,0 +1,41 @@
+package migration
+
+import (
+ "database/sql"
+
+ "github.com/pressly/goose"
+)
+
+func init() {
+ goose.AddMigration(Up20201012210022, Down20201012210022)
+}
+
+func Up20201012210022(tx *sql.Tx) error {
+ _, err := tx.Exec(`
+alter table artist
+ add size integer default 0 not null;
+create index if not exists artist_size
+ on artist(size);
+
+alter table playlist
+ add size integer default 0 not null;
+create index if not exists playlist_size
+ on playlist(size);
+
+update playlist set size = ifnull((
+ select sum(size)
+ from media_file f
+ left join playlist_tracks pt on f.id = pt.media_file_id
+ where pt.playlist_id = playlist.id
+), 0);`)
+
+ if err != nil {
+ return err
+ }
+ notice(tx, "A full rescan will be performed to calculate artists (discographies) and playlists sizes.")
+ return forceFullRescan(tx)
+}
+
+func Down20201012210022(tx *sql.Tx) error {
+ return nil
+}
diff --git a/model/artist.go b/model/artist.go
index 4bbcce1a1..c99232280 100644
--- a/model/artist.go
+++ b/model/artist.go
@@ -10,6 +10,7 @@ type Artist struct {
FullText string `json:"fullText"`
SortArtistName string `json:"sortArtistName"`
OrderArtistName string `json:"orderArtistName"`
+ Size int64 `json:"size"`
}
type Artists []Artist
diff --git a/persistence/artist_repository.go b/persistence/artist_repository.go
index bd28b89e2..cd68a191b 100644
--- a/persistence/artist_repository.go
+++ b/persistence/artist_repository.go
@@ -133,7 +133,7 @@ func (r *artistRepository) refresh(ids ...string) error {
var artists []refreshArtist
sel := Select("f.album_artist_id as id", "f.album_artist as name", "count(*) as album_count", "a.id as current_id",
"f.sort_album_artist_name as sort_artist_name", "f.order_album_artist_name as order_artist_name",
- "sum(f.song_count) as song_count").
+ "sum(f.song_count) as song_count", "sum(f.size) as size").
From("album f").
LeftJoin("artist a on f.album_artist_id = a.id").
Where(Eq{"f.album_artist_id": ids}).
diff --git a/ui/src/album/AlbumActions.js b/ui/src/album/AlbumActions.js
index b9547da90..62a8f5359 100644
--- a/ui/src/album/AlbumActions.js
+++ b/ui/src/album/AlbumActions.js
@@ -13,10 +13,13 @@ import CloudDownloadOutlinedIcon from '@material-ui/icons/CloudDownloadOutlined'
import { RiPlayListAddFill, RiPlayList2Fill } from 'react-icons/ri'
import { playNext, addTracks, playTracks, shuffleTracks } from '../audioplayer'
import subsonic from '../subsonic'
+import { formatBytes } from '../common/SizeField'
+import { useMediaQuery } from '@material-ui/core'
const AlbumActions = ({ className, ids, data, record, ...rest }) => {
const dispatch = useDispatch()
const translate = useTranslate()
+ const isDesktop = useMediaQuery((theme) => theme.breakpoints.up('md'))
const handlePlay = React.useCallback(() => {
dispatch(playTracks(data, ids))
@@ -66,7 +69,10 @@ const AlbumActions = ({ className, ids, data, record, ...rest }) => {
diff --git a/ui/src/common/ContextMenus.js b/ui/src/common/ContextMenus.js
index d0a31d062..9ee7bdbca 100644
--- a/ui/src/common/ContextMenus.js
+++ b/ui/src/common/ContextMenus.js
@@ -11,6 +11,7 @@ import { playNext, addTracks, playTracks, shuffleTracks } from '../audioplayer'
import { openAddToPlaylist } from '../dialogs/dialogState'
import subsonic from '../subsonic'
import StarButton from './StarButton'
+import { formatBytes } from './SizeField'
const useStyles = makeStyles({
noWrap: {
@@ -40,32 +41,34 @@ const ContextMenu = ({
const options = {
play: {
needData: true,
- label: 'resources.album.actions.playAll',
+ label: translate('resources.album.actions.playAll'),
action: (data, ids) => dispatch(playTracks(data, ids)),
},
playNext: {
needData: true,
- label: 'resources.album.actions.playNext',
+ label: translate('resources.album.actions.playNext'),
action: (data, ids) => dispatch(playNext(data, ids)),
},
addToQueue: {
needData: true,
- label: 'resources.album.actions.addToQueue',
+ label: translate('resources.album.actions.addToQueue'),
action: (data, ids) => dispatch(addTracks(data, ids)),
},
shuffle: {
needData: true,
- label: 'resources.album.actions.shuffle',
+ label: translate('resources.album.actions.shuffle'),
action: (data, ids) => dispatch(shuffleTracks(data, ids)),
},
addToPlaylist: {
needData: true,
- label: 'resources.album.actions.addToPlaylist',
+ label: translate('resources.album.actions.addToPlaylist'),
action: (data, ids) => dispatch(openAddToPlaylist({ selectedIds: ids })),
},
download: {
needData: false,
- label: 'resources.album.actions.download',
+ label: `${translate('resources.album.actions.download')} (${formatBytes(
+ record.size
+ )})`,
action: () => subsonic.download(record.id),
},
}
@@ -140,7 +143,7 @@ const ContextMenu = ({
>
{Object.keys(options).map((key) => (
))}
diff --git a/ui/src/common/SizeField.js b/ui/src/common/SizeField.js
index c288a5ff3..e1159b196 100644
--- a/ui/src/common/SizeField.js
+++ b/ui/src/common/SizeField.js
@@ -5,7 +5,7 @@ const SizeField = ({ record = {}, source }) => {
return {formatBytes(record[source])}
}
-function formatBytes(bytes, decimals = 2) {
+export const formatBytes = (bytes, decimals = 2) => {
if (bytes === 0) return '0 Bytes'
const k = 1024
diff --git a/ui/src/common/SongContextMenu.js b/ui/src/common/SongContextMenu.js
index 35b18894e..5df5f6d86 100644
--- a/ui/src/common/SongContextMenu.js
+++ b/ui/src/common/SongContextMenu.js
@@ -9,6 +9,7 @@ import { playNext, addTracks, setTrack } from '../audioplayer'
import { openAddToPlaylist } from '../dialogs/dialogState'
import subsonic from '../subsonic'
import StarButton from './StarButton'
+import { formatBytes } from './SizeField'
const useStyles = makeStyles({
noWrap: {
@@ -32,19 +33,19 @@ const SongContextMenu = ({
const [anchorEl, setAnchorEl] = useState(null)
const options = {
playNow: {
- label: 'resources.song.actions.playNow',
+ label: translate('resources.song.actions.playNow'),
action: (record) => dispatch(setTrack(record)),
},
playNext: {
- label: 'resources.song.actions.playNext',
+ label: translate('resources.song.actions.playNext'),
action: (record) => dispatch(playNext({ [record.id]: record })),
},
addToQueue: {
- label: 'resources.song.actions.addToQueue',
+ label: translate('resources.song.actions.addToQueue'),
action: (record) => dispatch(addTracks({ [record.id]: record })),
},
addToPlaylist: {
- label: 'resources.song.actions.addToPlaylist',
+ label: translate('resources.song.actions.addToPlaylist'),
action: (record) =>
dispatch(
openAddToPlaylist({
@@ -54,7 +55,9 @@ const SongContextMenu = ({
),
},
download: {
- label: 'resources.song.actions.download',
+ label: `${translate('resources.song.actions.download')} (${formatBytes(
+ record.size
+ )})`,
action: (record) => subsonic.download(record.id),
},
}
@@ -95,7 +98,7 @@ const SongContextMenu = ({
>
{Object.keys(options).map((key) => (
))}