openlist多目录支持

This commit is contained in:
mtvpls
2026-01-10 10:25:14 +08:00
parent afdf0b7af5
commit 00a5055817
17 changed files with 212 additions and 66 deletions

View File

@@ -2763,7 +2763,7 @@ const OpenListConfigComponent = ({
const [url, setUrl] = useState(''); const [url, setUrl] = useState('');
const [username, setUsername] = useState(''); const [username, setUsername] = useState('');
const [password, setPassword] = useState(''); const [password, setPassword] = useState('');
const [rootPath, setRootPath] = useState('/'); const [rootPaths, setRootPaths] = useState<string[]>(['/']);
const [offlineDownloadPath, setOfflineDownloadPath] = useState('/'); const [offlineDownloadPath, setOfflineDownloadPath] = useState('/');
const [scanInterval, setScanInterval] = useState(0); const [scanInterval, setScanInterval] = useState(0);
const [scanMode, setScanMode] = useState<'torrent' | 'name' | 'hybrid'>('hybrid'); const [scanMode, setScanMode] = useState<'torrent' | 'name' | 'hybrid'>('hybrid');
@@ -2783,7 +2783,7 @@ const OpenListConfigComponent = ({
setUrl(config.OpenListConfig.URL || ''); setUrl(config.OpenListConfig.URL || '');
setUsername(config.OpenListConfig.Username || ''); setUsername(config.OpenListConfig.Username || '');
setPassword(config.OpenListConfig.Password || ''); setPassword(config.OpenListConfig.Password || '');
setRootPath(config.OpenListConfig.RootPath || '/'); setRootPaths(config.OpenListConfig.RootPaths || (config.OpenListConfig.RootPath ? [config.OpenListConfig.RootPath] : ['/']));
setOfflineDownloadPath(config.OpenListConfig.OfflineDownloadPath || '/'); setOfflineDownloadPath(config.OpenListConfig.OfflineDownloadPath || '/');
setScanInterval(config.OpenListConfig.ScanInterval || 0); setScanInterval(config.OpenListConfig.ScanInterval || 0);
setScanMode(config.OpenListConfig.ScanMode || 'hybrid'); setScanMode(config.OpenListConfig.ScanMode || 'hybrid');
@@ -2824,7 +2824,7 @@ const OpenListConfigComponent = ({
URL: url, URL: url,
Username: username, Username: username,
Password: password, Password: password,
RootPath: rootPath, RootPaths: rootPaths,
OfflineDownloadPath: offlineDownloadPath, OfflineDownloadPath: offlineDownloadPath,
ScanInterval: scanInterval, ScanInterval: scanInterval,
ScanMode: scanMode, ScanMode: scanMode,
@@ -3104,18 +3104,49 @@ const OpenListConfigComponent = ({
<div> <div>
<label className='block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2'> <label className='block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2'>
</label> </label>
<input <div className='space-y-2'>
type='text' {rootPaths.map((path, index) => (
value={rootPath} <div key={index} className='flex gap-2'>
onChange={(e) => setRootPath(e.target.value)} <input
disabled={!enabled} type='text'
placeholder='/' value={path}
className='w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:border-transparent disabled:opacity-50 disabled:cursor-not-allowed' onChange={(e) => {
/> const newPaths = [...rootPaths];
newPaths[index] = e.target.value;
setRootPaths(newPaths);
}}
disabled={!enabled}
placeholder='/'
className='flex-1 px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:border-transparent disabled:opacity-50 disabled:cursor-not-allowed'
/>
{rootPaths.length > 1 && (
<button
type='button'
onClick={() => {
const newPaths = rootPaths.filter((_, i) => i !== index);
setRootPaths(newPaths);
}}
disabled={!enabled}
className='px-3 py-2 bg-red-500 text-white rounded-lg hover:bg-red-600 disabled:opacity-50 disabled:cursor-not-allowed'
>
</button>
)}
</div>
))}
<button
type='button'
onClick={() => setRootPaths([...rootPaths, '/'])}
disabled={!enabled}
className='w-full px-3 py-2 border-2 border-dashed border-gray-300 dark:border-gray-600 rounded-lg text-gray-600 dark:text-gray-400 hover:border-blue-500 hover:text-blue-500 disabled:opacity-50 disabled:cursor-not-allowed'
>
+
</button>
</div>
<p className='mt-1 text-xs text-gray-500 dark:text-gray-400'> <p className='mt-1 text-xs text-gray-500 dark:text-gray-400'>
OpenList / OpenList
</p> </p>
</div> </div>

View File

@@ -26,7 +26,7 @@ export async function POST(request: NextRequest) {
try { try {
const body = await request.json(); const body = await request.json();
const { action, Enabled, URL, Username, Password, RootPath, OfflineDownloadPath, ScanInterval, ScanMode } = body; const { action, Enabled, URL, Username, Password, RootPaths, OfflineDownloadPath, ScanInterval, ScanMode } = body;
const authInfo = getAuthInfoFromCookie(request); const authInfo = getAuthInfoFromCookie(request);
if (!authInfo || !authInfo.username) { if (!authInfo || !authInfo.username) {
@@ -53,7 +53,7 @@ export async function POST(request: NextRequest) {
URL: URL || '', URL: URL || '',
Username: Username || '', Username: Username || '',
Password: Password || '', Password: Password || '',
RootPath: RootPath || '/', RootPaths: RootPaths || ['/'],
OfflineDownloadPath: OfflineDownloadPath || '/', OfflineDownloadPath: OfflineDownloadPath || '/',
LastRefreshTime: adminConfig.OpenListConfig?.LastRefreshTime, LastRefreshTime: adminConfig.OpenListConfig?.LastRefreshTime,
ResourceCount: adminConfig.OpenListConfig?.ResourceCount, ResourceCount: adminConfig.OpenListConfig?.ResourceCount,
@@ -77,6 +77,14 @@ export async function POST(request: NextRequest) {
); );
} }
// 验证 RootPaths
if (!Array.isArray(RootPaths) || RootPaths.length === 0) {
return NextResponse.json(
{ error: '请至少提供一个根目录' },
{ status: 400 }
);
}
// 验证扫描间隔 // 验证扫描间隔
let scanInterval = parseInt(ScanInterval) || 0; let scanInterval = parseInt(ScanInterval) || 0;
if (scanInterval > 0 && scanInterval < 60) { if (scanInterval > 0 && scanInterval < 60) {
@@ -104,7 +112,7 @@ export async function POST(request: NextRequest) {
URL, URL,
Username, Username,
Password, Password,
RootPath: RootPath || '/', RootPaths,
OfflineDownloadPath: OfflineDownloadPath || '/', OfflineDownloadPath: OfflineDownloadPath || '/',
LastRefreshTime: adminConfig.OpenListConfig?.LastRefreshTime, LastRefreshTime: adminConfig.OpenListConfig?.LastRefreshTime,
ResourceCount: adminConfig.OpenListConfig?.ResourceCount, ResourceCount: adminConfig.OpenListConfig?.ResourceCount,

View File

@@ -268,14 +268,14 @@ async function handleOpenListProxy(request: NextRequest) {
); );
// 读取 metainfo (从数据库或缓存) // 读取 metainfo (从数据库或缓存)
let metaInfo: MetaInfo | null = getCachedMetaInfo(rootPath); let metaInfo: MetaInfo | null = getCachedMetaInfo();
if (!metaInfo) { if (!metaInfo) {
try { try {
const metainfoJson = await db.getGlobalValue('video.metainfo'); const metainfoJson = await db.getGlobalValue('video.metainfo');
if (metainfoJson) { if (metainfoJson) {
metaInfo = JSON.parse(metainfoJson) as MetaInfo; metaInfo = JSON.parse(metainfoJson) as MetaInfo;
setCachedMetaInfo(rootPath, metaInfo); setCachedMetaInfo(metaInfo);
} }
} catch (error) { } catch (error) {
return NextResponse.json( return NextResponse.json(

View File

@@ -45,13 +45,13 @@ export async function GET(request: NextRequest) {
const { getCachedMetaInfo, setCachedMetaInfo } = await import('@/lib/openlist-cache'); const { getCachedMetaInfo, setCachedMetaInfo } = await import('@/lib/openlist-cache');
const { db } = await import('@/lib/db'); const { db } = await import('@/lib/db');
metaInfo = getCachedMetaInfo(rootPath); metaInfo = getCachedMetaInfo();
if (!metaInfo) { if (!metaInfo) {
const metainfoJson = await db.getGlobalValue('video.metainfo'); const metainfoJson = await db.getGlobalValue('video.metainfo');
if (metainfoJson) { if (metainfoJson) {
metaInfo = JSON.parse(metainfoJson); metaInfo = JSON.parse(metainfoJson);
setCachedMetaInfo(rootPath, metaInfo); setCachedMetaInfo(metaInfo);
} }
} }

View File

@@ -76,7 +76,7 @@ export async function GET(
const { getCachedMetaInfo, setCachedMetaInfo } = await import('@/lib/openlist-cache'); const { getCachedMetaInfo, setCachedMetaInfo } = await import('@/lib/openlist-cache');
const { db } = await import('@/lib/db'); const { db } = await import('@/lib/db');
let metaInfo = getCachedMetaInfo(rootPath); let metaInfo = getCachedMetaInfo();
if (!metaInfo) { if (!metaInfo) {
try { try {
@@ -84,7 +84,7 @@ export async function GET(
if (metainfoJson) { if (metainfoJson) {
metaInfo = JSON.parse(metainfoJson); metaInfo = JSON.parse(metainfoJson);
if (metaInfo) { if (metaInfo) {
setCachedMetaInfo(rootPath, metaInfo); setCachedMetaInfo(metaInfo);
} }
} }
} catch (error) { } catch (error) {

View File

@@ -65,7 +65,6 @@ export async function POST(request: NextRequest) {
); );
} }
const rootPath = openListConfig.RootPath || '/';
const client = new OpenListClient( const client = new OpenListClient(
openListConfig.URL, openListConfig.URL,
openListConfig.Username, openListConfig.Username,
@@ -73,7 +72,7 @@ export async function POST(request: NextRequest) {
); );
// 读取现有 metainfo (从数据库或缓存) // 读取现有 metainfo (从数据库或缓存)
let metaInfo: MetaInfo | null = getCachedMetaInfo(rootPath); let metaInfo: MetaInfo | null = getCachedMetaInfo();
if (!metaInfo) { if (!metaInfo) {
try { try {
@@ -132,8 +131,8 @@ export async function POST(request: NextRequest) {
await db.setGlobalValue('video.metainfo', metainfoContent); await db.setGlobalValue('video.metainfo', metainfoContent);
// 更新缓存 // 更新缓存
invalidateMetaInfoCache(rootPath); invalidateMetaInfoCache();
setCachedMetaInfo(rootPath, metaInfo); setCachedMetaInfo(metaInfo);
return NextResponse.json({ return NextResponse.json({
success: true, success: true,

View File

@@ -49,8 +49,6 @@ export async function POST(request: NextRequest) {
); );
} }
const rootPath = openListConfig.RootPath || '/';
// 从数据库读取 metainfo // 从数据库读取 metainfo
const metainfoContent = await db.getGlobalValue('video.metainfo'); const metainfoContent = await db.getGlobalValue('video.metainfo');
if (!metainfoContent) { if (!metainfoContent) {
@@ -78,8 +76,8 @@ export async function POST(request: NextRequest) {
await db.setGlobalValue('video.metainfo', updatedMetainfoContent); await db.setGlobalValue('video.metainfo', updatedMetainfoContent);
// 更新缓存 // 更新缓存
invalidateMetaInfoCache(rootPath); invalidateMetaInfoCache();
setCachedMetaInfo(rootPath, metaInfo); setCachedMetaInfo(metaInfo);
// 更新配置中的资源数量 // 更新配置中的资源数量
if (config.OpenListConfig) { if (config.OpenListConfig) {

View File

@@ -45,8 +45,8 @@ export async function GET(request: NextRequest) {
return NextResponse.json({ error: 'OpenList 未配置或未启用' }, { status: 400 }); return NextResponse.json({ error: 'OpenList 未配置或未启用' }, { status: 400 });
} }
const rootPath = openListConfig.RootPath || '/'; // folderName 已经是完整路径,直接使用
const folderPath = `${rootPath}${rootPath.endsWith('/') ? '' : '/'}${folderName}`; const folderPath = folderName;
const client = new OpenListClient( const client = new OpenListClient(
openListConfig.URL, openListConfig.URL,
openListConfig.Username, openListConfig.Username,

View File

@@ -48,7 +48,6 @@ export async function GET(request: NextRequest) {
); );
} }
const rootPath = openListConfig.RootPath || '/';
const client = new OpenListClient( const client = new OpenListClient(
openListConfig.URL, openListConfig.URL,
openListConfig.Username, openListConfig.Username,
@@ -62,7 +61,7 @@ export async function GET(request: NextRequest) {
if (noCache) { if (noCache) {
// noCache 模式:跳过缓存 // noCache 模式:跳过缓存
} else { } else {
metaInfo = getCachedMetaInfo(rootPath); metaInfo = getCachedMetaInfo();
} }
if (!metaInfo) { if (!metaInfo) {
@@ -83,7 +82,7 @@ export async function GET(request: NextRequest) {
// 只有在不是 noCache 模式时才更新缓存 // 只有在不是 noCache 模式时才更新缓存
if (!noCache) { if (!noCache) {
setCachedMetaInfo(rootPath, metaInfo); setCachedMetaInfo(metaInfo);
} }
} catch (parseError) { } catch (parseError) {
console.error('[OpenList List] JSON 解析或验证失败:', parseError); console.error('[OpenList List] JSON 解析或验证失败:', parseError);

View File

@@ -41,8 +41,8 @@ export async function GET(request: NextRequest) {
return NextResponse.json({ error: 'OpenList 未配置或未启用' }, { status: 400 }); return NextResponse.json({ error: 'OpenList 未配置或未启用' }, { status: 400 });
} }
const rootPath = openListConfig.RootPath || '/'; // folderName 已经是完整路径,直接使用
const folderPath = `${rootPath}${rootPath.endsWith('/') ? '' : '/'}${folderName}`; const folderPath = folderName;
const filePath = `${folderPath}/${fileName}`; const filePath = `${folderPath}/${fileName}`;
const client = new OpenListClient( const client = new OpenListClient(

View File

@@ -40,8 +40,8 @@ export async function POST(request: NextRequest) {
return NextResponse.json({ error: 'OpenList 未配置或未启用' }, { status: 400 }); return NextResponse.json({ error: 'OpenList 未配置或未启用' }, { status: 400 });
} }
const rootPath = openListConfig.RootPath || '/'; // folder 已经是完整路径,直接使用
const folderPath = `${rootPath}${rootPath.endsWith('/') ? '' : '/'}${folder}`; const folderPath = folder;
const client = new OpenListClient( const client = new OpenListClient(
openListConfig.URL, openListConfig.URL,
openListConfig.Username, openListConfig.Username,

View File

@@ -105,15 +105,14 @@ export async function GET(request: NextRequest) {
const { getTMDBImageUrl } = await import('@/lib/tmdb.search'); const { getTMDBImageUrl } = await import('@/lib/tmdb.search');
const { db } = await import('@/lib/db'); const { db } = await import('@/lib/db');
const rootPath = config.OpenListConfig!.RootPath || '/'; let metaInfo = getCachedMetaInfo();
let metaInfo = getCachedMetaInfo(rootPath);
if (!metaInfo) { if (!metaInfo) {
const metainfoJson = await db.getGlobalValue('video.metainfo'); const metainfoJson = await db.getGlobalValue('video.metainfo');
if (metainfoJson) { if (metainfoJson) {
metaInfo = JSON.parse(metainfoJson); metaInfo = JSON.parse(metainfoJson);
if (metaInfo) { if (metaInfo) {
setCachedMetaInfo(rootPath, metaInfo); setCachedMetaInfo(metaInfo);
} }
} }
} }

View File

@@ -213,15 +213,14 @@ export async function GET(request: NextRequest) {
const { getTMDBImageUrl } = await import('@/lib/tmdb.search'); const { getTMDBImageUrl } = await import('@/lib/tmdb.search');
const { db } = await import('@/lib/db'); const { db } = await import('@/lib/db');
const rootPath = config.OpenListConfig!.RootPath || '/'; let metaInfo = getCachedMetaInfo();
let metaInfo = getCachedMetaInfo(rootPath);
if (!metaInfo) { if (!metaInfo) {
const metainfoJson = await db.getGlobalValue('video.metainfo'); const metainfoJson = await db.getGlobalValue('video.metainfo');
if (metainfoJson) { if (metainfoJson) {
metaInfo = JSON.parse(metainfoJson); metaInfo = JSON.parse(metainfoJson);
if (metaInfo) { if (metaInfo) {
setCachedMetaInfo(rootPath, metaInfo); setCachedMetaInfo(metaInfo);
} }
} }
} }

View File

@@ -149,13 +149,13 @@ export async function GET(request: NextRequest) {
const { getCachedMetaInfo, setCachedMetaInfo } = await import('@/lib/openlist-cache'); const { getCachedMetaInfo, setCachedMetaInfo } = await import('@/lib/openlist-cache');
const { db } = await import('@/lib/db'); const { db } = await import('@/lib/db');
metaInfo = getCachedMetaInfo(rootPath); metaInfo = getCachedMetaInfo();
if (!metaInfo) { if (!metaInfo) {
const metainfoJson = await db.getGlobalValue('video.metainfo'); const metainfoJson = await db.getGlobalValue('video.metainfo');
if (metainfoJson) { if (metainfoJson) {
metaInfo = JSON.parse(metainfoJson); metaInfo = JSON.parse(metainfoJson);
setCachedMetaInfo(rootPath, metaInfo); setCachedMetaInfo(metaInfo);
} }
} }

View File

@@ -106,7 +106,8 @@ export interface AdminConfig {
URL: string; // OpenList 服务器地址 URL: string; // OpenList 服务器地址
Username: string; // 账号用于登录获取Token Username: string; // 账号用于登录获取Token
Password: string; // 密码用于登录获取Token Password: string; // 密码用于登录获取Token
RootPath: string; // 根目录路径,默认 "/" RootPath?: string; // 旧字段:根目录路径(向后兼容,迁移后删除)
RootPaths?: string[]; // 新字段:多根目录路径列表
OfflineDownloadPath: string; // 离线下载目录,默认 "/" OfflineDownloadPath: string; // 离线下载目录,默认 "/"
LastRefreshTime?: number; // 上次刷新时间戳 LastRefreshTime?: number; // 上次刷新时间戳
ResourceCount?: number; // 资源数量 ResourceCount?: number; // 资源数量

View File

@@ -49,28 +49,30 @@ export interface VideoInfo {
last_updated: number; last_updated: number;
} }
// MetaInfo 缓存操作 // MetaInfo 缓存操作(使用固定键)
export function getCachedMetaInfo(rootPath: string): MetaInfo | null { const METAINFO_CACHE_KEY = 'openlist_meta';
const entry = METAINFO_CACHE.get(rootPath);
export function getCachedMetaInfo(): MetaInfo | null {
const entry = METAINFO_CACHE.get(METAINFO_CACHE_KEY);
if (!entry) return null; if (!entry) return null;
if (entry.expiresAt <= Date.now()) { if (entry.expiresAt <= Date.now()) {
METAINFO_CACHE.delete(rootPath); METAINFO_CACHE.delete(METAINFO_CACHE_KEY);
return null; return null;
} }
return entry.data; return entry.data;
} }
export function setCachedMetaInfo(rootPath: string, data: MetaInfo): void { export function setCachedMetaInfo(data: MetaInfo): void {
METAINFO_CACHE.set(rootPath, { METAINFO_CACHE.set(METAINFO_CACHE_KEY, {
expiresAt: Date.now() + METAINFO_CACHE_TTL_MS, expiresAt: Date.now() + METAINFO_CACHE_TTL_MS,
data, data,
}); });
} }
export function invalidateMetaInfoCache(rootPath: string): void { export function invalidateMetaInfoCache(): void {
METAINFO_CACHE.delete(rootPath); METAINFO_CACHE.delete(METAINFO_CACHE_KEY);
} }
// VideoInfo 缓存操作 // VideoInfo 缓存操作

View File

@@ -20,6 +20,65 @@ import {
import { parseSeasonFromTitle } from '@/lib/season-parser'; import { parseSeasonFromTitle } from '@/lib/season-parser';
import { searchTMDB, getTVSeasonDetails } from '@/lib/tmdb.search'; import { searchTMDB, getTVSeasonDetails } from '@/lib/tmdb.search';
import parseTorrentName from 'parse-torrent-name'; import parseTorrentName from 'parse-torrent-name';
import type { AdminConfig } from '@/lib/admin.types';
/**
* 获取根目录列表(兼容新旧配置)
*/
function getRootPaths(openListConfig: AdminConfig['OpenListConfig']): string[] {
if (!openListConfig) {
return ['/'];
}
// 如果有新字段 RootPaths直接使用
if (openListConfig.RootPaths && openListConfig.RootPaths.length > 0) {
return openListConfig.RootPaths;
}
// 如果只tPath返回单元素数组
if (openListConfig.RootPath) {
return [openListConfig.RootPath];
}
// 默认值
return ['/'];
}
/**
* 迁移旧版单根目录配置到多根目录
*/
async function migrateToMultiRoot(openListConfig: NonNullable<AdminConfig['OpenListConfig']>): Promise<void> {
const oldRootPath = openListConfig.RootPath!;
console.log('[OpenList Migration] 检测到旧版配置,开始迁移...');
// 1. 读取现有 metainfo
const metainfoContent = await db.getGlobalValue('video.metainfo');
if (metainfoContent) {
const metaInfo: MetaInfo = JSON.parse(metainfoContent);
// 2. 迁移 folderName加上原根路径前缀
for (const [key, info] of Object.entries(metaInfo.folders)) {
const oldFolderName = info.folderName;
const newFolderName = `${oldRootPath}${oldRootPath.endsWith('/') ? '' : '/'}${oldFolderName}`;
info.folderName = newFolderName;
console.log(`[Migration] ${oldFolderName} -> ${newFolderName}`);
}
// 3. 保存迁移后的 metainfo
await db.setGlobalValue('video.metainfo', JSON.stringify(metaInfo));
console.log('[OpenList Migration] MetaInfo 迁移完成');
}
// 4. 更新配置RootPath -> RootPaths
const config = await getConfig();
config.OpenListConfig!.RootPaths = [oldRootPath];
delete config.OpenListConfig!.RootPath;
await db.saveAdminConfig(config);
console.log('[OpenList Migration] 配置迁移完成');
}
/** /**
* 启动 OpenList 刷新任务 * 启动 OpenList 刷新任务
@@ -45,13 +104,24 @@ export async function startOpenListRefresh(clearMetaInfo: boolean = false): Prom
throw new Error('TMDB API Key 未配置'); throw new Error('TMDB API Key 未配置');
} }
// 检测是否需要迁移
if (openListConfig.RootPath && !openListConfig.RootPaths) {
await migrateToMultiRoot(openListConfig);
// 重新加载配置
const newConfig = await getConfig();
Object.assign(openListConfig, newConfig.OpenListConfig);
}
cleanupOldTasks(); cleanupOldTasks();
const taskId = createScanTask(); const taskId = createScanTask();
performScan( const rootPaths = getRootPaths(openListConfig);
// 顺序扫描多个根目录
performMultiRootScan(
taskId, taskId,
openListConfig.URL, openListConfig.URL,
openListConfig.RootPath || '/', rootPaths,
tmdbApiKey, tmdbApiKey,
tmdbProxy, tmdbProxy,
openListConfig.Username, openListConfig.Username,
@@ -66,6 +136,43 @@ export async function startOpenListRefresh(clearMetaInfo: boolean = false): Prom
return { taskId }; return { taskId };
} }
/**
* 扫描多个根目录
*/
async function performMultiRootScan(
taskId: string,
url: string,
rootPaths: string[],
tmdbApiKey: string,
tmdbProxy: string | undefined,
username: string,
password: string,
clearMetaInfo: boolean,
scanMode: 'torrent' | 'name' | 'hybrid'
): Promise<void> {
for (let i = 0; i < rootPaths.length; i++) {
const rootPath = rootPaths[i];
console.log(`[OpenList Refresh] 扫描根目录 (${i + 1}/${rootPaths.length}): ${rootPath}`);
try {
await performScan(
taskId,
url,
rootPath,
tmdbApiKey,
tmdbProxy,
username,
password,
clearMetaInfo && i === 0, // 只在第一个根目录时清除
scanMode
);
} catch (error) {
console.error(`[OpenList Refresh] 根目录 ${rootPath} 扫描失败:`, error);
// 继续扫描其他根目录
}
}
}
/** /**
* 执行扫描任务 * 执行扫描任务
*/ */
@@ -112,7 +219,7 @@ async function performScan(
} }
} }
invalidateMetaInfoCache(rootPath); invalidateMetaInfoCache();
const folders: any[] = []; const folders: any[] = [];
let currentPage = 1; let currentPage = 1;
@@ -155,12 +262,15 @@ async function performScan(
updateScanTaskProgress(taskId, i + 1, folders.length, folder.name); updateScanTaskProgress(taskId, i + 1, folders.length, folder.name);
if (!clearMetaInfo && folderNameToKey.has(folder.name)) { // folderName 存储完整路径(包含根目录)
const fullFolderPath = `${rootPath}${rootPath.endsWith('/') ? '' : '/'}${folder.name}`;
if (!clearMetaInfo && folderNameToKey.has(fullFolderPath)) {
existingCount++; existingCount++;
continue; continue;
} }
const folderKey = generateFolderKey(folder.name, existingKeys); const folderKey = generateFolderKey(fullFolderPath, existingKeys);
existingKeys.add(folderKey); existingKeys.add(folderKey);
try { try {
@@ -197,7 +307,7 @@ async function performScan(
const result = searchResult.result; const result = searchResult.result;
const folderInfo: any = { const folderInfo: any = {
folderName: folder.name, folderName: fullFolderPath,
tmdb_id: result.id, tmdb_id: result.id,
title: result.title || result.name || folder.name, title: result.title || result.name || folder.name,
poster_path: result.poster_path, poster_path: result.poster_path,
@@ -249,7 +359,7 @@ async function performScan(
newCount++; newCount++;
} else { } else {
metaInfo.folders[folderKey] = { metaInfo.folders[folderKey] = {
folderName: folder.name, folderName: fullFolderPath,
tmdb_id: 0, tmdb_id: 0,
title: folder.name, title: folder.name,
poster_path: null, poster_path: null,
@@ -267,7 +377,7 @@ async function performScan(
} catch (error) { } catch (error) {
console.error(`[OpenList Refresh] 处理文件夹失败: ${folder.name}`, error); console.error(`[OpenList Refresh] 处理文件夹失败: ${folder.name}`, error);
metaInfo.folders[folderKey] = { metaInfo.folders[folderKey] = {
folderName: folder.name, folderName: fullFolderPath,
tmdb_id: 0, tmdb_id: 0,
title: folder.name, title: folder.name,
poster_path: null, poster_path: null,
@@ -287,8 +397,8 @@ async function performScan(
const metainfoContent = JSON.stringify(metaInfo); const metainfoContent = JSON.stringify(metaInfo);
await db.setGlobalValue('video.metainfo', metainfoContent); await db.setGlobalValue('video.metainfo', metainfoContent);
invalidateMetaInfoCache(rootPath); invalidateMetaInfoCache();
setCachedMetaInfo(rootPath, metaInfo); setCachedMetaInfo(metaInfo);
const config = await getConfig(); const config = await getConfig();
config.OpenListConfig!.LastRefreshTime = Date.now(); config.OpenListConfig!.LastRefreshTime = Date.now();