feat(rclone): akıllı cache yönetimi ve streaming performans ayarları ekle

Disk doluluk oranını izleyen ve otomatik temizleme yapan akıllı cache sistemi
eklendi. Streaming performansı için buffer size, VFS read ahead ve chunk size
ayarları yapılandırılabilir hale getirildi. Rclone crash durumunda otomatik
yeniden başlatma mekanizması eklendi. UI'da disk kullanım bilgileri ve VFS
cache modu görüntülenmeye başlandı.
This commit is contained in:
2026-02-02 21:58:32 +03:00
parent e34b8fc024
commit c61f1b0288
4 changed files with 290 additions and 20 deletions

View File

@@ -64,6 +64,26 @@ const RCLONE_VFS_CACHE_MAX_SIZE =
process.env.RCLONE_VFS_CACHE_MAX_SIZE || "20G";
const RCLONE_VFS_CACHE_MAX_AGE =
process.env.RCLONE_VFS_CACHE_MAX_AGE || "24h";
// --- Streaming performans ayarları ---
const RCLONE_BUFFER_SIZE = process.env.RCLONE_BUFFER_SIZE || "8M";
const RCLONE_VFS_READ_AHEAD = process.env.RCLONE_VFS_READ_AHEAD || "128M";
const RCLONE_VFS_READ_CHUNK_SIZE = process.env.RCLONE_VFS_READ_CHUNK_SIZE || "32M";
const RCLONE_VFS_READ_CHUNK_SIZE_LIMIT = process.env.RCLONE_VFS_READ_CHUNK_SIZE_LIMIT || "64M";
// Disk doluluk oranı eşik değeri (百分比) - Bu oran aşıldığında cache temizlenir
const RCLONE_CACHE_CLEAN_THRESHOLD =
Number(process.env.RCLONE_CACHE_CLEAN_THRESHOLD) || 85;
// Cache temizleme sırasında korunacak minimum boş alan (GB)
const RCLONE_MIN_FREE_SPACE_GB =
Number(process.env.RCLONE_MIN_FREE_SPACE_GB) || 5;
// Auto-restart enable/disable
const RCLONE_AUTO_RESTART = ["1", "true", "yes", "on"].includes(
String(process.env.RCLONE_AUTO_RESTART || "1").toLowerCase()
);
// Auto-restart için retry sayısı ve delay
const RCLONE_AUTO_RESTART_MAX_RETRIES =
Number(process.env.RCLONE_AUTO_RESTART_MAX_RETRIES) || 5;
const RCLONE_AUTO_RESTART_DELAY_MS =
Number(process.env.RCLONE_AUTO_RESTART_DELAY_MS) || 5000;
const MEDIA_DEBUG_LOG = ["1", "true", "yes", "on"].includes(
String(process.env.MEDIA_DEBUG_LOG || "").toLowerCase()
);
@@ -762,6 +782,9 @@ let rcloneProcess = null;
let rcloneLastError = null;
const rcloneAuthSessions = new Map();
let rcloneCacheCleanTimer = null;
// Auto-restart sayaçları
let rcloneRestartCount = 0;
let rcloneRestartInProgress = false;
function logRcloneMoveError(context, error) {
if (!error) return;
@@ -1029,17 +1052,17 @@ function startRcloneCacheCleanSchedule(minutes) {
if (!interval || interval <= 0) return;
rcloneCacheCleanTimer = setInterval(() => {
if (!RCLONE_RC_ENABLED) return;
fetch(`http://${RCLONE_RC_ADDR}/vfs/refresh`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ recursive: true })
// Query string format kullan
const params = new URLSearchParams();
params.append("recursive", "true");
fetch(`http://${RCLONE_RC_ADDR}/vfs/refresh?${params.toString()}`, {
method: "POST"
})
.then((resp) => {
if (resp.status === 404) {
return fetch(`http://${RCLONE_RC_ADDR}/rc/vfs/refresh`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ recursive: true })
return fetch(`http://${RCLONE_RC_ADDR}/rc/vfs/refresh?${params.toString()}`, {
method: "POST"
});
}
return resp;
@@ -1057,16 +1080,21 @@ async function runRcloneCacheClean() {
const wasRunning = Boolean(rcloneProcess && !rcloneProcess.killed);
try {
if (wasRunning && RCLONE_RC_ENABLED) {
const resp = await fetch(`http://${RCLONE_RC_ADDR}/vfs/refresh`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ recursive: true })
// Rclone RC API doğru format: Query string kullanılır
// POST /vfs/refresh with form data: recursive=true
const params = new URLSearchParams();
params.append("recursive", "true");
const resp = await fetch(`http://${RCLONE_RC_ADDR}/vfs/refresh?${params.toString()}`, {
method: "POST"
});
if (resp.status === 404) {
const fallback = await fetch(`http://${RCLONE_RC_ADDR}/rc/vfs/refresh`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ recursive: true })
// Fallback for older rclone versions
const fallbackParams = new URLSearchParams();
fallbackParams.append("recursive", "true");
const fallback = await fetch(`http://${RCLONE_RC_ADDR}/rc/vfs/refresh?${fallbackParams.toString()}`, {
method: "POST"
});
if (!fallback.ok) {
const body = await fallback.text();
@@ -1083,6 +1111,7 @@ async function runRcloneCacheClean() {
return { ok: false, error: "Rclone RC kapalıyken mount durdurulmadan cache temizlenemez." };
}
// RC kapalıysa dosya sisteminden temizle
fs.rmSync(RCLONE_VFS_CACHE_DIR, { recursive: true, force: true });
fs.mkdirSync(RCLONE_VFS_CACHE_DIR, { recursive: true });
return { ok: true, method: "fs", restarted: false };
@@ -1091,6 +1120,64 @@ async function runRcloneCacheClean() {
}
}
// --- Akıllı cache yönetimi ---
/**
* Disk alanını kontrol eder ve gerekirse cache temizler
* @returns {Promise<{ok: boolean, cleaned: boolean, diskUsage: object, message: string}>}
*/
async function checkAndCleanCacheIfNeeded() {
try {
const diskInfo = await getDiskSpace(RCLONE_VFS_CACHE_DIR);
const usedPercent = diskInfo.usedPercent || 0;
const availableGB = parseFloat(diskInfo.availableGB) || 0;
const shouldClean = usedPercent >= RCLONE_CACHE_CLEAN_THRESHOLD || availableGB < RCLONE_MIN_FREE_SPACE_GB;
if (!shouldClean) {
return {
ok: true,
cleaned: false,
diskUsage: { usedPercent, availableGB, threshold: RCLONE_CACHE_CLEAN_THRESHOLD, minFreeGB: RCLONE_MIN_FREE_SPACE_GB },
message: `Disk durumu iyi (${usedPercent}% kullanılıyor, ${availableGB}GB boş)`
};
}
console.warn(`⚠️ Disk doluluk oranı yüksek (${usedPercent}%) veya boş alan az (${availableGB}GB). Cache temizleniyor...`);
const result = await runRcloneCacheClean();
if (result.ok) {
// Temizleme sonrası disk durumunu tekrar kontrol et
const newDiskInfo = await getDiskSpace(RCLONE_VFS_CACHE_DIR);
return {
ok: true,
cleaned: true,
diskUsage: {
before: { usedPercent, availableGB },
after: { usedPercent: newDiskInfo.usedPercent, availableGB: parseFloat(newDiskInfo.availableGB) }
},
message: `Cache temizlendi. Öncesi: ${usedPercent}%, Sonrası: ${newDiskInfo.usedPercent}%`,
method: result.method
};
} else {
return {
ok: false,
cleaned: false,
error: result.error,
message: `Cache temizleme başarısız: ${result.error}`
};
}
} catch (err) {
return {
ok: false,
cleaned: false,
error: err?.message || String(err),
message: `Cache kontrolü başarısız: ${err?.message}`
};
}
}
function isRcloneMounted(mountDir) {
if (!mountDir) return false;
try {
@@ -1157,6 +1244,14 @@ function startRcloneMount(settings) {
RCLONE_VFS_CACHE_MAX_SIZE,
"--vfs-cache-max-age",
RCLONE_VFS_CACHE_MAX_AGE,
"--buffer-size",
RCLONE_BUFFER_SIZE,
"--vfs-read-ahead",
RCLONE_VFS_READ_AHEAD,
"--vfs-read-chunk-size",
RCLONE_VFS_READ_CHUNK_SIZE,
"--vfs-read-chunk-size-limit",
RCLONE_VFS_READ_CHUNK_SIZE_LIMIT,
"--dir-cache-time",
RCLONE_DIR_CACHE_TIME,
"--poll-interval",
@@ -1190,10 +1285,39 @@ function startRcloneMount(settings) {
console.warn(`⚠️ rclone: ${msg}`);
}
});
rcloneProcess.on("exit", (code) => {
rcloneProcess.on("exit", async (code) => {
if (code !== 0) {
rcloneLastError = `rclone exit: ${code}`;
console.warn(`⚠️ rclone mount durdu (code ${code})`);
// Auto-restart mekanizması
if (RCLONE_AUTO_RESTART && !rcloneRestartInProgress) {
const settings = loadRcloneSettings();
if (settings.autoMount && rcloneRestartCount < RCLONE_AUTO_RESTART_MAX_RETRIES) {
rcloneRestartInProgress = true;
rcloneRestartCount++;
console.warn(`🔄 Rclone otomatik yeniden başlatılıyor (${rcloneRestartCount}/${RCLONE_AUTO_RESTART_MAX_RETRIES})...`);
// Bekle ve yeniden başlat
setTimeout(async () => {
const result = startRcloneMount(settings);
if (result.ok) {
console.log(`✅ Rclone başarıyla yeniden başlatıldı.`);
rcloneRestartCount = 0; // Başarılı olunca sayacı sıfırla
} else {
console.error(`❌ Rclone yeniden başlatılamadı: ${result.error}`);
}
rcloneRestartInProgress = false;
}, RCLONE_AUTO_RESTART_DELAY_MS);
} else if (rcloneRestartCount >= RCLONE_AUTO_RESTART_MAX_RETRIES) {
console.error(`❌ Rclone yeniden başlatma sayısııldı (${RCLONE_AUTO_RESTART_MAX_RETRIES}). Otomatik yeniden başlatma devre dışı.`);
}
}
} else {
// Normal exit (code 0) - sayacı sıfırla
rcloneRestartCount = 0;
}
rcloneProcess = null;
});
@@ -9068,6 +9192,20 @@ app.post("/api/youtube/settings", requireAuth, (req, res) => {
app.get("/api/rclone/status", requireAuth, async (req, res) => {
const settings = loadRcloneSettings();
const mounted = isRcloneMounted(settings.mountDir);
// Disk durumunu da ekle
let diskUsage = null;
try {
const diskInfo = await getDiskSpace(RCLONE_VFS_CACHE_DIR);
diskUsage = {
usedPercent: diskInfo.usedPercent || 0,
availableGB: parseFloat(diskInfo.availableGB) || 0,
totalGB: parseFloat(diskInfo.totalGB) || 0
};
} catch (err) {
// Disk bilgisi alınamazsa null kalsın
}
res.json({
enabled: RCLONE_ENABLED,
mounted,
@@ -9081,7 +9219,18 @@ 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,
// Performans ayarları
vfsCacheMode: RCLONE_VFS_CACHE_MODE,
bufferSize: RCLONE_BUFFER_SIZE,
vfsReadAhead: RCLONE_VFS_READ_AHEAD,
vfsReadChunkSize: RCLONE_VFS_READ_CHUNK_SIZE,
vfsReadChunkSizeLimit: RCLONE_VFS_READ_CHUNK_SIZE_LIMIT,
// Disk kullanımı
diskUsage,
// Cache temizleme threshold
cacheCleanThreshold: RCLONE_CACHE_CLEAN_THRESHOLD,
minFreeSpaceGB: RCLONE_MIN_FREE_SPACE_GB
});
});
@@ -9223,6 +9372,15 @@ app.post("/api/rclone/cache/clean", requireAuth, async (req, res) => {
return res.json({ ok: true, ...result });
});
// Akıllı cache kontrolü - disk durumunu kontrol eder ve gerekirse temizler
app.post("/api/rclone/cache/check-and-clean", requireAuth, async (req, res) => {
const result = await checkAndCleanCacheIfNeeded();
if (!result.ok) {
return res.status(500).json({ ok: false, error: result.error, message: result.message });
}
return res.json({ ok: true, ...result });
});
app.get("/api/rclone/conf", requireAuth, (req, res) => {
try {
if (!fs.existsSync(RCLONE_CONFIG_PATH)) {
@@ -10526,6 +10684,20 @@ if (WEBDAV_ENABLED) {
// --- ☁️ Rclone auto mount ---
const initialRcloneSettings = loadRcloneSettings();
// Başlangıçta disk kontrolü yap - cache temizleme gerekirse yap
if (RCLONE_ENABLED) {
checkAndCleanCacheIfNeeded().then(result => {
if (result.cleaned) {
console.log(`🧹 Başlangıç cache temizlemesi: ${result.message}`);
} else {
console.log(`✅ Disk durumu: ${result.message}`);
}
}).catch(err => {
console.warn(`⚠️ Başlangıç cache kontrolü başarısız: ${err.message}`);
});
}
if (RCLONE_ENABLED && initialRcloneSettings.autoMount) {
const result = startRcloneMount(initialRcloneSettings);
if (!result.ok) {
@@ -10534,6 +10706,16 @@ if (RCLONE_ENABLED && initialRcloneSettings.autoMount) {
}
startRcloneCacheCleanSchedule(initialRcloneSettings.cacheCleanMinutes || 0);
// --- Disk alanı izleme - periyodik kontrol (her 5 dakikada bir) ---
setInterval(async () => {
if (RCLONE_ENABLED) {
const result = await checkAndCleanCacheIfNeeded();
if (result.cleaned) {
console.log(`🧹 Otomatik cache temizlemesi: ${result.message}`);
}
}
}, 5 * 60 * 1000);
// --- ✅ Client build (frontend) dosyalarını sun ---
const publicDir = path.join(__dirname, "public");
if (fs.existsSync(publicDir)) {