Compare commits
3 Commits
c61f1b0288
...
20da34beb2
| Author | SHA1 | Date | |
|---|---|---|---|
| 20da34beb2 | |||
| 2b5bb86b3e | |||
| 1e4fb38cfb |
@@ -468,6 +468,7 @@
|
||||
let pendingPlayTarget = null;
|
||||
let activeMenu = null; // Aktif menü öğesi
|
||||
let menuPosition = { top: 0, left: 0 }; // Menü pozisyonu
|
||||
let deleteConfirmPending = false; // Silme onayı beklemede mi
|
||||
let showMatchModal = false;
|
||||
let matchingFile = null;
|
||||
let matchTitle = "";
|
||||
@@ -1444,48 +1445,53 @@
|
||||
|
||||
async function deleteFile(item) {
|
||||
if (!item) return;
|
||||
const target = resolveDeletionTargets(item);
|
||||
if (!target) {
|
||||
|
||||
// Eğer onay beklemedeyse, silme işlemini gerçekleştir
|
||||
if (deleteConfirmPending) {
|
||||
const target = resolveDeletionTargets(item);
|
||||
if (!target) {
|
||||
closeMenu();
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await performDeletion(target);
|
||||
deleteConfirmPending = false; // Reset flag
|
||||
|
||||
if (!result.ok) {
|
||||
alert("Silme hatası: " + (result.error || "Bilinmeyen hata"));
|
||||
closeMenu();
|
||||
return;
|
||||
}
|
||||
|
||||
if (item.isDirectory) {
|
||||
const displayKey = normalizePath(
|
||||
item.displayPath ||
|
||||
(item.name?.startsWith("dir:") ? item.name.slice(4) : ""),
|
||||
);
|
||||
if (displayKey || displayKey === "") {
|
||||
pendingFolders.delete(displayKey);
|
||||
}
|
||||
}
|
||||
|
||||
await loadFiles();
|
||||
await Promise.all([refreshMovieCount(), refreshTvShowCount(), fetchTrashItems()]);
|
||||
selectedItems = new Set(
|
||||
[...selectedItems].filter((name) => name !== item.name),
|
||||
);
|
||||
closeMenu();
|
||||
return;
|
||||
}
|
||||
|
||||
const label =
|
||||
target.type === "directory"
|
||||
? target.label || item.displayName || "Klasör"
|
||||
: target.label || cleanFileName(item.name);
|
||||
const message =
|
||||
target.type === "directory"
|
||||
? `"${label}" klasörünü silmek istediğine emin misin?`
|
||||
: `"${label}" dosyasını silmek istediğinizden emin misiniz?`;
|
||||
|
||||
if (!confirm(message)) {
|
||||
closeMenu();
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await performDeletion(target);
|
||||
if (!result.ok) {
|
||||
alert("Silme hatası: " + (result.error || "Bilinmeyen hata"));
|
||||
closeMenu();
|
||||
return;
|
||||
}
|
||||
if (item.isDirectory) {
|
||||
const displayKey = normalizePath(
|
||||
item.displayPath ||
|
||||
(item.name?.startsWith("dir:") ? item.name.slice(4) : ""),
|
||||
);
|
||||
if (displayKey || displayKey === "") {
|
||||
pendingFolders.delete(displayKey);
|
||||
}
|
||||
// İlk tıklama - onay moduna geç
|
||||
deleteConfirmPending = true;
|
||||
}
|
||||
|
||||
await loadFiles();
|
||||
await Promise.all([refreshMovieCount(), refreshTvShowCount(), fetchTrashItems()]);
|
||||
selectedItems = new Set(
|
||||
[...selectedItems].filter((name) => name !== item.name),
|
||||
);
|
||||
closeMenu();
|
||||
// Menü kapandığında onay durumunu resetle
|
||||
function closeMenu() {
|
||||
activeMenu = null;
|
||||
deleteConfirmPending = false;
|
||||
showMatchModal = false;
|
||||
matchingFile = null;
|
||||
}
|
||||
|
||||
// Klasör oluşturma fonksiyonları
|
||||
@@ -2419,11 +2425,11 @@
|
||||
</button>
|
||||
<div class="menu-divider"></div>
|
||||
<button
|
||||
class="menu-item delete"
|
||||
class="menu-item delete {deleteConfirmPending ? 'confirming' : ''}"
|
||||
on:click|stopPropagation={() => deleteFile(activeMenu)}
|
||||
>
|
||||
<i class="fa-solid fa-trash"></i>
|
||||
<span>Sil</span>
|
||||
<span>{deleteConfirmPending ? 'Emin misiniz?' : 'Sil'}</span>
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -3979,7 +3985,17 @@
|
||||
.menu-item.delete {
|
||||
color: #e53935;
|
||||
}
|
||||
|
||||
|
||||
.menu-item.delete.confirming {
|
||||
color: #fff;
|
||||
background-color: #e53935;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.menu-item.delete.confirming:hover {
|
||||
background-color: #c62828;
|
||||
}
|
||||
|
||||
.menu-item.delete:hover {
|
||||
background-color: #ffebee;
|
||||
}
|
||||
|
||||
@@ -198,7 +198,24 @@
|
||||
if (!resp.ok || !data?.ok) {
|
||||
throw new Error(data?.error || `HTTP ${resp.status}`);
|
||||
}
|
||||
success = "Rclone mount başlatıldı.";
|
||||
|
||||
// Mount başlatıldı, birkaç saniye bekleyip tekrar kontrol et
|
||||
success = "Rclone mount başlatılıyor...";
|
||||
|
||||
// 2 saniye sonra status güncelle
|
||||
setTimeout(async () => {
|
||||
await loadRcloneStatus();
|
||||
// Status yüklendikten sonra mesajı güncelle
|
||||
if (rcloneStatus?.mounted) {
|
||||
success = "Rclone mount başarıyla başlatıldı.";
|
||||
} else if (rcloneStatus?.running) {
|
||||
success = "Rclone mount başlatıldı, mount tamamlanıyor...";
|
||||
} else {
|
||||
error = "Rclone mount başlatılamadı.";
|
||||
}
|
||||
}, 2000);
|
||||
|
||||
// İlk status güncellemesi
|
||||
await loadRcloneStatus();
|
||||
} catch (err) {
|
||||
error = err?.message || "Rclone mount başlatılamadı.";
|
||||
@@ -468,7 +485,17 @@
|
||||
<div class="card muted" style="margin-top:10px;">
|
||||
<div><strong>Durum:</strong></div>
|
||||
<div>Enabled: {rcloneStatus.enabled ? "Evet" : "Hayır"}</div>
|
||||
<div>Mounted: {rcloneStatus.mounted ? "Evet" : "Hayır"}</div>
|
||||
<div>
|
||||
Mounted:
|
||||
{#if rcloneStatus.mountStatus === "starting"}
|
||||
<span style="color: #f57c00;">Başlatılıyor...</span>
|
||||
{:else if rcloneStatus.mounted}
|
||||
<span style="color: #388e3c;">Evet</span>
|
||||
{:else}
|
||||
Hayır
|
||||
{/if}
|
||||
</div>
|
||||
<div>Running: {rcloneStatus.running ? "Evet" : "Hayır"}</div>
|
||||
<div>Remote: {rcloneStatus.remoteConfigured ? "Hazır" : "Eksik"}</div>
|
||||
{#if rcloneStatus.vfsCacheMode}
|
||||
<div>VFS Cache Mode: <code>{rcloneStatus.vfsCacheMode}</code></div>
|
||||
@@ -489,7 +516,14 @@
|
||||
</div>
|
||||
{/if}
|
||||
{#if rcloneStatus.lastError}
|
||||
<div style="margin-top: 8px; color: #d32f2f;">Son hata: {rcloneStatus.lastError}</div>
|
||||
<div style="margin-top: 8px; color: #d32f2f;">
|
||||
<i class="fa-solid fa-circle-exclamation"></i> Son hata: {rcloneStatus.lastError}
|
||||
</div>
|
||||
{/if}
|
||||
{#if rcloneStatus.lastLog && !rcloneStatus.lastError}
|
||||
<div style="margin-top: 8px; font-size: 11px; color: #666;">
|
||||
<i class="fa-solid fa-circle-info"></i> Son log: {rcloneStatus.lastLog}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
118
server/server.js
118
server/server.js
@@ -780,6 +780,7 @@ function resolveRootDir(rootFolder) {
|
||||
|
||||
let rcloneProcess = null;
|
||||
let rcloneLastError = null;
|
||||
let rcloneLastLogMessage = null; // Tüm log mesajları için (NOTICE dahil)
|
||||
const rcloneAuthSessions = new Map();
|
||||
let rcloneCacheCleanTimer = null;
|
||||
// Auto-restart sayaçları
|
||||
@@ -1276,12 +1277,34 @@ function startRcloneMount(settings) {
|
||||
|
||||
rcloneProcess.stdout.on("data", (data) => {
|
||||
const msg = data.toString().trim();
|
||||
if (msg) console.log(`🌀 rclone: ${msg}`);
|
||||
if (msg) {
|
||||
rcloneLastLogMessage = msg;
|
||||
// NOTICE mesajları için farklı ikon, diğerleri için normal
|
||||
if (msg.toUpperCase().includes("NOTICE")) {
|
||||
console.log(`📡 rclone: ${msg}`);
|
||||
} else {
|
||||
console.log(`🌀 rclone: ${msg}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
rcloneProcess.stderr.on("data", (data) => {
|
||||
const msg = data.toString().trim();
|
||||
if (msg) {
|
||||
rcloneLastError = msg;
|
||||
rcloneLastLogMessage = msg;
|
||||
// NOTICE ve INFO seviyesindeki loglar hata değil
|
||||
// Sadece ERROR, FATAL, CRITICAL seviyesindekileri "son hata" olarak işaretle
|
||||
const upperMsg = msg.toUpperCase();
|
||||
if (upperMsg.includes("ERROR") ||
|
||||
upperMsg.includes("FATAL") ||
|
||||
upperMsg.includes("CRITICAL") ||
|
||||
upperMsg.includes("FAILED") ||
|
||||
upperMsg.includes("COULDN'T") ||
|
||||
upperMsg.includes("CANNOT") ||
|
||||
upperMsg.includes("REFUSED") ||
|
||||
upperMsg.includes("TIMEOUT") ||
|
||||
upperMsg.includes("CONNECTION")) {
|
||||
rcloneLastError = msg;
|
||||
}
|
||||
console.warn(`⚠️ rclone: ${msg}`);
|
||||
}
|
||||
});
|
||||
@@ -7744,17 +7767,58 @@ app.delete("/api/file", requireAuth, (req, res) => {
|
||||
if (!filePath) return res.status(400).json({ error: "path gerekli" });
|
||||
|
||||
const safePath = sanitizeRelative(filePath);
|
||||
const fullPath = path.join(DOWNLOAD_DIR, safePath);
|
||||
let folderId = (safePath.split(/[\/]/)[0] || "").trim();
|
||||
let rootDir = folderId ? path.join(DOWNLOAD_DIR, folderId) : null;
|
||||
let folderIsDirectory = false;
|
||||
if (rootDir && fs.existsSync(rootDir)) {
|
||||
try {
|
||||
folderIsDirectory = fs.statSync(rootDir).isDirectory();
|
||||
} catch (err) {
|
||||
folderIsDirectory = false;
|
||||
|
||||
// Dosyanın nerede olduğunu bul (DOWNLOAD_DIR veya GDRIVE_ROOT)
|
||||
let fullPath = null;
|
||||
let storageBase = null; // Dosyanın bulunduğu storage base (DOWNLOAD_DIR veya GDRIVE_ROOT)
|
||||
|
||||
for (const baseDir of getStorageRoots()) {
|
||||
const testPath = path.join(baseDir, safePath);
|
||||
if (fs.existsSync(testPath)) {
|
||||
fullPath = testPath;
|
||||
storageBase = baseDir;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Dosya bulunamadı
|
||||
if (!fullPath) {
|
||||
return res.status(404).json({ error: "Dosya bulunamadı" });
|
||||
}
|
||||
|
||||
let folderId = (safePath.split(/[\/]/)[0] || "").trim();
|
||||
let rootDir = null;
|
||||
let folderIsDirectory = false;
|
||||
|
||||
// GDrive'da ise special handling - GDrive'ın kendisi root olarak kabul edilir
|
||||
const isGDriveFile = storageBase === GDRIVE_ROOT;
|
||||
|
||||
if (isGDriveFile) {
|
||||
// GDrive'da klasör yapısı farklıdır
|
||||
// folderId varsa ve GDRIVE_ROOT/folderId bir klasörse
|
||||
const testRootDir = path.join(GDRIVE_ROOT, folderId);
|
||||
if (folderId && fs.existsSync(testRootDir)) {
|
||||
try {
|
||||
folderIsDirectory = fs.statSync(testRootDir).isDirectory();
|
||||
if (folderIsDirectory) {
|
||||
rootDir = GDRIVE_ROOT; // GDrive root'u
|
||||
}
|
||||
} catch (err) {
|
||||
folderIsDirectory = false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Downloads klasörü için normal mantık
|
||||
rootDir = folderId ? path.join(DOWNLOAD_DIR, folderId) : null;
|
||||
if (rootDir && fs.existsSync(rootDir)) {
|
||||
try {
|
||||
folderIsDirectory = fs.statSync(rootDir).isDirectory();
|
||||
} catch (err) {
|
||||
folderIsDirectory = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Kök dosyalarda ilk segment dosya adıdır; klasör değilse root davranışı uygula
|
||||
if (folderId && !folderIsDirectory) {
|
||||
folderId = "";
|
||||
@@ -7783,6 +7847,31 @@ app.delete("/api/file", requireAuth, (req, res) => {
|
||||
const isDirectory = stats.isDirectory();
|
||||
const relWithinRoot = safePath.split(/[\\/]/).slice(1).join("/");
|
||||
let trashEntry = null;
|
||||
|
||||
// GDrive dosyaları için özel handling - doğrudan sil
|
||||
const isGDriveFile = storageBase === GDRIVE_ROOT;
|
||||
|
||||
if (isGDriveFile) {
|
||||
// GDrive dosyaları için doğrudan silme (trash sistemi yok)
|
||||
try {
|
||||
if (isDirectory) {
|
||||
fs.rmSync(fullPath, { recursive: true, force: true });
|
||||
console.log(`🗑️ GDrive klasör silindi: ${safePath}`);
|
||||
} else {
|
||||
fs.unlinkSync(fullPath);
|
||||
console.log(`🗑️ GDrive dosya silindi: ${safePath}`);
|
||||
}
|
||||
removeThumbnailsForPath(safePath);
|
||||
broadcastFileUpdate("gdrive");
|
||||
broadcastDiskSpace();
|
||||
return res.json({ ok: true, filesRemoved: true, deletedFrom: "gdrive" });
|
||||
} catch (deleteErr) {
|
||||
console.error(`❌ GDrive dosya silme hatası: ${deleteErr.message}`);
|
||||
return res.status(500).json({ error: `Silme hatası: ${deleteErr.message}` });
|
||||
}
|
||||
}
|
||||
|
||||
// Downloads klasörü için normal trash sistemi
|
||||
if (folderId && folderIsDirectory && rootDir) {
|
||||
const infoBeforeDelete = readInfoForRoot(folderId);
|
||||
mediaFlags = detectMediaFlagsForPath(
|
||||
@@ -9210,6 +9299,10 @@ app.get("/api/rclone/status", requireAuth, async (req, res) => {
|
||||
enabled: RCLONE_ENABLED,
|
||||
mounted,
|
||||
running: Boolean(rcloneProcess),
|
||||
// Mount durumu hakkında daha fazla bilgi
|
||||
mountStatus: !rcloneProcess ? "stopped" :
|
||||
mounted ? "mounted" :
|
||||
"starting", // Process çalışıyor ama mount henüz tamamlanmadı
|
||||
mountDir: settings.mountDir,
|
||||
remoteName: settings.remoteName,
|
||||
remotePath: settings.remotePath,
|
||||
@@ -9219,7 +9312,8 @@ app.get("/api/rclone/status", requireAuth, async (req, res) => {
|
||||
cacheCleanMinutes: settings.cacheCleanMinutes || 0,
|
||||
configExists: fs.existsSync(settings.configPath),
|
||||
remoteConfigured: rcloneConfigHasRemote(settings.remoteName),
|
||||
lastError: rcloneLastError || null,
|
||||
lastError: rcloneLastError || null, // Sadece gerçek hatalar
|
||||
lastLog: rcloneLastLogMessage || null, // Son log mesajı (NOTICE dahil)
|
||||
// Performans ayarları
|
||||
vfsCacheMode: RCLONE_VFS_CACHE_MODE,
|
||||
bufferSize: RCLONE_BUFFER_SIZE,
|
||||
|
||||
Reference in New Issue
Block a user