Runs after NFO refresh during scheduled rescans. Renames folders that are missing a year (e.g. 'Naruto' → 'Naruto (1999)') using the year from the database record. Safety: _build_target_folder() always strips any existing year suffix first, preventing double/triple year accumulation like 'Naruto (1999) (1999) (1999)'. Changes: - New FolderNamingService (folder_naming_service.py) with safe target name construction, DB update, and in-memory cache update - New SchedulerConfig field: folder_naming_after_nfo_scan (default True) - Integrated as step 3 in scheduler _perform_rescan() after NFO scan - Runtime UI: existing 'folder-scan-enabled' checkbox in index.html now wired to toggle the feature (app.js + scheduler-config.js) - Setup screen: new checkbox in setup.html Scheduler Settings section - API: scheduler config endpoint returns all scan toggles - Tests: 39 unit tests covering static helpers, rename logic, safety guard, and integration cases (folder_naming_service.py) - Docs: testing guide updated with FolderNamingService examples
164 lines
6.2 KiB
JavaScript
164 lines
6.2 KiB
JavaScript
/**
|
|
* AniWorld - Scheduler Config Module
|
|
*
|
|
* Handles scheduler configuration and scheduled rescan settings.
|
|
*
|
|
* Dependencies: constants.js, api-client.js, ui-utils.js
|
|
*/
|
|
|
|
var AniWorld = window.AniWorld || {};
|
|
|
|
AniWorld.SchedulerConfig = (function() {
|
|
'use strict';
|
|
|
|
const API = AniWorld.Constants.API;
|
|
|
|
/**
|
|
* Load scheduler configuration
|
|
*/
|
|
async function load() {
|
|
try {
|
|
const response = await AniWorld.ApiClient.get(API.SCHEDULER_CONFIG);
|
|
if (!response) return;
|
|
const data = await response.json();
|
|
|
|
if (data.success) {
|
|
const config = data.config;
|
|
const runtimeStatus = data.status || {};
|
|
|
|
// Update UI elements
|
|
document.getElementById('scheduled-rescan-enabled').checked = config.enabled;
|
|
document.getElementById('scheduled-rescan-time').value = config.schedule_time || '03:00';
|
|
|
|
const autoDownload = document.getElementById('auto-download-after-rescan');
|
|
if (autoDownload) {
|
|
autoDownload.checked = config.auto_download_after_rescan || false;
|
|
}
|
|
|
|
const folderNaming = document.getElementById('folder-scan-enabled');
|
|
if (folderNaming) {
|
|
folderNaming.checked = config.folder_naming_after_nfo_scan || false;
|
|
}
|
|
|
|
// Update schedule day checkboxes
|
|
const days = config.schedule_days || ['mon','tue','wed','thu','fri','sat','sun'];
|
|
['mon','tue','wed','thu','fri','sat','sun'].forEach(function(day) {
|
|
const cb = document.getElementById('scheduler-day-' + day);
|
|
if (cb) cb.checked = days.indexOf(day) !== -1;
|
|
});
|
|
|
|
// Update status display (runtime fields come from data.status)
|
|
const nextRunEl = document.getElementById('scheduler-next-run');
|
|
if (nextRunEl) {
|
|
nextRunEl.textContent = runtimeStatus.next_run
|
|
? new Date(runtimeStatus.next_run).toLocaleString()
|
|
: 'Not scheduled';
|
|
}
|
|
|
|
const statusBadge = document.getElementById('scheduler-running-status');
|
|
if (statusBadge) {
|
|
statusBadge.textContent = runtimeStatus.is_running ? 'Running' : 'Stopped';
|
|
statusBadge.className = 'info-value status-badge ' + (runtimeStatus.is_running ? 'running' : 'stopped');
|
|
}
|
|
|
|
// Enable/disable time input based on checkbox
|
|
toggleTimeInput();
|
|
}
|
|
} catch (error) {
|
|
console.error('Error loading scheduler config:', error);
|
|
AniWorld.UI.showToast('Failed to load scheduler configuration', 'error');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Save scheduler configuration
|
|
*/
|
|
async function save() {
|
|
try {
|
|
const enabled = document.getElementById('scheduled-rescan-enabled').checked;
|
|
const scheduleTime = document.getElementById('scheduled-rescan-time').value || '03:00';
|
|
|
|
// Collect checked day checkboxes
|
|
const scheduleDays = ['mon','tue','wed','thu','fri','sat','sun'].filter(function(day) {
|
|
const cb = document.getElementById('scheduler-day-' + day);
|
|
return cb && cb.checked;
|
|
});
|
|
|
|
const autoDownloadEl = document.getElementById('auto-download-after-rescan');
|
|
const autoDownload = autoDownloadEl ? autoDownloadEl.checked : false;
|
|
|
|
// POST directly to the scheduler config endpoint
|
|
const payload = {
|
|
enabled: enabled,
|
|
schedule_time: scheduleTime,
|
|
schedule_days: scheduleDays,
|
|
auto_download_after_rescan: autoDownload,
|
|
folder_naming_after_nfo_scan: document.getElementById('folder-scan-enabled') ? document.getElementById('folder-scan-enabled').checked : false
|
|
};
|
|
|
|
const response = await AniWorld.ApiClient.post(API.SCHEDULER_CONFIG, payload);
|
|
if (!response) return;
|
|
|
|
const result = await response.json();
|
|
if (result.success) {
|
|
AniWorld.UI.showToast('Scheduler configuration saved successfully', 'success');
|
|
// Update next run display from response
|
|
const nextRunEl = document.getElementById('scheduler-next-run');
|
|
if (nextRunEl && result.status) {
|
|
nextRunEl.textContent = result.status.next_run
|
|
? new Date(result.status.next_run).toLocaleString()
|
|
: 'Not scheduled';
|
|
}
|
|
} else {
|
|
AniWorld.UI.showToast('Failed to save scheduler configuration', 'error');
|
|
}
|
|
} catch (error) {
|
|
console.error('Error saving scheduler config:', error);
|
|
AniWorld.UI.showToast('Failed to save scheduler configuration', 'error');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test scheduled rescan
|
|
*/
|
|
async function testRescan() {
|
|
try {
|
|
const response = await AniWorld.ApiClient.post(API.SCHEDULER_TRIGGER, {});
|
|
|
|
if (!response) return;
|
|
const data = await response.json();
|
|
|
|
if (data.success) {
|
|
AniWorld.UI.showToast('Test rescan triggered successfully', 'success');
|
|
} else {
|
|
AniWorld.UI.showToast('Failed to trigger test rescan: ' + data.error, 'error');
|
|
}
|
|
} catch (error) {
|
|
console.error('Error triggering test rescan:', error);
|
|
AniWorld.UI.showToast('Failed to trigger test rescan', 'error');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Toggle scheduler time input visibility
|
|
*/
|
|
function toggleTimeInput() {
|
|
const enabled = document.getElementById('scheduled-rescan-enabled').checked;
|
|
const timeConfig = document.getElementById('rescan-time-config');
|
|
|
|
if (enabled) {
|
|
timeConfig.classList.add('enabled');
|
|
} else {
|
|
timeConfig.classList.remove('enabled');
|
|
}
|
|
}
|
|
|
|
// Public API
|
|
return {
|
|
load: load,
|
|
save: save,
|
|
testRescan: testRescan,
|
|
toggleTimeInput: toggleTimeInput
|
|
};
|
|
})();
|