From 7950acb21b22e7bc6f10c50e1427850de2834b24 Mon Sep 17 00:00:00 2001 From: Ken D'Ambrosio Date: Wed, 13 May 2026 17:46:28 +0000 Subject: Add folder rename and background update trigger to admin UI - Admin edit form: "Folder name" field renames the directory on save; also moves the thumbnail cache subtree to match the new path - Admin edit page: "Run Update" button spawns update.rb in a background thread, streams output into a terminal-style log panel via 1.5s polling; shows Done/Error status when complete Co-Authored-By: Claude Sonnet 4.6 --- app.rb | 67 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) (limited to 'app.rb') diff --git a/app.rb b/app.rb index 5aba1b4..0678c67 100644 --- a/app.rb +++ b/app.rb @@ -15,6 +15,9 @@ CACHE_ROOT = (ENV['CACHE_ROOT'] || '/opt/albumen/cache/thumbs').freeze CONFIG_PATH = (ENV['CONFIG_PATH'] || '/opt/albumen/config.yml').freeze THUMB_SIZE = 300 +UPDATE_JOBS = {} +UPDATE_JOBS_MUTEX = Mutex.new + IMAGE_EXTS = %w[jpg jpeg png gif webp heic heif tiff bmp].freeze VIDEO_EXTS = %w[mp4 mov avi mkv webm m4v ogv].freeze AUDIO_EXTS = %w[mp3 flac ogg wav m4a aac].freeze @@ -409,6 +412,28 @@ post '/admin/edit/*' do require_admin! rel = params[:splat].first.chomp('/') dir = resolve_dir(rel) + + unless rel.empty? + new_name = params['folder_name'].to_s.strip + new_name = '' if new_name.include?('/') || new_name.include?("\x00") || + new_name == '.' || new_name == '..' + old_name = File.basename(dir) + if !new_name.empty? && new_name != old_name + parent_dir = File.dirname(dir) + new_dir = File.join(parent_dir, new_name) + unless File.exist?(new_dir) + parent_rel = rel.include?('/') ? rel.split('/')[0..-2].join('/') : '' + new_rel = parent_rel.empty? ? new_name : "#{parent_rel}/#{new_name}" + old_cache = File.join(CACHE_ROOT, rel) + new_cache = File.join(CACHE_ROOT, new_rel) + FileUtils.mv(old_cache, new_cache) if File.directory?(old_cache) + FileUtils.mv(dir, new_dir) + save_edits(new_rel, new_dir) + redirect "/admin/edit/#{new_rel}" + end + end + end + save_edits(rel, dir) redirect "/admin/edit/#{rel}" end @@ -435,6 +460,48 @@ def save_edits(rel, dir) atomic_write(File.join(dir, 'album.json'), JSON.pretty_generate(data)) end +# ── Background update ───────────────────────────────────────────────────────── + +post '/admin/update' do + require_admin! + rel = params[:rel].to_s.chomp('/') + job_id = SecureRandom.hex(8) + script = File.join(__dir__, 'scripts', 'update.rb') + cmd = rel.empty? ? ['ruby', script] : ['ruby', script, rel] + + UPDATE_JOBS_MUTEX.synchronize do + UPDATE_JOBS[job_id] = { status: :running, lines: [] } + end + + Thread.new do + begin + IO.popen(cmd, err: [:child, :out]) do |io| + io.each_line do |line| + UPDATE_JOBS_MUTEX.synchronize { UPDATE_JOBS[job_id][:lines] << line.chomp } + end + end + code = $?.exitstatus + UPDATE_JOBS_MUTEX.synchronize { UPDATE_JOBS[job_id][:status] = code == 0 ? :done : :error } + rescue => e + UPDATE_JOBS_MUTEX.synchronize do + UPDATE_JOBS[job_id][:status] = :error + UPDATE_JOBS[job_id][:lines] << "Error: #{e.message}" + end + end + end + + content_type :json + { job_id: job_id }.to_json +end + +get '/admin/update/:id' do + require_admin! + job = UPDATE_JOBS_MUTEX.synchronize { UPDATE_JOBS[params[:id]]&.dup } + halt 404 unless job + content_type :json + { status: job[:status], lines: job[:lines] }.to_json +end + # ── Thumbnail generation ─────────────────────────────────────────────────────── def generate_thumb(source, dest, ext) -- cgit v1.2.3