Select Git revision
files.jl 9.12 KiB
struct WorkingDirectory
controller
root::String # could be hidden (not stored)
path::Validator{String}
content::Observable{Vector{String}}
end
WorkingDirectory(controller, root::String, path::String="") = WorkingDirectory(controller, root, Validator(path), Observable(String[]))
function workingdir(controller, root::String, path::String=""; secure::Bool=true)
if secure
root = realpath(root)
end
isdir(root) || throw(ArgumentError("cannot find root directory"))
if isempty(path)
path = "."
elseif isabspath(path)
path = relpath(path, root)
startswith(path, "..") && throw(ArgumentError("path not below root: $path"))
end
wd = WorkingDirectory(controller, root, path)
on(wd.path, Val(1)) do newpath
try
if isempty(newpath)
newpath = "."
elseif isabspath(newpath)
fullpath = newpath
newpath = relpath(newpath, root)
end
fullpath = joinpath(root, newpath)
if secure
fullpath = realpath(fullpath)
end
if startswith(relpath(fullpath, root), "..")
@logmsg SecurityAlert "Path outside of chroot" path=newpath client=identifyclient(controller)
throw(Exception)
elseif !isdir(fullpath)
@debug "Path is not a directory" path=newpath
tryopenfile(controller, fullpath; reload=true)
throw(Exception)
end
return Validate(newpath)
catch
return Invalidate()
end
end
on(wd.path.model) do dir
@assert !isempty(dir)
content = [entry for entry in readdir(joinpath(root, dir))
if entry[1] != '.']
if dir != "."
pushfirst!(content, "..")
end
wd.content[] = content
end
return wd
end
function getworkingdir(controller)
hub = gethub(controller)
wd = nothing
try
wd = hub[:workingdirectory]
catch
hub[:workingdirectory] = wd = workingdir(hub, pwd())
end
return wd
end
getpath(wd::WorkingDirectory) = getview!(wd.path)
getpath(controller) = getpath(getworkingdir(controller))
function cwd(wd, subdir::String="")
path = getpath(wd)
if !isempty(subdir)
path[] = normpath(joinpath(path[], subdir))
end
return path[]
end
getwdcontent(wd::WorkingDirectory) = wd.content
getwdcontent(controller) = getwdcontent(getworkingdir(controller))
# open/save
function tryopenfile(controller, path; reload::Bool=false)
hub = gethub(controller)
refresh_view = reload
if path isa Ref
input, path = path, path[]
if haskey(hub, :input)
@assert hub[:input] === input
else
hub[:input] = input
end
isnothing(path) && return
end
if refresh_view
turn_load_animation_on(controller)
end
#
records = loadfile(path)
if isempty(records)
@info "Cannot load file" file=path
return
elseif haskey(hub, :input)
hub[:input][] = isabspath(path) ? relpath(path) : path
end
# tag_lut
tag_lut = ObservableTag[]
if hasproperty(records, :tagcolors)
existingtags = records.tags
existingcolors = records.tagcolors
for (tag, color) in zip(existingtags, existingcolors)
original = TagModel(tag, color, true)
push!(tag_lut, ObservableTag(original; color=color, active=true))
end
elseif hasproperty(records, :tags)
default_convention = Dict(
:small_motion => :grey50,
:stop_large => :green,
:run_large => :black,
:cast_large => :red,
:hunch_large => :blue,
:back_large => :cyan,
:roll_large => :yellow,
)
fallback_color = theme[:LarvaPlot][:fallback_color]
existingtags = records.tags
for tag in existingtags
active = tag in keys(default_convention)
color = active ? default_convention[tag] : fallback_color
original = TagModel(tag)
push!(tag_lut, ObservableTag(original; color=color, active=active))
end
end
#
hub[:output] = records.output
getpath(hub)[] = dirname(path)
newcontroller(hub, :larva, LarvaController,
records.tracks, tag_lut, records.timestamps)
if refresh_view
globalrefresh(hub[:frontend], hub)
end
end
function loadfile(path)
file = Loaders.load(path)
data = isempty(file.run) ? file.timeseries : file.run
# tracks
times = PlanarLarvae.times(data)
if data isa PlanarLarvae.LarvaBase.Larvae
tracks = [LarvaModel(id, ts, times) for (id, ts) in pairs(data)]
else
tracks = [LarvaModel(track, times) for track in values(data)]
end
# dataset
metadata = getmetadata(file)
if isempty(metadata)
metadata = extract_metadata_from_filepath(path)
run = get!(metadata, :date_time, "NA")
end
output = Dataset([Run(run; metadata...)])
#
labels = getlabels(file)
existingtags = labels[:names]
if haskey(labels, :colors)
tagcolors = labels[:colors]
return (tracks=tracks, timestamps=times, tags=existingtags, tagcolors=tagcolors, output=output)
else
return (tracks=tracks, timestamps=times, tags=existingtags, output=output)
end
end
getoutput(controller) = gethub(controller)[:output]
interpolate(s="yyyymmdd_HHMMSS") = Dates.format(Dates.now(), s)
function savetofile(controller, file; datafile=nothing)
if '{' in file
prefix, remainder = split(file, '{'; limit=2)
if '}' in remainder
parts = split(remainder, '}')
if length(parts) == 2
infix, suffix = parts
else
infix, suffix = join(parts[1:end-1], '}'), parts[end]
end
file = prefix * interpolate(infix) * suffix
end
end
filepath = joinpath(cwd(controller), file)
dataset = getoutput(controller)
@assert length(dataset) == 1
run = first(values(dataset))
lut = gettags(controller)[]
larvafilter = getlarvafilter(controller)
empty = true
for larvainfo in larvafilter.entries
if larvainfo.included[]
id = larvainfo.id
larva = getlarva(controller, id)
timeseries = [t for (t, _) in larva.fullstates]
track = Track(id, timeseries)
labels = Union{String, Vector{String}}[]
for timestep in larva.alignedsteps
tags = getusertags(larva, timestep; lut=lut)
tags = [tag.name[] for tag in tags if tag.active[]]
if isempty(tags)
tags = String[] # convert Any to String
end
push!(labels, length(tags) == 1 ? tags[1] : tags)
end
track[:labels] = labels
run[id] = track
empty = false
end
end
metadata = run.attributes[:metadata]
mdt = gethub(controller)[:metadatatable][]
for item in mdt.entries
name = item.name.model[]
value = item.value.model[]
if !(isempty(name) || isempty(value))
metadata[Symbol(name)] = value
end
end
if empty
@info "Not any larva included"
else
@info "Saving to file" file=filepath
dataset = encodelabels(dataset)
labels = dataset.attributes[:labels]
if labels isa Vector
labels = Dict{Symbol, Any}(:names => labels)
dataset.attributes[:labels] = labels
end
colors = [lookup(lut, label, true).color[] for label in labels[:names]]
labels[:colors] = colors
if isnothing(datafile)
datafile = getinputfile(controller)[]
if isempty(datafile)
datafile = gethub(controller)[:input][]
end
end
datafilepath = isfile(datafile) ? datafile : joinpath(cwd(controller), datafile)
push_dependency!(dataset, datafilepath)
PlanarLarvae.Datasets.to_json_file(filepath, dataset)
end
end
function getinputfile(controller)
hub = gethub(controller)
inputfile = nothing
try
inputfile = hub[:inputfile]
catch
hub[:inputfile] = inputfile = Observable{Union{Nothing, String}}("")
on(inputfile) do f
if isnothing(f)
inputfile.val = ""
else
tryopenfile(controller, f; reload=true)
end
end
end
return inputfile
end
function getoutputfile(controller)
hub = gethub(controller)
outputfile = nothing
try
outputfile = hub[:outputfile]
catch
hub[:outputfile] = outputfile = Observable{Union{Nothing, String}}("{yyyymmdd_HHMMSS}.labels")
on(outputfile) do file
if isnothing(file)
outputfile.val = file = "{yyyymmdd_HHMMSS}.labels"
else
savetofile(hub, file)
end
end
end
return outputfile
end