allow saving users and playlists
This commit is contained in:
parent
77f18ab080
commit
c7a7dbd7fa
|
@ -5,6 +5,7 @@ using Beets
|
|||
using LightXML
|
||||
import UUIDs
|
||||
import HTTP
|
||||
using JSON2
|
||||
|
||||
const domain = "nixo.xyz"
|
||||
|
||||
|
|
|
@ -116,7 +116,7 @@ function getArtist(id::String)
|
|||
# Create the response
|
||||
(xdoc, xroot) = subsonic()
|
||||
artistXML = push!(xroot, artist)
|
||||
for album in Beets.albums(artist)
|
||||
for album in Beets.album(artist)
|
||||
push!(artistXML, album)
|
||||
end
|
||||
return subsonic_return(xdoc)
|
||||
|
@ -292,7 +292,7 @@ function createPlaylist(req)
|
|||
return not_found("playlistId")
|
||||
end
|
||||
elseif !isempty(name)
|
||||
playlist = Playlist(req[:login][:name], name = name)
|
||||
playlist = Playlist(req[:login][:user].name, name = name)
|
||||
push!(playlist, song)
|
||||
else
|
||||
return missing_parameter("either name or playlistId")
|
||||
|
@ -301,17 +301,19 @@ function createPlaylist(req)
|
|||
# Return the playlist
|
||||
(xdoc, xroot) = subsonic()
|
||||
push!(xroot, playlist)
|
||||
saveplaylists()
|
||||
return subsonic_return(xdoc)
|
||||
end
|
||||
|
||||
"Returns all playlists a user is allowed to play."
|
||||
function getPlaylists(req)
|
||||
global user_playlists
|
||||
user = req[:login][:name]
|
||||
# FIXME: add support for admin (ask other user's playlists, v 1.8.0)
|
||||
(xdoc, xroot) = subsonic(version = "1.0.0")
|
||||
playlistsXML = new_child(xroot, "playlists")
|
||||
for playlist in filter(p -> p.owner == user, user_playlists)
|
||||
for playlist in sort(filter(p -> canread(req[:login][:user], p),
|
||||
user_playlists),
|
||||
by = p -> p.name)
|
||||
push!(playlistsXML, playlist)
|
||||
end
|
||||
return subsonic_return(xdoc)
|
||||
|
@ -320,14 +322,13 @@ end
|
|||
"Returns a listing of files in a saved playlist."
|
||||
function getPlaylist(req)
|
||||
global user_playlists
|
||||
|
||||
query = HTTP.URIs.queryparams(req[:query])
|
||||
id = get(query, "id", "")
|
||||
isempty(id) && return missing_parameter("id")
|
||||
|
||||
m = findfirst(x -> x.owner == req[:login][:name], user_playlists)
|
||||
m = findfirst(p -> p.uuid == id,
|
||||
filter(p -> canread(req[:login][:user], p),
|
||||
user_playlists))
|
||||
m == nothing && return not_found("id")
|
||||
|
||||
(xdoc, xroot) = subsonic()
|
||||
append!(xroot, user_playlists[m])
|
||||
return subsonic_return(xroot)
|
||||
|
@ -365,6 +366,7 @@ function updatePlaylist(req)
|
|||
if songIndexToRemove > 0 && songIndexToRemove <= length(playlist.songs)
|
||||
deleteat!(playlist.songs, songIndexToRemove)
|
||||
end
|
||||
saveplaylists()
|
||||
@subsonic(nothing)
|
||||
end
|
||||
|
||||
|
@ -376,6 +378,7 @@ function deletePlaylist(req)
|
|||
isempty(id) && return missing_parameter("id")
|
||||
# FIXME: check ownership
|
||||
filter!(p -> p.uuid != id, user_playlists)
|
||||
saveplaylists()
|
||||
@subsonic(nothing)
|
||||
end
|
||||
|
||||
|
@ -418,3 +421,30 @@ function sendfile(path; suffix = nothing)
|
|||
return Dict(:body => read(path),
|
||||
:headers => headers)
|
||||
end
|
||||
|
||||
function saveplaylists(; file = expanduser("~/.config/beets/playlists.jsonl"))
|
||||
global user_playlists
|
||||
open(file, "w") do f
|
||||
write(f,
|
||||
join(JSON2.write.(user_playlists), "\n"))
|
||||
end
|
||||
end
|
||||
|
||||
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
|
||||
|
|
|
@ -10,22 +10,23 @@ function push!(root::XMLElement, p::Playlist)
|
|||
("public", string(p.public)),
|
||||
("songCount", string(length(p.songs))),
|
||||
("duration", reduce(+, p.songs, init = 0.0) |> floor |> Int |> string),
|
||||
("created", "FIXME"),
|
||||
("created", "2012-04-17T19:53:44"),
|
||||
("coverArt", p.cover),
|
||||
])
|
||||
playlistXML
|
||||
#=
|
||||
<allowedUser>sindre</allowedUser>
|
||||
<allowedUser>john</allowedUser>
|
||||
=#
|
||||
end
|
||||
|
||||
function append!(root::XMLElement, p::Playlist)
|
||||
playlistXML = push!(root, p)
|
||||
# FIXME: 1. allowed users
|
||||
# Allowed users
|
||||
for al in p.allowed
|
||||
set_content(new_child(playlistXML, "allowedUser"),
|
||||
al)
|
||||
end
|
||||
for song in p.songs
|
||||
entry = new_child(playlistXML, "entry")
|
||||
set_attributes(entry, props(song))
|
||||
set_attribute(entry, "artist", Beets.artist(song).name)
|
||||
end
|
||||
playlistXML
|
||||
end
|
||||
|
@ -69,7 +70,7 @@ function push!(root::XMLElement, artist::Beets.Artist)
|
|||
("id", artist.uuid),
|
||||
("name", artist.name),
|
||||
("coverArt", artist.uuid),
|
||||
("albumCount", Beets.albums(artist) |> length |> string)
|
||||
("albumCount", Beets.album(artist) |> length |> string)
|
||||
])
|
||||
artistXML
|
||||
end
|
||||
|
|
|
@ -1,15 +1,8 @@
|
|||
mutable struct Playlist
|
||||
uuid::String
|
||||
name::String
|
||||
comment::String
|
||||
owner::String
|
||||
public::Bool
|
||||
songs::Vector{Song}
|
||||
cover::String
|
||||
end
|
||||
import Random
|
||||
|
||||
mutable struct User
|
||||
name::String
|
||||
password::String
|
||||
email::String
|
||||
# scrobbling::Bool
|
||||
admin::Bool
|
||||
|
@ -25,17 +18,31 @@ mutable struct User
|
|||
share::Bool
|
||||
end
|
||||
|
||||
mutable struct Playlist
|
||||
uuid::String
|
||||
name::String
|
||||
comment::String
|
||||
owner::String
|
||||
public::Bool
|
||||
# FIXME: replace with uuids only (and check they exists when importing)
|
||||
songs::Vector{Song}
|
||||
cover::String
|
||||
allowed::Vector{String}
|
||||
end
|
||||
|
||||
function Playlist(owner::String
|
||||
; uuid = string(UUIDs.uuid4()),
|
||||
name = "New Playlist",
|
||||
comment = "",
|
||||
public = false,
|
||||
songs = Song[],
|
||||
cover = "",)
|
||||
Playlist(uuid, name, comment, owner, public, songs, cover)
|
||||
cover = "",
|
||||
allowed = String[])
|
||||
Playlist(uuid, name, comment, owner, public, songs, cover, allowed)
|
||||
end
|
||||
|
||||
function User(name::String)
|
||||
User(name, string(name, "@", domain),
|
||||
User(name, Random.randstring(30),
|
||||
string(name, "@", domain),
|
||||
false, false, false, false, false, false, false, false, false)
|
||||
end
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
# -*- restclient -*-
|
||||
|
||||
:url = http://localhost:8000/rest
|
||||
:token = fc75eb09895ad4fe4909b81f48f9f4b4
|
||||
:token = aca7a0d5412138863f361df08e738378
|
||||
:username = nixo
|
||||
:salt = 12b71ql5m8l8i72g5684hu0769
|
||||
:auth = ?username=:username&t=:token&s=:salt
|
||||
:salt = 2ob56atgdr8htkpdg5478c6tfj
|
||||
:auth = ?u=:username&t=:token&s=:salt
|
||||
|
||||
# Used to test connectivity with the server. Takes no extra parameters.
|
||||
GET :url/ping
|
||||
|
@ -39,3 +39,11 @@ GET :url/getCoverArt:auth&id=7167f941-efef-49dd-a54f-8e2d41e3f4a7
|
|||
# Stream
|
||||
GET :url/stream:auth&id=df5937fd-d79b-40b5-bf14-8c29c54e1bdb
|
||||
|
||||
# Get playlists
|
||||
GET :url/getPlaylists:auth
|
||||
|
||||
# Get single playlist
|
||||
GET :url/getPlaylist:auth&id=512c6d5e-798f-47f7-a50d-116ef647109e
|
||||
|
||||
|
||||
|
||||
|
|
39
login.jl
39
login.jl
|
@ -1,5 +1,9 @@
|
|||
using MD5
|
||||
using HTTP
|
||||
using JSON2
|
||||
|
||||
const users = JlSonic.User[]
|
||||
|
||||
function getlogin(app, req)
|
||||
query = HTTP.URIs.queryparams(req[:query])
|
||||
username = string(get(query, "u", ""))
|
||||
|
@ -15,25 +19,44 @@ function getlogin(app, req)
|
|||
end
|
||||
|
||||
function checkpassword(app, req)
|
||||
# FIXME: do not hardcode the password here!
|
||||
password = "test"
|
||||
# @show bytes2hex(MD5.md5(string(password, salt)))
|
||||
# @show req[:login][:token]
|
||||
# @show req[:login][:salt]
|
||||
global users
|
||||
usern = findfirst(u -> u.name == req[:login][:name], users)
|
||||
usern === nothing && return app(req)
|
||||
user = users[usern]
|
||||
req[:login][:user] = user
|
||||
if !isempty(req[:login][:salt])
|
||||
if bytes2hex(MD5.md5(string(password, req[:login][:salt]))) ==
|
||||
if bytes2hex(MD5.md5(string(user.password, req[:login][:salt]))) ==
|
||||
req[:login][:token]
|
||||
req[:login][:login] = true
|
||||
end
|
||||
elseif !isempty(req[:login][:password])
|
||||
if startswith(req[:login][:password], "enc:")
|
||||
req[:login][:login] =
|
||||
String(hex2bytes(split(req[:login][:password], ":")[2])) == password
|
||||
String(hex2bytes(split(req[:login][:password], ":")[2])) ==
|
||||
user.password
|
||||
else
|
||||
req[:login][:login] = password == req[:login][:password]
|
||||
req[:login][:login] = user.password == req[:login][:password]
|
||||
end
|
||||
end
|
||||
return app(req)
|
||||
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
|
||||
|
||||
sonic_login = stack(getlogin, checkpassword)
|
||||
|
|
|
@ -7,10 +7,12 @@ import Beets
|
|||
Beets.update_albums();
|
||||
push!(LOAD_PATH, realpath("JlSonic"))
|
||||
using JlSonic
|
||||
|
||||
JlSonic.loadplaylists()
|
||||
|
||||
include("router.jl")
|
||||
include("login.jl")
|
||||
loadusers()
|
||||
|
||||
@app sonic = (
|
||||
Mux.defaults,
|
||||
restp("ping", _ -> ping()),
|
||||
|
@ -24,4 +26,3 @@ if !isdefined(Main, :started)
|
|||
serve(sonic)
|
||||
started = true
|
||||
end
|
||||
|
||||
|
|
Loading…
Reference in New Issue