Skip to content
GitLab
Menu
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
AURA
tank
Commits
8f067adf
Commit
8f067adf
authored
Jun 13, 2020
by
Christian Pointner
Browse files
Merge branch 'topic/playlist-entry-durations'
parents
52f14a64
0f16221a
Pipeline
#724
failed with stages
in 5 minutes and 52 seconds
Changes
5
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
api/v1/utils.go
View file @
8f067adf
...
...
@@ -48,6 +48,8 @@ func statusCodeFromError(err error) (code int, response ErrorResponse) {
response
.
Detail
=
err
case
store
.
ErrInvalidMetadataField
:
code
=
http
.
StatusBadRequest
case
store
.
ErrInvalidPlaylistEntry
:
code
=
http
.
StatusBadRequest
case
*
importer
.
JobSourceResult
:
response
.
Detail
=
err
.
(
*
importer
.
JobSourceResult
)
.
Log
default
:
...
...
@@ -69,6 +71,8 @@ func statusCodeFromError(err error) (code int, response ErrorResponse) {
code
=
http
.
StatusConflict
case
store
.
ErrFileImportNotDone
:
code
=
http
.
StatusConflict
case
store
.
ErrPlaylistHasMultipleNullDurationEntries
:
code
=
http
.
StatusBadRequest
case
importer
.
ErrNotImplemented
:
code
=
http
.
StatusNotImplemented
...
...
store/migrations.go
View file @
8f067adf
...
...
@@ -217,6 +217,30 @@ var (
return
tx
.
Table
(
"files"
)
.
DropColumn
(
"metadata__isrc"
)
.
Error
},
},
{
ID
:
"202006130203"
,
Migrate
:
func
(
tx
*
gorm
.
DB
)
error
{
type
PlaylistEntry
struct
{
ID
uint64
`json:"-" gorm:"primary_key"`
PlaylistID
uint64
`json:"-" gorm:"not null;index;unique_index:unique_playlist_line_numbers"`
LineNum
uint
`json:"-" gorm:"not null;unique_index:unique_playlist_line_numbers"`
URI
string
`json:"uri" gorm:"size:1024"`
Duration
*
time
.
Duration
`json:"duration,omitempty"`
File
*
File
`json:"file,omitempty" gorm:"association_autoupdate:false;association_autocreate:false"`
FileID
*
uint64
`json:"-" gorm:"index"`
}
// actually all playlists would need to be verfied if they still fit the new contstraint
// that only allows a sinle non-file entry with durtion == NULL per playlist.
// However we are still pre-first-release and all migrations will likely be squashed before that
// release anyway...
return
tx
.
AutoMigrate
(
&
PlaylistEntry
{})
.
Error
},
Rollback
:
func
(
tx
*
gorm
.
DB
)
error
{
return
tx
.
Table
(
"playlist_entries"
)
.
DropColumn
(
"duration"
)
.
Error
},
},
}
)
...
...
store/playlists.go
View file @
8f067adf
...
...
@@ -33,31 +33,76 @@ import (
"github.com/jinzhu/gorm"
)
func
(
p
*
Playlist
)
BeforeSave
()
error
{
func
generateFileURI
(
file
*
File
)
string
{
uri
:=
url
.
URL
{
Scheme
:
FileURIScheme
,
Host
:
file
.
ShowName
,
Path
:
strconv
.
FormatUint
(
file
.
ID
,
10
)}
return
uri
.
String
()
}
func
(
p
*
Playlist
)
BeforeSave
(
tx
*
gorm
.
DB
)
error
{
referencedFiles
:=
make
(
map
[
uint64
]
*
File
)
hasEntryWithoutDuration
:=
false
for
idx
:=
range
p
.
Entries
{
p
.
Entries
[
idx
]
.
LineNum
=
uint
(
idx
)
if
p
.
Entries
[
idx
]
.
File
!=
nil
{
p
.
Entries
[
idx
]
.
URI
=
""
// this will be regenerated in AfterFind()
}
else
if
p
.
Entries
[
idx
]
.
URI
!=
""
{
uri
,
err
:=
url
.
Parse
(
p
.
Entries
[
idx
]
.
URI
)
if
p
.
Entries
[
idx
]
.
File
.
ShowName
==
""
{
p
.
Entries
[
idx
]
.
File
.
ShowName
=
p
.
ShowName
}
referencedFiles
[
p
.
Entries
[
idx
]
.
File
.
ID
]
=
nil
continue
}
if
p
.
Entries
[
idx
]
.
URI
==
""
{
return
ErrInvalidPlaylistEntry
{
idx
,
"entries must either reference a File or have a URI set"
}
}
uri
,
err
:=
url
.
Parse
(
p
.
Entries
[
idx
]
.
URI
)
if
err
!=
nil
{
return
ErrInvalidPlaylistEntry
{
idx
,
err
.
Error
()}
}
if
uri
.
Scheme
==
FileURIScheme
{
if
uri
.
Host
==
""
||
uri
.
Path
==
""
{
return
ErrInvalidPlaylistEntry
{
idx
,
"uri must be in the format "
+
FileURIScheme
+
"://<show>/<file-id>"
}
}
fileID
,
err
:=
strconv
.
ParseUint
(
strings
.
TrimPrefix
(
uri
.
Path
,
"/"
),
10
,
64
)
if
err
!=
nil
{
return
fmt
.
Errorf
(
"p
laylist
e
ntry
#%d is invalid: %v"
,
idx
,
err
)
return
ErrInvalidP
laylist
E
ntry
{
idx
,
err
.
Error
()}
}
if
uri
.
Scheme
==
FileURIScheme
{
if
uri
.
Host
==
""
||
uri
.
Path
==
""
{
return
fmt
.
Errorf
(
"playlist entry #%d is invalid: uri must be in the format %s://<show>/<file-id>"
,
idx
,
FileURIScheme
)
}
fileID
,
err
:=
strconv
.
ParseUint
(
strings
.
TrimPrefix
(
uri
.
Path
,
"/"
),
10
,
64
)
if
err
!=
nil
{
return
fmt
.
Errorf
(
"playlist entry #%d is invalid: %v"
,
idx
,
err
)
}
p
.
Entries
[
idx
]
.
File
=
&
File
{
ID
:
fileID
,
ShowName
:
uri
.
Host
}
p
.
Entries
[
idx
]
.
URI
=
""
// this will be regenerated in AfterFind()
p
.
Entries
[
idx
]
.
File
=
&
File
{
ID
:
fileID
,
ShowName
:
uri
.
Host
}
referencedFiles
[
fileID
]
=
nil
p
.
Entries
[
idx
]
.
URI
=
""
// this will be regenerated in AfterFind()
}
else
if
p
.
Entries
[
idx
]
.
Duration
==
nil
{
if
hasEntryWithoutDuration
{
return
ErrPlaylistHasMultipleNullDurationEntries
}
}
else
{
return
fmt
.
Errorf
(
"playlist entry #%d is invalid, entries must either contain a File or have a URI set"
,
idx
)
hasEntryWithoutDuration
=
true
}
}
var
fileEntryIDs
[]
uint64
for
id
,
_
:=
range
referencedFiles
{
fileEntryIDs
=
append
(
fileEntryIDs
,
id
)
}
var
files
[]
*
File
if
err
:=
tx
.
Where
(
"id IN (?)"
,
fileEntryIDs
)
.
Model
(
&
File
{})
.
Select
(
"id, show_name, duration"
)
.
Scan
(
&
files
)
.
Error
;
err
!=
nil
{
return
nil
}
for
_
,
file
:=
range
files
{
referencedFiles
[
file
.
ID
]
=
file
}
for
idx
:=
range
p
.
Entries
{
if
p
.
Entries
[
idx
]
.
File
==
nil
{
continue
}
if
referencedFiles
[
p
.
Entries
[
idx
]
.
File
.
ID
]
==
nil
||
referencedFiles
[
p
.
Entries
[
idx
]
.
File
.
ID
]
.
ShowName
!=
p
.
Entries
[
idx
]
.
File
.
ShowName
{
return
ErrInvalidPlaylistEntry
{
idx
,
fmt
.
Sprintf
(
"file '%s/%d' does not exist"
,
p
.
Entries
[
idx
]
.
File
.
ShowName
,
p
.
Entries
[
idx
]
.
File
.
ID
)}
}
if
p
.
Entries
[
idx
]
.
Duration
!=
nil
&&
referencedFiles
[
p
.
Entries
[
idx
]
.
File
.
ID
]
.
Duration
!=
*
(
p
.
Entries
[
idx
]
.
Duration
)
{
return
ErrInvalidPlaylistEntry
{
idx
,
"provided duration and file duration mismatch"
}
}
p
.
Entries
[
idx
]
.
Duration
=
nil
// this will be regenerated in AfterFind()
}
return
nil
}
...
...
@@ -68,10 +113,8 @@ func (p *Playlist) AfterSave(tx *gorm.DB) error {
func
(
p
*
Playlist
)
AfterFind
()
error
{
for
idx
:=
range
p
.
Entries
{
if
p
.
Entries
[
idx
]
.
File
!=
nil
{
urihost
:=
p
.
Entries
[
idx
]
.
File
.
ShowName
uripath
:=
strconv
.
FormatUint
(
p
.
Entries
[
idx
]
.
File
.
ID
,
10
)
uri
:=
url
.
URL
{
Scheme
:
FileURIScheme
,
Host
:
urihost
,
Path
:
uripath
}
p
.
Entries
[
idx
]
.
URI
=
uri
.
String
()
p
.
Entries
[
idx
]
.
URI
=
generateFileURI
(
p
.
Entries
[
idx
]
.
File
)
p
.
Entries
[
idx
]
.
Duration
=
&
p
.
Entries
[
idx
]
.
File
.
Duration
}
}
return
nil
...
...
store/store_test.go
View file @
8f067adf
...
...
@@ -32,6 +32,7 @@ import (
"path/filepath"
"reflect"
"testing"
"time"
//"github.com/jinzhu/gorm"
)
...
...
@@ -574,9 +575,14 @@ func TestFilesSourceHash(t *testing.T) {
// Playlists
//
func
generateTestPlaylist
(
uris
...
string
)
(
p
Playlist
)
{
for
_
,
u
:=
range
uris
{
e
:=
PlaylistEntry
{
URI
:
u
}
type
playlistTestEntry
struct
{
uri
string
duration
*
time
.
Duration
}
func
generateTestPlaylist
(
entries
...
playlistTestEntry
)
(
p
Playlist
)
{
for
_
,
entry
:=
range
entries
{
e
:=
PlaylistEntry
{
URI
:
entry
.
uri
,
Duration
:
entry
.
duration
}
p
.
Entries
=
append
(
p
.
Entries
,
e
)
}
return
p
...
...
@@ -601,8 +607,14 @@ func TestPlaylistsListCreateDelete(t *testing.T) {
t
.
Fatalf
(
"listing playlists of not existing show should return and empty list but ListPlaylists returned: %v"
,
playlists
)
}
in
:=
generateTestPlaylist
(
"audioin://1"
,
"http://stream.example.com/live.mp"
)
in
:=
generateTestPlaylist
(
playlistTestEntry
{
"audioin://1"
,
nil
},
playlistTestEntry
{
"http://stream.example.com/live.mp"
,
nil
}
)
testPlaylist
,
err
:=
store
.
CreatePlaylist
(
testShowName
,
in
)
if
err
!=
ErrPlaylistHasMultipleNullDurationEntries
{
t
.
Fatalf
(
"creating playlist with more than one non-file entry without duration should fail with specific error but returned: %v"
,
err
)
}
testDuration
:=
time
.
Second
in
=
generateTestPlaylist
(
playlistTestEntry
{
"audioin://1"
,
&
testDuration
},
playlistTestEntry
{
"http://stream.example.com/live.mp"
,
nil
})
testPlaylist
,
err
=
store
.
CreatePlaylist
(
testShowName
,
in
)
if
err
!=
nil
{
t
.
Fatalf
(
"creating playlist in test show failed: %v"
,
err
)
}
...
...
@@ -615,12 +627,12 @@ func TestPlaylistsListCreateDelete(t *testing.T) {
t
.
Fatalf
(
"ListPlaylists should return a single playlist but returned: %v"
,
playlists
)
}
in1
:=
generateTestPlaylist
(
"audioin://1"
,
"http://stream.example.com/live.mp3"
)
in1
:=
generateTestPlaylist
(
playlistTestEntry
{
"audioin://1"
,
&
testDuration
},
playlistTestEntry
{
"http://stream.example.com/live.mp3"
,
nil
}
)
_
,
err
=
store
.
CreatePlaylist
(
testShow1
,
in1
)
if
err
!=
nil
{
t
.
Fatalf
(
"creating playlist in not existing show shouldn't throw an error but CreatePlaylist returned: %v"
,
err
)
}
in2
:=
generateTestPlaylist
(
"https://stream.example.com/other.ogg"
,
"audioin://2"
)
in2
:=
generateTestPlaylist
(
playlistTestEntry
{
"https://stream.example.com/other.ogg"
,
&
testDuration
},
playlistTestEntry
{
"audioin://2"
,
nil
}
)
_
,
err
=
store
.
CreatePlaylist
(
testShow1
,
in2
)
if
err
!=
nil
{
t
.
Fatalf
(
"creating playlist in not existing show shouldn't throw an error but CreatePlaylist returned: %v"
,
err
)
...
...
@@ -667,7 +679,7 @@ func TestPlaylistsCreateAndGet(t *testing.T) {
t
.
Fatalf
(
"getting playlist in not-existing show should return ErrNotFound, but GetPlaylist returned: %v"
,
err
)
}
p
:=
generateTestPlaylist
(
"http://stream.example.com/stream.mp3"
)
p
:=
generateTestPlaylist
(
playlistTestEntry
{
"http://stream.example.com/stream.mp3"
,
nil
}
)
p
.
Entries
=
append
(
p
.
Entries
,
PlaylistEntry
{
File
:
&
File
{
ShowName
:
file1
.
ShowName
,
ID
:
file1
.
ID
}})
list1
,
err
:=
store
.
CreatePlaylist
(
testShow1
,
p
)
if
err
!=
nil
{
...
...
@@ -679,7 +691,7 @@ func TestPlaylistsCreateAndGet(t *testing.T) {
}
// TODO: check if playlists are equal
p
=
generateTestPlaylist
(
"http://stream.example.com/other.mp3"
,
fmt
.
Sprintf
(
"file://%s/%d"
,
file1
.
ShowName
,
file1
.
ID
))
p
=
generateTestPlaylist
(
playlistTestEntry
{
"http://stream.example.com/other.mp3"
,
nil
},
playlistTestEntry
{
fmt
.
Sprintf
(
"file://%s/%d"
,
file1
.
ShowName
,
file1
.
ID
)
,
nil
}
)
if
_
,
err
=
store
.
CreatePlaylist
(
testShow1
,
p
);
err
!=
nil
{
t
.
Fatalf
(
"unexpected error: %v"
,
err
)
}
...
...
@@ -719,7 +731,7 @@ func TestFileUsage(t *testing.T) {
t
.
Fatalf
(
"file should be in use by any playlist but got %d entries in usage list"
,
len
(
lists
))
}
p
:=
generateTestPlaylist
(
"http://stream.example.com/stream.mp3"
)
p
:=
generateTestPlaylist
(
playlistTestEntry
{
"http://stream.example.com/stream.mp3"
,
nil
}
)
p
.
Entries
=
append
(
p
.
Entries
,
PlaylistEntry
{
File
:
&
File
{
ShowName
:
file
.
ShowName
,
ID
:
file
.
ID
}})
list
,
err
:=
store
.
CreatePlaylist
(
testShow1
,
p
)
if
err
!=
nil
{
...
...
store/types.go
View file @
8f067adf
...
...
@@ -48,6 +48,8 @@ var (
ErrShowAlreadyExists
=
errors
.
New
(
"show already exists"
)
ErrFileNotNew
=
errors
.
New
(
"file does not exist or is not new"
)
ErrFileImportNotDone
=
errors
.
New
(
"file import is not done"
)
ErrPlaylistHasMultipleNullDurationEntries
=
errors
.
New
(
"playlists may only have one entry without a duration"
)
)
type
ErrFileInUse
struct
{
...
...
@@ -64,6 +66,15 @@ func (e ErrInvalidMetadataField) Error() string {
return
"invalid metadata field: "
+
string
(
e
)
}
type
ErrInvalidPlaylistEntry
struct
{
idx
int
message
string
}
func
(
e
ErrInvalidPlaylistEntry
)
Error
()
string
{
return
fmt
.
Sprintf
(
"playlist entry #%d is invalid: %s"
,
e
.
idx
,
e
.
message
)
}
//******* Logs
type
LogLine
struct
{
...
...
@@ -199,12 +210,13 @@ type ImportLogs map[string]*Log
//******* Playlists
type
PlaylistEntry
struct
{
ID
uint64
`json:"-" gorm:"primary_key"`
PlaylistID
uint64
`json:"-" gorm:"not null;index;unique_index:unique_playlist_line_numbers"`
LineNum
uint
`json:"-" gorm:"not null;unique_index:unique_playlist_line_numbers"`
URI
string
`json:"uri" gorm:"size:1024"`
File
*
File
`json:"file,omitempty" gorm:"association_autoupdate:false;association_autocreate:false"`
FileID
*
uint64
`json:"-" gorm:"index"`
ID
uint64
`json:"-" gorm:"primary_key"`
PlaylistID
uint64
`json:"-" gorm:"not null;index;unique_index:unique_playlist_line_numbers"`
LineNum
uint
`json:"-" gorm:"not null;unique_index:unique_playlist_line_numbers"`
URI
string
`json:"uri" gorm:"size:1024"`
Duration
*
time
.
Duration
`json:"duration,omitempty"`
File
*
File
`json:"file,omitempty" gorm:"association_autoupdate:false;association_autocreate:false"`
FileID
*
uint64
`json:"-" gorm:"index"`
}
type
Playlist
struct
{
...
...
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment