From 0fa3a818ae15c632a3d1eff5380842f30aee0688 Mon Sep 17 00:00:00 2001 From: wisecolt Date: Mon, 2 Feb 2026 11:35:05 +0300 Subject: [PATCH] feat(rclone): Google Drive entegrasyonu ekle MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Dockerfile ve docker-compose yapılandırması Rclone ve FUSE için güncellendi. Backend API'leri Rclone durumunu, ayarlarını, yetkilendirmesini ve mount işlemlerini yönetmek için eklendi. İndirmeler tamamlandığında (Torrent, YouTube, Mail.ru) dosyaların otomatik veya manuel olarak Google Drive'a taşınması sağlandı. Dosya sistemi hem yerel hem de mount edilmiş GDrive yollarını destekleyecek şekilde güncellendi. Ayarlar ve Dosyalar arayüzüne ilgili kontroller eklendi. --- .env.example | 24 + Dockerfile | 2 +- client/src/routes/Files.svelte | 31 +- client/src/routes/Settings.svelte | 270 ++++++++ client/src/routes/Transfers.svelte | 84 ++- docker-compose.yml | 16 + server/server.js | 1036 +++++++++++++++++++++++++--- 7 files changed, 1349 insertions(+), 114 deletions(-) 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}