allow saving users and playlists

master
nixo 2019-05-19 22:24:18 +02:00
parent 77f18ab080
commit c7a7dbd7fa
7 changed files with 111 additions and 40 deletions

View File

@ -5,6 +5,7 @@ using Beets
using LightXML
import UUIDs
import HTTP
using JSON2
const domain = "nixo.xyz"

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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