TL;DR
- End-to-end audit + hardening of the new-download / new-upload pipelines. Bounded the previously unbounded preprocess retry loop (a single persistent exception used to pin a worker at 100% CPU forever), plugged six silent-failure paths in URL parsing /
#F*->#N!conversion / folder-node decryption / the new-upload Runnable, and widened the exit snapshot to capture every download AND upload still in flight (running + waitstart + aux + provision + preprocess_global) so closing the app mid-batch no longer loses URLs or files. BothDownload.provisionItandUpload.provisionItnow honour_main_panel.isExit()so they do not race the SqliteSingleton shutdown. - Right-click context menu on the JTree. Both
FolderLinkDialog(downloads) andFileGrabberDialog(uploads) now expose Remove / Keep only this / Expand all / Collapse all via right-click on the tree. Reuses the same prune utilities as the legacyREMOVE THIS/REMOVE ALL EXCEPT THISbuttons (which stay), and respects multi-selection -- right-clicking a node already in the selection keeps the selection so the action applies to every marked item. - 4 new i18n keys translated to all 8 bundles. Everything that shipped in 8.41 is included; 8.42 is 8.41 + the changes above. No behaviour changes outside the new-download / new-upload paths and the picker dialogs.
New in 8.42
Pipeline hardening (downloads)
-
Bounded preprocess retry loop (
TransferenceManager.run). The per-Runnable body used to be wrapped in an unboundeddo/while-on-error: any persistent exception (NPE from a malformed link, ClassCastException on a folder-link type, etc.) re-ran the SAME Runnable forever, pinning aTHREAD_POOLworker at 100% CPU and floodingmegabasterd.logwith the same stack trace. Capped at 3 attempts with a 500 ms backoff; the outerwhilealso breaks when_main_panel.isExit()is set so a closing user does not keep spawning provision work behind the shutdown drain. -
new_downloadURL loop catches everything (MainPanelView.new_download_menuActionPerformed). Thefor(url:urls)loop only caughtUnsupportedEncodingExceptionandInterruptedException.URLDecoder.decodethrowsIllegalArgumentException(notUnsupportedEncodingException) on malformed%-escapes, and any NPE / ClassCastException / regex mismatch from a single dodgy URL bubbled out of the loop and took down the rest of the batch -- with the previously unbounded preprocess retry that meant a 100% CPU infinite loop. Added aRuntimeExceptioncatch-all so a bad URL is logged and skipped while the others keep going, and the loop now respectsisExit(). Also widened thegetMega_active_accounts().get(mega_account)lookup with a null fallback (the selected account may have been removed by a concurrentSettingsDialogedit; falls back to an anonymousMegaAPI). -
GENERATE_N_LINKSstops evaporating#F*links (MegaAPI.GENERATE_N_LINKS). Two silent-failure paths in the file-in-folder converter:-
If the
#F*regex did not match,folder_id/folder_key/file_idcame back null and the link got buried under the sentinel"null:null"map key. The resultinggetFolderNodescall failed at the API level and yielded an empty nlinks list -- the caller'surls.removeAll(folder_file_links); urls.addAll(nlinks)pattern then DELETED the original#F*URL with nothing to replace it. A user who pasted a malformed file-in-folder link saw nothing happen and no error. Now we log aWARNING, skip the link, and echo it back at the end ofnlinksso the caller's remove/add pattern is a no-op for those entries instead of a silent drop. -
getNLinksFromFoldersilently skipped anyfile_idthe API did not return (deleted file, key mismatch, etc.). Log aWARNINGwith the folder + file id so an under-populated batch is diagnosable.
-
-
FolderLinkDialogcache prompt wrapped inGUIRunAndWait._loadMegaDirTreeruns onTHREAD_POOLbut the previous code invokedJOptionPane.showConfirmDialogdirectly, mixing Swing state across threads.
Pipeline hardening (uploads)
-
_new_upload_dialogRunnable (MainPanelView). Concrete fixes for verified silent failures:- Null
MegaAPIguard. If the selected account vanished frommega_active_accountsbetween the dialog show and the Runnable execution (race withSettingsDialogremoving a login),ma.genFolderKey()used to NPE and the outer catch loggedSEVERE; the user just saw nothing happen. Now we surface a clear popup, drain the queuedFileentries frompreprocess_global_queueso the status bar finishes, and bail out. - Empty
dir_name.root_namefell back to the auto-generated<file>_<id>only whendir_namewas null, not when it was"". An empty text field produced an upload folder named""on MEGA which the API then rejected for the whole batch. - Drive-root file NPE. For files at a drive root (
Z:\file.txt)f.getParentFile()returns null andparent.getAbsolutePath()used to NPE; the outer catch silently dropped that file. Fall back tobase_pathwhen parent is null. - Honour
isExit(). Thefor(File f : files)loop now breaks on_main_panel.isExit()so a user closing the app mid-batch stops spawning newcreateDirAPI calls and lets the surviving files reach the_byebyesnapshot.
- Null
-
Upload.provisionIt+UploadManager.provision. Three closely related upload-pipeline holes:provisionItdid not check_closedor_main_panel.isExit()beforeDBTools.insertUpload. A user closing the app mid-batch could racebyebyenow'sSqliteSingleton.shutdown, leaving the upload row half-written.provisionItonly caughtSQLException.genUploadKeycan NPE on a non-logged-in MA, and any otherRuntimeExceptionused to bubble out ofprovisionItback intoUploadManager.provision-- which had no try/catch -- so the upload got stuck inside theBoundedExecutortask: it never reachedaux_queueorfinished_queue, and the row's UI showedProvisioning...forever. Wrap the body in aRuntimeExceptioncatch, and add a belt-and-braces catch inUploadManager.provisionso the transference always ends up routed somewhere even if a future code path re-introduces a thrown runtime.
Exit snapshot widened (downloads AND uploads)
MainPanel._byebye used to capture only running_list + waitstart_queue. Anything mid-provisioning, in the aux flush queue, or still a String URL / File in the preprocess phase was lost silently on exit:
provision_queue:Download/Uploadobjects whoseinsertDownloadReturningName/insertUploadhad not yet run.waitstart_aux_queue: provisioned objects still waiting to flush towaitstart_queue.preprocess_global_queue(download-side): raw String URLs the user pasted that never made it to aDownloadobject.preprocess_global_queue(upload-side): rawFileobjects whose Upload object had not been built yet.
Now both sides snapshot all four sources via a LinkedHashSet so we preserve priority order and dedupe. On the download side resumeDownloads gains a fallback for URLs that have no downloads row: build a metadata-less Download against the default download path so the user gets their pasted link back instead of seeing it vanish. On the upload side the broader sources serve mostly as a debugging trail -- full recovery from preprocess_global_queue (which carries File objects without their target MEGA account / parent folder) would need a small DB migration and is deferred.
Pair the snapshot widening with Download.provisionIt / Upload.provisionIt isExit guards so they bail out before the row insert when the app is shutting down. byebyenow's SqliteSingleton.shutdown no longer races a half-written insert.
UI: right-click context menu on the JTree dialogs
Both FolderLinkDialog (download folder-link picker) and FileGrabberDialog (new upload file picker) used to require the user to Ctrl+click for multi-selection and then click a separate REMOVE THIS / REMOVE ALL EXCEPT THIS button to prune the tree. Now the same actions are available on a right-click context menu mounted directly on the tree:
- Remove -- same semantics as
REMOVE THIS(reusesdeleteSelectedTreeItems). - Keep only this -- same semantics as
REMOVE ALL EXCEPT THIS(reusesdeleteAllExceptSelectedTreeItems). - Expand all / Collapse all -- comfort items, no equivalent button before.
Multi-selection is preserved: right-clicking a node that is already in the selection leaves the selection alone (so the action affects every marked item, like with the buttons today); right-clicking a node that is not selected replaces the selection with just that node (standard OS-native tree convention).
The legacy REMOVE THIS / REMOVE ALL EXCEPT THIS buttons stay in place so users who learned that flow keep their muscle memory. A new MiscTools.attachTreeContextMenu(tree, onChange) helper drives both dialogs; each dialog extracts its post-mutation refresh block into a private _afterTreeMutation() so the buttons and the context menu share the same code path.
4 new i18n keys (ui.tree.context.{remove,keep_only,expand_all,collapse_all}) added to every messages*.properties bundle (de, en, es, hu, it, tr, vi, zh).
Build artefact: MegaBasterd_8.42.jar is the full fat-JAR (-jar-with-dependencies), drop-in replacement for the 8.41 jar.
Translators: 4 new i18n keys -- see #397