Compare commits
3 Commits
c61f1b0288
...
20da34beb2
| Author | SHA1 | Date | |
|---|---|---|---|
| 20da34beb2 | |||
| 2b5bb86b3e | |||
| 1e4fb38cfb |
@@ -468,6 +468,7 @@
|
|||||||
let pendingPlayTarget = null;
|
let pendingPlayTarget = null;
|
||||||
let activeMenu = null; // Aktif menü öğesi
|
let activeMenu = null; // Aktif menü öğesi
|
||||||
let menuPosition = { top: 0, left: 0 }; // Menü pozisyonu
|
let menuPosition = { top: 0, left: 0 }; // Menü pozisyonu
|
||||||
|
let deleteConfirmPending = false; // Silme onayı beklemede mi
|
||||||
let showMatchModal = false;
|
let showMatchModal = false;
|
||||||
let matchingFile = null;
|
let matchingFile = null;
|
||||||
let matchTitle = "";
|
let matchTitle = "";
|
||||||
@@ -1444,48 +1445,53 @@
|
|||||||
|
|
||||||
async function deleteFile(item) {
|
async function deleteFile(item) {
|
||||||
if (!item) return;
|
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();
|
closeMenu();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const label =
|
// İlk tıklama - onay moduna geç
|
||||||
target.type === "directory"
|
deleteConfirmPending = true;
|
||||||
? 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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await loadFiles();
|
// Menü kapandığında onay durumunu resetle
|
||||||
await Promise.all([refreshMovieCount(), refreshTvShowCount(), fetchTrashItems()]);
|
function closeMenu() {
|
||||||
selectedItems = new Set(
|
activeMenu = null;
|
||||||
[...selectedItems].filter((name) => name !== item.name),
|
deleteConfirmPending = false;
|
||||||
);
|
showMatchModal = false;
|
||||||
closeMenu();
|
matchingFile = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Klasör oluşturma fonksiyonları
|
// Klasör oluşturma fonksiyonları
|
||||||
@@ -2419,11 +2425,11 @@
|
|||||||
</button>
|
</button>
|
||||||
<div class="menu-divider"></div>
|
<div class="menu-divider"></div>
|
||||||
<button
|
<button
|
||||||
class="menu-item delete"
|
class="menu-item delete {deleteConfirmPending ? 'confirming' : ''}"
|
||||||
on:click|stopPropagation={() => deleteFile(activeMenu)}
|
on:click|stopPropagation={() => deleteFile(activeMenu)}
|
||||||
>
|
>
|
||||||
<i class="fa-solid fa-trash"></i>
|
<i class="fa-solid fa-trash"></i>
|
||||||
<span>Sil</span>
|
<span>{deleteConfirmPending ? 'Emin misiniz?' : 'Sil'}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
@@ -3980,6 +3986,16 @@
|
|||||||
color: #e53935;
|
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 {
|
.menu-item.delete:hover {
|
||||||
background-color: #ffebee;
|
background-color: #ffebee;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -198,7 +198,24 @@
|
|||||||
if (!resp.ok || !data?.ok) {
|
if (!resp.ok || !data?.ok) {
|
||||||
throw new Error(data?.error || `HTTP ${resp.status}`);
|
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();
|
await loadRcloneStatus();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
error = err?.message || "Rclone mount başlatılamadı.";
|
error = err?.message || "Rclone mount başlatılamadı.";
|
||||||
@@ -468,7 +485,17 @@
|
|||||||
<div class="card muted" style="margin-top:10px;">
|
<div class="card muted" style="margin-top:10px;">
|
||||||
<div><strong>Durum:</strong></div>
|
<div><strong>Durum:</strong></div>
|
||||||
<div>Enabled: {rcloneStatus.enabled ? "Evet" : "Hayır"}</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>
|
<div>Remote: {rcloneStatus.remoteConfigured ? "Hazır" : "Eksik"}</div>
|
||||||
{#if rcloneStatus.vfsCacheMode}
|
{#if rcloneStatus.vfsCacheMode}
|
||||||
<div>VFS Cache Mode: <code>{rcloneStatus.vfsCacheMode}</code></div>
|
<div>VFS Cache Mode: <code>{rcloneStatus.vfsCacheMode}</code></div>
|
||||||
@@ -489,7 +516,14 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if rcloneStatus.lastError}
|
{#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}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
118
server/server.js
118
server/server.js
@@ -780,6 +780,7 @@ function resolveRootDir(rootFolder) {
|
|||||||
|
|
||||||
let rcloneProcess = null;
|
let rcloneProcess = null;
|
||||||
let rcloneLastError = null;
|
let rcloneLastError = null;
|
||||||
|
let rcloneLastLogMessage = null; // Tüm log mesajları için (NOTICE dahil)
|
||||||
const rcloneAuthSessions = new Map();
|
const rcloneAuthSessions = new Map();
|
||||||
let rcloneCacheCleanTimer = null;
|
let rcloneCacheCleanTimer = null;
|
||||||
// Auto-restart sayaçları
|
// Auto-restart sayaçları
|
||||||
@@ -1276,12 +1277,34 @@ function startRcloneMount(settings) {
|
|||||||
|
|
||||||
rcloneProcess.stdout.on("data", (data) => {
|
rcloneProcess.stdout.on("data", (data) => {
|
||||||
const msg = data.toString().trim();
|
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) => {
|
rcloneProcess.stderr.on("data", (data) => {
|
||||||
const msg = data.toString().trim();
|
const msg = data.toString().trim();
|
||||||
if (msg) {
|
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}`);
|
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" });
|
if (!filePath) return res.status(400).json({ error: "path gerekli" });
|
||||||
|
|
||||||
const safePath = sanitizeRelative(filePath);
|
const safePath = sanitizeRelative(filePath);
|
||||||
const fullPath = path.join(DOWNLOAD_DIR, safePath);
|
|
||||||
let folderId = (safePath.split(/[\/]/)[0] || "").trim();
|
// Dosyanın nerede olduğunu bul (DOWNLOAD_DIR veya GDRIVE_ROOT)
|
||||||
let rootDir = folderId ? path.join(DOWNLOAD_DIR, folderId) : null;
|
let fullPath = null;
|
||||||
let folderIsDirectory = false;
|
let storageBase = null; // Dosyanın bulunduğu storage base (DOWNLOAD_DIR veya GDRIVE_ROOT)
|
||||||
if (rootDir && fs.existsSync(rootDir)) {
|
|
||||||
try {
|
for (const baseDir of getStorageRoots()) {
|
||||||
folderIsDirectory = fs.statSync(rootDir).isDirectory();
|
const testPath = path.join(baseDir, safePath);
|
||||||
} catch (err) {
|
if (fs.existsSync(testPath)) {
|
||||||
folderIsDirectory = false;
|
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
|
// Kök dosyalarda ilk segment dosya adıdır; klasör değilse root davranışı uygula
|
||||||
if (folderId && !folderIsDirectory) {
|
if (folderId && !folderIsDirectory) {
|
||||||
folderId = "";
|
folderId = "";
|
||||||
@@ -7783,6 +7847,31 @@ app.delete("/api/file", requireAuth, (req, res) => {
|
|||||||
const isDirectory = stats.isDirectory();
|
const isDirectory = stats.isDirectory();
|
||||||
const relWithinRoot = safePath.split(/[\\/]/).slice(1).join("/");
|
const relWithinRoot = safePath.split(/[\\/]/).slice(1).join("/");
|
||||||
let trashEntry = null;
|
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) {
|
if (folderId && folderIsDirectory && rootDir) {
|
||||||
const infoBeforeDelete = readInfoForRoot(folderId);
|
const infoBeforeDelete = readInfoForRoot(folderId);
|
||||||
mediaFlags = detectMediaFlagsForPath(
|
mediaFlags = detectMediaFlagsForPath(
|
||||||
@@ -9210,6 +9299,10 @@ app.get("/api/rclone/status", requireAuth, async (req, res) => {
|
|||||||
enabled: RCLONE_ENABLED,
|
enabled: RCLONE_ENABLED,
|
||||||
mounted,
|
mounted,
|
||||||
running: Boolean(rcloneProcess),
|
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,
|
mountDir: settings.mountDir,
|
||||||
remoteName: settings.remoteName,
|
remoteName: settings.remoteName,
|
||||||
remotePath: settings.remotePath,
|
remotePath: settings.remotePath,
|
||||||
@@ -9219,7 +9312,8 @@ app.get("/api/rclone/status", requireAuth, async (req, res) => {
|
|||||||
cacheCleanMinutes: settings.cacheCleanMinutes || 0,
|
cacheCleanMinutes: settings.cacheCleanMinutes || 0,
|
||||||
configExists: fs.existsSync(settings.configPath),
|
configExists: fs.existsSync(settings.configPath),
|
||||||
remoteConfigured: rcloneConfigHasRemote(settings.remoteName),
|
remoteConfigured: rcloneConfigHasRemote(settings.remoteName),
|
||||||
lastError: rcloneLastError || null,
|
lastError: rcloneLastError || null, // Sadece gerçek hatalar
|
||||||
|
lastLog: rcloneLastLogMessage || null, // Son log mesajı (NOTICE dahil)
|
||||||
// Performans ayarları
|
// Performans ayarları
|
||||||
vfsCacheMode: RCLONE_VFS_CACHE_MODE,
|
vfsCacheMode: RCLONE_VFS_CACHE_MODE,
|
||||||
bufferSize: RCLONE_BUFFER_SIZE,
|
bufferSize: RCLONE_BUFFER_SIZE,
|
||||||
|
|||||||
Reference in New Issue
Block a user