diff --git a/engine/common.go b/engine/common.go index 991fcfc87..f7d84cf0f 100644 --- a/engine/common.go +++ b/engine/common.go @@ -21,7 +21,7 @@ type Entry struct { Starred time.Time Track int Duration int - Size string + Size int Suffix string BitRate int ContentType string diff --git a/model/mediafile.go b/model/mediafile.go index 395e5d41e..d46d038b3 100644 --- a/model/mediafile.go +++ b/model/mediafile.go @@ -18,7 +18,7 @@ type MediaFile struct { TrackNumber int DiscNumber int Year int - Size string + Size int Suffix string Duration int BitRate int diff --git a/persistence/mediafile_repository.go b/persistence/mediafile_repository.go index 76d3f3d14..5ecbb094d 100644 --- a/persistence/mediafile_repository.go +++ b/persistence/mediafile_repository.go @@ -11,26 +11,26 @@ import ( ) type mediaFile struct { - ID string `orm:"pk;column(id)"` - Path string `orm:"index"` - Title string `orm:"index"` - Album string `` - Artist string `` - ArtistID string `orm:"column(artist_id)"` - AlbumArtist string `` - AlbumID string `orm:"column(album_id);index"` - HasCoverArt bool `` - TrackNumber int `` - DiscNumber int `` - Year int `` - Size string `` - Suffix string `` - Duration int `` - BitRate int `` - Genre string `orm:"index"` - Compilation bool `` - CreatedAt time.Time `orm:"null"` - UpdatedAt time.Time `orm:"null"` + ID string `json:"id" orm:"pk;column(id)"` + Path string `json:"path" orm:"index"` + Title string `json:"title" orm:"index"` + Album string `json:"album"` + Artist string `json:"artist"` + ArtistID string `json:"artistId" orm:"column(artist_id)"` + AlbumArtist string `json:"albumArtist"` + AlbumID string `json:"albumId" orm:"column(album_id);index"` + HasCoverArt bool `json:"-"` + TrackNumber int `json:"trackNumber"` + DiscNumber int `json:"discNumber"` + Year int `json:"year"` + Size int `json:"size"` + Suffix string `json:"suffix"` + Duration int `json:"duration"` + BitRate int `json:"bitRate"` + Genre string `json:"genre" orm:"index"` + Compilation bool `json:"compilation"` + CreatedAt time.Time `json:"createdAt" orm:"null"` + UpdatedAt time.Time `json:"updatedAt" orm:"null"` } type mediaFileRepository struct { diff --git a/scanner/tag_scanner.go b/scanner/tag_scanner.go index e2705b2a8..bd2ec555a 100644 --- a/scanner/tag_scanner.go +++ b/scanner/tag_scanner.go @@ -8,7 +8,6 @@ import ( "path" "path/filepath" "sort" - "strconv" "strings" "time" @@ -243,7 +242,7 @@ func (s *TagScanner) toMediaFile(md *Metadata) model.MediaFile { mf.BitRate = md.BitRate() mf.Path = md.FilePath() mf.Suffix = md.Suffix() - mf.Size = strconv.Itoa(md.Size()) + mf.Size = md.Size() mf.HasCoverArt = md.HasPicture() // TODO Get Creation time. https://github.com/djherbis/times ? diff --git a/server/app/app.go b/server/app/app.go index 462712b05..a59c38f3d 100644 --- a/server/app/app.go +++ b/server/app/app.go @@ -53,6 +53,9 @@ func (app *Router) routes() http.Handler { R(r, "/user", func(ctx context.Context) rest.Repository { return app.ds.Resource(model.User{}) }) + R(r, "/song", func(ctx context.Context) rest.Repository { + return app.ds.Resource(model.MediaFile{}) + }) }) return r } diff --git a/server/subsonic/helpers.go b/server/subsonic/helpers.go index 4561611e0..e1d9036e4 100644 --- a/server/subsonic/helpers.go +++ b/server/subsonic/helpers.go @@ -181,7 +181,7 @@ func ToChild(entry engine.Entry) responses.Child { child.CoverArt = entry.CoverArt child.Track = entry.Track child.Duration = entry.Duration - child.Size = entry.Size + child.Size = strconv.Itoa(entry.Size) child.Suffix = entry.Suffix child.BitRate = entry.BitRate child.ContentType = entry.ContentType diff --git a/server/subsonic/stream.go b/server/subsonic/stream.go index 3218a5f4b..6220e40bc 100644 --- a/server/subsonic/stream.go +++ b/server/subsonic/stream.go @@ -2,6 +2,7 @@ package subsonic import ( "net/http" + "strconv" "github.com/cloudsonic/sonic-server/engine" "github.com/cloudsonic/sonic-server/log" @@ -54,7 +55,7 @@ func (c *StreamController) Stream(w http.ResponseWriter, r *http.Request) (*resp // contentLength = strconv.Itoa((mf.Duration + 1) * maxBitRate * 1000 / 8) //} h := w.Header() - h.Set("Content-Length", mf.Size) + h.Set("Content-Length", strconv.Itoa(mf.Size)) h.Set("Content-Type", "audio/mpeg") h.Set("Expires", "0") h.Set("Cache-Control", "must-revalidate") diff --git a/ui/src/App.js b/ui/src/App.js index dd3fd5201..3ba608460 100644 --- a/ui/src/App.js +++ b/ui/src/App.js @@ -5,6 +5,7 @@ import dataProvider from './dataProvider' import authProvider from './authProvider' import { Login } from './layout' import user from './user' +import song from './song' const App = () => ( ( authProvider={authProvider} loginPage={Login} > + ) diff --git a/ui/src/common/BitrateField.js b/ui/src/common/BitrateField.js new file mode 100644 index 000000000..7042484e6 --- /dev/null +++ b/ui/src/common/BitrateField.js @@ -0,0 +1,18 @@ +import React from 'react' +import PropTypes from 'prop-types' + +const BitrateField = ({ record = {}, source }) => { + return {`${record[source]} kbps`} +} + +BitrateField.propTypes = { + label: PropTypes.string, + record: PropTypes.object, + source: PropTypes.string.isRequired +} + +BitrateField.defaultProps = { + addLabel: true +} + +export default BitrateField diff --git a/ui/src/common/DurationField.js b/ui/src/common/DurationField.js new file mode 100644 index 000000000..1e4811303 --- /dev/null +++ b/ui/src/common/DurationField.js @@ -0,0 +1,25 @@ +import React from 'react' +import PropTypes from 'prop-types' + +const DurationField = ({ record = {}, source }) => { + return {format(record[source])} +} + +const format = (d) => { + const date = new Date(null) + date.setSeconds(d) + const fmt = date.toISOString().substr(11, 8) + return fmt.replace(/^00:/, '') +} + +DurationField.propTypes = { + label: PropTypes.string, + record: PropTypes.object, + source: PropTypes.string.isRequired +} + +DurationField.defaultProps = { + addLabel: true +} + +export default DurationField diff --git a/ui/src/common/Title.js b/ui/src/common/Title.js new file mode 100644 index 000000000..d5d0a9233 --- /dev/null +++ b/ui/src/common/Title.js @@ -0,0 +1,7 @@ +import React from 'react' + +const Title = ({ subTitle }) => { + return CloudSonic {subTitle ? ` - ${subTitle}` : ''} +} + +export default Title diff --git a/ui/src/common/index.js b/ui/src/common/index.js new file mode 100644 index 000000000..f603c77a7 --- /dev/null +++ b/ui/src/common/index.js @@ -0,0 +1,5 @@ +import Title from './Title' +import DurationField from './DurationField' +import BitrateField from './BitrateField' + +export { Title, DurationField, BitrateField } diff --git a/ui/src/song/SongList.js b/ui/src/song/SongList.js new file mode 100644 index 000000000..95eaf83ce --- /dev/null +++ b/ui/src/song/SongList.js @@ -0,0 +1,59 @@ +import React from 'react' +import { + BooleanField, + Datagrid, + DateField, + Filter, + List, + NumberField, + SearchInput, + Show, + SimpleShowLayout, + TextField +} from 'react-admin' +import { BitrateField, DurationField, Title } from '../common' + +const SongFilter = (props) => ( + + + +) + +const SongDetails = (props) => { + return ( + + + + + + + + + + + ) +} + +const SongList = (props) => ( + } + sort={{ field: 'title', order: 'ASC' }} + exporter={false} + bulkActionButtons={false} + filters={} + perPage={15} + > + }> + + + + + + + + + +) + +export default SongList diff --git a/ui/src/song/index.js b/ui/src/song/index.js new file mode 100644 index 000000000..7f83b4d50 --- /dev/null +++ b/ui/src/song/index.js @@ -0,0 +1,7 @@ +import MusicNote from '@material-ui/icons/MusicNote' +import SongList from './SongList' + +export default { + list: SongList, + icon: MusicNote +} diff --git a/ui/src/user/UserCreate.js b/ui/src/user/UserCreate.js index 6a5dcab26..314928d45 100644 --- a/ui/src/user/UserCreate.js +++ b/ui/src/user/UserCreate.js @@ -8,9 +8,10 @@ import { email, SimpleForm } from 'react-admin' +import { Title } from '../common' const UserCreate = (props) => ( - + } {...props}> diff --git a/ui/src/user/UserEdit.js b/ui/src/user/UserEdit.js index 18345dd2c..efbad5fba 100644 --- a/ui/src/user/UserEdit.js +++ b/ui/src/user/UserEdit.js @@ -9,9 +9,10 @@ import { email, SimpleForm } from 'react-admin' +import { Title } from '../common' const UserTitle = ({ record }) => { - return User {record ? record.name : ''} + return } const UserEdit = (props) => ( <Edit title={<UserTitle />} {...props}> diff --git a/ui/src/user/UserList.js b/ui/src/user/UserList.js index 3f740523c..c4b292ae4 100644 --- a/ui/src/user/UserList.js +++ b/ui/src/user/UserList.js @@ -10,6 +10,7 @@ import { TextField } from 'react-admin' import { useMediaQuery } from '@material-ui/core' +import { Title } from '../common' const UserFilter = (props) => ( <Filter {...props}> @@ -23,6 +24,7 @@ const UserList = (props) => { return ( <List {...props} + title={<Title subTitle={'Users'} />} sort={{ field: 'userName', order: 'ASC' }} exporter={false} filters={<UserFilter />}