Skip to content

Feature/asynchron ftp upload#1463

Open
flacoonb wants to merge 4 commits intoPhotoboothProject:devfrom
flacoonb:feature/asynchronFtpUpload
Open

Feature/asynchron ftp upload#1463
flacoonb wants to merge 4 commits intoPhotoboothProject:devfrom
flacoonb:feature/asynchronFtpUpload

Conversation

@flacoonb
Copy link
Contributor

Prerequisites checklist

What is the purpose of this pull request? (put an "x" next to an item)

  • Documentation update
  • Bug fix
  • New feature
  • Other, please explain:

What changes did you make? (Give an overview)

Asynchronous FTP/SFTP upload queue

Previously, photos were uploaded to the FTP/SFTP server synchronously during image processing, blocking the UI until the transfer completed. This PR replaces that with a persistent, SQLite-backed upload queue and a background worker:

  • New UploadQueueService manages jobs (pending → in_progress → completed / failed, up to 5 retries).
  • Each photo gets a random 32-hex-character remote filename so original filenames are never exposed on the server.
  • New Symfony Console command photobooth:upload:worker (--once or continuous mode, configurable poll interval). A systemd unit is provided in resources/config/photobooth-upload-worker.service.
  • New api/ftpListFolders.php allows browsing the remote directory tree from the admin panel before saving the configuration.
  • New EncryptionService encrypts the FTP/SFTP password at rest using libsodium secretbox; the key is stored in var/run/.
  • Admin panel shows pending and failed job counts in the debug panel.

Bug fixes

  • RemoteStorageService::getWebpageUri() now correctly appends baseFolder to the website URL. Previously the QR code pointed to the bare domain, ignoring the configured subfolder.
  • createWebpage() removes legacy images/.htaccess / thumbs/.htaccess files (which caused HTTP 403 on hosts without AllowOverride Options) and replaces them with index.php redirect guards.
  • api/deletePhoto.php now resolves the random remote filename via UploadQueueService::getRemoteFilename() before deleting on FTP, instead of using the local filename which no longer matches.
  • ZIP download in the remote gallery was producing corrupt archives because ZipArchive::CREATE fails silently when tempnam() pre-creates the file; fixed with ZipArchive::CREATE | ZipArchive::OVERWRITE.

Remote gallery improvements (resources/template/index.php)

  • Single-image view: replaced PHP-side file_exists() polling with a JS onload/onerror spinner. While the upload is in progress the visitor sees "Your photo is being uploaded…"; on onerror the page reloads every 3 s automatically.
  • Gallery mode: fixed an else that rendered the gallery even when gallery_enabled was false.
  • Images sorted newest-first via usort + filemtime.
  • Share button replaced: uses the Web Share API with native file sharing where supported, falls back to a wa.me deep-link. Icon changed from fa-brands fa-whatsapp to fa-solid fa-share-nodes.
  • Lightbox navigation: prev/next arrow buttons with wrap-around and keyboard support (ArrowLeft / ArrowRight / Escape).

Admin UI / config cleanup

  • Removed deprecated folder and urlTemplate fields from admin UI and en.json; the QR URL is now built automatically from website + baseFolder.
  • Added folder-picker browser, queue status display, and improved test-connection feedback.

Documentation

  • docs/faq/index.md: rewrote the FTP/SFTP section to reflect async uploads, removed obsolete fields, documented worker setup, online gallery features, and the delete-on-local-delete option.

CI

  • build-faq.yml: fixed hardcoded git push origin dev that caused the workflow to fail when triggered via workflow_dispatch from a non-dev branch; replaced with git push origin HEAD:${{ github.ref_name }}.

Is there anything you'd like reviewers to focus on?

  • Worker lifecycle: the UploadWorkerCommand reloads the config on every job so admin panel changes are picked up without restarting — please verify this is a safe pattern.
  • EncryptionService: existing plain-text passwords in saved config are transparently encrypted on first save; please confirm the migration path is acceptable.
  • Remote gallery template: the JS polling / retry logic and the Web Share API fallback behaviour on various browsers.

AI used to create this Pull Request?

This Pull Request was developed with the assistance of Claude (Anthropic). The AI contributed to architecture decisions (async queue design, libsodium encryption, systemd service), code implementation across all changed files, bug analysis and fixes (403 .htaccess issue, corrupt ZIP, wrong remote filename on delete), and the remote gallery JS rewrite. All generated code was reviewed, tested, and adjusted iteratively based on real-world test results on a live Photobooth installation.

Photobooth and others added 4 commits February 19, 2026 09:40
… remote gallery

This commit introduces a fully asynchronous upload pipeline for FTP/SFTP
remote storage, decoupling image upload from the photo capture workflow so
the Photobooth UI is never blocked while files are transferred.

Core architecture
-----------------
- New SQLite-backed UploadQueueService (src/Service/UploadQueueService.php)
  manages a persistent job queue (pending → in_progress → completed/failed).
  Each job stores a random 32-hex-character remote filename so local and
  remote filenames are never the same, preventing enumeration.
- New EncryptionService (src/Service/EncryptionService.php) encrypts the FTP
  password at rest using libsodium secretbox; key is stored in var/run/.
- New Symfony Console command UploadWorkerCommand
  (src/Command/UploadWorkerCommand.php) runs as a long-lived background
  worker (or once with --once). Picks up jobs, calls RemoteStorageService,
  retries up to 5 times on failure, and reloads config on every run so admin
  panel changes are picked up without restarting.
- New api/ftpListFolders.php lets the admin panel browse remote directories
  before saving configuration.
- resources/config/photobooth-upload-worker.service: systemd unit for running
  the worker as www-data.

API / backend changes
---------------------
- api/applyEffects.php: instead of uploading directly, enqueues a job via
  UploadQueueService and triggers the worker process in the background.
- api/deletePhoto.php: resolves the remote random filename via
  UploadQueueService::getRemoteFilename() before deleting on FTP, so the
  correct remote file is removed.
- api/qrcode.php, api/print.php: QR URLs now use UploadQueueService to look
  up the remote filename when FTP + useForQr is enabled.
- api/admin.php: exposes upload queue status (pending/failed counts) for the
  debug panel.
- api/serverInfo.php: includes FTP queue stats in server-info response.
- RemoteStorageService::getWebpageUri() correctly appends baseFolder to the
  website URL.
- RemoteStorageService::createWebpage() removes legacy .htaccess files
  (caused 403 on hosts without AllowOverride Options) and instead uploads
  index.php redirect guards to images/ and thumbs/.
- ConfigurationService: decrypts FTP password on load via EncryptionService.
- FtpConfiguration: password field marked as sensitive.
- src/Console/Application.php: registers UploadWorkerCommand.

Admin UI
--------
- lib/configsetup.inc.php: removed deprecated folder/urlTemplate fields;
  added folder browser (ftpListFolders API), queue status display, and
  "Test Connection" improvements.
- assets/js/admin/buttons.js: folder-browser modal, queue status polling,
  FTP test-connection wiring.
- resources/lang/en.json: removed ftp:folder / ftp:urlTemplate keys; updated
  manual:ftp:website description.

Remote gallery (resources/template/index.php)
---------------------------------------------
- Single-image view: image is rendered immediately with a JS onload/onerror
  spinner; onerror auto-reloads every 3 s while upload is in progress.
  Removed the PHP-side file_exists() upload-wait loop.
- Gallery mode: fixed guard so gallery is only rendered when
  gallery_enabled=true (was falling through to else).
- Images sorted newest-first via usort + filemtime.
- ZIP download: fixed corrupt archive by using ZipArchive::CREATE |
  ZipArchive::OVERWRITE (tempnam creates an existing file).
- Share button: uses Web Share API with native file sharing where supported;
  falls back to wa.me deep-link. Icon changed from fa-brands fa-whatsapp to
  fa-solid fa-share-nodes.
- Lightbox navigation: prev/next arrow buttons with wrap-around and keyboard
  support (ArrowLeft / ArrowRight / Escape).

Code style
----------
- PHP CS Fixer: expanded single-line closures in Configuration/Section/*
  to multi-line form for consistency with project style rules.
- Remove obsolete 'folder' and 'urlTemplate' config fields
- Document SFTP support and type selector
- Explain async upload queue (SQLite-backed, background worker)
- Add systemd service setup and manual worker commands
- Describe random remote filenames, directory listing protection
- Document automatic QR URL building (no template needed)
- Describe online gallery features: spinner, share button, lightbox nav
- Add 'Delete on local delete' subsection
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant