Download pause/resume özelliği eklendi.
This commit is contained in:
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
let torrents = [];
|
let torrents = [];
|
||||||
let ws;
|
let ws;
|
||||||
|
let isAllPaused = false;
|
||||||
|
|
||||||
// Modal / player state
|
// Modal / player state
|
||||||
let showModal = false;
|
let showModal = false;
|
||||||
@@ -26,7 +27,11 @@
|
|||||||
ws = new WebSocket(url);
|
ws = new WebSocket(url);
|
||||||
ws.onmessage = (e) => {
|
ws.onmessage = (e) => {
|
||||||
const d = JSON.parse(e.data);
|
const d = JSON.parse(e.data);
|
||||||
if (d.type === "progress") torrents = d.torrents || [];
|
if (d.type === "progress") {
|
||||||
|
torrents = d.torrents || [];
|
||||||
|
// Tüm torrentlerin pause durumunu kontrol et
|
||||||
|
updateAllPausedState();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,6 +39,7 @@
|
|||||||
const r = await apiFetch("/api/torrents"); // ✅ fetch yerine apiFetch
|
const r = await apiFetch("/api/torrents"); // ✅ fetch yerine apiFetch
|
||||||
if (!r.ok) return;
|
if (!r.ok) return;
|
||||||
torrents = await r.json();
|
torrents = await r.json();
|
||||||
|
updateAllPausedState();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function upload(e) {
|
async function upload(e) {
|
||||||
@@ -67,6 +73,79 @@
|
|||||||
await list();
|
await list();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function removeAllTorrents() {
|
||||||
|
if (!confirm("Tüm torrent listesini silmek istediğinizden emin misiniz?")) return;
|
||||||
|
|
||||||
|
// Tüm torrentleri API üzerinden sil
|
||||||
|
for (const torrent of torrents) {
|
||||||
|
await apiFetch(`/api/torrents/${torrent.infoHash}`, { method: "DELETE" });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listeyi güncelle
|
||||||
|
await list();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function toggleAllTorrents() {
|
||||||
|
const action = isAllPaused ? "resume" : "pause";
|
||||||
|
|
||||||
|
try {
|
||||||
|
const r = await apiFetch("/api/torrents/toggle-all", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ action })
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!r.ok) return;
|
||||||
|
|
||||||
|
const result = await r.json();
|
||||||
|
console.log(`${action} işlemi: ${result.updatedCount}/${result.totalCount} torrent güncellendi`);
|
||||||
|
|
||||||
|
// Durumu güncelle
|
||||||
|
isAllPaused = !isAllPaused;
|
||||||
|
|
||||||
|
// Listeyi yenile
|
||||||
|
await list();
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Toggle all torrents error:", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateAllPausedState() {
|
||||||
|
if (torrents.length === 0) {
|
||||||
|
isAllPaused = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Eğer tüm torrentler paused ise, global durumu paused yap
|
||||||
|
const allPaused = torrents.every(t => t.paused === true);
|
||||||
|
isAllPaused = allPaused;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function toggleSingleTorrent(hash) {
|
||||||
|
const torrent = torrents.find(t => t.infoHash === hash);
|
||||||
|
if (!torrent) return;
|
||||||
|
|
||||||
|
const action = torrent.paused ? "resume" : "pause";
|
||||||
|
|
||||||
|
try {
|
||||||
|
const r = await apiFetch(`/api/torrents/${hash}/toggle`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ action })
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!r.ok) return;
|
||||||
|
|
||||||
|
const result = await r.json();
|
||||||
|
console.log(`Single torrent ${action}:`, result);
|
||||||
|
|
||||||
|
// Listeyi güncelle
|
||||||
|
await list();
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Toggle single torrent error:", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function streamURL(hash, index = 0) {
|
function streamURL(hash, index = 0) {
|
||||||
const token = localStorage.getItem("token");
|
const token = localStorage.getItem("token");
|
||||||
return `${API}/stream/${hash}?index=${index}&token=${token}`;
|
return `${API}/stream/${hash}?index=${index}&token=${token}`;
|
||||||
@@ -303,19 +382,41 @@
|
|||||||
<section class="files">
|
<section class="files">
|
||||||
<h2>Transfers</h2>
|
<h2>Transfers</h2>
|
||||||
|
|
||||||
<div style="display:flex; gap:10px; margin-bottom:10px;">
|
<div style="display:flex; gap:10px; margin-bottom:10px; justify-content: space-between;">
|
||||||
<label class="btn-primary" style="cursor:pointer;">
|
<div style="display:flex; gap:10px;">
|
||||||
<i class="fa-solid fa-plus btn-icon"></i> NEW TRANSFER
|
<label class="btn-primary" style="cursor:pointer;">
|
||||||
<input
|
<i class="fa-solid fa-plus btn-icon"></i> NEW TRANSFER
|
||||||
type="file"
|
<input
|
||||||
accept=".torrent"
|
type="file"
|
||||||
on:change={upload}
|
accept=".torrent"
|
||||||
style="display:none;"
|
on:change={upload}
|
||||||
/>
|
style="display:none;"
|
||||||
</label>
|
/>
|
||||||
<label class="btn-primary" on:click={addMagnet}>
|
</label>
|
||||||
<i class="fa-solid fa-magnet btn-icon"></i> MAGNET
|
<label class="btn-primary" on:click={addMagnet}>
|
||||||
</label>
|
<i class="fa-solid fa-magnet btn-icon"></i> MAGNET
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div style="display:flex; gap:10px;">
|
||||||
|
<button
|
||||||
|
class="btn-toggle-all"
|
||||||
|
on:click={toggleAllTorrents}
|
||||||
|
title={isAllPaused ? "Resume All Torrents" : "Pause All Torrents"}
|
||||||
|
>
|
||||||
|
{#if isAllPaused}
|
||||||
|
<i class="fa-solid fa-play"></i>
|
||||||
|
{:else}
|
||||||
|
<i class="fa-solid fa-pause"></i>
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="btn-remove-all"
|
||||||
|
on:click={removeAllTorrents}
|
||||||
|
title="Remove All Torrent List"
|
||||||
|
>
|
||||||
|
<i class="fa-solid fa-trash"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if torrents.length === 0}
|
{#if torrents.length === 0}
|
||||||
@@ -358,11 +459,24 @@
|
|||||||
<div class="torrent-info">
|
<div class="torrent-info">
|
||||||
<div class="torrent-header">
|
<div class="torrent-header">
|
||||||
<div class="torrent-name">{t.name}</div>
|
<div class="torrent-name">{t.name}</div>
|
||||||
<button
|
<div style="display:flex; gap:5px;">
|
||||||
class="remove-btn"
|
<button
|
||||||
on:click|stopPropagation={() => removeTorrent(t.infoHash)}
|
class="toggle-btn"
|
||||||
title="Sil">❌</button
|
on:click|stopPropagation={() => toggleSingleTorrent(t.infoHash)}
|
||||||
>
|
title={t.paused ? "Devam Ettir" : "Durdur"}
|
||||||
|
>
|
||||||
|
{#if t.paused}
|
||||||
|
<i class="fa-solid fa-play"></i>
|
||||||
|
{:else}
|
||||||
|
<i class="fa-solid fa-pause"></i>
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="remove-btn"
|
||||||
|
on:click|stopPropagation={() => removeTorrent(t.infoHash)}
|
||||||
|
title="Sil">❌</button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="torrent-hash">
|
<div class="torrent-hash">
|
||||||
@@ -583,6 +697,22 @@
|
|||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.toggle-btn {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
font-size: 18px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: transform 0.15s;
|
||||||
|
color: #4caf50;
|
||||||
|
padding: 2px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-btn:hover {
|
||||||
|
transform: scale(1.2);
|
||||||
|
background: rgba(76, 175, 80, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
.remove-btn {
|
.remove-btn {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border: none;
|
border: none;
|
||||||
@@ -800,4 +930,79 @@
|
|||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* --- Toggle All Torrents Button --- */
|
||||||
|
.btn-toggle-all {
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
color: #666;
|
||||||
|
padding: 10px 14px;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 36px;
|
||||||
|
width: 36px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-toggle-all:hover {
|
||||||
|
background: var(--yellow);
|
||||||
|
border-color: var(--yellow-dark);
|
||||||
|
color: #222;
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-toggle-all:active {
|
||||||
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Remove All Torrent List Button --- */
|
||||||
|
.btn-remove-all {
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
color: #666;
|
||||||
|
padding: 10px 14px;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 36px;
|
||||||
|
width: 36px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-remove-all:hover {
|
||||||
|
background: #ff4444;
|
||||||
|
border-color: #cc0000;
|
||||||
|
color: white;
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-remove-all:active {
|
||||||
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive adjustments for toggle and remove buttons */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.btn-toggle-all,
|
||||||
|
.btn-remove-all {
|
||||||
|
height: 36px;
|
||||||
|
width: 36px;
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.btn-toggle-all,
|
||||||
|
.btn-remove-all {
|
||||||
|
height: 34px;
|
||||||
|
width: 34px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
209
server/server.js
209
server/server.js
@@ -2091,7 +2091,7 @@ function broadcastSnapshot() {
|
|||||||
// --- Snapshot (thumbnail dahil, tracker + tarih eklendi) ---
|
// --- Snapshot (thumbnail dahil, tracker + tarih eklendi) ---
|
||||||
function snapshot() {
|
function snapshot() {
|
||||||
return Array.from(torrents.values()).map(
|
return Array.from(torrents.values()).map(
|
||||||
({ torrent, selectedIndex, savePath, added }) => {
|
({ torrent, selectedIndex, savePath, added, paused }) => {
|
||||||
const rootFolder = path.basename(savePath);
|
const rootFolder = path.basename(savePath);
|
||||||
const bestVideoIndex = pickBestVideoFile(torrent);
|
const bestVideoIndex = pickBestVideoFile(torrent);
|
||||||
const bestVideo = torrent.files[bestVideoIndex];
|
const bestVideo = torrent.files[bestVideoIndex];
|
||||||
@@ -2110,12 +2110,13 @@ function snapshot() {
|
|||||||
name: torrent.name,
|
name: torrent.name,
|
||||||
progress: torrent.progress,
|
progress: torrent.progress,
|
||||||
downloaded: torrent.downloaded,
|
downloaded: torrent.downloaded,
|
||||||
downloadSpeed: torrent.downloadSpeed,
|
downloadSpeed: paused ? 0 : torrent.downloadSpeed, // Pause durumunda hız 0
|
||||||
uploadSpeed: torrent.uploadSpeed,
|
uploadSpeed: paused ? 0 : torrent.uploadSpeed, // Pause durumunda hız 0
|
||||||
numPeers: torrent.numPeers,
|
numPeers: paused ? 0 : torrent.numPeers, // Pause durumunda peer sayısı 0
|
||||||
tracker: torrent.announce?.[0] || null,
|
tracker: torrent.announce?.[0] || null,
|
||||||
added,
|
added,
|
||||||
savePath, // 🆕 BURASI!
|
savePath, // 🆕 BURASI!
|
||||||
|
paused: paused || false, // Pause durumunu ekle
|
||||||
files: torrent.files.map((f, i) => ({
|
files: torrent.files.map((f, i) => ({
|
||||||
index: i,
|
index: i,
|
||||||
name: f.name,
|
name: f.name,
|
||||||
@@ -2171,7 +2172,8 @@ app.post("/api/transfer", requireAuth, upload.single("torrent"), (req, res) => {
|
|||||||
torrent,
|
torrent,
|
||||||
selectedIndex: 0,
|
selectedIndex: 0,
|
||||||
savePath,
|
savePath,
|
||||||
added
|
added,
|
||||||
|
paused: false
|
||||||
});
|
});
|
||||||
|
|
||||||
// --- Metadata geldiğinde ---
|
// --- Metadata geldiğinde ---
|
||||||
@@ -2181,7 +2183,8 @@ app.post("/api/transfer", requireAuth, upload.single("torrent"), (req, res) => {
|
|||||||
torrent,
|
torrent,
|
||||||
selectedIndex,
|
selectedIndex,
|
||||||
savePath,
|
savePath,
|
||||||
added
|
added,
|
||||||
|
paused: false
|
||||||
});
|
});
|
||||||
const rootFolder = path.basename(savePath);
|
const rootFolder = path.basename(savePath);
|
||||||
upsertInfoFile(savePath, {
|
upsertInfoFile(savePath, {
|
||||||
@@ -2422,6 +2425,200 @@ app.delete("/api/torrents/:hash", requireAuth, (req, res) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function getPieceCount(torrent) {
|
||||||
|
if (!torrent) return 0;
|
||||||
|
if (Array.isArray(torrent.pieces)) return torrent.pieces.length;
|
||||||
|
const pieces = torrent.pieces;
|
||||||
|
if (pieces && typeof pieces.length === "number") return pieces.length;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function pauseTorrentEntry(entry) {
|
||||||
|
const torrent = entry?.torrent;
|
||||||
|
if (!torrent || torrent._destroyed || entry.paused) return false;
|
||||||
|
|
||||||
|
entry.previousSelection = entry.selectedIndex;
|
||||||
|
|
||||||
|
const pieceCount = getPieceCount(torrent);
|
||||||
|
if (pieceCount > 0 && typeof torrent.deselect === "function") {
|
||||||
|
try {
|
||||||
|
torrent.deselect(0, pieceCount - 1, 0);
|
||||||
|
} catch (err) {
|
||||||
|
console.warn("Torrent deselect failed during pause:", err.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(torrent.files)) {
|
||||||
|
for (const file of torrent.files) {
|
||||||
|
if (file && typeof file.deselect === "function") {
|
||||||
|
try {
|
||||||
|
file.deselect();
|
||||||
|
} catch (err) {
|
||||||
|
console.warn(
|
||||||
|
`File deselect failed during pause (${torrent.infoHash}):`,
|
||||||
|
err.message
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof torrent.pause === "function") {
|
||||||
|
try {
|
||||||
|
torrent.pause();
|
||||||
|
} catch (err) {
|
||||||
|
console.warn("Torrent pause method failed:", err.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
entry.paused = true;
|
||||||
|
entry.pausedAt = Date.now();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resumeTorrentEntry(entry) {
|
||||||
|
const torrent = entry?.torrent;
|
||||||
|
if (!torrent || torrent._destroyed || !entry.paused) return false;
|
||||||
|
|
||||||
|
const pieceCount = getPieceCount(torrent);
|
||||||
|
if (pieceCount > 0 && typeof torrent.select === "function") {
|
||||||
|
try {
|
||||||
|
torrent.select(0, pieceCount - 1, 0);
|
||||||
|
} catch (err) {
|
||||||
|
console.warn("Torrent select failed during resume:", err.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(torrent.files)) {
|
||||||
|
const preferredIndex =
|
||||||
|
entry.previousSelection !== undefined
|
||||||
|
? entry.previousSelection
|
||||||
|
: entry.selectedIndex ?? 0;
|
||||||
|
const targetFile =
|
||||||
|
torrent.files[preferredIndex] || torrent.files[0] || null;
|
||||||
|
if (targetFile && typeof targetFile.select === "function") {
|
||||||
|
try {
|
||||||
|
targetFile.select();
|
||||||
|
} catch (err) {
|
||||||
|
console.warn(
|
||||||
|
`File select failed during resume (${torrent.infoHash}):`,
|
||||||
|
err.message
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof torrent.resume === "function") {
|
||||||
|
try {
|
||||||
|
torrent.resume();
|
||||||
|
} catch (err) {
|
||||||
|
console.warn("Torrent resume method failed:", err.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
entry.paused = false;
|
||||||
|
delete entry.pausedAt;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Tüm torrentleri durdur/devam ettir ---
|
||||||
|
app.post("/api/torrents/toggle-all", requireAuth, (req, res) => {
|
||||||
|
try {
|
||||||
|
const { action } = req.body; // 'pause' veya 'resume'
|
||||||
|
|
||||||
|
if (!action || (action !== 'pause' && action !== 'resume')) {
|
||||||
|
return res.status(400).json({ error: "action 'pause' veya 'resume' olmalı" });
|
||||||
|
}
|
||||||
|
|
||||||
|
let updatedCount = 0;
|
||||||
|
const pausedTorrents = new Set();
|
||||||
|
|
||||||
|
for (const [infoHash, entry] of torrents.entries()) {
|
||||||
|
if (!entry?.torrent || entry.torrent._destroyed) continue;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const changed =
|
||||||
|
action === "pause"
|
||||||
|
? pauseTorrentEntry(entry)
|
||||||
|
: resumeTorrentEntry(entry);
|
||||||
|
if (changed) updatedCount++;
|
||||||
|
if (entry.paused) pausedTorrents.add(infoHash);
|
||||||
|
} catch (err) {
|
||||||
|
console.warn(
|
||||||
|
`⚠️ Torrent ${infoHash} ${action} işleminde hata:`,
|
||||||
|
err.message
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
global.pausedTorrents = pausedTorrents;
|
||||||
|
|
||||||
|
broadcastSnapshot();
|
||||||
|
res.json({
|
||||||
|
ok: true,
|
||||||
|
action,
|
||||||
|
updatedCount,
|
||||||
|
totalCount: torrents.size
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error("❌ Toggle all torrents error:", err.message);
|
||||||
|
res.status(500).json({ error: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// --- Tek torrent'i durdur/devam ettir ---
|
||||||
|
app.post("/api/torrents/:hash/toggle", requireAuth, (req, res) => {
|
||||||
|
try {
|
||||||
|
const { action } = req.body; // 'pause' veya 'resume'
|
||||||
|
const infoHash = req.params.hash;
|
||||||
|
|
||||||
|
if (!action || (action !== 'pause' && action !== 'resume')) {
|
||||||
|
return res.status(400).json({ error: "action 'pause' veya 'resume' olmalı" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const entry = torrents.get(infoHash);
|
||||||
|
if (!entry || !entry.torrent || entry.torrent._destroyed) {
|
||||||
|
return res.status(404).json({ error: "torrent bulunamadı" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const changed =
|
||||||
|
action === "pause"
|
||||||
|
? pauseTorrentEntry(entry)
|
||||||
|
: resumeTorrentEntry(entry);
|
||||||
|
|
||||||
|
if (!changed) {
|
||||||
|
const message =
|
||||||
|
action === "pause"
|
||||||
|
? "Torrent zaten durdurulmuş"
|
||||||
|
: "Torrent zaten devam ediyor";
|
||||||
|
return res.json({
|
||||||
|
ok: true,
|
||||||
|
action,
|
||||||
|
infoHash,
|
||||||
|
paused: entry.paused,
|
||||||
|
message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const pausedTorrents = new Set();
|
||||||
|
for (const [hash, item] of torrents.entries()) {
|
||||||
|
if (item?.paused) pausedTorrents.add(hash);
|
||||||
|
}
|
||||||
|
global.pausedTorrents = pausedTorrents;
|
||||||
|
|
||||||
|
broadcastSnapshot();
|
||||||
|
res.json({
|
||||||
|
ok: true,
|
||||||
|
action,
|
||||||
|
infoHash,
|
||||||
|
paused: entry.paused
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error("❌ Toggle single torrent error:", err.message);
|
||||||
|
res.status(500).json({ error: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// --- GENEL MEDYA SUNUMU (🆕 resimler + videolar) ---
|
// --- GENEL MEDYA SUNUMU (🆕 resimler + videolar) ---
|
||||||
app.get("/media/:path(*)", requireAuth, (req, res) => {
|
app.get("/media/:path(*)", requireAuth, (req, res) => {
|
||||||
const relPath = req.params.path;
|
const relPath = req.params.path;
|
||||||
|
|||||||
Reference in New Issue
Block a user