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) => (
} {...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) => (
@@ -23,6 +24,7 @@ const UserList = (props) => {
return (
}
sort={{ field: 'userName', order: 'ASC' }}
exporter={false}
filters={}