Git Komut Rehberi & Terminal Asistanı
Sıfırdan Yeni Bir Depo Başlatın:
Mevcut Bir Git Deposu veya Klasörü Gönderin:
Hataları Geri Alma, İptal & Temizlik:
Son commit'i geri al (yapılan değişiklikleri korur):
git reset --soft HEAD~1
Yapılan yerel değişiklikleri iptal et (çalışma ağacını temizler):
git restore .
Takip edilmeyen gereksiz dosyaları sil:
git clean -fd
Değişiklikleri geçici sakla ve geri getir (stash):
git stash git stash pop
Dal (Branch) ve Sürüm Etiketleri:
Yeni dal aç ve geçiş yap:
git checkout -b yeni-ozellik git push -u origin yeni-ozellik
Sürüm etiketi (tag) oluştur ve gönder:
git tag -a v1.0.0 -m "Sürüm 1.0.0" git push origin --tags
feat(storage): add configurable max-disk-usage-percent auto-pruner setting & update APK download filename (v1.5.9)
This commit is contained in:
@@ -4962,7 +4962,7 @@ try {
|
|||||||
apkBtn.id = 'yaktube-apk-download-btn';
|
apkBtn.id = 'yaktube-apk-download-btn';
|
||||||
apkBtn.href = '/yaktube.apk';
|
apkBtn.href = '/yaktube.apk';
|
||||||
apkBtn.className = 'yaktube-apk-btn';
|
apkBtn.className = 'yaktube-apk-btn';
|
||||||
apkBtn.setAttribute('download', 'YakTube_Mobile_v1.0.apk');
|
apkBtn.setAttribute('download', 'YakTube.apk');
|
||||||
apkBtn.setAttribute('aria-label', 'YakTube Android APK İndir (4.5 MB)');
|
apkBtn.setAttribute('aria-label', 'YakTube Android APK İndir (4.5 MB)');
|
||||||
apkBtn.style.cssText =
|
apkBtn.style.cssText =
|
||||||
'background: #1e293b; color: #ff8f37; border: 1px solid #ff8f37; padding: 6px 12px; border-radius: 20px; font-weight: 700; font-size: 11px; text-decoration: none; display: inline-flex; align-items: center; gap: 4px; margin-right: 6px; transition: all 0.2s; box-shadow: 0 4px 12px rgba(0,0,0,0.3);';
|
'background: #1e293b; color: #ff8f37; border: 1px solid #ff8f37; padding: 6px 12px; border-radius: 20px; font-weight: 700; font-size: 11px; text-decoration: none; display: inline-flex; align-items: center; gap: 4px; margin-right: 6px; transition: all 0.2s; box-shadow: 0 4px 12px rgba(0,0,0,0.3);';
|
||||||
@@ -4996,7 +4996,7 @@ try {
|
|||||||
sidebarLink.id = 'yaktube-sidebar-apk-link';
|
sidebarLink.id = 'yaktube-sidebar-apk-link';
|
||||||
sidebarLink.href = '/yaktube.apk';
|
sidebarLink.href = '/yaktube.apk';
|
||||||
sidebarLink.className = 'menu-link item-link';
|
sidebarLink.className = 'menu-link item-link';
|
||||||
sidebarLink.setAttribute('download', 'YakTube_Mobile_v1.0.apk');
|
sidebarLink.setAttribute('download', 'YakTube.apk');
|
||||||
sidebarLink.setAttribute('aria-label', 'YakTube Android Uygulamasını İndir (4.5 MB)');
|
sidebarLink.setAttribute('aria-label', 'YakTube Android Uygulamasını İndir (4.5 MB)');
|
||||||
sidebarLink.style.cssText =
|
sidebarLink.style.cssText =
|
||||||
'display: flex; align-items: center; gap: 10px; padding: 10px 16px; margin: 8px 12px; background: rgba(255, 143, 55, 0.1); color: #ff8f37; border: 1px solid rgba(255, 143, 55, 0.3); border-radius: 8px; font-weight: 700; font-size: 13px; text-decoration: none;';
|
'display: flex; align-items: center; gap: 10px; padding: 10px 16px; margin: 8px 12px; background: rgba(255, 143, 55, 0.1); color: #ff8f37; border: 1px solid rgba(255, 143, 55, 0.3); border-radius: 8px; font-weight: 700; font-size: 13px; text-decoration: none;';
|
||||||
|
|||||||
@@ -95,10 +95,13 @@ function postInnerTubeJSON(url, data) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let pruneIntervalTimer = null;
|
||||||
|
|
||||||
async function register({ getRouter, registerSetting, settingsManager, peertubeHelpers, logger }) {
|
async function register({ getRouter, registerSetting, settingsManager, peertubeHelpers, logger }) {
|
||||||
const router = getRouter();
|
const router = getRouter();
|
||||||
|
|
||||||
let guestImportLimit = 5;
|
let guestImportLimit = 5;
|
||||||
|
let maxDiskUsagePercent = 85;
|
||||||
let customChannelId = '';
|
let customChannelId = '';
|
||||||
let customYtDlpPath = '';
|
let customYtDlpPath = '';
|
||||||
|
|
||||||
@@ -113,6 +116,16 @@ async function register({ getRouter, registerSetting, settingsManager, peertubeH
|
|||||||
default: '5'
|
default: '5'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
registerSetting({
|
||||||
|
name: 'max-disk-usage-percent',
|
||||||
|
label: 'Otomatik Disk Temizleme Sınırı (%)',
|
||||||
|
type: 'input',
|
||||||
|
description:
|
||||||
|
'Sunucu diski bu doluluk yüzdesine (Örn: 85) ulaştığında, en eski ve en az izlenen videolar otomatik olarak silinerek yer açılır. Otomatik silmeyi kapatmak için 0 yazın.',
|
||||||
|
private: false,
|
||||||
|
default: '85'
|
||||||
|
});
|
||||||
|
|
||||||
registerSetting({
|
registerSetting({
|
||||||
name: 'default-channel-id',
|
name: 'default-channel-id',
|
||||||
label: 'Varsayılan Video Kanalı ID (Opsiyonel)',
|
label: 'Varsayılan Video Kanalı ID (Opsiyonel)',
|
||||||
@@ -146,6 +159,16 @@ async function register({ getRouter, registerSetting, settingsManager, peertubeH
|
|||||||
}
|
}
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const diskVal = await settingsManager.getSetting('max-disk-usage-percent');
|
||||||
|
if (diskVal !== undefined && diskVal !== null && String(diskVal).trim() !== '') {
|
||||||
|
const parsedDisk = parseInt(String(diskVal).trim(), 10);
|
||||||
|
maxDiskUsagePercent = isNaN(parsedDisk) || parsedDisk < 0 ? 85 : Math.min(99, parsedDisk);
|
||||||
|
} else {
|
||||||
|
maxDiskUsagePercent = 85;
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const chVal = await settingsManager.getSetting('default-channel-id');
|
const chVal = await settingsManager.getSetting('default-channel-id');
|
||||||
customChannelId = chVal ? String(chVal).trim() : '';
|
customChannelId = chVal ? String(chVal).trim() : '';
|
||||||
@@ -404,10 +427,178 @@ async function register({ getRouter, registerSetting, settingsManager, peertubeH
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1. Quota Config Route
|
// Universal Disk Storage & Auto-Pruner (Works on ANY PeerTube server without bridge.js)
|
||||||
|
const MIN_FREE_GB = 25;
|
||||||
|
const MIN_VIDEO_AGE_MS = 7 * 24 * 60 * 60 * 1000;
|
||||||
|
|
||||||
|
function getStorageCheckPath() {
|
||||||
|
try {
|
||||||
|
if (
|
||||||
|
peertubeHelpers &&
|
||||||
|
peertubeHelpers.plugin &&
|
||||||
|
typeof peertubeHelpers.plugin.getDataDirectoryPath === 'function'
|
||||||
|
) {
|
||||||
|
let dir = peertubeHelpers.plugin.getDataDirectoryPath();
|
||||||
|
while (dir && dir !== path.dirname(dir)) {
|
||||||
|
if (fs.existsSync(dir)) return dir;
|
||||||
|
dir = path.dirname(dir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
if (process.platform === 'win32' && fs.existsSync('D:\\yaktube_storage')) {
|
||||||
|
return 'D:\\';
|
||||||
|
}
|
||||||
|
return process.cwd();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getStorageStats() {
|
||||||
|
await loadPluginSettings();
|
||||||
|
const checkPath = getStorageCheckPath();
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
fs.statfs(checkPath, (err, stats) => {
|
||||||
|
if (err) return reject(err);
|
||||||
|
const totalBytes = stats.blocks * stats.bsize;
|
||||||
|
const freeBytes = stats.bfree * stats.bsize;
|
||||||
|
const usedBytes = totalBytes - freeBytes;
|
||||||
|
const totalGB = totalBytes / (1024 * 1024 * 1024);
|
||||||
|
const freeGB = freeBytes / (1024 * 1024 * 1024);
|
||||||
|
const usedGB = usedBytes / (1024 * 1024 * 1024);
|
||||||
|
const usedPercent = totalBytes > 0 ? (usedBytes / totalBytes) * 100 : 0;
|
||||||
|
const isLowSpace = maxDiskUsagePercent > 0 && (usedPercent >= maxDiskUsagePercent || freeGB < MIN_FREE_GB);
|
||||||
|
resolve({
|
||||||
|
totalBytes,
|
||||||
|
freeBytes,
|
||||||
|
usedBytes,
|
||||||
|
totalGB: Number(totalGB.toFixed(2)),
|
||||||
|
freeGB: Number(freeGB.toFixed(2)),
|
||||||
|
usedGB: Number(usedGB.toFixed(2)),
|
||||||
|
usedPercent: Number(usedPercent.toFixed(1)),
|
||||||
|
maxDiskUsagePercent,
|
||||||
|
isLowSpace
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checkAndPruneStorage() {
|
||||||
|
try {
|
||||||
|
let stats = await getStorageStats();
|
||||||
|
if (maxDiskUsagePercent <= 0) {
|
||||||
|
return { pruned: 0, freedGB: 0, status: 'Auto-pruning disabled (max-disk-usage-percent = 0)' };
|
||||||
|
}
|
||||||
|
if (!stats.isLowSpace) {
|
||||||
|
return {
|
||||||
|
pruned: 0,
|
||||||
|
freedGB: 0,
|
||||||
|
status: `Disk space healthy (${stats.freeGB} GB free, ${stats.usedPercent}% used, limit: ${maxDiskUsagePercent}%)`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetPercent = Math.max(10, maxDiskUsagePercent - 10);
|
||||||
|
if (logger) {
|
||||||
|
logger.info(
|
||||||
|
`[YakTube Auto-Pruner] Low disk space detected! Used: ${stats.usedPercent}% (Limit: ${maxDiskUsagePercent}%), Free: ${stats.freeGB} GB. Starting cleanup...`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = await getAdminToken();
|
||||||
|
const { hostname, port, hostHeader } = getLocalServerTarget();
|
||||||
|
const vids = await dbQuery(
|
||||||
|
`SELECT id, uuid, name, views, "createdAt"
|
||||||
|
FROM "video"
|
||||||
|
WHERE remote = false
|
||||||
|
ORDER BY views ASC, "createdAt" ASC
|
||||||
|
LIMIT 100`
|
||||||
|
);
|
||||||
|
|
||||||
|
const now = Date.now();
|
||||||
|
let prunedCount = 0;
|
||||||
|
|
||||||
|
for (const vid of vids) {
|
||||||
|
const createdAt = new Date(vid.createdAt).getTime();
|
||||||
|
if (now - createdAt < MIN_VIDEO_AGE_MS) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (logger) {
|
||||||
|
logger.info(`[YakTube Auto-Pruner] Pruning unused video: "${vid.name}" (ID: ${vid.id}, Views: ${vid.views})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
await new Promise(resolve => {
|
||||||
|
const delReq = http.request(
|
||||||
|
{
|
||||||
|
hostname,
|
||||||
|
port,
|
||||||
|
path: '/api/v1/videos/' + vid.id,
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: {
|
||||||
|
Host: hostHeader,
|
||||||
|
Authorization: 'Bearer ' + token
|
||||||
|
}
|
||||||
|
},
|
||||||
|
delRes => {
|
||||||
|
delRes.on('data', () => {});
|
||||||
|
delRes.on('end', resolve);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
delReq.on('error', resolve);
|
||||||
|
delReq.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
prunedCount++;
|
||||||
|
stats = await getStorageStats();
|
||||||
|
if (stats.usedPercent <= targetPercent && stats.freeGB >= MIN_FREE_GB) {
|
||||||
|
if (logger) {
|
||||||
|
logger.info(
|
||||||
|
`[YakTube Auto-Pruner] Target disk space reached (${stats.usedPercent}% used). Pruning completed.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { pruned: prunedCount, status: 'Pruning completed', currentStats: stats };
|
||||||
|
} catch (err) {
|
||||||
|
if (logger) logger.error('[YakTube Auto-Pruner] Error during prune check: ' + err.message);
|
||||||
|
return { error: err.message };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pruneIntervalTimer) clearInterval(pruneIntervalTimer);
|
||||||
|
pruneIntervalTimer = setInterval(
|
||||||
|
() => {
|
||||||
|
checkAndPruneStorage().catch(() => {});
|
||||||
|
},
|
||||||
|
4 * 60 * 60 * 1000
|
||||||
|
);
|
||||||
|
|
||||||
|
// 1. Quota & Storage Config Routes
|
||||||
router.get('/quota-config', async (_req, res) => {
|
router.get('/quota-config', async (_req, res) => {
|
||||||
await loadPluginSettings();
|
await loadPluginSettings();
|
||||||
return res.json({ ok: true, guestImportLimit });
|
return res.json({ ok: true, guestImportLimit, maxDiskUsagePercent });
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/storage-status', async (_req, res) => {
|
||||||
|
try {
|
||||||
|
const stats = await getStorageStats();
|
||||||
|
return res.json({
|
||||||
|
ok: true,
|
||||||
|
stats,
|
||||||
|
policy: {
|
||||||
|
maxUsagePercent: maxDiskUsagePercent,
|
||||||
|
targetUsagePercent: maxDiskUsagePercent > 0 ? Math.max(10, maxDiskUsagePercent - 10) : 0,
|
||||||
|
minFreeGB: MIN_FREE_GB,
|
||||||
|
gracePeriodDays: 7
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
return res.status(500).json({ error: e.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post('/prune-check', async (_req, res) => {
|
||||||
|
const result = await checkAndPruneStorage();
|
||||||
|
return res.json(result);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 2. YouTube Search Route (aliases: /youtube-search and /search)
|
// 2. YouTube Search Route (aliases: /youtube-search and /search)
|
||||||
@@ -504,6 +695,9 @@ async function register({ getRouter, registerSetting, settingsManager, peertubeH
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Non-blocking background check to prune old videos if disk usage exceeds maxDiskUsagePercent
|
||||||
|
checkAndPruneStorage().catch(() => {});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const token = await getAdminToken();
|
const token = await getAdminToken();
|
||||||
const resolvedChannelId = await resolveTargetChannelId(title, channelId);
|
const resolvedChannelId = await resolveTargetChannelId(title, channelId);
|
||||||
@@ -863,7 +1057,12 @@ async function register({ getRouter, registerSetting, settingsManager, peertubeH
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function unregister() {}
|
async function unregister() {
|
||||||
|
if (pruneIntervalTimer) {
|
||||||
|
clearInterval(pruneIntervalTimer);
|
||||||
|
pruneIntervalTimer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
register,
|
register,
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "peertube-plugin-yaktube-ondemand",
|
"name": "peertube-plugin-yaktube-ondemand",
|
||||||
"version": "1.5.8",
|
"version": "1.5.9",
|
||||||
"description": "Accessible On-Demand YouTube Import, Live Streams, Smart Recommendations, Sleep Timer, Touch Gestures, Voice Assistant & A11y Engine for PeerTube",
|
"description": "Accessible On-Demand YouTube Import, Live Streams, Smart Recommendations, Sleep Timer, Touch Gestures, Voice Assistant & A11y Engine for PeerTube",
|
||||||
"main": "main.js",
|
"main": "main.js",
|
||||||
"library": "./main.js",
|
"library": "./main.js",
|
||||||
|
|||||||
Reference in New Issue
Block a user