import * as BackgroundTask from 'expo-background-task'; import * as TaskManager from 'expo-task-manager'; import pLimit from 'p-limit'; import { filterExistingFiles, initUploadTable, insertUploadTask, setAppState, updateUploadTaskStatus } from '../db'; import { getMediaByDateRange } from './media'; import { processAndUploadMedia } from './uploader'; const BACKGROUND_UPLOAD_TASK = 'background-upload-task'; const CONCURRENCY_LIMIT = 1; // 后台上传并发数,例如同时上传3个文件 const limit = pLimit(CONCURRENCY_LIMIT); // 注册后台任务 export async function registerBackgroundUploadTask() { try { // 初始化数据库表 await initUploadTable(); const isRegistered = await TaskManager.isTaskRegisteredAsync(BACKGROUND_UPLOAD_TASK); if (isRegistered) { console.log('Background task already registered.'); } else { await BackgroundTask.registerTaskAsync(BACKGROUND_UPLOAD_TASK, { minimumInterval: 15 * 60, // 15 分钟 }); console.log('Background task registered successfully.'); } return true; } catch (error) { console.error('Error registering background task:', error); return false; } }; // 触发手动或后台上传的通用函数 export async function triggerManualUpload(startDate: Date, endDate: Date): Promise { try { console.log(`Triggering upload for range: ${startDate.toISOString()} to ${endDate.toISOString()}`); const allMedia = await getMediaByDateRange(startDate, endDate); if (allMedia.length === 0) { console.log('No media files found in the specified range.'); return null; } // 1. Batch filter out files that have already been successfully uploaded. const allFileUris = allMedia.map(a => a.uri); const newFileUris = await filterExistingFiles(allFileUris); if (newFileUris.length === 0) { console.log('No new files to upload. All are already synced.'); return null; } const filesToUpload = allMedia.filter(a => newFileUris.includes(a.uri)); // 2. Batch pre-register all new files in the database as 'pending'. console.log(`Registering ${filesToUpload.length} new files as 'pending' in the database.`); for (const file of filesToUpload) { await insertUploadTask({ uri: file.uri, filename: file.filename, status: 'pending', progress: 0 }); } // 3. Start the upload session. const startTime = Math.floor(Date.now() / 1000).toString(); await setAppState('uploadSessionStartTime', startTime); // 4. Create upload promises for the new files. const uploadPromises = filesToUpload.map((file) => limit(async () => { try { // 5. Mark the task as 'uploading' right before the upload starts. await updateUploadTaskStatus(file.uri, 'uploading'); const result = await processAndUploadMedia(file); if (result === null) { // Skipped case await updateUploadTaskStatus(file.uri, 'skipped'); return { status: 'skipped' }; } if (result.originalSuccess) { await updateUploadTaskStatus(file.uri, 'success', result.fileIds?.original); return { status: 'success' }; } else { await updateUploadTaskStatus(file.uri, 'failed'); return { status: 'failed' }; } } catch (e) { console.error('Upload failed for', file.uri, e); await updateUploadTaskStatus(file.uri, 'failed'); return { status: 'failed' }; } }) ); // We don't wait for all uploads to complete. The function returns after starting them. // The UI will then poll for progress. Promise.allSettled(uploadPromises).then((results) => { console.log('All upload tasks have been settled.'); const successfulUploads = results.filter( (result) => result.status === 'fulfilled' && result.value.status === 'success' ).length; console.log(`${successfulUploads} files uploaded successfully.`); }); return startTime; } catch (error) { console.error('Error during upload trigger:', error); return null; } } // 定义后台任务 TaskManager.defineTask(BACKGROUND_UPLOAD_TASK, async () => { try { console.log('Running background upload task...'); const now = new Date(); const oneDayAgo = new Date(now.getTime() - 24 * 60 * 60 * 1000); await triggerManualUpload(oneDayAgo, now); return BackgroundTask.BackgroundTaskResult.Success; } catch (error) { console.error('Background task error:', error); return BackgroundTask.BackgroundTaskResult.Failed; } });