Re-add direct download via URL parameter (#1914)

* Re-add direct download via URL parameter

* Add comments

* Confirm before downloading remote file

* Allow aborting remote downloads

* Preemptively add AbortController to fetch call
puter.net.fetch currently doesn't support it (yet!)

* Add filename to download dialog/progress dialogs
This commit is contained in:
Hariz S.
2025-11-08 02:32:06 -05:00
committed by GitHub
parent e5e242512f
commit 7d78381fe1
2 changed files with 124 additions and 8 deletions

View File

@@ -30,6 +30,7 @@ import UIWindowFeedback from "./UIWindowFeedback.js"
import UIWindowLogin from "./UIWindowLogin.js"
import UIWindowQR from "./UIWindowQR.js"
import UIWindowRefer from "./UIWindowRefer.js"
import UIWindowProgress from "./UIWindowProgress.js"
import UITaskbar from "./UITaskbar.js"
import new_context_menu_item from "../helpers/new_context_menu_item.js"
import refresh_item_container from "../helpers/refresh_item_container.js"
@@ -1304,8 +1305,7 @@ async function UIDesktop(options) {
// load from direct app URLs: /app/app-name
// ---------------------------------------------
else if (window.app_launched_from_url) {
let qparams = new URLSearchParams(window.location.search);
if (!qparams.has('c')) {
if (!window.url_query_params.has('c')) {
let posargs = undefined;
if (window.app_query_params && window.app_query_params.posargs) {
posargs = JSON.parse(window.app_query_params.posargs);
@@ -1313,8 +1313,8 @@ async function UIDesktop(options) {
launch_app({
app: window.app_launched_from_url.name,
app_obj: window.app_launched_from_url,
readURL: qparams.get('readURL'),
maximized: qparams.get('maximized'),
readURL: window.url_query_params.get('readURL'),
maximized: window.url_query_params.get('maximized'),
params: window.app_query_params ?? [],
...(posargs ? {
args: {
@@ -1678,11 +1678,11 @@ async function UIDesktop(options) {
// i.e. https://puter.com/@<username>
//--------------------------------------------------------------------------------------
const url_paths = window.location.pathname.split('/').filter(element => element);
if (url_paths[0]?.startsWith('@')) {
const username = url_paths[0].substring(1);
if (window.url_paths[0]?.startsWith('@')) {
const username = window.url_paths[0].substring(1);
let item_path = '/' + username + '/Public';
if ( url_paths.length > 1 ) {
item_path += '/' + url_paths.slice(1).join('/');
if ( window.url_paths.length > 1 ) {
item_path += '/' + window.url_paths.slice(1).join('/');
}
// GUARD: avoid invalid user directories
@@ -1781,6 +1781,117 @@ async function UIDesktop(options) {
app: 'explorer',
});
}
//--------------------------------------------------------------------------------------
// Direct download link
// i.e. https://puter.com/?download=<file_url>
//--------------------------------------------------------------------------------------
if (window.url_paths.length === 0 && window.url_query_params.has('download')) {
const url = window.url_query_params.get('download');
let file_name = url.split('/').pop().split('?')[0];
let response = await UIAlert({
message: i18n('confirm_download_file_to_desktop', file_name),
type: 'confirm',
buttons: [
{ label: i18n('alert_yes'), value: true, type: "primary" },
{ label: i18n('alert_no'), value: false, type: "secondary" }
],
});
if (!response)
return;
let cancelled = false;
let upload_xhr = null;
const abort_controller = new AbortController();
// create progressbar dialog
let progwin = await UIWindowProgress({
title: i18n('downloading'),
icon: window.icons[`app-icon-uploader.svg`],
operation_id: window.uuidv4(),
show_progress: true,
on_cancel: () => {
cancelled = true;
abort_controller.abort();
if (upload_xhr) {
upload_xhr.abort();
}
}
});
progwin?.set_status(i18n('downloading_file', file_name));
(async () => {
try {
// download the file
const response = await puter.net.fetch(url, {
signal: abort_controller.signal,
});
const total = Number(response.headers.get('content-length'));
const reader = response.body.getReader();
const chunks = [];
let received = 0;
while (true) {
const { done, value } = await reader.read();
if (done || cancelled) break;
if (value) {
// store the chunk
chunks.push(value);
received += value.length;
// calculate progress
const progress = Number.isFinite(total) && total > 0
? received / total
: 0;
// update progressbar
progwin?.set_progress(Math.floor(progress * 100));
}
}
if (cancelled) {
progwin?.close();
return;
}
// combine chunks into a blob
let blob = new Blob(chunks, {
type: response.headers.get('content-type') ?? 'application/octet-stream',
});
// reset progressbar
progwin?.set_progress(0);
progwin?.set_status(i18n('uploading_file', file_name));
// upload to user's desktop
await puter.fs.write(`~/Desktop/${file_name}`, blob, {
dedupeName: true,
progress: (_, percent) => {
// update progressbar
progwin?.set_progress(percent);
},
init: (_, xhr) => {
upload_xhr = xhr;
}
});
} catch (e) {
// alert the user if there's a genuine error
if (!cancelled && e.name !== 'AbortError') {
await UIAlert({
message: i18n('error_download_failed') + ': ' + e.message,
type: 'error',
});
}
}
// close progress window
progwin?.close();
})();
}
}
$(document).on('contextmenu taphold', '.taskbar', function (event) {

View File

@@ -116,8 +116,11 @@ const en = {
documents: 'Documents',
dont_allow: 'Don\'t Allow',
download: 'Download',
confirm_download_file_to_desktop: 'Are you sure you want to download %% to your Desktop?',
download_file: 'Download File',
downloading: "Downloading",
downloading_file: "Downloading %%",
error_download_failed: "Failed to download file",
email: "Email",
email_change_confirmation_sent: "A confirmation email has been sent to your new email address. Please check your inbox and follow the instructions to complete the process.",
email_invalid: 'Email is invalid.',
@@ -324,6 +327,8 @@ const en = {
untar: "Untar",
untarring: "Untarring %strong%",
upload: 'Upload',
uploading: "Uploading",
uploading_file: "Uploading %%",
upload_here: 'Upload here',
used_of: '{{used}} used of {{available}}',
usage: 'Usage',