154 lines
4.6 KiB
JavaScript
154 lines
4.6 KiB
JavaScript
|
|
'use strict';
|
||
|
|
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
// Cloud import — browse a user-configured WebDAV server and pull a book in.
|
||
|
|
//
|
||
|
|
// The download is proxied by the server because the remote host is a different
|
||
|
|
// origin and sends no CORS headers. It still lands here as raw bytes and goes
|
||
|
|
// through _importEbookBuffer like any local file, so what gets stored is
|
||
|
|
// ciphertext the server cannot read.
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
let _cloudSources = [];
|
||
|
|
let _cloudSourceId = null;
|
||
|
|
let _cloudPath = '';
|
||
|
|
|
||
|
|
async function toggleCloudImport() {
|
||
|
|
const panel = $('cloud-browser');
|
||
|
|
if (!panel) return;
|
||
|
|
if (panel.style.display !== 'none') { closeCloudImport(); return; }
|
||
|
|
|
||
|
|
try {
|
||
|
|
const res = await fetch('/books/cloud/sources/');
|
||
|
|
_cloudSources = (await res.json()).sources || [];
|
||
|
|
} catch (e) {
|
||
|
|
await customAlert('Cloud-Verbindungen konnten nicht geladen werden: ' + e.message);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!_cloudSources.length) {
|
||
|
|
await customAlert(
|
||
|
|
'Noch keine Cloud-Verbindung eingerichtet. Unter Einstellungen → '
|
||
|
|
+ '„Cloud-Verbindungen für Bücher“ kannst du eine hinzufügen.'
|
||
|
|
);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
const select = $('cloud-source-select');
|
||
|
|
if (select) {
|
||
|
|
select.innerHTML = '';
|
||
|
|
for (const source of _cloudSources) {
|
||
|
|
const option = document.createElement('option');
|
||
|
|
option.value = source.id;
|
||
|
|
option.textContent = source.label;
|
||
|
|
select.appendChild(option);
|
||
|
|
}
|
||
|
|
select.style.display = _cloudSources.length > 1 ? '' : 'none';
|
||
|
|
}
|
||
|
|
|
||
|
|
panel.style.display = '';
|
||
|
|
_cloudSourceId = _cloudSources[0].id;
|
||
|
|
_cloudBrowse('');
|
||
|
|
}
|
||
|
|
|
||
|
|
function closeCloudImport() {
|
||
|
|
const panel = $('cloud-browser');
|
||
|
|
if (panel) panel.style.display = 'none';
|
||
|
|
}
|
||
|
|
|
||
|
|
function _onCloudSourceChange(value) {
|
||
|
|
_cloudSourceId = parseInt(value, 10);
|
||
|
|
_cloudBrowse('');
|
||
|
|
}
|
||
|
|
|
||
|
|
function _cloudParentPath(path) {
|
||
|
|
const parts = path.split('/').filter(Boolean);
|
||
|
|
parts.pop();
|
||
|
|
return parts.join('/');
|
||
|
|
}
|
||
|
|
|
||
|
|
function _formatBytes(n) {
|
||
|
|
if (!n) return '';
|
||
|
|
if (n < 1024) return n + ' B';
|
||
|
|
if (n < 1024 * 1024) return Math.round(n / 1024) + ' KB';
|
||
|
|
return (n / 1024 / 1024).toFixed(1) + ' MB';
|
||
|
|
}
|
||
|
|
|
||
|
|
function _cloudEntryRow(label, onClick, sizeText) {
|
||
|
|
const li = document.createElement('li');
|
||
|
|
li.className = 'cloud-entry';
|
||
|
|
|
||
|
|
const button = document.createElement('button');
|
||
|
|
button.type = 'button';
|
||
|
|
button.className = 'cloud-entry-name';
|
||
|
|
button.textContent = label;
|
||
|
|
button.addEventListener('click', onClick);
|
||
|
|
li.appendChild(button);
|
||
|
|
|
||
|
|
if (sizeText) {
|
||
|
|
const size = document.createElement('span');
|
||
|
|
size.className = 'cloud-entry-size';
|
||
|
|
size.textContent = sizeText;
|
||
|
|
li.appendChild(size);
|
||
|
|
}
|
||
|
|
return li;
|
||
|
|
}
|
||
|
|
|
||
|
|
async function _cloudBrowse(path) {
|
||
|
|
const listEl = $('cloud-entries');
|
||
|
|
const pathEl = $('cloud-path');
|
||
|
|
const statusEl = $('cloud-status');
|
||
|
|
if (!listEl) return;
|
||
|
|
|
||
|
|
if (statusEl) statusEl.textContent = 'Lade…';
|
||
|
|
listEl.innerHTML = '';
|
||
|
|
|
||
|
|
let data;
|
||
|
|
try {
|
||
|
|
const res = await fetch(`/books/cloud/${_cloudSourceId}/browse/?path=${encodeURIComponent(path)}`);
|
||
|
|
data = await res.json();
|
||
|
|
if (!res.ok) throw new Error(data.error || `HTTP ${res.status}`);
|
||
|
|
} catch (e) {
|
||
|
|
if (statusEl) statusEl.textContent = 'Fehler: ' + e.message;
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
_cloudPath = data.path || '';
|
||
|
|
if (pathEl) pathEl.textContent = '/' + _cloudPath;
|
||
|
|
if (statusEl) statusEl.textContent = '';
|
||
|
|
|
||
|
|
if (_cloudPath) {
|
||
|
|
listEl.appendChild(_cloudEntryRow('⬑ ..', () => _cloudBrowse(_cloudParentPath(_cloudPath))));
|
||
|
|
}
|
||
|
|
|
||
|
|
const entries = (data.entries || []).filter(e => e.is_dir || e.is_book);
|
||
|
|
if (!entries.length && statusEl) {
|
||
|
|
statusEl.textContent = 'Keine Bücher oder Ordner in diesem Verzeichnis.';
|
||
|
|
}
|
||
|
|
|
||
|
|
for (const entry of entries) {
|
||
|
|
listEl.appendChild(entry.is_dir
|
||
|
|
? _cloudEntryRow('📁 ' + entry.name, () => _cloudBrowse(entry.path))
|
||
|
|
: _cloudEntryRow('📖 ' + entry.name, () => _cloudImport(entry), _formatBytes(entry.size)));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
async function _cloudImport(entry) {
|
||
|
|
const statusEl = $('cloud-status');
|
||
|
|
if (statusEl) statusEl.textContent = `Lade „${entry.name}“…`;
|
||
|
|
|
||
|
|
try {
|
||
|
|
const res = await fetch(`/books/cloud/${_cloudSourceId}/fetch/?path=${encodeURIComponent(entry.path)}`);
|
||
|
|
if (!res.ok) {
|
||
|
|
const err = await res.json().catch(() => ({}));
|
||
|
|
throw new Error(err.error || `HTTP ${res.status}`);
|
||
|
|
}
|
||
|
|
const buf = await res.arrayBuffer();
|
||
|
|
if (await _importEbookBuffer(buf, entry.name, statusEl) && statusEl) {
|
||
|
|
statusEl.textContent = `✓ „${entry.name}“ importiert`;
|
||
|
|
}
|
||
|
|
} catch (e) {
|
||
|
|
if (statusEl) statusEl.textContent = 'Import fehlgeschlagen: ' + e.message;
|
||
|
|
}
|
||
|
|
}
|