make data export a bool config setting
This commit is contained in:
@@ -59,6 +59,7 @@
|
||||
"abyss_enabled": true,
|
||||
"meme_creator": true,
|
||||
"enable_cleanup": false,
|
||||
"enable_data_export": true,
|
||||
"cleanup_timeframe_days": 30,
|
||||
|
||||
"web_url_upload": true,
|
||||
|
||||
@@ -1499,30 +1499,76 @@
|
||||
|
||||
exportStatusMsg.textContent = exportStatusText.dataset.generating || 'Generating ZIP file';
|
||||
exportAnimatedDots.style.display = 'inline';
|
||||
const content = await new Promise((resolve, reject) => {
|
||||
const chunks = [];
|
||||
zip.generateInternalStream({ type: 'uint8array', compression: 'DEFLATE', compressionOptions: { level: 6 } })
|
||||
.on('data', (chunk) => chunks.push(chunk))
|
||||
.on('error', reject)
|
||||
.on('end', () => {
|
||||
try {
|
||||
resolve(new Blob(chunks, { type: 'application/zip' }));
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
})
|
||||
.resume();
|
||||
});
|
||||
exportAnimatedDots.style.display = 'none';
|
||||
|
||||
const link = document.createElement('a');
|
||||
link.href = URL.createObjectURL(content);
|
||||
link.download = `f0ckm_export_${new Date().toISOString().split('T')[0]}.zip`;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
|
||||
exportStatusMsg.textContent = exportStatusText.dataset.complete || 'Export complete!';
|
||||
const zipOptions = {
|
||||
type: 'uint8array',
|
||||
compression: 'STORE' // Media is already compressed, STORE saves massive CPU/RAM
|
||||
};
|
||||
|
||||
// Try Direct-to-Disk saving if supported (Chrome/Edge/Opera)
|
||||
// This bypasses RAM entirely for the final file construction.
|
||||
if ('showSaveFilePicker' in window) {
|
||||
try {
|
||||
const handle = await window.showSaveFilePicker({
|
||||
suggestedName: `f0ckm_export_${new Date().toISOString().split('T')[0]}.zip`,
|
||||
types: [{
|
||||
description: 'ZIP Archive',
|
||||
accept: { 'application/zip': ['.zip'] },
|
||||
}],
|
||||
});
|
||||
|
||||
const writable = await handle.createWritable();
|
||||
await new Promise((resolve, reject) => {
|
||||
zip.generateInternalStream(zipOptions)
|
||||
.on('data', async (chunk) => {
|
||||
try {
|
||||
await writable.write(chunk);
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
})
|
||||
.on('error', reject)
|
||||
.on('end', resolve)
|
||||
.resume();
|
||||
});
|
||||
await writable.close();
|
||||
|
||||
exportStatusMsg.textContent = exportStatusText.dataset.complete || 'Export complete!';
|
||||
} catch (e) {
|
||||
if (e.name === 'AbortError') {
|
||||
exportStatusMsg.textContent = 'Export cancelled.';
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Fallback: Original Blob method (uses RAM)
|
||||
const content = await new Promise((resolve, reject) => {
|
||||
const chunks = [];
|
||||
zip.generateInternalStream(zipOptions)
|
||||
.on('data', (chunk) => chunks.push(chunk))
|
||||
.on('error', reject)
|
||||
.on('end', () => {
|
||||
try {
|
||||
resolve(new Blob(chunks, { type: 'application/zip' }));
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
})
|
||||
.resume();
|
||||
});
|
||||
|
||||
const link = document.createElement('a');
|
||||
link.href = URL.createObjectURL(content);
|
||||
link.download = `f0ckm_export_${new Date().toISOString().split('T')[0]}.zip`;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
|
||||
exportStatusMsg.textContent = exportStatusText.dataset.complete || 'Export complete!';
|
||||
}
|
||||
|
||||
exportAnimatedDots.style.display = 'none';
|
||||
btnStartExport.disabled = false;
|
||||
} catch (err) {
|
||||
console.error('Export failed:', err);
|
||||
|
||||
@@ -49,6 +49,7 @@ export default (router, tpl) => {
|
||||
email: user?.email || '',
|
||||
joined: user?.created_at || null,
|
||||
enable_swf: cfg.enable_swf,
|
||||
enable_data_export: cfg.websrv.enable_data_export,
|
||||
session: (req.session && req.session.user) ? { ...req.session } : false,
|
||||
page_meta: {
|
||||
title: 'settings',
|
||||
@@ -59,6 +60,10 @@ export default (router, tpl) => {
|
||||
});
|
||||
});
|
||||
group.get('/export-data', auth, async (req, res) => {
|
||||
if (!cfg.websrv.enable_data_export) {
|
||||
res.status(403).reply({ body: 'Export disabled' });
|
||||
return;
|
||||
}
|
||||
const uploads = await db`
|
||||
select id, dest, mime from items
|
||||
where username = ${req.session.user} and active = true
|
||||
|
||||
@@ -257,6 +257,7 @@
|
||||
@endif
|
||||
|
||||
</div>
|
||||
@if(enable_data_export)
|
||||
<h2>{{ t('settings.export_data_title') || 'Export Data' }}</h2>
|
||||
<div class="export-settings-wrapper" style="background: rgba(0,0,0,0.1); padding: 20px; border-radius: 4px; border: 1px solid var(--nav-border-color); margin-bottom: 30px;">
|
||||
<p>{{ t('settings.export_data_desc') || 'Download a copy of your data. This process happens entirely in your browser to protect your privacy and save server resources.' }}</p>
|
||||
@@ -295,6 +296,7 @@
|
||||
<i class="fa-solid fa-download" style="margin-right: 5px;"></i> {{ t('settings.start_export') || 'Generate Export (ZIP)' }}
|
||||
</button>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<h2>{{ t('settings.account') }}</h2>
|
||||
<div class="account-settings-wrapper"
|
||||
|
||||
Reference in New Issue
Block a user