diff --git a/.env.example b/.env.example index b3f06bf..1d844f8 100644 --- a/.env.example +++ b/.env.example @@ -43,3 +43,27 @@ WEBDAV_PATH=/webdav WEBDAV_READONLY=1 # WebDAV index yeniden oluşturma süresi (ms). WEBDAV_INDEX_TTL=60000 + +# --- Rclone / Google Drive --- +# Rclone entegrasyonunu aç/kapat +RCLONE_ENABLED=0 +# Rclone config dosyası konumu (container içinde) +RCLONE_CONFIG_PATH=/config/rclone/rclone.conf +# Google Drive mount edilecek dizin (container içinde) +RCLONE_MOUNT_DIR=/app/server/gdrive +# Rclone remote adı +RCLONE_REMOTE_NAME=dupe +# Google Drive içinde kullanılacak klasör adı +RCLONE_REMOTE_PATH=Dupe +# Rclone mount tazeleme/poll süresi +RCLONE_POLL_INTERVAL=1m +# Rclone dizin cache süresi +RCLONE_DIR_CACHE_TIME=1m +# Rclone VFS cache modu (off, minimal, writes, full) +RCLONE_VFS_CACHE_MODE=full +# Rclone VFS cache dizini +RCLONE_VFS_CACHE_DIR=/app/server/cache/rclone-vfs +# Rclone debug log (taşıma hatalarını detaylı loglamak için) +RCLONE_DEBUG_MODE_LOG=0 +# Media stream debug log (akış kaynağını loglamak için) +MEDIA_DEBUG_LOG=0 diff --git a/Dockerfile b/Dockerfile index 8cc139e..aa72cb9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,7 +8,7 @@ RUN npm run build # Build server FROM node:22-slim -RUN apt-get update && apt-get install -y ffmpeg curl aria2 && rm -rf /var/lib/apt/lists/* +RUN apt-get update && apt-get install -y ffmpeg curl aria2 rclone fuse3 && rm -rf /var/lib/apt/lists/* RUN curl -L https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp -o /usr/local/bin/yt-dlp \ && chmod a+rx /usr/local/bin/yt-dlp WORKDIR /app/server diff --git a/client/src/routes/Files.svelte b/client/src/routes/Files.svelte index a71a3d2..2332ee7 100644 --- a/client/src/routes/Files.svelte +++ b/client/src/routes/Files.svelte @@ -604,6 +604,27 @@ refreshMovieCount(); refreshTvShowCount(); } + + async function moveToGdrive(entry) { + if (!entry?.name) return; + const ok = confirm("Bu öğe GDrive'a taşınsın mı?"); + if (!ok) return; + try { + const resp = await apiFetch("/api/rclone/move", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ path: entry.name }) + }); + const data = await resp.json().catch(() => ({})); + if (!resp.ok || !data?.ok) { + alert(data?.error || "GDrive taşıma başarısız oldu."); + return; + } + await loadFiles(); + } catch (err) { + alert(err?.message || "GDrive taşıma başarısız oldu."); + } + } function formatSize(bytes) { if (!bytes) return "0 MB"; if (bytes < 1e6) return (bytes / 1e3).toFixed(1) + " KB"; @@ -1237,7 +1258,7 @@ const button = event.currentTarget; const rect = button.getBoundingClientRect(); const menuWidth = 160; - const menuHeight = 140; // Yaklaşık menü yüksekliği + const menuHeight = 180; // Yaklaşık menü yüksekliği // Üç noktanın son noktası ile menünün sol kenarını hizala // Düğme genişliği 34px, son nokta sağ kenara yakın @@ -2389,6 +2410,14 @@ {/if} + + + + + +
+ +
+ +
+ {#if rcloneAuthUrl} + + {/if} +
+ +
+ + +
+
+ + +
+
+ + +
+ +
+ + + +
+ + {#if rcloneStatus} +
+
Enabled: {rcloneStatus.enabled ? "Evet" : "Hayır"}
+
Mounted: {rcloneStatus.mounted ? "Evet" : "Hayır"}
+
Remote: {rcloneStatus.remoteConfigured ? "Hazır" : "Eksik"}
+ {#if rcloneStatus.lastError} +
Son hata: {rcloneStatus.lastError}
+ {/if} +
+ {/if} + + {#if error} +
+ + {error} +
+ {/if} + {#if success} +
+ + {success} +
+ {/if} + {:else if activeTab === "advanced"}
Gelişmiş ayarlar burada yer alacak.
{/if} diff --git a/client/src/routes/Transfers.svelte b/client/src/routes/Transfers.svelte index 0da202a..10e4d50 100644 --- a/client/src/routes/Transfers.svelte +++ b/client/src/routes/Transfers.svelte @@ -9,6 +9,8 @@ let totalDownloaded = 0; let totalDownloadSpeed = 0; let pollTimer; + let moveToGdriveDefault = false; + let loadingRcloneStatus = false; // Modal / player state let showModal = false; @@ -79,11 +81,28 @@ updateTransferStats(); } + async function loadRcloneStatus() { + loadingRcloneStatus = true; + try { + const resp = await apiFetch("/api/rclone/status"); + if (!resp.ok) return; + const data = await resp.json().catch(() => ({})); + if (typeof data?.autoMove === "boolean") { + moveToGdriveDefault = data.autoMove; + } + } catch (err) { + console.warn("Rclone durumu alınamadı:", err); + } finally { + loadingRcloneStatus = false; + } + } + async function upload(e) { const f = e.target.files?.[0]; if (!f) return; const fd = new FormData(); fd.append("torrent", f); + fd.append("moveToGdrive", moveToGdriveDefault ? "1" : "0"); await apiFetch("/api/transfer", { method: "POST", body: fd }); // ✅ await list(); } @@ -132,7 +151,7 @@ await apiFetch("/api/transfer", { method: "POST", headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ magnet: input }) + body: JSON.stringify({ magnet: input, moveToGdrive: moveToGdriveDefault }) }); await list(); return; @@ -142,7 +161,7 @@ const resp = await apiFetch("/api/youtube/download", { method: "POST", headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ url: normalizedYoutube }) + body: JSON.stringify({ url: normalizedYoutube, moveToGdrive: moveToGdriveDefault }) }); if (!resp.ok) { const data = await resp.json().catch(() => null); @@ -157,7 +176,7 @@ const resp = await apiFetch("/api/mailru/download", { method: "POST", headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ url: normalizedMailRu }) + body: JSON.stringify({ url: normalizedMailRu, moveToGdrive: moveToGdriveDefault }) }); if (!resp.ok) { const data = await resp.json().catch(() => null); @@ -369,6 +388,18 @@ } } + async function toggleTransferGdrive(infoHash, enabled) { + try { + await apiFetch(`/api/transfer/${infoHash}/gdrive`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ enabled: enabled ? "1" : "0" }) + }); + } catch (err) { + console.warn("GDrive toggle hatası:", err); + } + } + function streamURL(hash, index = 0) { const base = `${API}/stream/${hash}?index=${index}`; return withToken(base); @@ -581,6 +612,7 @@ for (const file of torrentsToUpload) { const fd = new FormData(); fd.append("torrent", file); + fd.append("moveToGdrive", moveToGdriveDefault ? "1" : "0"); await apiFetch("/api/transfer", { method: "POST", body: fd }); } @@ -603,6 +635,7 @@ onMount(() => { list(); // 🔒 token'lı liste çekimi wsConnect(); // 🔒 token'lı WebSocket + loadRcloneStatus(); addGlobalDragListeners(); const slider = document.querySelector(".volume-slider"); if (slider) { @@ -642,6 +675,14 @@ +
@@ -734,6 +775,15 @@ {/if}
+ {#if t.type === "torrent" || !t.type}