Introduce a `one-time` retention option and persist it on the manifest as `one_time_download`. One-time download boxes bypass retention expiry scheduling, force zip downloads, and reject download attempts until all files are complete to prevent partial retrievals.feat(boxstore): add one-time download retention mode Introduce a `one-time` retention option and persist it on the manifest as `one_time_download`. One-time download boxes bypass retention expiry scheduling, force zip downloads, and reject download attempts until all files are complete to prevent partial retrievals.
90 lines
2.9 KiB
JavaScript
90 lines
2.9 KiB
JavaScript
const boxPanel = document.querySelector(".box-panel[data-box-id]");
|
|
const boxStatus = document.querySelector(".box-statusbar span:first-child");
|
|
const zipOnly = boxPanel && boxPanel.dataset.zipOnly === "true";
|
|
|
|
document.querySelectorAll('.box-file[aria-disabled="true"]').forEach((item) => {
|
|
item.addEventListener("click", (event) => {
|
|
if (item.getAttribute("aria-disabled") === "true") {
|
|
event.preventDefault();
|
|
}
|
|
});
|
|
});
|
|
|
|
function updateBoxFile(file) {
|
|
const item = document.querySelector(`.box-file[data-file-id="${file.id}"]`);
|
|
if (!item) {
|
|
return;
|
|
}
|
|
|
|
const meta = item.querySelector(".box-file-meta");
|
|
const icon = item.querySelector(".box-file-icon");
|
|
const isComplete = file.status === "complete";
|
|
const isFailed = file.status === "failed";
|
|
|
|
item.classList.toggle("is-complete", isComplete);
|
|
item.classList.toggle("is-failed", isFailed);
|
|
item.classList.toggle("is-loading", !isComplete && !isFailed);
|
|
item.classList.toggle("has-thumbnail", Boolean(file.thumbnail_path));
|
|
item.dataset.status = file.status;
|
|
item.title = file.title;
|
|
|
|
if (isComplete && !zipOnly) {
|
|
item.href = file.download_path;
|
|
item.setAttribute("download", "");
|
|
item.removeAttribute("aria-disabled");
|
|
} else {
|
|
item.href = "#";
|
|
item.removeAttribute("download");
|
|
item.setAttribute("aria-disabled", "true");
|
|
}
|
|
|
|
if (meta) {
|
|
meta.textContent = `${file.status_label} · ${file.size_label}`;
|
|
}
|
|
|
|
if (icon) {
|
|
icon.src = file.thumbnail_path || file.icon_path;
|
|
}
|
|
}
|
|
|
|
async function refreshBoxStatus() {
|
|
if (!boxPanel) {
|
|
return false;
|
|
}
|
|
|
|
const boxID = boxPanel.dataset.boxId;
|
|
const response = await fetch(`/box/${boxID}/status`);
|
|
if (!response.ok) {
|
|
return true;
|
|
}
|
|
|
|
const result = await response.json();
|
|
result.files.forEach(updateBoxFile);
|
|
|
|
if (boxStatus) {
|
|
const completeCount = result.files.filter((file) => file.status === "complete").length;
|
|
boxStatus.textContent = `${completeCount}/${result.files.length} ready`;
|
|
}
|
|
|
|
return result.files.some((file) => {
|
|
const isUploading = file.status === "pending" || file.status === "uploading";
|
|
const isWaitingForThumbnail = file.status === "complete" && !file.thumbnail_status && !file.thumbnail_path;
|
|
return isUploading || isWaitingForThumbnail || file.thumbnail_status === "processing";
|
|
});
|
|
}
|
|
|
|
if (boxPanel) {
|
|
const pollMS = Number.parseInt(boxPanel.dataset.pollMs, 10) || 5000;
|
|
const timer = setInterval(async () => {
|
|
try {
|
|
const hasLoadingFiles = await refreshBoxStatus();
|
|
if (!hasLoadingFiles) {
|
|
clearInterval(timer);
|
|
}
|
|
} catch (error) {
|
|
// Keep polling through temporary network/server hiccups; otherwise
|
|
// an in-progress file can appear stuck forever after one bad poll.
|
|
}
|
|
}, pollMS);
|
|
}
|