主要合入内容:
- 文件管理器分页加载、排序偏好、桌面表头排序、移动端排序菜单、Ctrl/Cmd+F搜索、Esc 关闭搜索、鼠标右滑返回上级。 - 移动端同步状态页、同步统计卡片、同步任务/累计统计事件、同步页布局更新。 - Android 相册同步默认路径改为 DCIM/Camera,下载路径改用 external_path。 - Rust sync-core 更新:Android 日志依赖、AlbumUpload/AlbumDownload、MirrorWcf 统计修复、Linux FUSE镜像挂载与读写同步支持。 - 依赖更新:external_path、fl_chart、pdfrx 升级,并修了 pdfrx 新版本的deprecated API。 - 新增文件包括:sort_options.dart、sync_page_android.dart、sync_stats_card.dart、platform/fuse.rs、sync_engine/fuse.rs。
This commit is contained in:
@@ -2,6 +2,7 @@ import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'package:background_downloader/background_downloader.dart' as bd;
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:external_path/external_path.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
import '../core/constants/storage_keys.dart';
|
||||
@@ -27,7 +28,7 @@ class DownloadService {
|
||||
|
||||
// 回调处理器
|
||||
static Function(String taskId, DownloadStatus status, int progress)?
|
||||
_callbackHandler;
|
||||
_callbackHandler;
|
||||
|
||||
final FileService _fileService = FileService();
|
||||
final Map<String, StreamController<DownloadTaskModel>> _progressControllers =
|
||||
@@ -36,7 +37,8 @@ class DownloadService {
|
||||
|
||||
/// 设置回调处理器
|
||||
static void setCallbackHandler(
|
||||
Function(String taskId, DownloadStatus status, int progress) handler) {
|
||||
Function(String taskId, DownloadStatus status, int progress) handler,
|
||||
) {
|
||||
_callbackHandler = handler;
|
||||
}
|
||||
|
||||
@@ -65,7 +67,10 @@ class DownloadService {
|
||||
throw Exception('存储权限被拒绝');
|
||||
}
|
||||
|
||||
final directory = Directory('/storage/emulated/0/Download');
|
||||
final downloadPath = await ExternalPath.getExternalStoragePublicDirectory(
|
||||
ExternalPath.DIRECTORY_DOWNLOAD,
|
||||
);
|
||||
final directory = Directory(downloadPath);
|
||||
if (!await directory.exists()) {
|
||||
await directory.create(recursive: true);
|
||||
}
|
||||
@@ -103,22 +108,23 @@ class DownloadService {
|
||||
|
||||
/// 读取 WiFi-only 下载设置
|
||||
Future<bool> isWifiOnlyEnabled() async {
|
||||
return await StorageService.instance
|
||||
.getBool(StorageKeys.downloadWifiOnly) ??
|
||||
return await StorageService.instance.getBool(
|
||||
StorageKeys.downloadWifiOnly,
|
||||
) ??
|
||||
false;
|
||||
}
|
||||
|
||||
/// 读取重试次数设置
|
||||
Future<int> getRetries() async {
|
||||
return await StorageService.instance
|
||||
.getInt(StorageKeys.downloadRetries) ??
|
||||
return await StorageService.instance.getInt(StorageKeys.downloadRetries) ??
|
||||
3;
|
||||
}
|
||||
|
||||
/// 初始化下载器
|
||||
Future<void> initialize(
|
||||
{Function(String taskId, DownloadStatus status, int progress)?
|
||||
callbackHandler}) async {
|
||||
Future<void> initialize({
|
||||
Function(String taskId, DownloadStatus status, int progress)?
|
||||
callbackHandler,
|
||||
}) async {
|
||||
if (callbackHandler != null) {
|
||||
setCallbackHandler(callbackHandler);
|
||||
AppLogger.d('回调处理器已更新');
|
||||
@@ -133,9 +139,10 @@ class DownloadService {
|
||||
if (Platform.isAndroid) {
|
||||
bd.FileDownloader().configureNotification(
|
||||
running: const bd.TaskNotification(
|
||||
'正在下载', '文件: {filename} - {progress}'),
|
||||
complete:
|
||||
const bd.TaskNotification('下载完成', '文件: {filename} 已保存'),
|
||||
'正在下载',
|
||||
'文件: {filename} - {progress}',
|
||||
),
|
||||
complete: const bd.TaskNotification('下载完成', '文件: {filename} 已保存'),
|
||||
error: const bd.TaskNotification('下载失败', '文件: {filename} 下载出错'),
|
||||
paused: const bd.TaskNotification('已暂停', '文件: {filename} 已暂停'),
|
||||
progressBar: true,
|
||||
@@ -170,14 +177,15 @@ class DownloadService {
|
||||
_internalIdToExternalTaskId[metaInternalId] = update.task.taskId;
|
||||
_bdTasks[metaInternalId] = update.task as bd.DownloadTask;
|
||||
AppLogger.d(
|
||||
'从 metaData 恢复映射: bdTaskId=${update.task.taskId}, internalId=$metaInternalId');
|
||||
'从 metaData 恢复映射: bdTaskId=${update.task.taskId}, internalId=$metaInternalId',
|
||||
);
|
||||
}
|
||||
|
||||
final resolvedInternalId =
|
||||
_externalTaskIdToInternalId[update.task.taskId];
|
||||
final resolvedInternalId = _externalTaskIdToInternalId[update.task.taskId];
|
||||
if (resolvedInternalId == null) {
|
||||
AppLogger.d(
|
||||
'background_downloader 状态回调: 未找到内部任务ID, taskId=${update.task.taskId}');
|
||||
'background_downloader 状态回调: 未找到内部任务ID, taskId=${update.task.taskId}',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -201,7 +209,8 @@ class DownloadService {
|
||||
}
|
||||
|
||||
AppLogger.d(
|
||||
'background_downloader 状态更新: taskId=${update.task.taskId}, internalId=$resolvedInternalId, status=$status');
|
||||
'background_downloader 状态更新: taskId=${update.task.taskId}, internalId=$resolvedInternalId, status=$status',
|
||||
);
|
||||
|
||||
final progress = status == DownloadStatus.completed ? 100 : 0;
|
||||
_callbackHandler?.call(resolvedInternalId, status, progress);
|
||||
@@ -261,7 +270,10 @@ class DownloadService {
|
||||
|
||||
/// 使用 background_downloader 开始下载
|
||||
Future<String?> _startBdDownload(
|
||||
DownloadTaskModel task, String url, Directory dir) async {
|
||||
DownloadTaskModel task,
|
||||
String url,
|
||||
Directory dir,
|
||||
) async {
|
||||
final wifiOnly = await isWifiOnlyEnabled();
|
||||
final retries = await getRetries();
|
||||
final bdTask = bd.DownloadTask(
|
||||
@@ -291,14 +303,14 @@ class DownloadService {
|
||||
task.backgroundTaskId = bdTask.taskId;
|
||||
|
||||
AppLogger.d(
|
||||
'background_downloader 任务已添加: taskId=${bdTask.taskId}, internalId=${task.id}, requiresWiFi=$wifiOnly, retries=$retries');
|
||||
'background_downloader 任务已添加: taskId=${bdTask.taskId}, internalId=${task.id}, requiresWiFi=$wifiOnly, retries=$retries',
|
||||
);
|
||||
|
||||
return bdTask.taskId;
|
||||
}
|
||||
|
||||
/// 恢复下载(用于重启后恢复暂停的任务)
|
||||
Future<String?> resumeDownloadAfterRestart(
|
||||
DownloadTaskModel task) async {
|
||||
Future<String?> resumeDownloadAfterRestart(DownloadTaskModel task) async {
|
||||
try {
|
||||
if (!_isInitialized) {
|
||||
await initialize();
|
||||
@@ -349,11 +361,11 @@ class DownloadService {
|
||||
task.backgroundTaskId = bdTask.taskId;
|
||||
|
||||
// 如果有已下载的部分,尝试 resume;否则 enqueue
|
||||
final partialFile =
|
||||
File('${dir.path}/${task.fileName}.part');
|
||||
final partialFile = File('${dir.path}/${task.fileName}.part');
|
||||
if (task.downloadedBytes > 0 && await partialFile.exists()) {
|
||||
AppLogger.d(
|
||||
'断点续传: ${task.fileName}, 已下载 ${task.downloadedBytes} bytes');
|
||||
'断点续传: ${task.fileName}, 已下载 ${task.downloadedBytes} bytes',
|
||||
);
|
||||
await bd.FileDownloader().resume(bdTask);
|
||||
} else {
|
||||
AppLogger.d('重新下载: ${task.fileName}');
|
||||
|
||||
@@ -81,11 +81,13 @@ class StorageService {
|
||||
|
||||
/// 设置
|
||||
Future<String?> get themeMode => getString(StorageKeys.themeMode);
|
||||
Future<bool> setThemeMode(String value) => setString(StorageKeys.themeMode, value);
|
||||
Future<bool> setThemeMode(String value) =>
|
||||
setString(StorageKeys.themeMode, value);
|
||||
|
||||
/// 服务器地址配置
|
||||
Future<String?> get customBaseUrl => getString(StorageKeys.customBaseUrl);
|
||||
Future<bool> setCustomBaseUrl(String? value) => setString(StorageKeys.customBaseUrl, value);
|
||||
Future<bool> setCustomBaseUrl(String? value) =>
|
||||
setString(StorageKeys.customBaseUrl, value);
|
||||
Future<bool> removeCustomBaseUrl() => remove(StorageKeys.customBaseUrl);
|
||||
|
||||
/// 服务器列表
|
||||
@@ -115,8 +117,10 @@ class StorageService {
|
||||
}
|
||||
|
||||
/// 上次选中的服务器 label
|
||||
Future<String?> get lastSelectedServerLabel => getString(StorageKeys.lastSelectedServer);
|
||||
Future<bool> setLastSelectedServerLabel(String? value) => setString(StorageKeys.lastSelectedServer, value);
|
||||
Future<String?> get lastSelectedServerLabel =>
|
||||
getString(StorageKeys.lastSelectedServer);
|
||||
Future<bool> setLastSelectedServerLabel(String? value) =>
|
||||
setString(StorageKeys.lastSelectedServer, value);
|
||||
|
||||
/// 搜索历史(最新在前,最多 20 条)
|
||||
Future<List<String>> getSearchHistory() async {
|
||||
@@ -188,6 +192,25 @@ class StorageService {
|
||||
Future<void> clearSyncData() async {
|
||||
await remove(StorageKeys.syncConfig);
|
||||
await remove(StorageKeys.syncState);
|
||||
await remove(StorageKeys.syncCumStats);
|
||||
}
|
||||
|
||||
/// 保存同步累积统计
|
||||
Future<bool> setSyncCumStats(Map<String, int> stats) async {
|
||||
final json = jsonEncode(stats);
|
||||
return await setString(StorageKeys.syncCumStats, json);
|
||||
}
|
||||
|
||||
/// 读取同步累积统计
|
||||
Future<Map<String, int>?> getSyncCumStats() async {
|
||||
final json = await getString(StorageKeys.syncCumStats);
|
||||
if (json == null) return null;
|
||||
try {
|
||||
final map = jsonDecode(json) as Map<String, dynamic>;
|
||||
return map.map((k, v) => MapEntry(k, v as int));
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// ===== client_id 持久化 =====
|
||||
|
||||
+141
-60
@@ -14,6 +14,7 @@ class SyncService {
|
||||
SyncService._();
|
||||
|
||||
bool _initialized = false;
|
||||
StreamSubscription<ffi_types.SyncEventFfi>? _rustEventSub;
|
||||
|
||||
/// 事件流,供 SyncProvider 订阅
|
||||
final _eventController = StreamController<SyncEventModel>.broadcast();
|
||||
@@ -22,17 +23,22 @@ class SyncService {
|
||||
/// 初始化同步引擎(已初始化时更新配置)
|
||||
Future<void> init(SyncConfigModel config) async {
|
||||
if (_initialized) {
|
||||
AppLogger.d('[FFI] → updateSyncConfig: mode=${config.syncMode}, conflict=${config.conflictStrategy}, concurrent=${config.maxConcurrentTransfers}, bandwidth=${config.bandwidthLimitKbps}kbps');
|
||||
AppLogger.d(
|
||||
'[FFI] → updateSyncConfig: mode=${config.syncMode}, conflict=${config.conflictStrategy}, concurrent=${config.maxConcurrentTransfers}, bandwidth=${config.bandwidthLimitKbps}kbps',
|
||||
);
|
||||
await ffi.updateSyncConfig(config: config.toFfi());
|
||||
AppLogger.d('[FFI] ← updateSyncConfig: ok');
|
||||
return;
|
||||
}
|
||||
|
||||
AppLogger.d('[FFI] → initSyncEngine: localRoot=${config.localRoot}, mode=${config.syncMode}, conflict=${config.conflictStrategy}, logLevel=${config.logLevel}');
|
||||
AppLogger.d(
|
||||
'[FFI] → initSyncEngine: localRoot=${config.localRoot}, mode=${config.syncMode}, conflict=${config.conflictStrategy}, logLevel=${config.logLevel}',
|
||||
);
|
||||
|
||||
await ffi.initSyncEngine(config: config.toFfi());
|
||||
|
||||
_initialized = true;
|
||||
_subscribeRustEvents();
|
||||
AppLogger.d('[FFI] ← initSyncEngine: ok');
|
||||
}
|
||||
|
||||
@@ -40,7 +46,9 @@ class SyncService {
|
||||
Future<SyncSummaryModel> startInitialSync() async {
|
||||
AppLogger.d('[FFI] → startInitialSync');
|
||||
final summary = await ffi.startInitialSync();
|
||||
AppLogger.d('[FFI] ← startInitialSync: uploaded=${summary.uploaded}, downloaded=${summary.downloaded}, conflicts=${summary.conflicts}, failed=${summary.failed}');
|
||||
AppLogger.d(
|
||||
'[FFI] ← startInitialSync: uploaded=${summary.uploaded}, downloaded=${summary.downloaded}, conflicts=${summary.conflicts}, failed=${summary.failed}',
|
||||
);
|
||||
return SyncSummaryModel.fromFfi(summary);
|
||||
}
|
||||
|
||||
@@ -76,7 +84,9 @@ class SyncService {
|
||||
Future<SyncSummaryModel> forceSync() async {
|
||||
AppLogger.d('[FFI] → forceSync');
|
||||
final summary = await ffi.forceSync();
|
||||
AppLogger.d('[FFI] ← forceSync: uploaded=${summary.uploaded}, downloaded=${summary.downloaded}, conflicts=${summary.conflicts}, failed=${summary.failed}');
|
||||
AppLogger.d(
|
||||
'[FFI] ← forceSync: uploaded=${summary.uploaded}, downloaded=${summary.downloaded}, conflicts=${summary.conflicts}, failed=${summary.failed}',
|
||||
);
|
||||
return SyncSummaryModel.fromFfi(summary);
|
||||
}
|
||||
|
||||
@@ -90,7 +100,9 @@ class SyncService {
|
||||
/// 更新同步配置(推送到 Rust 引擎,引擎未初始化时忽略)
|
||||
Future<void> updateConfig(SyncConfigModel config) async {
|
||||
if (!_initialized) return;
|
||||
AppLogger.d('[FFI] → updateSyncConfig: mode=${config.syncMode}, conflict=${config.conflictStrategy}, concurrent=${config.maxConcurrentTransfers}, bandwidth=${config.bandwidthLimitKbps}kbps');
|
||||
AppLogger.d(
|
||||
'[FFI] → updateSyncConfig: mode=${config.syncMode}, conflict=${config.conflictStrategy}, concurrent=${config.maxConcurrentTransfers}, bandwidth=${config.bandwidthLimitKbps}kbps',
|
||||
);
|
||||
try {
|
||||
await ffi.updateSyncConfig(config: config.toFfi());
|
||||
AppLogger.d('[FFI] ← updateSyncConfig: ok');
|
||||
@@ -104,7 +116,9 @@ class SyncService {
|
||||
/// 获取同步状态快照(轮询高频调用,trace 级别)
|
||||
Future<SyncStatusModel> getStatus() async {
|
||||
final status = await ffi.getSyncStatus();
|
||||
AppLogger.t('[FFI] ← getSyncStatus: state=${status.state}, synced=${status.syncedFiles}, total=${status.totalFiles}');
|
||||
AppLogger.t(
|
||||
'[FFI] ← getSyncStatus: state=${status.state}, synced=${status.syncedFiles}, total=${status.totalFiles}',
|
||||
);
|
||||
return SyncStatusModel.fromFfi(status);
|
||||
}
|
||||
|
||||
@@ -119,17 +133,21 @@ class SyncService {
|
||||
Future<List<SyncTaskModel>> getActiveTasksTyped() async {
|
||||
final tasks = await ffi.getActiveTasks();
|
||||
AppLogger.t('[FFI] ← getActiveTasks: count=${tasks.length}');
|
||||
return tasks.map((t) => SyncTaskModel(
|
||||
id: t.id,
|
||||
trigger: t.trigger,
|
||||
totalCount: t.totalCount,
|
||||
completedCount: t.completedCount,
|
||||
failedCount: t.failedCount,
|
||||
status: t.status,
|
||||
createdAt: t.createdAt,
|
||||
updatedAt: t.updatedAt,
|
||||
finishedAt: t.finishedAt,
|
||||
)).toList();
|
||||
return tasks
|
||||
.map(
|
||||
(t) => SyncTaskModel(
|
||||
id: t.id,
|
||||
trigger: t.trigger,
|
||||
totalCount: t.totalCount,
|
||||
completedCount: t.completedCount,
|
||||
failedCount: t.failedCount,
|
||||
status: t.status,
|
||||
createdAt: t.createdAt,
|
||||
updatedAt: t.updatedAt,
|
||||
finishedAt: t.finishedAt,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
}
|
||||
|
||||
/// 获取最近同步任务列表(轮询高频调用,trace 级别)
|
||||
@@ -137,17 +155,21 @@ class SyncService {
|
||||
AppLogger.t('[FFI] → getRecentTasks: limit=$limit');
|
||||
final tasks = await ffi.getRecentTasks(limit: limit);
|
||||
AppLogger.t('[FFI] ← getRecentTasks: count=${tasks.length}');
|
||||
return tasks.map((t) => SyncTaskModel(
|
||||
id: t.id,
|
||||
trigger: t.trigger,
|
||||
totalCount: t.totalCount,
|
||||
completedCount: t.completedCount,
|
||||
failedCount: t.failedCount,
|
||||
status: t.status,
|
||||
createdAt: t.createdAt,
|
||||
updatedAt: t.updatedAt,
|
||||
finishedAt: t.finishedAt,
|
||||
)).toList();
|
||||
return tasks
|
||||
.map(
|
||||
(t) => SyncTaskModel(
|
||||
id: t.id,
|
||||
trigger: t.trigger,
|
||||
totalCount: t.totalCount,
|
||||
completedCount: t.completedCount,
|
||||
failedCount: t.failedCount,
|
||||
status: t.status,
|
||||
createdAt: t.createdAt,
|
||||
updatedAt: t.updatedAt,
|
||||
finishedAt: t.finishedAt,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
}
|
||||
|
||||
/// 获取任务详情(按需查询,trace 级别)
|
||||
@@ -155,17 +177,21 @@ class SyncService {
|
||||
AppLogger.t('[FFI] → getTaskDetail: taskId=$taskId');
|
||||
final items = await ffi.getTaskDetail(taskId: taskId);
|
||||
AppLogger.t('[FFI] ← getTaskDetail: count=${items.length}');
|
||||
return items.map((i) => SyncTaskItemModel(
|
||||
id: i.id.toInt(),
|
||||
taskId: i.taskId,
|
||||
relativePath: i.relativePath,
|
||||
actionType: i.actionType,
|
||||
status: i.status,
|
||||
fileSize: i.fileSize.toInt(),
|
||||
errorMessage: i.errorMessage,
|
||||
createdAt: i.createdAt,
|
||||
updatedAt: i.updatedAt,
|
||||
)).toList();
|
||||
return items
|
||||
.map(
|
||||
(i) => SyncTaskItemModel(
|
||||
id: i.id.toInt(),
|
||||
taskId: i.taskId,
|
||||
relativePath: i.relativePath,
|
||||
actionType: i.actionType,
|
||||
status: i.status,
|
||||
fileSize: i.fileSize.toInt(),
|
||||
errorMessage: i.errorMessage,
|
||||
createdAt: i.createdAt,
|
||||
updatedAt: i.updatedAt,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
}
|
||||
|
||||
/// 分页查询任务详情(trace 级别)
|
||||
@@ -174,24 +200,52 @@ class SyncService {
|
||||
int limit = 20,
|
||||
int offset = 0,
|
||||
}) async {
|
||||
AppLogger.t('[FFI] → queryTaskItems: taskId=$taskId, limit=$limit, offset=$offset');
|
||||
final items = await ffi.queryTaskItems(filter: ffi_types.TaskItemFilterFfi(
|
||||
taskId: taskId,
|
||||
limit: limit,
|
||||
offset: offset,
|
||||
));
|
||||
AppLogger.t(
|
||||
'[FFI] → queryTaskItems: taskId=$taskId, limit=$limit, offset=$offset',
|
||||
);
|
||||
final items = await ffi.queryTaskItems(
|
||||
filter: ffi_types.TaskItemFilterFfi(
|
||||
taskId: taskId,
|
||||
limit: limit,
|
||||
offset: offset,
|
||||
),
|
||||
);
|
||||
AppLogger.t('[FFI] ← queryTaskItems: count=${items.length}');
|
||||
return items.map((i) => SyncTaskItemModel(
|
||||
id: i.id.toInt(),
|
||||
taskId: i.taskId,
|
||||
relativePath: i.relativePath,
|
||||
actionType: i.actionType,
|
||||
status: i.status,
|
||||
fileSize: i.fileSize.toInt(),
|
||||
errorMessage: i.errorMessage,
|
||||
createdAt: i.createdAt,
|
||||
updatedAt: i.updatedAt,
|
||||
)).toList();
|
||||
return items
|
||||
.map(
|
||||
(i) => SyncTaskItemModel(
|
||||
id: i.id.toInt(),
|
||||
taskId: i.taskId,
|
||||
relativePath: i.relativePath,
|
||||
actionType: i.actionType,
|
||||
status: i.status,
|
||||
fileSize: i.fileSize.toInt(),
|
||||
errorMessage: i.errorMessage,
|
||||
createdAt: i.createdAt,
|
||||
updatedAt: i.updatedAt,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
}
|
||||
|
||||
/// 从 DB 聚合累积统计(轮询高频调用,trace 级别)
|
||||
Future<Map<String, int>> getCumStats() async {
|
||||
AppLogger.t('[FFI] → getSyncCumStats');
|
||||
final stats = await ffi.getSyncCumStats();
|
||||
AppLogger.t(
|
||||
'[FFI] ← getSyncCumStats: uploaded=${stats.uploaded}, downloaded=${stats.downloaded}, failed=${stats.failed}, conflicts=${stats.conflicts}, deletedLocal=${stats.deletedLocal}, deletedRemote=${stats.deletedRemote}, skipped=${stats.skipped}',
|
||||
);
|
||||
return {
|
||||
'uploaded': stats.uploaded,
|
||||
'downloaded': stats.downloaded,
|
||||
'renamed': stats.renamed,
|
||||
'moved': stats.moved,
|
||||
'failed': stats.failed,
|
||||
'conflicts': stats.conflicts,
|
||||
'deleted_local': stats.deletedLocal,
|
||||
'deleted_remote': stats.deletedRemote,
|
||||
'skipped': stats.skipped,
|
||||
};
|
||||
}
|
||||
|
||||
// ========== 以下为低频操作,保持 debug 级别 ==========
|
||||
@@ -207,12 +261,16 @@ class SyncService {
|
||||
Future<Map<String, dynamic>> checkCloudAlbumDirs(String baseUri) async {
|
||||
AppLogger.d('[FFI] → checkCloudAlbumDirs: uri=$baseUri');
|
||||
final result = await ffi.checkCloudAlbumDirs(baseUri: baseUri);
|
||||
AppLogger.d('[FFI] ← checkCloudAlbumDirs: dcim=${result.dcimExists}, pictures=${result.picturesExists}');
|
||||
AppLogger.d(
|
||||
'[FFI] ← checkCloudAlbumDirs: dcim=${result.dcimExists}, pictures=${result.picturesExists}, camera=${result.cameraExists}',
|
||||
);
|
||||
return {
|
||||
'dcimExists': result.dcimExists,
|
||||
'picturesExists': result.picturesExists,
|
||||
'dcimUri': result.dcimUri,
|
||||
'picturesUri': result.picturesUri,
|
||||
'cameraExists': result.cameraExists,
|
||||
'cameraUri': result.cameraUri,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -226,12 +284,35 @@ class SyncService {
|
||||
/// 销毁同步引擎
|
||||
Future<void> dispose() async {
|
||||
AppLogger.d('[FFI] → disposeSyncEngine');
|
||||
_rustEventSub?.cancel();
|
||||
_rustEventSub = null;
|
||||
await _eventController.close();
|
||||
await ffi.disposeSyncEngine();
|
||||
_initialized = false;
|
||||
AppLogger.d('[FFI] ← disposeSyncEngine: ok');
|
||||
}
|
||||
|
||||
/// 订阅 Rust 事件流,转换后转发到 _eventController
|
||||
void _subscribeRustEvents() {
|
||||
_rustEventSub?.cancel();
|
||||
try {
|
||||
final stream = ffi.registerSyncEventSink();
|
||||
_rustEventSub = stream.listen(
|
||||
(event) {
|
||||
final model = syncEventFromFfi(event);
|
||||
if (model != null && !_eventController.isClosed) {
|
||||
_eventController.add(model);
|
||||
}
|
||||
},
|
||||
onError: (e) => AppLogger.e('[FFI] Rust event stream error: $e'),
|
||||
onDone: () => AppLogger.d('[FFI] Rust event stream done'),
|
||||
);
|
||||
AppLogger.d('[FFI] Rust event stream subscribed');
|
||||
} catch (e) {
|
||||
AppLogger.e('[FFI] Failed to subscribe Rust event stream: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// 热修改日志级别(立即生效,无需重启)
|
||||
Future<void> setLogLevel(String level) async {
|
||||
AppLogger.d('[FFI] → setSyncLogLevel: level=$level');
|
||||
@@ -240,9 +321,9 @@ class SyncService {
|
||||
}
|
||||
|
||||
/// 重置同步:停止任务 → 清空 DB → 清空本地目录 → 回到初始状态
|
||||
Future<void> resetSync() async {
|
||||
AppLogger.d('[FFI] → resetSync');
|
||||
await ffi.resetSync();
|
||||
Future<void> resetSync({bool deleteLocalFiles = true}) async {
|
||||
AppLogger.d('[FFI] → resetSync: deleteLocalFiles=$deleteLocalFiles');
|
||||
await ffi.resetSync(deleteLocalFiles: deleteLocalFiles);
|
||||
AppLogger.d('[FFI] ← resetSync: ok');
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user