Grid/list seçim modunu ekle, meta bilgileri göster ve seçili öğeler için toplu silme butonu ekle.
This commit is contained in:
139
server/server.js
139
server/server.js
@@ -38,6 +38,7 @@ for (const dir of [THUMBNAIL_DIR, VIDEO_THUMB_ROOT, IMAGE_THUMB_ROOT]) {
|
||||
const VIDEO_THUMBNAIL_TIME = process.env.VIDEO_THUMBNAIL_TIME || "00:00:05";
|
||||
const VIDEO_EXTS = [".mp4", ".webm", ".mkv", ".mov", ".m4v"];
|
||||
const generatingThumbnails = new Set();
|
||||
const INFO_FILENAME = "info.json";
|
||||
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
@@ -59,6 +60,70 @@ function ensureDirForFile(filePath) {
|
||||
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
||||
}
|
||||
|
||||
function infoFilePath(savePath) {
|
||||
return path.join(savePath, INFO_FILENAME);
|
||||
}
|
||||
|
||||
function readInfoFile(savePath) {
|
||||
const target = infoFilePath(savePath);
|
||||
if (!fs.existsSync(target)) return null;
|
||||
try {
|
||||
return JSON.parse(fs.readFileSync(target, "utf-8"));
|
||||
} catch (err) {
|
||||
console.warn(`⚠️ info.json okunamadı (${target}): ${err.message}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function upsertInfoFile(savePath, partial) {
|
||||
const target = infoFilePath(savePath);
|
||||
try {
|
||||
ensureDirForFile(target);
|
||||
let current = {};
|
||||
if (fs.existsSync(target)) {
|
||||
try {
|
||||
current = JSON.parse(fs.readFileSync(target, "utf-8")) || {};
|
||||
} catch (err) {
|
||||
console.warn(`⚠️ info.json parse edilemedi (${target}): ${err.message}`);
|
||||
}
|
||||
}
|
||||
const timestamp = Date.now();
|
||||
const next = {
|
||||
...current,
|
||||
...partial,
|
||||
updatedAt: timestamp
|
||||
};
|
||||
if (!next.createdAt) {
|
||||
next.createdAt =
|
||||
current.createdAt ?? partial?.createdAt ?? timestamp;
|
||||
}
|
||||
if (!next.added && partial?.added) {
|
||||
next.added = partial.added;
|
||||
}
|
||||
if (!next.folder) {
|
||||
next.folder = path.basename(savePath);
|
||||
}
|
||||
fs.writeFileSync(target, JSON.stringify(next, null, 2), "utf-8");
|
||||
return next;
|
||||
} catch (err) {
|
||||
console.warn(`⚠️ info.json yazılamadı (${target}): ${err.message}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function readInfoForRoot(rootFolder) {
|
||||
const safe = sanitizeRelative(rootFolder);
|
||||
if (!safe) return null;
|
||||
const target = path.join(DOWNLOAD_DIR, safe, INFO_FILENAME);
|
||||
if (!fs.existsSync(target)) return null;
|
||||
try {
|
||||
return JSON.parse(fs.readFileSync(target, "utf-8"));
|
||||
} catch (err) {
|
||||
console.warn(`⚠️ info.json okunamadı (${target}): ${err.message}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function sanitizeRelative(relPath) {
|
||||
return relPath.replace(/^[\\/]+/, "");
|
||||
}
|
||||
@@ -181,14 +246,28 @@ function cleanupEmptyDirs(startDir) {
|
||||
while (
|
||||
dir &&
|
||||
dir.startsWith(THUMBNAIL_DIR) &&
|
||||
fs.existsSync(dir) &&
|
||||
fs.lstatSync(dir).isDirectory()
|
||||
fs.existsSync(dir)
|
||||
) {
|
||||
const entries = fs.readdirSync(dir);
|
||||
if (entries.length > 0) break;
|
||||
fs.rmdirSync(dir);
|
||||
dir = path.dirname(dir);
|
||||
if (dir === THUMBNAIL_DIR || dir === path.dirname(THUMBNAIL_DIR)) break;
|
||||
try {
|
||||
const stat = fs.lstatSync(dir);
|
||||
if (!stat.isDirectory()) break;
|
||||
const entries = fs.readdirSync(dir);
|
||||
if (entries.length > 0) break;
|
||||
fs.rmdirSync(dir);
|
||||
} catch (err) {
|
||||
console.warn(`⚠️ Thumbnail klasörü temizlenemedi (${dir}): ${err.message}`);
|
||||
break;
|
||||
}
|
||||
const parent = path.dirname(dir);
|
||||
if (
|
||||
!parent ||
|
||||
parent === dir ||
|
||||
parent.length < THUMBNAIL_DIR.length ||
|
||||
parent === THUMBNAIL_DIR
|
||||
) {
|
||||
break;
|
||||
}
|
||||
dir = parent;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -314,6 +393,16 @@ app.post("/api/transfer", requireAuth, upload.single("torrent"), (req, res) => {
|
||||
savePath,
|
||||
added
|
||||
});
|
||||
const rootFolder = path.basename(savePath);
|
||||
upsertInfoFile(savePath, {
|
||||
infoHash: torrent.infoHash,
|
||||
name: torrent.name,
|
||||
tracker: torrent.announce?.[0] || null,
|
||||
added,
|
||||
createdAt: added,
|
||||
folder: rootFolder
|
||||
});
|
||||
broadcastFileUpdate(rootFolder);
|
||||
res.json({
|
||||
ok: true,
|
||||
infoHash: torrent.infoHash,
|
||||
@@ -362,6 +451,13 @@ app.post("/api/transfer", requireAuth, upload.single("torrent"), (req, res) => {
|
||||
console.warn("⚠️ Eski thumbnail klasörü temizlenemedi:", err.message);
|
||||
}
|
||||
|
||||
upsertInfoFile(entry.savePath, {
|
||||
completedAt: Date.now(),
|
||||
totalBytes: torrent.downloaded,
|
||||
fileCount: torrent.files.length
|
||||
});
|
||||
broadcastFileUpdate(rootFolder);
|
||||
|
||||
broadcastSnapshot();
|
||||
});
|
||||
} catch (err) {
|
||||
@@ -546,6 +642,16 @@ app.get("/api/files", requireAuth, (req, res) => {
|
||||
);
|
||||
};
|
||||
|
||||
const infoCache = new Map();
|
||||
const getInfo = (relPath) => {
|
||||
const root = rootFromRelPath(relPath);
|
||||
if (!root) return null;
|
||||
if (!infoCache.has(root)) {
|
||||
infoCache.set(root, readInfoForRoot(root));
|
||||
}
|
||||
return infoCache.get(root);
|
||||
};
|
||||
|
||||
// --- 📁 Klasörleri dolaş ---
|
||||
const walk = (dir) => {
|
||||
let result = [];
|
||||
@@ -561,6 +667,8 @@ app.get("/api/files", requireAuth, (req, res) => {
|
||||
if (entry.isDirectory()) {
|
||||
result = result.concat(walk(full));
|
||||
} else {
|
||||
if (entry.name.toLowerCase() === INFO_FILENAME) continue;
|
||||
|
||||
const size = fs.statSync(full).size;
|
||||
const type = mime.lookup(full) || "application/octet-stream";
|
||||
|
||||
@@ -588,12 +696,27 @@ app.get("/api/files", requireAuth, (req, res) => {
|
||||
else queueImageThumbnail(full, safeRel);
|
||||
}
|
||||
|
||||
const info = getInfo(safeRel) || {};
|
||||
const rootFolder = rootFromRelPath(safeRel);
|
||||
const added =
|
||||
info.added ?? info.createdAt ?? null;
|
||||
const completedAt = info.completedAt ?? null;
|
||||
const tracker = info.tracker ?? null;
|
||||
const torrentName = info.name ?? null;
|
||||
const infoHash = info.infoHash ?? null;
|
||||
|
||||
result.push({
|
||||
name: safeRel,
|
||||
size,
|
||||
type,
|
||||
url,
|
||||
thumbnail: thumb
|
||||
thumbnail: thumb,
|
||||
rootFolder,
|
||||
added,
|
||||
completedAt,
|
||||
tracker,
|
||||
torrentName,
|
||||
infoHash
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user