feat: 自动上传组件 Co-authored-by: Junhui Chen <chenjunhui@fairclip.cn> Co-committed-by: Junhui Chen <chenjunhui@fairclip.cn>
123 lines
5.0 KiB
TypeScript
123 lines
5.0 KiB
TypeScript
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<string | null> {
|
||
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;
|
||
}
|
||
}); |