Skip to content
Snippets Groups Projects
Commit ec21b56e authored by François  LAURENT's avatar François LAURENT
Browse files

feat: session expiry and data clean-up

parent dbbc853d
No related branches found
No related tags found
1 merge request!6Set of commits to be tagged v0.2.1
Pipeline #154810 waiting for manual action
...@@ -52,9 +52,6 @@ RUN cd "${JULIA_PROJECT}" \ ...@@ -52,9 +52,6 @@ RUN cd "${JULIA_PROJECT}" \
# final stage # final stage
ARG LARVATAGGER_PORT=9286
ENV LARVATAGGER_PORT=${LARVATAGGER_PORT}
COPY --chown=julia larvatagger-entrypoint.sh /app/ COPY --chown=julia larvatagger-entrypoint.sh /app/
ENTRYPOINT ["/app/larvatagger-entrypoint.sh"] ENTRYPOINT ["/app/larvatagger-entrypoint.sh"]
......
...@@ -2,6 +2,14 @@ ...@@ -2,6 +2,14 @@
if [ -z "$LARVATAGGER_PORT" ]; then if [ -z "$LARVATAGGER_PORT" ]; then
LARVATAGGER_PORT=9286 LARVATAGGER_PORT=9286
else
echo "Using environment variable: LARVATAGGER_PORT= $LARVATAGGER_PORT"
fi fi
julia -e "using LarvaTagger.REST.Server; run_backend(\"/app\"; host=\"0.0.0.0\", port=$LARVATAGGER_PORT)" if [ -z "$LARVATAGGER_TOKEN_EXPIRY" ]; then
LARVATAGGER_TOKEN_EXPIRY=14400
else
echo "Using environment variable: LARVATAGGER_TOKEN_EXPIRY= $LARVATAGGER_TOKEN_EXPIRY"
fi
julia -e "using LarvaTagger.REST.Server; run_backend(\"/app\", $LARVATAGGER_TOKEN_EXPIRY; host=\"0.0.0.0\", port=$LARVATAGGER_PORT)"
...@@ -4,13 +4,22 @@ ...@@ -4,13 +4,22 @@
if [ -z "$LARVATAGGER_PORT" ]; then if [ -z "$LARVATAGGER_PORT" ]; then
LARVATAGGER_PORT=9286 LARVATAGGER_PORT=9286
else
echo "Using environment variable: LARVATAGGER_PORT= $LARVATAGGER_PORT"
fi fi
if [ -z "$LARVATAGGER_IMAGE" ]; then if [ -z "$LARVATAGGER_IMAGE" ]; then
LARVATAGGER_IMAGE=docker.io/flaur/larvatagger:0.20-bigfat LARVATAGGER_IMAGE=docker.io/flaur/larvatagger:0.20-bigfat
else
echo "Using environment variable: LARVATAGGER_IMAGE= $LARVATAGGER_IMAGE"
fi
if [ -z "$LARVATAGGER_TOKEN_EXPIRY" ]; then
LARVATAGGER_TOKEN_EXPIRY=3600
else
echo "Using environment variable: LARVATAGGER_TOKEN_EXPIRY= $LARVATAGGER_TOKEN_EXPIRY"
fi fi
podman run -d -p $LARVATAGGER_PORT:9285 --entrypoint=julia $LARVATAGGER_IMAGE \ podman run -d -p $LARVATAGGER_PORT:9285 --entrypoint=julia $LARVATAGGER_IMAGE --project=/app \
--project=/app -e 'using LarvaTagger.REST.Server; run_backend("/app"; host="0.0.0.0")' -e "using LarvaTagger.REST.Server; run_backend(\"/app\", $LARVATAGGER_TOKEN_EXPIRY; host=\"0.0.0.0\")"
CONTAINER=`podman ps | grep $LARVATAGGER_IMAGE | cut -d' ' -f1` CONTAINER=`podman ps | grep $LARVATAGGER_IMAGE | cut -d' ' -f1`
if ! [ -z "$CONTAINER" ]; then if ! [ -z "$CONTAINER" ]; then
......
module GenieExtras module GenieExtras
import Genie.Renderer.Html import Genie.Renderer.Html
import GenieSession: GenieSession, Session
using NyxWidgets.Base: Cache
export publish_css, appinfo, add_footer export publish_css, appinfo, add_footer, Session, SessionRegistry, getsessionid, getsession,
clearexpired
const project_root = dirname(Base.current_project()) const project_root = dirname(Base.current_project())
...@@ -49,4 +52,129 @@ add_footer(ui::AbstractString, footer=footer) = "$ui$(footer())" ...@@ -49,4 +52,129 @@ add_footer(ui::AbstractString, footer=footer) = "$ui$(footer())"
add_footer(ui::Function, footer=footer) = () -> add_footer(ui(), footer) add_footer(ui::Function, footer=footer) = () -> add_footer(ui(), footer)
struct SessionRegistry
register
idlength
purgesession
purgesession_args
end
function SessionRegistry(f, args=(:session_id,); idlength=32)
SessionRegistry(Cache{String, Dict{Symbol, Any}}(), idlength, f, args)
end
Base.lock(f::Function, registry::SessionRegistry) = lock(f, registry.register)
function check_session_id_length(registry, session_id)
if !isnothing(registry.idlength) && length(session_id) < registry.idlength
throw("wrong session_id length")
end
end
function Base.haskey(registry::SessionRegistry, session_id::AbstractString)
lock(registry) do
for session_long_id in keys(registry.register.cache)
if startswith(session_long_id, session_id)
return true
end
end
return false
end
end
function Base.getindex(registry::SessionRegistry, session_id::AbstractString)
check_session_id_length(registry, session_id)
lock(registry) do
for (session_long_id, records) in pairs(registry.register.cache)
if startswith(session_long_id, session_id)
records[:lastseen] = time()
return records
end
end
throw(KeyError(session_id))
end
end
Base.getindex(registry::SessionRegistry, session_id::AbstractString, record::Symbol
) = registry[session_id][record]
function getsessionid(session_long_id::String, idlength::Union{Nothing, <:Integer})
session_id = session_long_id
if !isnothing(idlength)
session_id = string(session_id[1:idlength])
end
return session_id
end
function getsessionid(registry::SessionRegistry)
session = GenieSession.session()
session_long_id = session.id
session_id = getsessionid(session_long_id, registry.idlength)
# create the entry in the register if missing
records = lock(registry) do
register = registry.register.cache
if session_id != session_long_id && session_id in keys(register)
# if the session has been registered with its short id (and `getsession`),
# update the short id with the long one
register[session_long_id] = pop!(register, session_id)
else
# registry.register is a NyxWidgets.Cache and `getindex` behaves like `get!`
registry.register[session_long_id]
end
end
# register minimum information
records[:session] = session
records[:lastseen] = time()
# run maintenance routine
clearexpired(registry)
return session_id
end
function getsession(registry::SessionRegistry, session_id::AbstractString)
if haskey(registry, session_id) # check for long id
registry[session_id]
else # call get! on short id (if session_id is short); not ideal
registry.register[session_id]
end
end
function clearexpired(registry, expiry::Real=604800)
lock(registry) do
register = registry.register.cache
for (session_long_id, records) in pairs(register)
if !haskey(records, :lastseen)
@warn "Missing key: lastseen"
continue
end
expiry <= time() - records[:lastseen] || continue
# prepare to clear the session; collect the arguments to purgesession
session_id = getsessionid(session_long_id, registry.idlength)
available_args = Dict(:session_id=>session_id,
:session_long_id=>session_long_id,
records...)
@info "Purging session" session_id
pop!(register, session_long_id)
if haskey(records, :session)
session = records[:session]
# see learn.genieframework.com/framework/genie.jl/recipes/session
empty!(session.data)
else
@warn "Missing key: session"
end
#
function get′(arg)
get(available_args, arg) do
@warn "Missing argument for purgesession" arg
nothing
end
end
try
registry.purgesession(get′.(registry.purgesession_args)...)
catch
@error "\`purgesession\` callback failed"
end
end
end
end
end end
...@@ -3,7 +3,7 @@ module LarvaTagger ...@@ -3,7 +3,7 @@ module LarvaTagger
using NyxUI, NyxUI.Storage, NyxUI.GenieExtras using NyxUI, NyxUI.Storage, NyxUI.GenieExtras
using NyxWidgets.Base: Cache using NyxWidgets.Base: Cache
import LarvaTagger as LT import LarvaTagger as LT
using GenieSession, Stipple using Stipple
import Stipple: @app, @init, @private, @in, @onchange, @onbutton, @click import Stipple: @app, @init, @private, @in, @onchange, @onbutton, @click
import StippleUI: tooltip import StippleUI: tooltip
...@@ -19,24 +19,30 @@ mutable struct Model ...@@ -19,24 +19,30 @@ mutable struct Model
kwargs kwargs
end end
const bonito_models = Cache{String, Model}() function purgesession(model, session_id)
if isnothing(model)
@warn "Session data prematurely lost"
elseif !isnothing(model.app)
close(model.app)
end
purge_appdata(session_id, "larvatagger")
BonitoServer.addsession(bonito_app, session_id; restart=true)
end
const session_registry = SessionRegistry(purgesession, (:model, :session_id))
const bonito_app = NamedApp("larvatagger", const bonito_app = NamedApp("larvatagger",
function (session) function (session)
bucket = getbucket(session, "larvatagger", :read) bucket = getbucket(session, "larvatagger", :read)
mkpath(bucket) mkpath(bucket)
model = lock(bonito_models) do model = get!(getsession(session_registry, session), :model) do
if haskey(bonito_models, session) kwargs = Dict{Symbol, Any}()
bonito_models.cache[session] if !isempty(get(ENV, "LARVATAGGER_BACKEND", ""))
else kwargs[:backend_directory] = backend = ENV["LARVATAGGER_BACKEND"]
kwargs = Dict{Symbol, Any}() @info "Using environment variable" LARVATAGGER_BACKEND=backend
if haskey(ENV, "LARVATAGGER_BACKEND")
kwargs[:backend_directory] = backend = ENV["LARVATAGGER_BACKEND"]
@info "Using environment variable" LARVATAGGER_BACKEND=backend
end
inputfile = Ref{Union{Nothing, String}}(nothing)
bonito_models.cache[session] = Model(1.0, nothing, inputfile, kwargs)
end end
inputfile = Ref{Union{Nothing, String}}(nothing)
Model(1.0, nothing, inputfile, kwargs)
end end
exportdir = joinpath("public", session, "larvatagger") exportdir = joinpath("public", session, "larvatagger")
function prepare_download(srcfile) function prepare_download(srcfile)
...@@ -66,13 +72,9 @@ const bonito_app = NamedApp("larvatagger", ...@@ -66,13 +72,9 @@ const bonito_app = NamedApp("larvatagger",
end end
) )
function purgesession(session) function purgesession(session_id)
model = lock(bonito_models) do model = pop!(session_registry[session_id], :model, nothing)
pop!(bonito_models.cache, session) purgesession(model, session_id)
end
isnothing(model.app) || close(model.app)
purge_appdata(session, "larvatagger")
BonitoServer.addsession(bonito_app, session; restart=true)
end end
...@@ -82,10 +84,10 @@ end ...@@ -82,10 +84,10 @@ end
@in reset_bonito_session = false @in reset_bonito_session = false
@onchange isready begin @onchange isready begin
genie_session = GenieSession.session() bonito_session_id = getsessionid(session_registry)
bonito_session_id = genie_session.id[1:32] if haskey(session_registry[bonito_session_id], :model)
if haskey(bonito_models, bonito_session_id) model = session_registry[bonito_session_id, :model]
sizefactor = sizefactors_str[bonito_models[bonito_session_id].sizefactor] sizefactor = sizefactors_str[model.sizefactor]
else else
BonitoServer.addsession(bonito_app, bonito_session_id) BonitoServer.addsession(bonito_app, bonito_session_id)
end end
...@@ -103,9 +105,10 @@ end ...@@ -103,9 +105,10 @@ end
@onchange sizefactor begin @onchange sizefactor begin
new_sizefactor = sizefactors[sizefactor] new_sizefactor = sizefactors[sizefactor]
if new_sizefactor != bonito_models[bonito_session_id].sizefactor model = session_registry[bonito_session_id, :model]
if new_sizefactor != model.sizefactor
@info "New size factor; refreshing the iframe" @info "New size factor; refreshing the iframe"
bonito_models[bonito_session_id].sizefactor = new_sizefactor model.sizefactor = new_sizefactor
BonitoServer.addsession(bonito_app, bonito_session_id; restart=true) # restart BonitoServer.addsession(bonito_app, bonito_session_id; restart=true) # restart
width = appwidth[sizefactor] width = appwidth[sizefactor]
height = appheight[sizefactor] height = appheight[sizefactor]
...@@ -119,7 +122,11 @@ end ...@@ -119,7 +122,11 @@ end
end end
@onbutton reset_bonito_session begin @onbutton reset_bonito_session begin
purgesession(bonito_session_id) try
purgesession(bonito_session_id)
catch
@error "Error while resetting session"
end
# reset UI to defaults # reset UI to defaults
bonito_session_id[!] = "" bonito_session_id[!] = ""
sizefactor[!] = "1.0" sizefactor[!] = "1.0"
......
...@@ -3,6 +3,7 @@ module Backbone ...@@ -3,6 +3,7 @@ module Backbone
using NyxWidgets.Base: Cache using NyxWidgets.Base: Cache
using NyxUI.MuscleActivities using NyxUI.MuscleActivities
using NyxUI.Storage using NyxUI.Storage
using NyxUI.GenieExtras
using Bonito using Bonito
include("MuscleWidgets.jl") include("MuscleWidgets.jl")
...@@ -32,37 +33,35 @@ export hasmodel, getmodel, setmodel, newsequence, getsequence, withsequences, ...@@ -32,37 +33,35 @@ export hasmodel, getmodel, setmodel, newsequence, getsequence, withsequences,
## models ## models
const __backbone_cache__ = Cache{String, MuscleWidget}() function purgesession end
const __valid_sessions__ = Dict{String, Bool}()
const session_registry = SessionRegistry(purgesession, (:session_id, ))
function app(persistent=""; title="Nyx muscle activity") function app(persistent=""; title="Nyx muscle activity")
App(; title=title) do session App(; title=title) do session
# # embedded apps are always renderded twice; save time # EDIT: except on Ctrl+R
# valid = get(__valid_sessions__, persistent, false)
# __valid_sessions__[persistent] = !valid
# valid || return
@info "New session" session.id @info "New session" session.id
cache_id = isempty(persistent) ? session.id : persistent cache_id = isempty(persistent) ? session.id : persistent
model = __backbone_cache__[cache_id] model = session_registry[cache_id, :model]
Bonito.jsrender(session, model) Bonito.jsrender(session, model)
end end
end end
## muscle model ## muscle model
getmodel(session_id) = __backbone_cache__[session_id]#.widget getmodel(session_id) = session_registry[session_id, :model]
# for debugging
getmodel() = first(values(__backbone_cache__))#.widget
hasmodel(session_id) = haskey(__backbone_cache__, session_id) hasmodel(session_id) = haskey(session_registry[session_id], :model)
function setmodel(session_id, sequence) function setmodel(session_id, sequence)
__backbone_cache__[session_id] = MuscleWidget(sequence) records = getsession(session_registry, session_id)
records[:model] = model = MuscleWidget(sequence)
# init :sequences
get!(records, :sequences) do
Cache{Int, MuscleActivity}()
end
return model
end end
# getchannel(session_id) = __backbone_cache__[session_id].channel
## sequence model ## sequence model
function newsequencename(stem, existing) function newsequencename(stem, existing)
...@@ -97,14 +96,13 @@ function newsequence(session_id, sequence_name, sequence_names, start_time, time ...@@ -97,14 +96,13 @@ function newsequence(session_id, sequence_name, sequence_names, start_time, time
return sequence return sequence
end end
const __sequences__ = Cache{String, Cache{Int, MuscleActivity}}()
function getsequence(session_id, sequence_name) function getsequence(session_id, sequence_name)
model = getmodel(session_id) model = getmodel(session_id)
sequence = nothing sequence = nothing
if model.sequence[].program_name != sequence_name if model.sequence[].program_name != sequence_name
lock(__sequences__[session_id]) do sequences = session_registry[session_id, :sequences]
for outer sequence in values(__sequences__[session_id]) lock(sequences) do
for outer sequence in values(sequences)
sequence.program_name == sequence_name && break sequence.program_name == sequence_name && break
end end
end end
...@@ -116,16 +114,16 @@ end ...@@ -116,16 +114,16 @@ end
getsequence(session_id) = getmodel(session_id).sequence[] getsequence(session_id) = getmodel(session_id).sequence[]
function addsequence(session_id, sequence) function addsequence(session_id, sequence)
lock(__sequences__[session_id]) do sequences = session_registry[session_id, :sequences]
sequences = __sequences__[session_id] lock(sequences) do
i = isempty(sequences) ? 0 : maximum(keys(sequences)) i = isempty(sequences) ? 0 : maximum(keys(sequences))
sequences[i+1] = sequence sequences[i+1] = sequence
end end
end end
function withsequences(f, session_id) function withsequences(f, session_id)
lock(__sequences__[session_id]) do sequences = session_registry[session_id, :sequences]
sequences = __sequences__[session_id] lock(sequences) do
return f(sequences) return f(sequences)
end end
end end
...@@ -153,7 +151,7 @@ end ...@@ -153,7 +151,7 @@ end
function deletesequence(session_id) function deletesequence(session_id)
model = getmodel(session_id) model = getmodel(session_id)
current = model.sequence[].program_name current = model.sequence[].program_name
sequences = __sequences__[session_id] sequences = session_registry[session_id, :sequences]
lock(sequences) do lock(sequences) do
for (ix, seq) in pairs(sequences) for (ix, seq) in pairs(sequences)
if seq.program_name == current if seq.program_name == current
...@@ -167,19 +165,7 @@ function deletesequence(session_id) ...@@ -167,19 +165,7 @@ function deletesequence(session_id)
end end
function purgesession(session_id; appname="muscles") function purgesession(session_id; appname="muscles")
model = lock(__backbone_cache__) do purge_appdata(session_id, appname)
if session_id in keys(__backbone_cache__.cache)
#pop!(__valid_sessions__, session_id) # not sure what to do with it
pop!(__backbone_cache__.cache, session_id)
end
end
lock(__sequences__) do
if session_id in keys(__sequences__.cache)
pop!(__sequences__.cache, session_id)
end
end
purge_appdata(session_id, appname)
return model
end end
end end
...@@ -2,7 +2,7 @@ module MuscleApp ...@@ -2,7 +2,7 @@ module MuscleApp
using NyxUI, NyxUI.Storage, NyxUI.GenieExtras using NyxUI, NyxUI.Storage, NyxUI.GenieExtras
using Observables using Observables
using Genie, GenieSession, Stipple, StippleUI using Genie, Stipple, StippleUI
import Stipple: @app, @init, @private, @in, @out, @onchange, @onbutton, @notify import Stipple: @app, @init, @private, @in, @out, @onchange, @onbutton, @notify
include("Backbone.jl") include("Backbone.jl")
...@@ -20,6 +20,8 @@ const maxlength = getconfig("muscle-activity", "maxlength") ...@@ -20,6 +20,8 @@ const maxlength = getconfig("muscle-activity", "maxlength")
const bonito_app = NamedApp(:inherit, Backbone.app) const bonito_app = NamedApp(:inherit, Backbone.app)
Backbone.purgesession(session_id) = purgesession(session_id; appname=bonito_app.name)
@app begin @app begin
@private bonito_session_id = "" @private bonito_session_id = ""
...@@ -95,7 +97,7 @@ const bonito_app = NamedApp(:inherit, Backbone.app) ...@@ -95,7 +97,7 @@ const bonito_app = NamedApp(:inherit, Backbone.app)
end end
@onbutton reset_bonito_session begin @onbutton reset_bonito_session begin
model = purgesession(bonito_session_id; appname=bonito_app.name) purgesession(bonito_session_id)
# reset UI to defaults # reset UI to defaults
#bonito_session_id[!] = "" #bonito_session_id[!] = ""
sequence[!] = MuscleActivity("") sequence[!] = MuscleActivity("")
...@@ -445,8 +447,7 @@ function init_model(muscle_model=nothing) ...@@ -445,8 +447,7 @@ function init_model(muscle_model=nothing)
muscle_model = @init muscle_model = @init
#@show muscle_model #@show muscle_model
end end
genie_session = GenieSession.session() session_id = getsessionid(Backbone.session_registry)
session_id = genie_session.id[1:32]
if hasmodel(session_id) if hasmodel(session_id)
withsequences(session_id) do sequences withsequences(session_id) do sequences
ids = sort!(collect(keys(sequences))) ids = sort!(collect(keys(sequences)))
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment