TL;DR
- Implements #774 -- two new independent "execute this command when the queue finishes" hooks, one for downloads and one for uploads. Configured under Settings → Advanced, right below the existing 509-bandwidth-limit command. Both new commands are fully independent of the legacy 509 command and of each other; if all three converge on the same tick they run concurrently. Each one has its own cooldown so neither can starve the others.
- Trigger is the natural "queue went from non-empty to empty" transition, gated by "at least one transference finished without error or cancellation" -- a queue that ended only in failures does not fire the user's post-processing script. Fires regardless of main-window visibility (the tray notification stays gated to hidden-window only, the script does not).
New in 8.54
#774 -- Independent post-finish hooks for downloads and uploads
-
What changed.
MainPanelgrows two new pairs of fields modelled on the existing 509 command:_run_command_dl_finish+_run_command_dl_finish_path(withLAST_DL_FINISH_COMMAND_TIMESTAMP) and the upload-side equivalents. Two new public methods,MainPanel.run_dl_finish_command()andMainPanel.run_ul_finish_command(), each short-circuit when their feature flag is off and otherwise spawn a non-blockingProcessBuilder.inheritIO().start()exactly the same way the legacy 509 path has been doing for years. The actual argv-resolution body (single-arg if the configured value is a real file, whitespace-token split otherwise) lives in a single shared_spawnExternalProcess(...)helper used by all three call sites so the three commands stay byte-for-byte identical in execution semantics. -
Where the trigger lives.
TransferenceManagergains a virtual hookonAllTransferencesFinished()(default no-op) and a new latch_finish_command_firedseparate from the existing_all_finished/_tray_icon_finishlatches._genStatus()fires the hook exactly once per "non-empty → empty with at least one OK finish" transition, regardless of main-window visibility (the existing tray-notification block still requires!isVisible()and is left untouched). The latch resets together with_all_finishedwhenever a new transference enters the pipeline, so each fresh batch retriggers the command on completion.DownloadManagerandUploadManageroverride the hook to call their respectiveMainPanel.run_*_finish_command(). -
Why a separate latch and timestamp. Sharing
LAST_EXTERNAL_COMMAND_TIMESTAMP(or even a single new finish timestamp) with both queues would mean a download finish landing inside the cooldown window of an upload finish silently skips its command, which is the opposite of what #774 asks for. Each command gets its own cooldown so all three (509, downloads-finish, uploads-finish) can fire on the same tick if the timing works out. The separate_finish_command_firedlatch (one perTransferenceManagersubclass, since each side has its own instance) keeps the natural "queue must transition from non-empty to empty" gate intact without coupling to the visibility-aware tray notification. -
Why gate on
_isOKFinishedInQueue(). A queue that emptied because the user cancelled everything, or because every transfer failed with an error, almost certainly does not have files for the post-processing script to act on. Silently no-firing on all-error / all-cancelled is the safer default than running the script against an empty result set. If you want the script to fire regardless, leaving at least one successful transfer in the finished queue before clearing it satisfies the gate.
Settings dialog (Advanced tab)
- Two new checkbox-and-textbox rows appear directly below the existing "Execute this command when MEGA download limit is reached:" row:
- Execute this command when ALL downloads finish:
- Execute this command when ALL uploads finish:
- Each row has its own Test button that spawns the configured command immediately, identical to the 509 row's Test. The textbox starts disabled and is enabled by toggling the checkbox above.
- Persistence: four new SQLite settings rows (
run_command_dl_finish,run_command_dl_finish_path,run_command_ul_finish,run_command_ul_finish_path) loaded in the dialog's constructor and saved by the Save handler alongside the existingrun_command/run_command_pathkeys. No migration is needed; missing rows default to "feature disabled".
i18n
- New keys
execute_this_command_when_all_downloads_finishandexecute_this_command_when_all_uploads_finishadded to all 8 bundles. EN carries the canonical English literal, ES has the Spanish translation, and IT/DE/HU/TR/VI/ZH carry the English placeholder pending translator pickup via the #397 contributor thread.LabelTranslatorSingleton's inverse-index lookup resolves the literal inSettingsDialogto the active locale's key at component-walk time, so the new checkboxes render localised wherever the bundle has a real translation and fall through to English otherwise.
Other
- Version bumped to 8.54 in
pom.xmlandMainPanel.VERSION. - No changes to the legacy 509 command path: same DB rows, same field names, same
run_external_command()entry point. Only the inner argv-builder body was factored into the shared helper that the new methods also use. - No changes to transfer-path logic, the global thread pool cap from 8.53, or the mono chunk buffer optimisation from 8.53. Memory mitigations from #773 stay intact.