refactor and fixes
This commit is contained in:
parent
61449fd235
commit
34956871d7
|
@ -11,15 +11,18 @@ using JSON2
|
||||||
# The idea is to sum all the album arts in some way. But it's easier to get one random
|
# The idea is to sum all the album arts in some way. But it's easier to get one random
|
||||||
# using FileIO, Images
|
# using FileIO, Images
|
||||||
|
|
||||||
const domain = "nixo.xyz"
|
|
||||||
|
|
||||||
include("types.jl")
|
include("types.jl")
|
||||||
export Playlist, Album, Artist
|
export Playlist, Album, Artist
|
||||||
|
|
||||||
|
const domain = "nixo.xyz"
|
||||||
|
const users = User[]
|
||||||
|
const user_playlists = Vector{Playlist}()
|
||||||
|
|
||||||
include("api.jl")
|
include("api.jl")
|
||||||
export ping, getLicense,
|
export ping, getLicense,
|
||||||
# Browsing
|
# Browsing
|
||||||
getMusicFolders, # getIndexes, getMusicDirectory,
|
getMusicFolders, # getIndexes,
|
||||||
|
getMusicDirectory,
|
||||||
getGenres, getArtists, getArtist, getAlbum, getSong,
|
getGenres, getArtists, getArtist, getAlbum, getSong,
|
||||||
# Album/song list
|
# Album/song list
|
||||||
getAlbumList, getAlbumList2, getRandomSongs,
|
getAlbumList, getAlbumList2, getRandomSongs,
|
||||||
|
@ -42,5 +45,6 @@ export auth_failed
|
||||||
|
|
||||||
include("beethelpers.jl")
|
include("beethelpers.jl")
|
||||||
include("beet2xml.jl")
|
include("beet2xml.jl")
|
||||||
|
include("login.jl")
|
||||||
|
|
||||||
end # module JlSonic
|
end # module JlSonic
|
||||||
|
|
168
JlSonic/api.jl
168
JlSonic/api.jl
|
@ -63,7 +63,88 @@ end
|
||||||
|
|
||||||
# Implement:
|
# Implement:
|
||||||
# getIndexes
|
# getIndexes
|
||||||
# getMusicDirectory
|
function getMusicDirectory(req)
|
||||||
|
query = HTTP.URIs.queryparams(req[:query])
|
||||||
|
id = get(query, "id", "")
|
||||||
|
isempty(id) && return missing_parameter()
|
||||||
|
(xdoc, xroot) = subsonic(version = "1.0.0")
|
||||||
|
directory = new_child(xroot, "directory")
|
||||||
|
# We simulate directory listing. Root directory has id ==
|
||||||
|
# 1. Other directories are identified by uuids
|
||||||
|
artists = Beets.getartists()
|
||||||
|
## Structure is: Music(id=1)/Artist/Album/Song
|
||||||
|
if id == "1"
|
||||||
|
# List artists
|
||||||
|
for artist in artists
|
||||||
|
content = new_child(directory, "child")
|
||||||
|
set_attributes(content,
|
||||||
|
[("id", artist.uuid),
|
||||||
|
# Always under /Music, id = 1
|
||||||
|
("parent", "1"),
|
||||||
|
("name", artist.name),
|
||||||
|
# ("starred", "FIXME")
|
||||||
|
])
|
||||||
|
end
|
||||||
|
else
|
||||||
|
# List content
|
||||||
|
# 1. Search if uuid matches artist
|
||||||
|
# 2. Else, check if matches albums
|
||||||
|
artistmatch = findfirst(a -> a.uuid == id, artists)
|
||||||
|
albums = Beets.getalbums();
|
||||||
|
if artistmatch != nothing
|
||||||
|
@show id
|
||||||
|
artist = artists[artistmatch]
|
||||||
|
set_attributes(directory,
|
||||||
|
[("id", artist.uuid),
|
||||||
|
# Always under /Music, id = 1
|
||||||
|
("parent", "1"),
|
||||||
|
("name", artist.name),
|
||||||
|
("starred", "2013-11-02T12:30:00")
|
||||||
|
])
|
||||||
|
# List albums
|
||||||
|
content = new_child(directory, "child")
|
||||||
|
for albumn in findall(alb -> alb.artist == artist, albums)
|
||||||
|
album = albums[albumn]
|
||||||
|
set_attributes(content, [
|
||||||
|
("id", album.uuid),
|
||||||
|
("parent", album.artist.uuid),
|
||||||
|
# ("artistId", album.artist.uuid),
|
||||||
|
("title", album.title),
|
||||||
|
("artist", album.artist.name),
|
||||||
|
("isDir", "true"),
|
||||||
|
("coverArt", album.uuid),
|
||||||
|
])
|
||||||
|
end
|
||||||
|
elseif false
|
||||||
|
content = new_child(directory, "child")
|
||||||
|
# List album content (songs)
|
||||||
|
set_attributes(content, [
|
||||||
|
("id", "FIXME"),
|
||||||
|
("parent", "PARENT:ID"),
|
||||||
|
("title", "FIXME"),
|
||||||
|
("isDir", "false"),
|
||||||
|
("album", "FIXME"),
|
||||||
|
("artist", "FIXME"),
|
||||||
|
("track", "FIXME"),
|
||||||
|
("year", "FIXME"),
|
||||||
|
("genre", "FIXME"),
|
||||||
|
("coverArt", "FIXME"),
|
||||||
|
("size", "FIXME"),
|
||||||
|
# FIXME
|
||||||
|
("contentType", "audio/mpeg"),
|
||||||
|
("suffix", "FIXME"),
|
||||||
|
("duration", "FIXME"),
|
||||||
|
("bitrate", "FIXME"),
|
||||||
|
("path", "FIXME"),
|
||||||
|
])
|
||||||
|
else
|
||||||
|
return not_found()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
doc_str = string(xdoc)
|
||||||
|
free(xdoc)
|
||||||
|
return doc_str
|
||||||
|
end
|
||||||
|
|
||||||
"Returns all genres."
|
"Returns all genres."
|
||||||
function getGenres()
|
function getGenres()
|
||||||
|
@ -98,7 +179,7 @@ function getArtists()
|
||||||
for index in string.(firstletters)
|
for index in string.(firstletters)
|
||||||
indexXML = new_child(indexes, "index")
|
indexXML = new_child(indexes, "index")
|
||||||
set_attribute(indexXML, "name", index)
|
set_attribute(indexXML, "name", index)
|
||||||
for artist in unique(filter(x -> startswith(x.name, index), artists))
|
for artist in filter(x -> startswith(x.name, index), artists)
|
||||||
artistXML = push!(indexXML, artist)
|
artistXML = push!(indexXML, artist)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -109,7 +190,7 @@ end
|
||||||
|
|
||||||
This method organizes music according to ID3 tags."""
|
This method organizes music according to ID3 tags."""
|
||||||
function getArtist(id::String)
|
function getArtist(id::String)
|
||||||
artists = Beets.getartists()
|
artists = Beets.artists()
|
||||||
matching = findfirst(a -> a.uuid == id, artists)
|
matching = findfirst(a -> a.uuid == id, artists)
|
||||||
matching === nothing && return not_found("id")
|
matching === nothing && return not_found("id")
|
||||||
artist = artists[matching]
|
artist = artists[matching]
|
||||||
|
@ -264,8 +345,6 @@ function search3(req)
|
||||||
return subsonic_return(xdoc)
|
return subsonic_return(xdoc)
|
||||||
end
|
end
|
||||||
|
|
||||||
const user_playlists = Vector{Playlist}()
|
|
||||||
|
|
||||||
"Create (or update) a playlist" # WTF create can update?
|
"Create (or update) a playlist" # WTF create can update?
|
||||||
function createPlaylist(req)
|
function createPlaylist(req)
|
||||||
global user_playlists
|
global user_playlists
|
||||||
|
@ -293,10 +372,10 @@ function createPlaylist(req)
|
||||||
end
|
end
|
||||||
elseif !isempty(name)
|
elseif !isempty(name)
|
||||||
playlist = Playlist(req[:login][:user].name,
|
playlist = Playlist(req[:login][:user].name,
|
||||||
name = name,
|
name = name # cover = ???
|
||||||
# cover = ???
|
|
||||||
)
|
)
|
||||||
push!(playlist, song)
|
push!(playlist, song)
|
||||||
|
@show "THERE"
|
||||||
else
|
else
|
||||||
return missing_parameter("either name or playlistId")
|
return missing_parameter("either name or playlistId")
|
||||||
end
|
end
|
||||||
|
@ -323,11 +402,11 @@ function getPlaylists(req)
|
||||||
end
|
end
|
||||||
|
|
||||||
import Base.get
|
import Base.get
|
||||||
function get(::Type{Playlist}, u::User, id::AbstractString)
|
function get(::Type{Playlist}, u::User, id::AbstractString)::Union{Nothing,Playlist}
|
||||||
global user_playlists
|
global user_playlists
|
||||||
findfirst(p -> p.uuid == id,
|
playlists = filter(p -> canread(u, p), user_playlists)
|
||||||
filter(p -> canread(u, p),
|
m = findfirst(p -> p.uuid == id, playlists)
|
||||||
user_playlists))
|
m == nothing ? nothing : playlists[m]
|
||||||
end
|
end
|
||||||
|
|
||||||
"Returns a listing of files in a saved playlist."
|
"Returns a listing of files in a saved playlist."
|
||||||
|
@ -336,10 +415,10 @@ function getPlaylist(req)
|
||||||
query = HTTP.URIs.queryparams(req[:query])
|
query = HTTP.URIs.queryparams(req[:query])
|
||||||
id = get(query, "id", "")
|
id = get(query, "id", "")
|
||||||
isempty(id) && return missing_parameter("id")
|
isempty(id) && return missing_parameter("id")
|
||||||
m = get(Playlist, req[:login][:user], id)
|
playlist = get(Playlist, req[:login][:user], id)
|
||||||
m == nothing && return not_found("id")
|
playlist == nothing && return not_found("id")
|
||||||
(xdoc, xroot) = subsonic()
|
(xdoc, xroot) = subsonic()
|
||||||
append!(xroot, user_playlists[m])
|
append!(xroot, playlist)
|
||||||
return subsonic_return(xroot)
|
return subsonic_return(xroot)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -349,16 +428,26 @@ function updatePlaylist(req)
|
||||||
query = HTTP.URIs.queryparams(req[:query])
|
query = HTTP.URIs.queryparams(req[:query])
|
||||||
playlistId = get(query, "playlistId", "")
|
playlistId = get(query, "playlistId", "")
|
||||||
isempty(playlistId) && return missing_parameter("playlistId")
|
isempty(playlistId) && return missing_parameter("playlistId")
|
||||||
m = get(Playlist, req[:login][:user], playlistId)
|
playlist = get(Playlist, req[:login][:user], playlistId)
|
||||||
m == nothing && return not_found("playlistId")
|
playlist == nothing && return not_found("playlistId")
|
||||||
playlist = user_playlists[m]
|
|
||||||
|
|
||||||
# Check ownership (if not allowed, should not even reach this (canread is false))
|
# Check ownership (if not allowed, should not even reach this (canread is false))
|
||||||
canedit(req[:login][:user], playlist) || return not_allowed()
|
canedit(req[:login][:user], playlist) || return unuthorized()
|
||||||
|
# Want to make public. Is allowed?
|
||||||
|
wantpublic = try
|
||||||
|
parse(Bool,get(query, "public", string(playlist.public)))
|
||||||
|
catch e
|
||||||
|
isa(e, ArgumentError) ? false : @error e
|
||||||
|
end
|
||||||
|
if wantpublic
|
||||||
|
canmakepublic(req[:login][:user]) || return unuthorized()
|
||||||
|
playlist.public = true
|
||||||
|
else
|
||||||
|
playlist.public = false
|
||||||
|
end
|
||||||
|
|
||||||
playlist.name = get(query, "name", playlist.name)
|
playlist.name = get(query, "name", playlist.name)
|
||||||
playlist.comment = get(query, "comment", playlist.comment)
|
playlist.comment = get(query, "comment", playlist.comment)
|
||||||
# FIXME: use try/catch
|
|
||||||
playlist.public = parse(Bool,get(query, "public", string(playlist.public)))
|
|
||||||
songIdAdd = get(query, "songIdToAdd", "")
|
songIdAdd = get(query, "songIdToAdd", "")
|
||||||
# WTF by the index!?
|
# WTF by the index!?
|
||||||
IndexToRemove = get(query, "songIndexToRemove", "")
|
IndexToRemove = get(query, "songIndexToRemove", "")
|
||||||
|
@ -416,9 +505,11 @@ function getCoverArt(req::Dict)
|
||||||
return sendfile(Beets.albums[n].cover)
|
return sendfile(Beets.albums[n].cover)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
canstream(u::User) = u.stream
|
||||||
"Streams a given media file."
|
"Streams a given media file."
|
||||||
function stream(req::Dict)
|
function stream(req::Dict)
|
||||||
query = HTTP.URIs.queryparams(req[:query])
|
query = HTTP.URIs.queryparams(req[:query])
|
||||||
|
canstream(req[:login][:user]) || return unuthorized()
|
||||||
id = get(query, "id", "")
|
id = get(query, "id", "")
|
||||||
isempty(id) && return missing_parameter("id")
|
isempty(id) && return missing_parameter("id")
|
||||||
songs = Beets.songs()
|
songs = Beets.songs()
|
||||||
|
@ -432,35 +523,20 @@ function sendfile(path; suffix = nothing)
|
||||||
isfile(path) || return Dict{String,String}(:body => "Not Found")
|
isfile(path) || return Dict{String,String}(:body => "Not Found")
|
||||||
suffix = suffix == nothing ? lowercase(split(path, '.')[end]) : suffix
|
suffix = suffix == nothing ? lowercase(split(path, '.')[end]) : suffix
|
||||||
headers = Dict{String,String}()
|
headers = Dict{String,String}()
|
||||||
headers["Content-Type"] = Mux.mimetypes[suffix]
|
mime = suffix in keys(Mux.mimetypes) ? Mux.mimetypes[suffix] : suffix
|
||||||
|
headers["Content-Type"] = mime
|
||||||
headers["Content-Length"] = string(filesize(path))
|
headers["Content-Length"] = string(filesize(path))
|
||||||
return Dict(:body => read(path),
|
return Dict(:body => read(path),
|
||||||
:headers => headers)
|
:headers => headers)
|
||||||
end
|
end
|
||||||
|
|
||||||
function saveplaylists(; file = expanduser("~/.config/beets/playlists.jsonl"))
|
canread(u::User, p::Playlist) = p.public ||
|
||||||
global user_playlists
|
(u.admin || p.owner == u.name) ||
|
||||||
open(file, "w") do f
|
u in p.allowed
|
||||||
write(f,
|
function canedit(u::User, p::Playlist)
|
||||||
join(JSON2.write.(user_playlists), "\n"))
|
@show p.owner
|
||||||
end
|
@show u.name
|
||||||
|
@show u.admin
|
||||||
|
(p.owner == u.name) || u.admin
|
||||||
end
|
end
|
||||||
|
canmakepublic(u::User) = u.playlist
|
||||||
function loadplaylists(; file = expanduser("~/.config/beets/playlists.jsonl"))
|
|
||||||
global user_playlists
|
|
||||||
isfile(file) || touch(file)
|
|
||||||
ps = JSON2.readlines(file)
|
|
||||||
empty!(user_playlists)
|
|
||||||
for p in ps
|
|
||||||
# try
|
|
||||||
pl = JSON2.read(p, Playlist)
|
|
||||||
push!(user_playlists, pl)
|
|
||||||
# catch e
|
|
||||||
# @warn "Failed to read with error $e"
|
|
||||||
# isa(e, ArgumentError) && continue
|
|
||||||
# end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
canread(u::User, p::Playlist) = p.public || p.owner == u.name || u in p.allowed
|
|
||||||
canedit(u::User, p::Playlist) = p.owner == u.name
|
|
||||||
|
|
|
@ -1,5 +1,12 @@
|
||||||
|
import Dates
|
||||||
|
ms2string(m::Dates.DateTime) = Dates.format(m, Dates.dateformat"YYYY-mm-ddTHH:MM:SS")
|
||||||
|
|
||||||
import Base.push!
|
import Base.push!
|
||||||
|
|
||||||
|
# Try to fix missing mime types
|
||||||
|
Mux.mimetypes["alac"] = "audio/x-m4a"
|
||||||
|
Mux.mimetypes["m4a"] = "audio/x-m4a"
|
||||||
|
|
||||||
function push!(root::XMLElement, p::Playlist)
|
function push!(root::XMLElement, p::Playlist)
|
||||||
playlistXML = new_child(root, "playlist")
|
playlistXML = new_child(root, "playlist")
|
||||||
set_attributes(playlistXML, [
|
set_attributes(playlistXML, [
|
||||||
|
@ -10,7 +17,7 @@ function push!(root::XMLElement, p::Playlist)
|
||||||
("public", string(p.public)),
|
("public", string(p.public)),
|
||||||
("songCount", string(length(p.songs))),
|
("songCount", string(length(p.songs))),
|
||||||
("duration", reduce(+, p.songs, init = 0.0) |> floor |> Int |> string),
|
("duration", reduce(+, p.songs, init = 0.0) |> floor |> Int |> string),
|
||||||
("created", "2012-04-17T19:53:44"),
|
("created", ms2string(p.created)),
|
||||||
("coverArt", p.cover),
|
("coverArt", p.cover),
|
||||||
])
|
])
|
||||||
playlistXML
|
playlistXML
|
||||||
|
@ -20,8 +27,7 @@ function append!(root::XMLElement, p::Playlist)
|
||||||
playlistXML = push!(root, p)
|
playlistXML = push!(root, p)
|
||||||
# Allowed users
|
# Allowed users
|
||||||
for al in p.allowed
|
for al in p.allowed
|
||||||
set_content(new_child(playlistXML, "allowedUser"),
|
set_content(new_child(playlistXML, "allowedUser"), al)
|
||||||
al)
|
|
||||||
end
|
end
|
||||||
for song in p.songs
|
for song in p.songs
|
||||||
entry = new_child(playlistXML, "entry")
|
entry = new_child(playlistXML, "entry")
|
||||||
|
@ -40,7 +46,7 @@ function push!(root::XMLElement, album::Beets.Album)
|
||||||
("name", album.title),
|
("name", album.title),
|
||||||
("coverArt", album.uuid),
|
("coverArt", album.uuid),
|
||||||
("songCount", string(length(album.songs))),
|
("songCount", string(length(album.songs))),
|
||||||
("created", "0"), # FIXME
|
("created", ms2string(album.added)),
|
||||||
("duration", string(sum([t.length for t in album.songs]) |> floor |> Int)),
|
("duration", string(sum([t.length for t in album.songs]) |> floor |> Int)),
|
||||||
("artist", album.artist.name),
|
("artist", album.artist.name),
|
||||||
("artistId", album.artist.uuid)
|
("artistId", album.artist.uuid)
|
||||||
|
@ -78,16 +84,17 @@ end
|
||||||
function push!(root::XMLElement, song::Beets.Song)
|
function push!(root::XMLElement, song::Beets.Song)
|
||||||
songXML = new_child(root, "song")
|
songXML = new_child(root, "song")
|
||||||
suffix = lowercase(song.format)
|
suffix = lowercase(song.format)
|
||||||
|
mime = suffix in keys(Mux.mimetypes) ? Mux.mimetypes[suffix] : suffix
|
||||||
set_attributes(songXML, [
|
set_attributes(songXML, [
|
||||||
("id", song.uuid),
|
("id", song.uuid),
|
||||||
("title", song.title),
|
("title", song.title),
|
||||||
("isDir", "false"),
|
("isDir", "false"),
|
||||||
("created", "FIXME"),
|
("created", ms2string(song.added)),
|
||||||
("duration", string(floor(song.length) |> Int)),
|
("duration", string(floor(song.length) |> Int)),
|
||||||
("bitrate", string(song.bitrate)),
|
("bitrate", string(song.bitrate)),
|
||||||
("size", string(filesize(song.path))),
|
("size", string(filesize(song.path))),
|
||||||
("suffix", suffix),
|
("suffix", suffix),
|
||||||
("contentType", Mux.mimetypes[suffix]),
|
("contentType", mime),
|
||||||
("isVideo", "false"),
|
("isVideo", "false"),
|
||||||
("path", relpath(song.path, Beets.musicdir())),
|
("path", relpath(song.path, Beets.musicdir())),
|
||||||
("type", "music")
|
("type", "music")
|
||||||
|
@ -99,6 +106,7 @@ function push!(root::XMLElement, song_album::Tuple{Beets.Song,Beets.Album})
|
||||||
songXML = new_child(root, "song")
|
songXML = new_child(root, "song")
|
||||||
song, album = song_album
|
song, album = song_album
|
||||||
suffix = lowercase(song.format)
|
suffix = lowercase(song.format)
|
||||||
|
mime = suffix in keys(Mux.mimetypes) ? Mux.mimetypes[suffix] : suffix
|
||||||
set_attributes(songXML, [
|
set_attributes(songXML, [
|
||||||
("id", song.uuid),
|
("id", song.uuid),
|
||||||
("parent", album.artist.uuid), # Not clear
|
("parent", album.artist.uuid), # Not clear
|
||||||
|
@ -107,12 +115,12 @@ function push!(root::XMLElement, song_album::Tuple{Beets.Song,Beets.Album})
|
||||||
("artist", album.artist.name),
|
("artist", album.artist.name),
|
||||||
("isDir", "false"),
|
("isDir", "false"),
|
||||||
("coverArt", album.uuid),
|
("coverArt", album.uuid),
|
||||||
("created", "FIXME"),
|
("created", ms2string(album.added)),
|
||||||
("duration", string(floor(song.length) |> Int)),
|
("duration", string(floor(song.length) |> Int)),
|
||||||
("bitrate", string(song.bitrate)),
|
("bitrate", string(song.bitrate)),
|
||||||
("size", string(filesize(song.path))),
|
("size", string(filesize(song.path))),
|
||||||
("suffix", suffix),
|
("suffix", suffix),
|
||||||
("contentType", Mux.mimetypes[suffix]), # mpeg
|
("contentType", mime),
|
||||||
("isVideo", "false"),
|
("isVideo", "false"),
|
||||||
("path", relpath(song.path, Beets.musicdir())),
|
("path", relpath(song.path, Beets.musicdir())),
|
||||||
("albumId", album.uuid),
|
("albumId", album.uuid),
|
||||||
|
@ -124,6 +132,7 @@ end
|
||||||
|
|
||||||
function props(song::Song)
|
function props(song::Song)
|
||||||
suffix = lowercase(song.format)
|
suffix = lowercase(song.format)
|
||||||
|
mime = suffix in keys(Mux.mimetypes) ? Mux.mimetypes[suffix] : suffix
|
||||||
[
|
[
|
||||||
("id", song.uuid),
|
("id", song.uuid),
|
||||||
# ("parent", album.artist.uuid), # Not clear
|
# ("parent", album.artist.uuid), # Not clear
|
||||||
|
@ -132,12 +141,12 @@ function props(song::Song)
|
||||||
# ("artist", song.album.artist.name),
|
# ("artist", song.album.artist.name),
|
||||||
("isDir", "false"),
|
("isDir", "false"),
|
||||||
("coverArt", song.uuid),
|
("coverArt", song.uuid),
|
||||||
("created", "FIXME"),
|
("created", ms2string(song.added)),
|
||||||
("duration", string(floor(song.length) |> Int)),
|
("duration", string(floor(song.length) |> Int)),
|
||||||
("bitrate", string(song.bitrate)),
|
("bitrate", string(song.bitrate)),
|
||||||
("size", string(filesize(song.path))),
|
("size", string(filesize(song.path))),
|
||||||
("suffix", suffix),
|
("suffix", suffix),
|
||||||
("contentType", Mux.mimetypes[suffix]), # mpeg
|
("contentType", mime), # mpeg
|
||||||
("isVideo", "false"),
|
("isVideo", "false"),
|
||||||
("path", relpath(song.path, Beets.musicdir())),
|
("path", relpath(song.path, Beets.musicdir())),
|
||||||
# ("albumId", song.album.uuid),
|
# ("albumId", song.album.uuid),
|
||||||
|
|
|
@ -1,4 +1,44 @@
|
||||||
function allsongs()
|
playlistfile(path) = joinpath(path, "playlists.jsonl")
|
||||||
albums = [album.songs for album in Beets.getalbums()];
|
|
||||||
songs = Iterators.flatten(albums) |> collect;
|
function saveplaylists(; file = playlistfile(Beets.confdir()))
|
||||||
|
global user_playlists
|
||||||
|
open(file, "w") do f
|
||||||
|
write(f,
|
||||||
|
join(JSON2.write.(user_playlists), "\n"))
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function loadplaylists(; file = playlistfile(Beets.confdir()))
|
||||||
|
global user_playlists
|
||||||
|
isfile(file) || touch(file)
|
||||||
|
ps = JSON2.readlines(file)
|
||||||
|
empty!(user_playlists)
|
||||||
|
for p in ps
|
||||||
|
try
|
||||||
|
pl = JSON2.read(p, Playlist)
|
||||||
|
push!(user_playlists, pl)
|
||||||
|
catch e
|
||||||
|
@warn "Failed to read with error $e"
|
||||||
|
isa(e, ArgumentError) && continue
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function saveusers(file = expanduser("~/.config/beets/users.jsonl"))
|
||||||
|
global users
|
||||||
|
open(file, "w") do f
|
||||||
|
write(f, join(JSON2.write.(users), "\n"))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function loadusers(; file = expanduser("~/.config/beets/users.jsonl"))
|
||||||
|
global users
|
||||||
|
isfile(file) || touch(file)
|
||||||
|
ps = JSON2.readlines(file)
|
||||||
|
p = JSON2.read.(ps, JlSonic.User)
|
||||||
|
empty!(users)
|
||||||
|
for pl in p
|
||||||
|
push!(users, pl)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import Random
|
import Random
|
||||||
|
import Dates
|
||||||
mutable struct User
|
mutable struct User
|
||||||
name::String
|
name::String
|
||||||
password::String
|
password::String
|
||||||
|
@ -28,6 +28,7 @@ mutable struct Playlist
|
||||||
songs::Vector{Song}
|
songs::Vector{Song}
|
||||||
cover::String
|
cover::String
|
||||||
allowed::Vector{String}
|
allowed::Vector{String}
|
||||||
|
created::Dates.DateTime
|
||||||
end
|
end
|
||||||
|
|
||||||
function Playlist(owner::String
|
function Playlist(owner::String
|
||||||
|
@ -37,8 +38,12 @@ function Playlist(owner::String
|
||||||
public = false,
|
public = false,
|
||||||
songs = Song[],
|
songs = Song[],
|
||||||
cover = "",
|
cover = "",
|
||||||
allowed = String[])
|
allowed = String[],
|
||||||
Playlist(uuid, name, comment, owner, public, songs, cover, allowed)
|
creation = Dates.now())
|
||||||
|
Playlist(uuid,
|
||||||
|
name, comment, owner, public,
|
||||||
|
songs, cover,
|
||||||
|
allowed, creation)
|
||||||
end
|
end
|
||||||
|
|
||||||
function User(name::String)
|
function User(name::String)
|
||||||
|
|
|
@ -15,8 +15,8 @@ GET :url/getLicense
|
||||||
# Returns all configured top-level music folders. Takes no extra parameters.
|
# Returns all configured top-level music folders. Takes no extra parameters.
|
||||||
GET :url/getMusicFolders:auth
|
GET :url/getMusicFolders:auth
|
||||||
|
|
||||||
# getIndexes = Returns an indexed structure of all artists.
|
#
|
||||||
# getMusicDirectory = Returns a listing of all files in a music directory. Typically used to get list of albums for an artist, or list of songs for an album.
|
GET :url/getMusicDirectory:auth&id=fab34286-b8e1-4879-bce3-194e1358fbd2
|
||||||
|
|
||||||
# Returns all genres.
|
# Returns all genres.
|
||||||
GET :url/getGenres:auth
|
GET :url/getGenres:auth
|
||||||
|
@ -24,26 +24,27 @@ GET :url/getGenres:auth
|
||||||
# Similar to getIndexes, but organizes music according to ID3 tags.
|
# Similar to getIndexes, but organizes music according to ID3 tags.
|
||||||
GET :url/getArtists:auth
|
GET :url/getArtists:auth
|
||||||
|
|
||||||
# Returns details for an artist, including a list of albums. This method organizes music according to ID3 tags.
|
# Returns details for an artist, including a list of albums. This method organizes music according to ID3 tags.14d44067-99c2-4f77-b58b-138f0b6911fa
|
||||||
GET :url/getArtist:auth&id=14d44067-99c2-4f77-b58b-138f0b6911fa
|
GET :url/getArtist:auth&id=ba853904-ae25-4ebb-89d6-c44cfbd71bd2
|
||||||
|
|
||||||
|
|
||||||
# Returns details for an album, including a list of songs. This method organizes music according to ID3 tags.
|
# Returns details for an album, including a list of songs. This method organizes music according to ID3 tags.
|
||||||
GET :url/getAlbum:auth&id=d9522a40-887f-4a15-a59f-0d3bccfa908f
|
GET :url/getAlbum:auth&id=f281e63f-589d-4691-8f13-9906ccc09aa0
|
||||||
|
|
||||||
# Returns details for a song.
|
# Returns details for a song.
|
||||||
GET :url/getSong:auth&id=df5937fd-d79b-40b5-bf14-8c29c54e1bdb
|
GET :url/getSong:auth&id=e1ebe027-2e21-45c9-bff8-94ba538f895f
|
||||||
|
|
||||||
# Returns a cover art image.
|
# Returns a cover art image.
|
||||||
GET :url/getCoverArt:auth&id=7167f941-efef-49dd-a54f-8e2d41e3f4a7
|
GET :url/getCoverArt:auth&id=7167f941-efef-49dd-a54f-8e2d41e3f4a7
|
||||||
|
|
||||||
# Stream
|
# Stream
|
||||||
GET :url/stream:auth&id=df5937fd-d79b-40b5-bf14-8c29c54e1bdb
|
GET :url/stream:auth&id=e1ebe027-2e21-45c9-bff8-94ba538f895f
|
||||||
|
|
||||||
# Get playlists
|
# Get playlists
|
||||||
GET :url/getPlaylists:auth
|
GET :url/getPlaylists:auth
|
||||||
|
|
||||||
# Get single playlist
|
# Get single playlist
|
||||||
GET :url/getPlaylist:auth&id=a2df9320-4775-40a5-9830-8960f3eb9203
|
GET :url/getPlaylist:auth&id=1455e415-8718-4453-a5f5-490a00b62d34
|
||||||
|
|
||||||
# Get not owned playlist
|
# Get not owned playlist
|
||||||
GET :url/getPlaylist:auth&id=799f5074-5db2-4daa-b449-9677d0c7744c
|
GET :url/getPlaylist:auth&id=799f5074-5db2-4daa-b449-9677d0c7744c
|
||||||
|
@ -52,7 +53,7 @@ GET :url/getPlaylist:auth&id=799f5074-5db2-4daa-b449-9677d0c7744c
|
||||||
GET :url/deletePlaylist:auth&id=799f5074-5db2-4daa-b449-9677d0c7744c
|
GET :url/deletePlaylist:auth&id=799f5074-5db2-4daa-b449-9677d0c7744c
|
||||||
|
|
||||||
# Update not owned playlist
|
# Update not owned playlist
|
||||||
GET :url/updatePlaylist:auth&playlistId=799f5074-5db2-4daa-b449-9677d0c7744c
|
GET :url/updatePlaylist:auth&playlistId=e39d8798-473e-45a9-8a1f-d5d0485ed274
|
||||||
|
|
||||||
# Update owned playlist
|
# Update owned playlist
|
||||||
GET :url/updatePlaylist:auth&playlistId=a2df9320-4775-40a5-9830-8960f3eb9203&name=nuovo
|
GET :url/updatePlaylist:auth&playlistId=a2df9320-4775-40a5-9830-8960f3eb9203&name=nuovo
|
||||||
|
@ -60,5 +61,8 @@ GET :url/updatePlaylist:auth&playlistId=a2df9320-4775-40a5-9830-8960f3eb9203&nam
|
||||||
# Delete owned playlist
|
# Delete owned playlist
|
||||||
GET :url/deletePlaylist:auth&id=a2df9320-4775-40a5-9830-8960f3eb9203
|
GET :url/deletePlaylist:auth&id=a2df9320-4775-40a5-9830-8960f3eb9203
|
||||||
|
|
||||||
|
# Get random songs
|
||||||
|
GET :url/getRandomSongs:auth
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
57
login.jl
57
login.jl
|
@ -2,59 +2,28 @@ using MD5
|
||||||
using HTTP
|
using HTTP
|
||||||
using JSON2
|
using JSON2
|
||||||
|
|
||||||
const users = JlSonic.User[]
|
|
||||||
|
|
||||||
function getlogin(app, req)
|
function getlogin(app, req)
|
||||||
query = HTTP.URIs.queryparams(req[:query])
|
query = HTTP.URIs.queryparams(req[:query])
|
||||||
username = string(get(query, "u", ""))
|
req[:login] = Dict(:name => string(get(query, "u", "")),
|
||||||
token = get(query, "t", "")
|
:token => get(query, "t", ""),
|
||||||
salt = get(query, "s", "")
|
:salt => get(query, "s", ""),
|
||||||
password = get(query, "p", "")
|
:password => get(query, "p", ""),
|
||||||
req[:login] = Dict(:name => username,
|
|
||||||
:token => token,
|
|
||||||
:salt => salt,
|
|
||||||
:password => password,
|
|
||||||
:login => false)
|
:login => false)
|
||||||
return app(req)
|
return app(req)
|
||||||
end
|
end
|
||||||
|
|
||||||
function checkpassword(app, req)
|
function checkpassword(app, req)
|
||||||
global users
|
user = JlSonic.checkpass(req[:login][:name],
|
||||||
usern = findfirst(u -> u.name == req[:login][:name], users)
|
req[:login][:salt],
|
||||||
usern === nothing && return app(req)
|
req[:login][:token],
|
||||||
user = users[usern]
|
req[:login][:password])
|
||||||
req[:login][:user] = user
|
if user == nothing
|
||||||
if !isempty(req[:login][:salt])
|
req[:login][:login] = false
|
||||||
if bytes2hex(MD5.md5(string(user.password, req[:login][:salt]))) ==
|
else
|
||||||
req[:login][:token]
|
req[:login][:login] = true
|
||||||
req[:login][:login] = true
|
req[:login][:user] = user
|
||||||
end
|
|
||||||
elseif !isempty(req[:login][:password])
|
|
||||||
if startswith(req[:login][:password], "enc:")
|
|
||||||
req[:login][:login] =
|
|
||||||
String(hex2bytes(split(req[:login][:password], ":")[2])) ==
|
|
||||||
user.password
|
|
||||||
else
|
|
||||||
req[:login][:login] = user.password == req[:login][:password]
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
return app(req)
|
return app(req)
|
||||||
end
|
end
|
||||||
|
|
||||||
function saveusers(file = expanduser("~/.config/beets/users.jsonl"))
|
|
||||||
global users
|
|
||||||
open(file, "w") do f
|
|
||||||
write(f, join(JSON2.write.(users), "\n"))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function loadusers(; file = expanduser("~/.config/beets/users.jsonl"))
|
|
||||||
global users
|
|
||||||
isfile(file) || touch(file)
|
|
||||||
ps = JSON2.readlines(file)
|
|
||||||
p = JSON2.read.(ps, JlSonic.User)
|
|
||||||
empty!(users)
|
|
||||||
append!(users, p)
|
|
||||||
end
|
|
||||||
|
|
||||||
sonic_login = stack(getlogin, checkpassword)
|
sonic_login = stack(getlogin, checkpassword)
|
||||||
|
|
|
@ -9,7 +9,7 @@ restp(p, app...) = branch(req -> restpath!(p, req), app...)
|
||||||
dispatch = stack(
|
dispatch = stack(
|
||||||
# Browsing
|
# Browsing
|
||||||
restp("getMusicFolders", _ -> getMusicFolders()),
|
restp("getMusicFolders", _ -> getMusicFolders()),
|
||||||
restp("getMusicDirectory", req -> getmusicdirectory(req)),
|
restp("getMusicDirectory", req -> getMusicDirectory(req)),
|
||||||
restp("getAlbumList", req -> getAlbumList(req)),
|
restp("getAlbumList", req -> getAlbumList(req)),
|
||||||
restp("getGenres", _ -> getGenres()),
|
restp("getGenres", _ -> getGenres()),
|
||||||
restp("getArtists", _ -> getArtists()),
|
restp("getArtists", _ -> getArtists()),
|
||||||
|
|
|
@ -8,10 +8,10 @@ Beets.update_albums();
|
||||||
push!(LOAD_PATH, realpath("JlSonic"))
|
push!(LOAD_PATH, realpath("JlSonic"))
|
||||||
using JlSonic
|
using JlSonic
|
||||||
JlSonic.loadplaylists()
|
JlSonic.loadplaylists()
|
||||||
|
JlSonic.loadusers()
|
||||||
|
|
||||||
include("router.jl")
|
include("router.jl")
|
||||||
include("login.jl")
|
include("login.jl")
|
||||||
loadusers()
|
|
||||||
|
|
||||||
@app sonic = (
|
@app sonic = (
|
||||||
Mux.defaults,
|
Mux.defaults,
|
||||||
|
|
Loading…
Reference in New Issue