diff --git a/.github/workflows/build-packages.yml b/.github/workflows/build-packages.yml index c891248..9acc7a2 100644 --- a/.github/workflows/build-packages.yml +++ b/.github/workflows/build-packages.yml @@ -2,7 +2,7 @@ name: Build Packages on: push: branches: - - build-package + - build tags: - 'v*.*.*' jobs: @@ -107,4 +107,4 @@ jobs: if: startsWith(github.ref, 'refs/tags/') uses: 45drives/actions/sync-repo@main with: - directory: ${{ github.workspace }} + directory: ${{ github.workspace }} \ No newline at end of file diff --git a/manifest.json b/manifest.json index 4aa8250..656c40d 100644 --- a/manifest.json +++ b/manifest.json @@ -5,7 +5,7 @@ "description": "A File System Browser for Cockpit.", "version": "0.5.10", "build_number": "1", - "stable": true, + "stable": false, "author": "Josh Boudreau ", "git_url": "https://github.com/45Drives/cockpit-navigator", "license": "GPL-3.0+", @@ -14,8 +14,7 @@ }, "architecture": { "rocky": "x86_64", - "debian": "amd64", - "ubuntu": "amd64" + "ubuntu": "all" }, "dependencies": { "ubuntu_common": [ @@ -24,7 +23,8 @@ "rsync", "zip", "file", - "coreutils" + "coreutils", + "inotify-tools" ], "rocky_common": [ "cockpit", @@ -33,7 +33,8 @@ "zip", "file", "/bin/mkdir", - "/bin/rmdir" + "/bin/rmdir", + "inotify-tools" ] }, "builds": [ diff --git a/navigator/components/NavContextMenu.js b/navigator/components/NavContextMenu.js index bd23fe2..4d4cc61 100644 --- a/navigator/components/NavContextMenu.js +++ b/navigator/components/NavContextMenu.js @@ -100,46 +100,96 @@ export class NavContextMenu { } e.stopPropagation(); } - zip_for_download() { return new Promise((resolve, reject) => { - var cmd = [ - "/usr/share/cockpit/navigator/scripts/zip-for-download.py3", - this.nav_window_ref.pwd().path_str() - ]; - for (let entry of this.nav_window_ref.selected_entries) { - cmd.push(entry.path_str()); + const cmd = ["/usr/share/cockpit/navigator/scripts/zip-for-download.py3", + this.nav_window_ref.pwd().path_str()]; + for (const entry of this.nav_window_ref.selected_entries) cmd.push(entry.path_str()); + const proc = cockpit.spawn(cmd, { superuser: "try", err: "out" }); + + const safeParse = (raw) => { + const s = (raw || "").trim(); + const start = s.indexOf("{"); + const end = s.lastIndexOf("}"); + if (start === -1 || end === -1 || end < start) { + throw new Error("No JSON object in output: " + s.slice(0, 200)); } - var proc = cockpit.spawn(cmd, {superuser: "try", err: "out"}); - proc.fail((e, data) => { - reject(JSON.parse(data)); - }); - proc.done((data) => { - resolve(JSON.parse(data)); - }); - }); - } - - async download(e) { - var download_target = ""; - if (this.nav_window_ref.selected_entries.size === 1 && !(this.nav_window_ref.selected_entry() instanceof NavDir)) { - download_target = this.nav_window_ref.selected_entry(); - } else { - this.nav_window_ref.start_load(); - var result; + return JSON.parse(s.slice(start, end + 1)); + }; + + proc.done((data) => { try { - result = await this.zip_for_download(); - } catch(e) { - this.nav_window_ref.stop_load(); - this.nav_window_ref.modal_prompt.alert(e.message); - return; + resolve(safeParse(data)); + } catch (e) { + console.error("zip_for_download done(raw):", data); + reject({ message: e.message }); } - this.nav_window_ref.stop_load(); + }); + + proc.fail((e, data) => { + try { + reject(safeParse(data)); + } catch { + console.error("zip_for_download fail(raw):", data); + reject({ message: String(data || e || "zip_for_download failed") }); + } + }); + }); + } + + async download(e) { + let download_target = ""; + let result; // function-scoped so we can reference later + + if (this.nav_window_ref.selected_entries.size === 1 && + !(this.nav_window_ref.selected_entry() instanceof NavDir)) { + download_target = this.nav_window_ref.selected_entry(); + } else { + this.nav_window_ref.start_load(); + try { + result = await this.zip_for_download(); download_target = new NavFile(result["archive-path"], result["stat"], this.nav_window_ref); + console.log("prepared archive for download:", result["archive-path"]); + } catch (err) { + this.nav_window_ref.stop_load(); + this.nav_window_ref.modal_prompt.alert(err.message); + return; + } finally { + this.nav_window_ref.stop_load(); + } } - var download = new NavDownloader(download_target); - download.download(); - } + if (result?.["archive-path"]) { + const unitName = `nav-clean-on-open-${Date.now()}-${Math.random().toString(36).slice(2,8)}`; + const script = [ + 'set -euo pipefail', + 'if ! command -v inotifywait >/dev/null 2>&1; then ' + + 'sleep 300; rm -f -- "$ARCHIVE"; ' + + '[ -n "${TEMPDIR:-}" ] && [[ "$TEMPDIR" == /tmp/navigator-* ]] && rm -rf -- "$TEMPDIR"; ' + + 'exit 0; fi', + 'inotifywait -q -t 1800 -e open -- "$ARCHIVE" || true', + 'rm -f -- "$ARCHIVE"', + 'sleep 3600', + '[ -n "${TEMPDIR:-}" ] && [[ "$TEMPDIR" == /tmp/navigator-* ]] && rm -rf -- "$TEMPDIR" || :' + ].join(' && '); + const cmd = [ + 'systemd-run', + '--property=CollectMode=inactive-or-failed', + '--property=RuntimeMaxSec=90000', + '--unit', unitName, + '--setenv=ARCHIVE=' + result['archive-path'], + ...(result['temp-dir'] ? ['--setenv=TEMPDIR=' + result['temp-dir']] : []), + '/bin/bash','-lc', script + ]; + + await cockpit.spawn(cmd, { superuser: 'require', err: 'out' }).then(out => console.log(out)); + console.log("scheduled cleanup:", unitName); + console.log("Deleting :", result['archive-path'], "in 30 minutes or after download starts."); + } + + const downloader = new NavDownloader(download_target); + downloader.download(); + } + delete(e) { this.nav_window_ref.delete_selected(); diff --git a/navigator/components/NavWindow.js b/navigator/components/NavWindow.js index 37570f3..f4384cd 100644 --- a/navigator/components/NavWindow.js +++ b/navigator/components/NavWindow.js @@ -331,6 +331,19 @@ export class NavWindow { } update_selection_info() { + if (this.selected_entries.size > 1) { + // get first element of the Set + const it = this.selected_entries.values(); + const first = it.next().value; + const second = it.next().value; + if(first.path.length 1){ var name_fields = document.getElementsByClassName("nav-info-column-filename"); for (let name_field of name_fields) {