diff --git a/client/src/components/Sidebar.svelte b/client/src/components/Sidebar.svelte index 38ee8b6..f21789d 100644 --- a/client/src/components/Sidebar.svelte +++ b/client/src/components/Sidebar.svelte @@ -22,6 +22,7 @@ let hasMusic = false; // Store subscription'ı temizlemek için let unsubscribeDiskSpace; + let diskSpaceWs; // Store'u değişkene bağla unsubscribeDiskSpace = diskSpaceStore.subscribe(value => { @@ -59,6 +60,9 @@ const unsubscribeMusic = musicCount.subscribe((count) => { if (unsubscribeDiskSpace) { unsubscribeDiskSpace(); } + if (diskSpaceWs && (diskSpaceWs.readyState === WebSocket.OPEN || diskSpaceWs.readyState === WebSocket.CONNECTING)) { + diskSpaceWs.close(); + } }); // Menü öğesine tıklanınca sidebar'ı kapat @@ -96,10 +100,9 @@ const unsubscribeMusic = musicCount.subscribe((count) => { const wsUrl = `${wsProtocol}//${wsHost}`; console.log('🔌 Connecting to WebSocket at:', wsUrl); - // WebSocket bağlantısını global olarak saklayalım - window.diskSpaceWs = new WebSocket(wsUrl); + diskSpaceWs = new WebSocket(wsUrl); - window.diskSpaceWs.onmessage = (event) => { + diskSpaceWs.onmessage = (event) => { try { const data = JSON.parse(event.data); console.log('WebSocket message received:', data); @@ -112,23 +115,17 @@ const unsubscribeMusic = musicCount.subscribe((count) => { } }; - window.diskSpaceWs.onopen = () => { + diskSpaceWs.onopen = () => { console.log('WebSocket connected for disk space updates'); }; - window.diskSpaceWs.onerror = (error) => { + diskSpaceWs.onerror = (error) => { console.error('WebSocket error:', error); }; - window.diskSpaceWs.onclose = () => { + diskSpaceWs.onclose = () => { console.log('WebSocket disconnected'); }; - - onDestroy(() => { - if (window.diskSpaceWs && (window.diskSpaceWs.readyState === WebSocket.OPEN || window.diskSpaceWs.readyState === WebSocket.CONNECTING)) { - window.diskSpaceWs.close(); - } - }); }); diff --git a/server/server.js b/server/server.js index cfb3084..9923d07 100644 --- a/server/server.js +++ b/server/server.js @@ -24,6 +24,9 @@ const torrents = new Map(); const youtubeJobs = new Map(); let wss; const PORT = process.env.PORT || 3001; +const DEBUG_CPU = process.env.DEBUG_CPU === "1"; +const DISABLE_MEDIA_PROCESSING = process.env.DISABLE_MEDIA_PROCESSING === "1"; +const AUTO_PAUSE_ON_COMPLETE = process.env.AUTO_PAUSE_ON_COMPLETE === "1"; // --- İndirilen dosyalar için klasör oluştur --- const DOWNLOAD_DIR = path.join(__dirname, "downloads"); @@ -108,10 +111,40 @@ const FFPROBE_MAX_BUFFER = : 10 * 1024 * 1024; const AVATAR_PATH = path.join(__dirname, "..", "client", "src", "assets", "avatar.png"); +function getWsClientCount() { + if (!wss) return 0; + let count = 0; + wss.clients.forEach((c) => { + if (c.readyState === 1) count += 1; + }); + return count; +} + +function startCpuProfiler() { + if (!DEBUG_CPU) return; + const intervalMs = 5000; + let lastUsage = process.cpuUsage(); + let lastTime = process.hrtime.bigint(); + setInterval(() => { + const usage = process.cpuUsage(); + const now = process.hrtime.bigint(); + const deltaUser = usage.user - lastUsage.user; + const deltaSystem = usage.system - lastUsage.system; + const elapsedUs = Number(now - lastTime) / 1000; + const cpuPct = elapsedUs > 0 ? ((deltaUser + deltaSystem) / elapsedUs) * 100 : 0; + lastUsage = usage; + lastTime = now; + console.log( + `📈 CPU ${(cpuPct || 0).toFixed(1)}% | torrents:${torrents.size} yt:${youtubeJobs.size} ws:${getWsClientCount()}` + ); + }, intervalMs); +} + app.use(cors()); app.use(express.json()); app.use(express.urlencoded({ extended: true })); app.use("/downloads", express.static(DOWNLOAD_DIR)); +startCpuProfiler(); // --- En uygun video dosyasını seç --- function pickBestVideoFile(torrent) { @@ -1368,6 +1401,7 @@ function bytesFromHuman(value, unit = "B") { } async function extractMediaInfo(filePath, retryCount = 0) { + if (DISABLE_MEDIA_PROCESSING) return null; if (!filePath || !fs.existsSync(filePath)) return null; // Farklı ffprobe stratejileri @@ -1486,6 +1520,7 @@ async function extractMediaInfo(filePath, retryCount = 0) { } function queueVideoThumbnail(fullPath, relPath, retryCount = 0) { + if (DISABLE_MEDIA_PROCESSING) return; const { relThumb, absThumb } = getVideoThumbnailPaths(relPath); if (fs.existsSync(absThumb) || generatingThumbnails.has(absThumb)) return; @@ -1544,6 +1579,7 @@ function queueVideoThumbnail(fullPath, relPath, retryCount = 0) { } function queueImageThumbnail(fullPath, relPath, retryCount = 0) { + if (DISABLE_MEDIA_PROCESSING) return; const { relThumb, absThumb } = getImageThumbnailPaths(relPath); if (fs.existsSync(absThumb) || generatingThumbnails.has(absThumb)) return; @@ -2398,6 +2434,14 @@ async function ensureMovieData( bestVideoPath, precomputedMediaInfo = null ) { + if (DISABLE_MEDIA_PROCESSING) { + return { + mediaInfo: precomputedMediaInfo || null, + metadata: null, + cacheKey: null, + videoPath: bestVideoPath ? normalizeTrashPath(bestVideoPath) : null + }; + } const normalizedRoot = sanitizeRelative(rootFolder); if (!TMDB_API_KEY) { return { @@ -2910,6 +2954,7 @@ async function ensureSeriesData( seriesInfo, mediaInfo ) { + if (DISABLE_MEDIA_PROCESSING) return null; if (!TVDB_API_KEY || !seriesInfo) { console.log("📺 TVDB atlandı (eksik anahtar ya da seriesInfo yok):", { rootFolder, @@ -4211,6 +4256,7 @@ let pendingMediaRescan = { movies: false, tv: false }; let lastMediaRescanReason = "manual"; function queueMediaRescan({ movies = false, tv = false, reason = "manual" } = {}) { + if (DISABLE_MEDIA_PROCESSING) return; if (!movies && !tv) return; pendingMediaRescan.movies = pendingMediaRescan.movies || movies; pendingMediaRescan.tv = pendingMediaRescan.tv || tv; @@ -4578,7 +4624,7 @@ async function onTorrentDone({ torrent }) { }; const seriesInfo = parseSeriesInfo(file.name); - if (seriesInfo) { + if (seriesInfo && !DISABLE_MEDIA_PROCESSING) { try { const ensured = await ensureSeriesData( rootFolder, @@ -4665,53 +4711,53 @@ async function onTorrentDone({ torrent }) { infoUpdate.seriesEpisodes = seriesEpisodes; } - const ensuredMedia = await ensureMovieData( - rootFolder, - displayName, - bestVideoPath, - primaryMediaInfo - ); - if (ensuredMedia?.mediaInfo) { - infoUpdate.primaryMediaInfo = ensuredMedia.mediaInfo; - if (!infoUpdate.files) infoUpdate.files = perFileMetadata; - if (bestVideoPath) { - const entry = infoUpdate.files[bestVideoPath] || {}; - infoUpdate.files[bestVideoPath] = { - ...entry, - movieMatch: ensuredMedia.metadata - ? { - id: ensuredMedia.metadata.id ?? null, - title: - ensuredMedia.metadata.title || - ensuredMedia.metadata.matched_title || - displayName, - year: ensuredMedia.metadata.release_date - ? Number( - ensuredMedia.metadata.release_date.slice(0, 4) - ) - : ensuredMedia.metadata.matched_year || null, - poster: ensuredMedia.metadata.poster_path || null, - backdrop: ensuredMedia.metadata.backdrop_path || null, - cacheKey: ensuredMedia.cacheKey || null, - matchedAt: Date.now() - } - : entry.movieMatch - }; - const movieType = determineMediaType({ - tracker: torrent.announce?.[0] || null, - movieMatch: ensuredMedia.metadata, - seriesEpisode: seriesEpisodes[bestVideoPath] || null, - categories: null, - relPath: bestVideoPath, - audioOnly: false - }); - perFileMetadata[bestVideoPath] = { - ...(perFileMetadata[bestVideoPath] || {}), - type: movieType - }; - infoUpdate.files[bestVideoPath].type = movieType; + if (!DISABLE_MEDIA_PROCESSING) { + const ensuredMedia = await ensureMovieData( + rootFolder, + displayName, + bestVideoPath, + primaryMediaInfo + ); + if (ensuredMedia?.mediaInfo) { + infoUpdate.primaryMediaInfo = ensuredMedia.mediaInfo; + if (!infoUpdate.files) infoUpdate.files = perFileMetadata; + if (bestVideoPath) { + const fileEntry = infoUpdate.files[bestVideoPath] || {}; + infoUpdate.files[bestVideoPath] = { + ...fileEntry, + movieMatch: ensuredMedia.metadata + ? { + id: ensuredMedia.metadata.id ?? null, + title: + ensuredMedia.metadata.title || + ensuredMedia.metadata.matched_title || + displayName, + year: ensuredMedia.metadata.release_date + ? Number(ensuredMedia.metadata.release_date.slice(0, 4)) + : ensuredMedia.metadata.matched_year || null, + poster: ensuredMedia.metadata.poster_path || null, + backdrop: ensuredMedia.metadata.backdrop_path || null, + cacheKey: ensuredMedia.cacheKey || null, + matchedAt: Date.now() + } + : fileEntry.movieMatch + }; + const movieType = determineMediaType({ + tracker: torrent.announce?.[0] || null, + movieMatch: ensuredMedia.metadata, + seriesEpisode: seriesEpisodes[bestVideoPath] || null, + categories: null, + relPath: bestVideoPath, + audioOnly: false + }); + perFileMetadata[bestVideoPath] = { + ...(perFileMetadata[bestVideoPath] || {}), + type: movieType + }; + infoUpdate.files[bestVideoPath].type = movieType; + } + } } -} upsertInfoFile(entry.savePath, infoUpdate); broadcastFileUpdate(rootFolder); @@ -4719,6 +4765,13 @@ async function onTorrentDone({ torrent }) { // Torrent tamamlandığında disk space bilgisini güncelle broadcastDiskSpace(); + if (AUTO_PAUSE_ON_COMPLETE) { + const paused = pauseTorrentEntry(entry); + if (paused) { + console.log(`⏸️ Torrent otomatik durduruldu: ${torrent.infoHash}`); + } + } + // Medya tespiti tamamlandığında özel bildirim gönder if (Object.keys(seriesEpisodes).length > 0 || infoUpdate.primaryMediaInfo) { if (wss) { @@ -5923,6 +5976,10 @@ app.get("/api/movies", requireAuth, (req, res) => { }); async function rebuildMovieMetadata({ clearCache = false, resetSeriesData = false } = {}) { + if (DISABLE_MEDIA_PROCESSING) { + console.log("🎬 Medya işlemleri kapalı; movie metadata taraması atlandı."); + return []; + } if (!TMDB_API_KEY) { throw new Error("TMDB API key tanımlı değil."); } @@ -6746,6 +6803,10 @@ app.get("/api/music", requireAuth, (req, res) => { }); async function rebuildTvMetadata({ clearCache = false } = {}) { + if (DISABLE_MEDIA_PROCESSING) { + console.log("📺 Medya işlemleri kapalı; TV metadata taraması atlandı."); + return []; + } if (!TVDB_API_KEY) { throw new Error("TVDB API erişimi için gerekli anahtar tanımlı değil."); }