From c7d503cf99f9e2f13f6b70d27d6b39ebd35fb7c8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois=20Laurent?= <francois.laurent@posteo.net>
Date: Fri, 11 Apr 2025 18:53:49 +0200
Subject: [PATCH] feat: token expiry with automatic cleanup

---
 scripts/install.sh  |  5 ++---
 src/REST/Model.jl   | 28 +++++++++++++++++++++++++++-
 src/REST/Server.jl  | 11 ++++++++++-
 test/rest_client.sh |  4 +++-
 4 files changed, 42 insertions(+), 6 deletions(-)

diff --git a/scripts/install.sh b/scripts/install.sh
index 30f8023..74459a7 100755
--- a/scripts/install.sh
+++ b/scripts/install.sh
@@ -1,5 +1,7 @@
 #!/usr/bin/env bash
 
+set -e
+
 for flag in "$@"; do
   if [ "$flag" = "-h" -o "$flag" = "--help" ]; then
     echo "Command-line installer for LarvaTagger"
@@ -278,9 +280,6 @@ EOF
 }
 
 if [ -n "$WITH_BACKEND" ]; then
-  if [ "`uname`" = "Darwin" ]; then
-    echo "WARNING: the default tagging backend is not supported by macOS"
-  fi
   if ! command -v python$PYTHON_VERSION &>/dev/null; then
     if command -v pyenv &>/dev/null; then
       [ `pyenv versions | grep $PYTHON_VERSION` ] || pyenv install $PYTHON_VERSION
diff --git a/src/REST/Model.jl b/src/REST/Model.jl
index d712235..1450f65 100644
--- a/src/REST/Model.jl
+++ b/src/REST/Model.jl
@@ -14,13 +14,15 @@ struct LTBackend
     root
     tokens
     lock
+    token_expiry
 end
 
 function LTBackend()
     root = Ref{AbstractString}("")
     tokens = Dict{String, Dict{String, Dict{String, Float64}}}()
     lock = ReentrantLock()
-    LTBackend(root, tokens, lock)
+    token_expiry = Ref{Union{Nothing, Real}}(nothing)
+    LTBackend(root, tokens, lock, token_expiry)
 end
 
 Base.lock(f::Function, backend::LTBackend) = lock(f, backend.lock)
@@ -66,6 +68,7 @@ end
 
 function gettoken(lt_backend, backend_dir, model_instance)
     tagger = gettagger(lt_backend, backend_dir, model_instance)
+    resetdata(lt_backend) # perform server-wide maintenance
     return tagger.sandbox
 end
 
@@ -88,6 +91,29 @@ function resetdata(lt_backend, backend_dir, model_instance, token, datadir=nothi
     nothing
 end
 
+function resetdata(lt_backend, min_age)
+    isnothing(min_age) && return
+    @assert min_age isa Real
+    lock(lt_backend) do
+        for (backend_dir, instances) in pairs(lt_backend.tokens)
+            for (model_instance, tokens) in pairs(instances)
+                for (token, created) in pairs(copy(tokens))
+                    age = time() - created
+                    if min_age <= age
+                        @info "resetdata" backend_dir model_instance token age
+                        tagger = gettagger(lt_backend, backend_dir, model_instance, token)
+                        Taggers.resetdata(tagger)
+                        pop!(tokens, token)
+                    end
+                end
+            end
+        end
+    end
+    nothing
+end
+
+resetdata(lt_backend) = resetdata(lt_backend, lt_backend.token_expiry[])
+
 function listfiles(lt_backend, backend_dir, model_instance, token, data_dir)
     tagger = gettagger(lt_backend, backend_dir, model_instance, token)
     dir = Taggers.datadir(tagger, data_dir)
diff --git a/src/REST/Server.jl b/src/REST/Server.jl
index 4bd2bf5..bda8459 100644
--- a/src/REST/Server.jl
+++ b/src/REST/Server.jl
@@ -17,8 +17,9 @@ end
 # state
 const lt_backend = LTBackend()
 
-function run_backend(root::AbstractString; kwargs...)
+function run_backend(root::AbstractString, token_expiry=nothing; kwargs...)
     lt_backend.root[] = root
+    lt_backend.token_expiry[] = token_expiry
     run_backend(; kwargs...)
 end
 
@@ -49,6 +50,14 @@ end
 end
 
 
+@get "/reset-data/{min_age}" function (
+        request,
+        min_age::Int,
+    )
+    resetdata(lt_backend, min_age)
+end
+
+
 @get "/reset-data/{backend_dir}/{model_instance}/{token}" function(
         request,
         backend_dir::String,
diff --git a/test/rest_client.sh b/test/rest_client.sh
index 9763454..1213d5a 100755
--- a/test/rest_client.sh
+++ b/test/rest_client.sh
@@ -24,7 +24,7 @@ julia "+$JULIA_VERSION" --project="${larvatagger_jl_project_root}" -q -e "using
 # run and background the backend server
 JULIA_PROJECT="${larvatagger_project_root}/TaggingBackends" \
   julia "+$JULIA_VERSION" --project="${larvatagger_jl_project_root}" -i \
-  -e "using LarvaTagger.REST.Server; run_backend(\"${larvatagger_project_root}\"; port=${lt_backend_port})" &
+  -e "using LarvaTagger.REST.Server; run_backend(\"${larvatagger_project_root}\", 300; port=${lt_backend_port})" &
 lt_backend_pid=$!
 
 # run the frontend server
@@ -36,6 +36,8 @@ JULIA="julia +$JULIA_VERSION" ${larvatagger_jl_project_root}/scripts/larvatagger
 #   expected: a predicted.label is generated and the GUI reloads
 # * load a second tracking data file (binary if first was ascii or vice versa), select another model instance, click again on "Autotag";
 #   expected: a new token was issued + similar outcome as previous step, with tracking data file and tagging model properly identified in the predicted.label file
+# * wait for 5 min and click again on "Autotag";
+#   expected: the data directories corresponding to the previous tokens are empty
 
 kill $lt_backend_pid
 wait $lt_backend_pid
-- 
GitLab