From cfdb3603cf37526a6ccd49db5edc52a0dd1c7a7f Mon Sep 17 00:00:00 2001 From: Adrian Salceanu Date: Wed, 16 Oct 2024 20:02:54 +0200 Subject: [PATCH] Bug fixes and improvements in file uploader component --- Project.toml | 2 +- demos/uploader_2/app.jl | 42 +++++++++++++++++ demos/uploader_2/app.jl.html | 6 +++ src/Uploaders.jl | 89 ++++++++++++++++++++++++++++++++++-- 4 files changed, 134 insertions(+), 5 deletions(-) create mode 100644 demos/uploader_2/app.jl create mode 100644 demos/uploader_2/app.jl.html diff --git a/Project.toml b/Project.toml index 5b81acce..a51fe935 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "StippleUI" uuid = "a3c5d34a-b254-4859-a8fa-b86abb7e84a3" authors = ["Adrian Salceanu "] -version = "0.23.4" +version = "0.23.5" [deps] Colors = "5ae59095-9a9b-59fe-a467-6f913c188581" diff --git a/demos/uploader_2/app.jl b/demos/uploader_2/app.jl new file mode 100644 index 00000000..e9eb8d4c --- /dev/null +++ b/demos/uploader_2/app.jl @@ -0,0 +1,42 @@ +module App + +using GenieFramework +@genietools + +const UPLOADS_FOLDER = "public/uploads" +Genie.Watch.unwatch(UPLOADS_FOLDER) + +@app begin + @event rejected begin + @info "rejected" + @notify("File rejected") + end + + @event uploaded begin + @info "File uploaded" + end + + @event finished begin + @info "Upload finished" + end + + @event failed begin + @info "Upload failed" + @notify("File upload failed. Please try again.") + end + + @onchange fileuploads begin + uploaded_file = UploadedFile(fileuploads) + try + cp(uploaded_file, UPLOADS_FOLDER; force = true) + catch e + @error "Error processing file: $e" + @notify("Error processing file: $(uploaded_file.name)") + # rethrow(e) + end + end +end + +@page("/", "app.jl.html") + +end diff --git a/demos/uploader_2/app.jl.html b/demos/uploader_2/app.jl.html new file mode 100644 index 00000000..9299cbb0 --- /dev/null +++ b/demos/uploader_2/app.jl.html @@ -0,0 +1,6 @@ + \ No newline at end of file diff --git a/src/Uploaders.jl b/src/Uploaders.jl index 47551b69..87e7c4f6 100644 --- a/src/Uploaders.jl +++ b/src/Uploaders.jl @@ -1,9 +1,10 @@ module Uploaders using Genie, Stipple, StippleUI, StippleUI.API +using Dates import Genie.Renderer.Html: HTMLString, normal_element, register_normal_element -export uploader +export uploader, UploadedFile register_normal_element("q__uploader", context = @__MODULE__) @@ -24,15 +25,18 @@ function __init__() route("/____/upload/:channel", method = POST) do for f in Genie.Requests.filespayload() - uf = UploadedFile(tempname(), f[2].name, params(:channel)) + tmpdir = mktempdir(; cleanup = true) + tmpfile = mktemp(tmpdir; cleanup = true) try - write(uf.tmppath, f[2].data) + write(tmpfile[2], f[2].data) + close(tmpfile[2]) catch e @error "Error saving uploaded file: $e" rethrow(e) end + uf = UploadedFile(tmpfile[1], f[2].name, params(:channel)) push_uploaded_files(uf) for h in upload_handlers @@ -62,7 +66,7 @@ function push_uploaded_files(uf::UploadedFile) Stipple._push!(:fileuploads => filedict, uf.channel) - # # this won't be broadcasted back to the server so we need to do it manually + # this won't be broadcasted back to the server so we need to do it manually Stipple.WEB_TRANSPORT[].broadcast( Genie.WebChannels.tagbase64encode(""">eval:Genie.WebChannels.sendMessageTo( window.CHANNEL, @@ -153,4 +157,81 @@ function uploader(args...; q__uploader(args...; kw([kws..., kwargs...])...) end + +# API utilities to work with uploaded files + +""" + UploadedFile(d::T) where T <: AbstractDict + +Create an UploadedFile object from a dictionary + +Arguments +--------- + * `d::T` - Dictionary with keys `path`, `name`, and `channel` +""" +function UploadedFile(d::T) where T <: AbstractDict + UploadedFile(d["path"], d["name"], d["channel"]) +end + + +""" + cp(uf::UploadedFile, dest::String; filename::Union{String,Nothing} = nothing, create_dist::Bool = true, force::Bool = false) + +Copy an uploaded file to a destination folder + +Arguments +--------- + * `uf::UploadedFile` - Uploaded file object + * `dest::String` - Destination folder + * `filename::Union{String,Nothing}` - New filename (optional, defaults to the original filename) + * `create_dist::Bool` - Create destination folder if it doesn't exist + * `force::Bool` - Overwrite existing files +""" +function Base.cp(uf::UploadedFile, dest::String; filename::Union{String,Nothing} = nothing, create_dist::Bool = true, force::Bool = false) + create_dist && (isdir(dest) || mkpath(dest)) + isnothing(filename) && (filename = uf.name) + + isfile(uf.tmppath) || error("File not found: $(uf.tmppath)") + + file_path_dest = joinpath(dest, filename) + Base.Filesystem.cp(uf.tmppath, file_path_dest; force = force) + + return file_path_dest +end + + +""" + mv(uf::UploadedFile, dest::String; filename::Union{String,Nothing} = nothing, create_dist::Bool = true, force::Bool = false) + +Move an uploaded file to a destination folder + +Arguments +--------- + * `uf::UploadedFile` - Uploaded file object + * `dest::String` - Destination folder + * `filename::Union{String,Nothing}` - New filename (optional, defaults to the original filename) + * `create_dist::Bool` - Create destination folder if it doesn't exist + * `force::Bool` - Overwrite existing files +""" +function Base.mv(uf::UploadedFile, dest::String; filename::Union{String,Nothing} = nothing, create_dist::Bool = true, force::Bool = false) + result = cp(uf, dest; filename = filename, create_dist = create_dist, force = force) + rm(uf.tmppath) + + return result +end + + +""" + rm(uf::UploadedFile) + +Remove the temporary file associated with an UploadedFile object + +Arguments +--------- + * `uf::UploadedFile` - Uploaded file object +""" +function Base.rm(uf::UploadedFile) + isfile(uf.tmppath) && rm(uf.tmppath) +end + end