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:
218
server/server.js
218
server/server.js
@@ -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ı aşı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)) {
|
||||
|
||||
Reference in New Issue
Block a user