github tonikelope/megabasterd v8.42
MegaBasterd 8.42

3 hours ago

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. Both Download.provisionIt and Upload.provisionIt now honour _main_panel.isExit() so they do not race the SqliteSingleton shutdown.
  • Right-click context menu on the JTree. Both FolderLinkDialog (downloads) and FileGrabberDialog (uploads) now expose Remove / Keep only this / Expand all / Collapse all via right-click on the tree. Reuses the same prune utilities as the legacy REMOVE THIS / REMOVE ALL EXCEPT THIS buttons (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 unbounded do/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 a THREAD_POOL worker at 100% CPU and flooding megabasterd.log with the same stack trace. Capped at 3 attempts with a 500 ms backoff; the outer while also breaks when _main_panel.isExit() is set so a closing user does not keep spawning provision work behind the shutdown drain.

  • new_download URL loop catches everything (MainPanelView.new_download_menuActionPerformed). The for(url:urls) loop only caught UnsupportedEncodingException and InterruptedException. URLDecoder.decode throws IllegalArgumentException (not UnsupportedEncodingException) 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 a RuntimeException catch-all so a bad URL is logged and skipped while the others keep going, and the loop now respects isExit(). Also widened the getMega_active_accounts().get(mega_account) lookup with a null fallback (the selected account may have been removed by a concurrent SettingsDialog edit; falls back to an anonymous MegaAPI).

  • GENERATE_N_LINKS stops evaporating #F* links (MegaAPI.GENERATE_N_LINKS). Two silent-failure paths in the file-in-folder converter:

    1. If the #F* regex did not match, folder_id / folder_key / file_id came back null and the link got buried under the sentinel "null:null" map key. The resulting getFolderNodes call failed at the API level and yielded an empty nlinks list -- the caller's urls.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 a WARNING, skip the link, and echo it back at the end of nlinks so the caller's remove/add pattern is a no-op for those entries instead of a silent drop.

    2. getNLinksFromFolder silently skipped any file_id the API did not return (deleted file, key mismatch, etc.). Log a WARNING with the folder + file id so an under-populated batch is diagnosable.

  • FolderLinkDialog cache prompt wrapped in GUIRunAndWait. _loadMegaDirTree runs on THREAD_POOL but the previous code invoked JOptionPane.showConfirmDialog directly, mixing Swing state across threads.

Pipeline hardening (uploads)

  • _new_upload_dialog Runnable (MainPanelView). Concrete fixes for verified silent failures:

    • Null MegaAPI guard. If the selected account vanished from mega_active_accounts between the dialog show and the Runnable execution (race with SettingsDialog removing a login), ma.genFolderKey() used to NPE and the outer catch logged SEVERE; the user just saw nothing happen. Now we surface a clear popup, drain the queued File entries from preprocess_global_queue so the status bar finishes, and bail out.
    • Empty dir_name. root_name fell back to the auto-generated <file>_<id> only when dir_name was 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 and parent.getAbsolutePath() used to NPE; the outer catch silently dropped that file. Fall back to base_path when parent is null.
    • Honour isExit(). The for(File f : files) loop now breaks on _main_panel.isExit() so a user closing the app mid-batch stops spawning new createDir API calls and lets the surviving files reach the _byebye snapshot.
  • Upload.provisionIt + UploadManager.provision. Three closely related upload-pipeline holes:

    • provisionIt did not check _closed or _main_panel.isExit() before DBTools.insertUpload. A user closing the app mid-batch could race byebyenow's SqliteSingleton.shutdown, leaving the upload row half-written.
    • provisionIt only caught SQLException. genUploadKey can NPE on a non-logged-in MA, and any other RuntimeException used to bubble out of provisionIt back into UploadManager.provision -- which had no try/catch -- so the upload got stuck inside the BoundedExecutor task: it never reached aux_queue or finished_queue, and the row's UI showed Provisioning... forever. Wrap the body in a RuntimeException catch, and add a belt-and-braces catch in UploadManager.provision so 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 / Upload objects whose insertDownloadReturningName / insertUpload had not yet run.
  • waitstart_aux_queue: provisioned objects still waiting to flush to waitstart_queue.
  • preprocess_global_queue (download-side): raw String URLs the user pasted that never made it to a Download object.
  • preprocess_global_queue (upload-side): raw File objects 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 (reuses deleteSelectedTreeItems).
  • Keep only this -- same semantics as REMOVE ALL EXCEPT THIS (reuses deleteAllExceptSelectedTreeItems).
  • 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

Don't miss a new megabasterd release

NewReleases is sending notifications on new releases.