diff --git a/Config.toml b/Config.toml new file mode 100644 index 0000000000000000000000000000000000000000..c83f24dad1a0dad3919ac3316ecc6ff37366303b --- /dev/null +++ b/Config.toml @@ -0,0 +1,5 @@ +[per-session] +maxfiles = 20 + +[muscle-activity] +maxlength = 1001 diff --git a/Project.toml b/Project.toml index 3c346dd2cdfd26f11f88e55615bde911084c7466..92409a387837513b5781a01d55322e3bee3853d1 100644 --- a/Project.toml +++ b/Project.toml @@ -21,3 +21,4 @@ Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" Stipple = "4acbeb90-81a0-11ea-1966-bdaff8155998" StippleUI = "a3c5d34a-b254-4859-a8fa-b86abb7e84a3" StructTypes = "856f2bd8-1eba-4b0a-8007-ebc267875bd4" +TOML = "fa267f1f-6049-4f14-aa54-33bafae1ed76" diff --git a/front/Manifest.toml b/front/Manifest.toml index 38f7e3b2bcde9368c0975ebfd0d93075e81c3822..3de4c3baa3ba5bb3930c2adde55d3ba205fee0cf 100644 --- a/front/Manifest.toml +++ b/front/Manifest.toml @@ -1,8 +1,8 @@ # This file is machine-generated - editing it directly is not advised -julia_version = "1.10.4" +julia_version = "1.10.5" manifest_format = "2.0" -project_hash = "b5504022439d7b9ed52b08f10117d0c48e04d5a8" +project_hash = "7b4a57cbc9fc956109742c120d30a1b52ed917fd" [[deps.ArgParse]] deps = ["Logging", "TextWrap"] @@ -726,7 +726,7 @@ version = "1.2.13+1" [[deps.libblastrampoline_jll]] deps = ["Artifacts", "Libdl"] uuid = "8e850b90-86db-534c-a0d3-1478176c7d93" -version = "5.8.0+1" +version = "5.11.0+0" [[deps.nghttp2_jll]] deps = ["Artifacts", "Libdl"] diff --git a/src/MuscleActivities.jl b/src/MuscleActivities.jl index 6d09d77fcd829c5020647875a600506c222828af..86b74a322943c47270c03d17afa896e522d4ce12 100644 --- a/src/MuscleActivities.jl +++ b/src/MuscleActivities.jl @@ -6,6 +6,7 @@ using Random using JSON3 using StructTypes using OrderedCollections: OrderedDict +using ..Storage export MuscleActivity, HalfSegmentActivity, resample! @@ -194,6 +195,14 @@ function contiguous(signal, i) end function to_json_file(filepath::String, object) + session_bucket = getbucket(child_resource=filepath) + @assert isdir(session_bucket) + records = length(readdir(session_bucket)) + maxfiles = getconfig("per-session", "maxfiles") + if !(isnothing(maxfiles) || records <= maxfiles) + clear_oldest(session_bucket) + end + # open(filepath, "w") do f write_json(f, object) end diff --git a/src/NyxUI.jl b/src/NyxUI.jl index 47e418b11690c8e8c37c31ff35810ec988cd84a1..ed8342b1f50bf4e256eead96c821cfe10936f972 100644 --- a/src/NyxUI.jl +++ b/src/NyxUI.jl @@ -1,9 +1,11 @@ module NyxUI +include("Storage.jl") include("MuscleActivities.jl") include("BonitoServer.jl") include("GenieExtras.jl") +using .Storage using .MuscleActivities using .BonitoServer using .GenieExtras diff --git a/src/Storage.jl b/src/Storage.jl new file mode 100644 index 0000000000000000000000000000000000000000..24c879e4f79c269a1af82ceacf2e7e70b57f35aa --- /dev/null +++ b/src/Storage.jl @@ -0,0 +1,64 @@ +module Storage + +using Dates +using TOML + +export getconfig, getbucket, clear_oldest + +const __storage_location__ = get(ENV, "STORAGE", + joinpath(dirname(Base.current_project()), "storage")) + +const __config_file__ = get(ENV, "CONFIGFILE", + joinpath(dirname(Base.current_project()), "Config.toml")) + +const __config__ = Ref{Union{Nothing, Dict{String, Any}}}(nothing) + +function getconfig() + if isnothing(__config__[]) + __config__[] = TOML.parsefile(__config_file__) + end + return __config__[] +end + +function getconfig(key, morekeys...; default=nothing) + config = getconfig() + i = 0 + while key in keys(config) + config = config[key] + i += 1 + i <= length(morekeys) || break + key = morekeys[i] + end + config isa Dict ? default : config +end + +getbucket(session_id) = joinpath(__storage_location__, "exports", session_id) + +function getbucket(session_id, mode) + session_bucket = getbucket(session_id) + if mode === :read + session_bucket + elseif mode === :write + date_time = Dates.format(now(), "yyyymmdd_HHMMSS") + newdir = joinpath(session_bucket, date_time) + mkpath(newdir) + newdir + else + throw("`mode` not in [:read, :write]: mode=$mode") + end +end + +function getbucket(; child_resource) + parts = splitpath(child_resource) + session_id = parts[findfirst(==("exports"), parts) + 1] + return getbucket(session_id) +end + +function clear_oldest(session_bucket) + oldest = first(sort(readdir(session_bucket; join=false))) + oldest = joinpath(session_bucket, oldest) + @info "Deleting directory" dir=oldest + rm(oldest; recursive=true) +end + +end diff --git a/src/apps/muscles/Backbone.jl b/src/apps/muscles/Backbone.jl index 85940068810662c2323373bad17318f678f4dfba..21868e65dd545970b26baa197231faf9a1808589 100644 --- a/src/apps/muscles/Backbone.jl +++ b/src/apps/muscles/Backbone.jl @@ -2,8 +2,8 @@ module Backbone using NyxWidgets.Base: Cache using NyxUI.MuscleActivities +using NyxUI.Storage using Bonito -using Dates include("MuscleWidgets.jl") using .MuscleWidgets @@ -132,15 +132,10 @@ end ## persistent storage -const __storage_location__ = get(ENV, "STORAGE", - joinpath(dirname(Base.current_project()), "storage")) - function exportsequence(session_id) sequence = getsequence(session_id) isempty(sequence.program_name) && return "" - date_time = Dates.format(now(), "yyyymmdd_HHMMSS") - dir = joinpath(__storage_location__, "exports", session_id, date_time) - mkpath(dir) + dir = getbucket(session_id, :write) filepath = joinpath(dir, sequence.program_name * ".json") MuscleActivities.to_json_file(filepath, sequence) return filepath diff --git a/src/apps/muscles/app.jl b/src/apps/muscles/app.jl index 4a70c66b29d94adcab2afefc91d8c10a567e8e58..619b12b911ae9b4b8d7e7b64850f926054e35ff4 100644 --- a/src/apps/muscles/app.jl +++ b/src/apps/muscles/app.jl @@ -1,6 +1,6 @@ module MuscleApp -using NyxUI +using NyxUI, NyxUI.Storage using Genie, GenieSession, Stipple, StippleUI import Stipple: @app, @init, @private, @in, @out, @onchange, @onbutton, @notify @@ -13,6 +13,8 @@ const default_colormap = Backbone.MuscleWidgets.__default_colormap__ const colormaps = Backbone.MuscleWidgets.__colormaps__ +const maxlength = getconfig("muscle-activity", "maxlength") + const bonito_app = NamedApp(:inherit, Backbone.app) @app begin @@ -88,6 +90,10 @@ const bonito_app = NamedApp(:inherit, Backbone.app) @notify "time series length must be integer" :info series_length = round(series_length) valid = false + elseif !isnothing(maxlength) && maxlength < series_length + @notify "time series is too long" :info + series_length = length(seq.times) + valid = false end # if valid && !(start_time == seq.times[1] && time_interval == step(seq.times) &&