mirror of
https://github.com/ZhuJHua/moodiary.git
synced 2026-04-05 16:39:01 +08:00
feat(webdav): improve webdav behavior
This commit is contained in:
@@ -121,6 +121,7 @@ class LocalSendClientLogic extends GetxController {
|
||||
formData.fields.add(MapEntry('diary', jsonEncode(diary.toJson())));
|
||||
// 如果有分类,把分类名字带过去
|
||||
if (diary.categoryId != null) {
|
||||
Utils().logUtil.printInfo(diary.categoryId);
|
||||
var categoryName = Utils().isarUtil.getCategoryName(diary.categoryId!)!.categoryName;
|
||||
formData.fields.add(MapEntry('categoryName', categoryName));
|
||||
}
|
||||
|
||||
@@ -98,8 +98,7 @@ class LocalSendServerLogic extends GetxController {
|
||||
}
|
||||
// 插入日记
|
||||
await Utils().isarUtil.insertADiary(diary);
|
||||
await Bind.find<DiaryLogic>().updateCategory();
|
||||
await Bind.find<DiaryLogic>().updateDiary(null);
|
||||
await Bind.find<DiaryLogic>().refreshAll();
|
||||
state.receiveCount.value += 1;
|
||||
return shelf.Response.ok('Data and files received successfully');
|
||||
}
|
||||
|
||||
@@ -49,6 +49,7 @@ class WebDavDashboardLogic extends GetxController {
|
||||
void _findToUploadDiaries(Map<String, DateTime> localDiaryMap) {
|
||||
for (var diary in state.diaryList) {
|
||||
final remoteModifiedTime = state.webdavSyncMap[diary.id];
|
||||
if (remoteModifiedTime == 'delete') continue;
|
||||
if (remoteModifiedTime == null || DateTime.parse(remoteModifiedTime).isBefore(diary.lastModified)) {
|
||||
state.toUploadDiaries.add(diary);
|
||||
}
|
||||
@@ -58,6 +59,7 @@ class WebDavDashboardLogic extends GetxController {
|
||||
|
||||
void _findToDownloadIds(Map<String, DateTime> localDiaryMap) {
|
||||
for (var entry in state.webdavSyncMap.entries) {
|
||||
if (entry.value == 'delete') continue;
|
||||
final id = entry.key;
|
||||
final remoteModifiedTime = DateTime.parse(entry.value);
|
||||
|
||||
@@ -78,7 +80,7 @@ class WebDavDashboardLogic extends GetxController {
|
||||
|
||||
Future<void> fetchingWebDavSyncFlag() async {
|
||||
state.webdavSyncMap = await Utils().webDavUtil.fetchServerSyncData();
|
||||
state.webDavDiaryCount.value = state.webdavSyncMap.length.toString();
|
||||
state.webDavDiaryCount.value = state.webdavSyncMap.values.where((element) => element != 'delete').length.toString();
|
||||
}
|
||||
|
||||
void toWebDavPage() async {
|
||||
@@ -97,8 +99,7 @@ class WebDavDashboardLogic extends GetxController {
|
||||
}, onDownload: () async {
|
||||
state.toDownloadIdsCount.value = (int.parse(state.toDownloadIdsCount.value) - 1).toString();
|
||||
checkIsDownloading();
|
||||
await Bind.find<DiaryLogic>().updateCategory();
|
||||
await Bind.find<DiaryLogic>().updateDiary(null);
|
||||
await Bind.find<DiaryLogic>().refreshAll();
|
||||
}, onComplete: () {
|
||||
Get.backLegacy();
|
||||
Utils().noticeUtil.showToast('同步完成');
|
||||
|
||||
@@ -64,7 +64,8 @@ class DiaryDetailsLogic extends GetxController {
|
||||
|
||||
//放入回收站
|
||||
Future<void> delete(Diary diary) async {
|
||||
await Utils().isarUtil.updateADiary(diary..show = false);
|
||||
var newDiary = diary.clone()..show = false;
|
||||
await Utils().isarUtil.updateADiary(oldDiary: diary, newDiary: newDiary);
|
||||
Get.backLegacy(result: 'delete');
|
||||
}
|
||||
|
||||
|
||||
@@ -109,39 +109,39 @@ class EditLogic extends GetxController {
|
||||
} else {
|
||||
//如果是编辑,将日记对象赋值
|
||||
state.isNew = false;
|
||||
var oldDiary = Get.arguments as Diary;
|
||||
state.type = DiaryType.values.firstWhere((type) => type.value == oldDiary.type);
|
||||
state.currentDiary = oldDiary;
|
||||
state.originalDiary = Get.arguments as Diary;
|
||||
state.type = DiaryType.values.firstWhere((type) => type.value == state.originalDiary!.type);
|
||||
state.currentDiary = state.originalDiary!.clone();
|
||||
// 获取分类名称
|
||||
if (oldDiary.categoryId != null) {
|
||||
state.categoryName = Utils().isarUtil.getCategoryName(oldDiary.categoryId!)!.categoryName;
|
||||
if (state.originalDiary!.categoryId != null) {
|
||||
state.categoryName = Utils().isarUtil.getCategoryName(state.originalDiary!.categoryId!)!.categoryName;
|
||||
}
|
||||
// 初始化标题控制器
|
||||
titleTextEditingController.text = oldDiary.title;
|
||||
titleTextEditingController.text = state.originalDiary!.title;
|
||||
// 待替换的字符串map
|
||||
Map<String, String> replaceMap = {};
|
||||
//临时拷贝一份图片数据
|
||||
for (var name in oldDiary.imageName) {
|
||||
for (var name in state.originalDiary!.imageName) {
|
||||
// 生成一个临时文件
|
||||
var xFile = XFile(Utils().fileUtil.getRealPath('image', name));
|
||||
replaceMap[name] = xFile.path;
|
||||
state.imageFileList.add(xFile);
|
||||
}
|
||||
//临时拷贝一份拷贝音频数据到缓存目录
|
||||
for (var name in oldDiary.audioName) {
|
||||
for (var name in state.originalDiary!.audioName) {
|
||||
state.audioNameList.add(name);
|
||||
await File(Utils().fileUtil.getRealPath('audio', name)).copy(Utils().fileUtil.getCachePath(name));
|
||||
}
|
||||
//临时拷贝一份视频数据,别忘记了缩略图
|
||||
for (var name in oldDiary.videoName) {
|
||||
for (var name in state.originalDiary!.videoName) {
|
||||
// 生成一个临时文件
|
||||
var videoXFile = XFile(Utils().fileUtil.getRealPath('video', name));
|
||||
replaceMap[name] = videoXFile.path;
|
||||
state.videoFileList.add(videoXFile);
|
||||
}
|
||||
quillController = QuillController(
|
||||
document:
|
||||
Document.fromJson(jsonDecode(await Kmp.replaceWithKmp(text: oldDiary.content, replacements: replaceMap))),
|
||||
document: Document.fromJson(
|
||||
jsonDecode(await Kmp.replaceWithKmp(text: state.originalDiary!.content, replacements: replaceMap))),
|
||||
selection: const TextSelection.collapsed(offset: 0));
|
||||
state.totalCount.value = _toPlainText().length;
|
||||
}
|
||||
@@ -357,7 +357,6 @@ class EditLogic extends GetxController {
|
||||
..title = titleTextEditingController.text
|
||||
..content = content
|
||||
..type = state.type.value
|
||||
..lastModified = DateTime.now()
|
||||
..contentText = _toPlainText()
|
||||
..audioName = state.audioNameList
|
||||
..imageName = imageNameMap.values.toList()
|
||||
@@ -365,9 +364,8 @@ class EditLogic extends GetxController {
|
||||
..imageColor = await getCoverColor()
|
||||
..aspect = await getCoverAspect();
|
||||
|
||||
await Utils().isarUtil.updateADiary(state.currentDiary);
|
||||
await Utils().isarUtil.updateADiary(oldDiary: state.originalDiary, newDiary: state.currentDiary);
|
||||
state.isNew ? Get.backLegacy(result: state.currentDiary.categoryId ?? '') : Get.backLegacy(result: 'changed');
|
||||
if (Utils().webDavUtil.hasOption) unawaited(Utils().webDavUtil.uploadSingleDiary(state.currentDiary));
|
||||
Utils().noticeUtil.showToast(state.isNew ? '保存成功' : '修改成功');
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ import 'package:cross_file/cross_file.dart';
|
||||
import 'package:get/get_rx/src/rx_types/rx_types.dart';
|
||||
import 'package:mood_diary/common/models/isar/diary.dart';
|
||||
import 'package:mood_diary/common/values/diary_type.dart';
|
||||
import 'package:mood_diary/common/values/keyboard_state.dart';
|
||||
|
||||
import '../../utils/utils.dart';
|
||||
|
||||
@@ -10,6 +9,9 @@ class EditState {
|
||||
// 当前编辑的日记对象
|
||||
late Diary currentDiary;
|
||||
|
||||
// 编辑时的原始日记对象
|
||||
Diary? originalDiary;
|
||||
|
||||
List<XFile> imageFileList = [];
|
||||
|
||||
List<String> get imagePathList => imageFileList.map((e) => e.path).toList();
|
||||
@@ -34,7 +36,6 @@ class EditState {
|
||||
|
||||
bool isProcessing = false;
|
||||
|
||||
|
||||
// 总字数
|
||||
RxInt totalCount = 0.obs;
|
||||
|
||||
|
||||
@@ -43,8 +43,7 @@ class DiaryLogic extends GetxController with GetTickerProviderStateMixin {
|
||||
if (Utils().prefUtil.getValue<bool>('autoSync') == true) {
|
||||
var diary = await Utils().isarUtil.getAllDiaries();
|
||||
await Utils().webDavUtil.syncDiary(diary, onDownload: () async {
|
||||
await updateCategory();
|
||||
await updateDiary(null);
|
||||
await refreshAll();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -144,6 +143,16 @@ class DiaryLogic extends GetxController with GetTickerProviderStateMixin {
|
||||
await Bind.find<DiaryTabViewLogic>(tag: 'default').updateDiary();
|
||||
}
|
||||
|
||||
Future<void> refreshAll() async {
|
||||
await updateCategory();
|
||||
await updateDiary(null, jump: true);
|
||||
await Future.wait(
|
||||
state.categoryList.map(
|
||||
(category) => updateDiary(category.id, jump: false),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 分类刷新函数
|
||||
/// 需要在以下情况调用
|
||||
///
|
||||
|
||||
@@ -27,7 +27,7 @@ class DiaryPage extends StatelessWidget {
|
||||
),
|
||||
child: RiveAnimatedIcon(
|
||||
riveIcon: RiveIcon.reload,
|
||||
color: colorScheme.onSecondaryContainer,
|
||||
color: colorScheme.onPrimaryContainer,
|
||||
width: 24,
|
||||
height: 24,
|
||||
loopAnimation: true,
|
||||
|
||||
@@ -453,7 +453,6 @@ class SettingPage extends StatelessWidget {
|
||||
assignId: true,
|
||||
builder: (_) {
|
||||
return CustomScrollView(
|
||||
cacheExtent: size.height * 2,
|
||||
slivers: [
|
||||
SliverAppBar(
|
||||
title: Text(i18n.homeNavigatorSetting),
|
||||
@@ -464,6 +463,7 @@ class SettingPage extends StatelessWidget {
|
||||
padding: const EdgeInsets.all(4.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
spacing: 4.0,
|
||||
children: [
|
||||
buildDashboard(),
|
||||
|
||||
@@ -33,8 +33,11 @@ class RecycleLogic extends GetxController {
|
||||
}
|
||||
for (var name in state.diaryList[index].videoName) {
|
||||
Utils().fileUtil.deleteFile(Utils().fileUtil.getRealPath('video', name));
|
||||
// 删除缩略图
|
||||
Utils().fileUtil.deleteFile(Utils().fileUtil.getRealPath('thumbnail', name));
|
||||
}
|
||||
Utils().noticeUtil.showToast('删除成功');
|
||||
await Utils().webDavUtil.deleteSingleDiary(state.diaryList[index]);
|
||||
//重新获取
|
||||
getDiaryList();
|
||||
}
|
||||
@@ -43,9 +46,9 @@ class RecycleLogic extends GetxController {
|
||||
//重新显示
|
||||
Future<void> showDiary(Diary diary) async {
|
||||
//将show置为true
|
||||
diary.show = true;
|
||||
final newDiary = diary.clone()..show = true;
|
||||
//写入数据库
|
||||
await Utils().isarUtil.updateADiary(diary);
|
||||
await Utils().isarUtil.updateADiary(oldDiary: diary, newDiary: newDiary);
|
||||
//重新获取
|
||||
getDiaryList();
|
||||
update();
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -6,6 +7,7 @@ import 'package:isar/isar.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
import 'package:mood_diary/common/models/isar/category.dart';
|
||||
import 'package:mood_diary/common/models/isar/diary.dart';
|
||||
import 'package:mood_diary/common/models/isar/sync_record.dart';
|
||||
import 'package:mood_diary/common/models/map.dart';
|
||||
import 'package:mood_diary/common/values/diary_type.dart';
|
||||
import 'package:mood_diary/utils/utils.dart';
|
||||
@@ -133,10 +135,22 @@ class IsarUtil {
|
||||
}
|
||||
|
||||
//更新日记
|
||||
Future<void> updateADiary(Diary diary) async {
|
||||
Future<void> updateADiary({Diary? oldDiary, required Diary newDiary}) async {
|
||||
// 如果没有旧日记,说明是新增日记
|
||||
newDiary.lastModified = DateTime.now();
|
||||
await _isar.writeAsync((isar) {
|
||||
isar.diarys.put(diary);
|
||||
isar.diarys.put(newDiary);
|
||||
});
|
||||
// 更新日记, 旧日记不为空,说明是更新日记, 需要清理旧日记的媒体文件
|
||||
if (oldDiary != null) {
|
||||
// 清理本地媒体文件
|
||||
await Utils().fileUtil.cleanUpOldMediaFiles(oldDiary, newDiary);
|
||||
if (Utils().webDavUtil.hasOption) {
|
||||
unawaited(Utils().webDavUtil.updateSingleDiary(oldDiary: oldDiary, newDiary: newDiary));
|
||||
}
|
||||
} else {
|
||||
if (Utils().webDavUtil.hasOption) unawaited(Utils().webDavUtil.uploadSingleDiary(newDiary));
|
||||
}
|
||||
}
|
||||
|
||||
//查询日记
|
||||
@@ -378,4 +392,23 @@ class IsarUtil {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 添加sync任务
|
||||
Future<void> addSyncRecord(SyncRecord record) async {
|
||||
await _isar.writeAsync((isar) {
|
||||
isar.syncRecords.put(record);
|
||||
});
|
||||
}
|
||||
|
||||
// 获取sync任务
|
||||
Future<List<SyncRecord>> getSyncRecords() async {
|
||||
return await _isar.syncRecords.where().findAllAsync();
|
||||
}
|
||||
|
||||
// 删除sync任务
|
||||
Future<void> deleteSyncRecord(int id) async {
|
||||
await _isar.writeAsync((isar) {
|
||||
isar.syncRecords.delete(id);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,6 +104,12 @@ class PrefUtil {
|
||||
await compute(Utils().isarUtil.mergeToV2_6_0, Utils().fileUtil.getRealPath('database', ''));
|
||||
}
|
||||
|
||||
/// 修复bug
|
||||
/// v2.6.2
|
||||
if (appVersion != null && appVersion.split('+')[0].compareTo('2.6.2') < 0) {
|
||||
await Utils().mediaUtil.regenerateMissingThumbnails();
|
||||
}
|
||||
|
||||
// 如果是首次启动或版本不一致
|
||||
if (kDebugMode || firstStart || appVersion == null || appVersion != currentVersion) {
|
||||
await _prefs.setString('appVersion', currentVersion);
|
||||
|
||||
@@ -5,6 +5,8 @@ import 'package:mood_diary/utils/utils.dart';
|
||||
import 'package:path/path.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
import '../common/models/isar/diary.dart';
|
||||
|
||||
class FileUtil {
|
||||
late final _filePath = Utils().prefUtil.getValue<String>('supportPath')!;
|
||||
|
||||
@@ -190,4 +192,21 @@ class FileUtil {
|
||||
String getErrorLogPath() {
|
||||
return join(_filePath, 'error.log');
|
||||
}
|
||||
|
||||
Future<void> cleanUpOldMediaFiles(Diary oldDiary, Diary newDiary) async {
|
||||
// 通用删除方法
|
||||
Future<void> deleteMediaFiles(List<String> oldFiles, List<String> newFiles, String type) async {
|
||||
final tasks = oldFiles
|
||||
.where((file) => !newFiles.contains(file))
|
||||
.map((file) => Utils().fileUtil.deleteFile(Utils().fileUtil.getRealPath(type, file)))
|
||||
.toList();
|
||||
await Future.wait(tasks);
|
||||
}
|
||||
|
||||
// 删除旧的图片、视频和音频文件
|
||||
await deleteMediaFiles(oldDiary.imageName, newDiary.imageName, 'image');
|
||||
await deleteMediaFiles(oldDiary.videoName, newDiary.videoName, 'video');
|
||||
await deleteMediaFiles(oldDiary.audioName, newDiary.audioName, 'audio');
|
||||
await deleteMediaFiles(oldDiary.videoName, newDiary.videoName, 'thumbnail');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import 'package:image_picker_platform_interface/image_picker_platform_interface.
|
||||
import 'package:mime/mime.dart';
|
||||
import 'package:mood_diary/src/rust/api/compress.dart';
|
||||
import 'package:mood_diary/utils/utils.dart';
|
||||
import 'package:path/path.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
import '../src/rust/api/constants.dart' as r_type;
|
||||
@@ -100,6 +101,51 @@ class MediaUtil {
|
||||
return videoNameMap;
|
||||
}
|
||||
|
||||
Future<void> regenerateMissingThumbnails() async {
|
||||
// 获取视频和缩略图路径的工具方法
|
||||
String getThumbnailPath(String videoName) => Utils().fileUtil.getRealPath('thumbnail', videoName);
|
||||
|
||||
// 获取视频文件夹中的所有文件
|
||||
final videoDir = Directory(Utils().fileUtil.getRealPath('video', ''));
|
||||
if (!videoDir.existsSync()) return;
|
||||
|
||||
// 遍历视频文件
|
||||
final videoFiles = videoDir.listSync().whereType<File>();
|
||||
for (final videoFile in videoFiles) {
|
||||
final videoName = basename(videoFile.path);
|
||||
final thumbnailPath = getThumbnailPath(videoName);
|
||||
|
||||
// 检查是否存在缩略图
|
||||
if (!File(thumbnailPath).existsSync()) {
|
||||
print("Thumbnail missing for $videoName. Regenerating...");
|
||||
|
||||
try {
|
||||
// 生成临时缩略图路径
|
||||
final tempThumbnailPath = Utils().fileUtil.getCachePath('${const Uuid().v7()}.jpeg');
|
||||
|
||||
// 获取视频缩略图
|
||||
await Utils().mediaUtil.getVideoThumbnail(XFile(videoFile.path), tempThumbnailPath);
|
||||
|
||||
// 压缩并保存缩略图
|
||||
await _compressRust(
|
||||
XFile(tempThumbnailPath),
|
||||
thumbnailPath,
|
||||
r_type.CompressFormat.jpeg,
|
||||
);
|
||||
|
||||
// 删除临时文件
|
||||
await File(tempThumbnailPath).delete();
|
||||
|
||||
print("Thumbnail regenerated for $videoName.");
|
||||
} catch (e) {
|
||||
print("Failed to regenerate thumbnail for $videoName: $e");
|
||||
}
|
||||
} else {
|
||||
print("Thumbnail exists for $videoName.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//获取图片宽高
|
||||
Future<Size> getImageSize(ImageProvider imageProvider) async {
|
||||
final Completer<Size> completer = Completer<Size>();
|
||||
|
||||
@@ -91,6 +91,48 @@ class WebDavUtil {
|
||||
}
|
||||
}
|
||||
|
||||
//删除某一篇日记,将webdav中sync.json的对应日记id的value设置为delete
|
||||
Future<void> deleteSingleDiary(Diary diary) async {
|
||||
final serverSyncData = await fetchServerSyncData();
|
||||
if (!serverSyncData.containsKey(diary.id)) {
|
||||
return;
|
||||
}
|
||||
serverSyncData[diary.id] = 'delete';
|
||||
await updateServerSyncData(serverSyncData);
|
||||
// 删除日记json
|
||||
await _client!.remove('${WebDavOptions.diaryPath}/${diary.id}.json');
|
||||
// 遍历删除日记资源文件
|
||||
await _deleteFiles(diary.imageName, WebDavOptions.imagePath, 'image');
|
||||
await _deleteFiles(diary.audioName, WebDavOptions.audioPath, 'audio');
|
||||
await _deleteFiles(diary.videoName, WebDavOptions.videoPath, 'video');
|
||||
await _deleteFiles(diary.videoName.map((videoName) => 'thumbnail-${videoName.substring(6, 42)}.jpeg').toList(),
|
||||
WebDavOptions.videoPath, 'thumbnail');
|
||||
// 删除对应目录
|
||||
await _client!.remove('${WebDavOptions.imagePath}/${diary.id}');
|
||||
await _client!.remove('${WebDavOptions.audioPath}/${diary.id}');
|
||||
await _client!.remove('${WebDavOptions.videoPath}/${diary.id}');
|
||||
}
|
||||
|
||||
Future<void> _deleteDiary(Diary diary) async {
|
||||
// 删除文件的通用方法
|
||||
Future<void> deleteFiles(List<String> names, String folder) async {
|
||||
final tasks =
|
||||
names.map((name) => Utils().fileUtil.deleteFile(Utils().fileUtil.getRealPath(folder, name))).toList();
|
||||
await Future.wait(tasks);
|
||||
}
|
||||
|
||||
// 删除日记和关联文件
|
||||
if (await Utils().isarUtil.deleteADiary(diary.isarId)) {
|
||||
// 并行删除图片、音频、视频及其缩略图
|
||||
await Future.wait([
|
||||
deleteFiles(diary.imageName, 'image'),
|
||||
deleteFiles(diary.audioName, 'audio'),
|
||||
deleteFiles(diary.videoName, 'video'),
|
||||
deleteFiles(diary.videoName, 'thumbnail'), // 视频缩略图
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> syncDiary(
|
||||
List<Diary> localDiaries, {
|
||||
flutter.VoidCallback? onUpload,
|
||||
@@ -114,12 +156,30 @@ class WebDavUtil {
|
||||
}
|
||||
|
||||
final localLastModified = localDiaryMap[diaryId];
|
||||
//如果本地还有日记,但服务器中的日记已经被删除
|
||||
if (serverLastModified == 'delete') {
|
||||
if (localLastModified != null) {
|
||||
syncingDiaries.add(diaryId);
|
||||
await _deleteDiary(localDiaries.firstWhere((element) => element.id == diaryId));
|
||||
syncingDiaries.remove(diaryId);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (localLastModified == null || serverLastModified.compareTo(localLastModified) > 0) {
|
||||
// 本地不存在该日记,下载
|
||||
//本地不存在该日记,下载
|
||||
if (localLastModified == null) {
|
||||
syncingDiaries.add(diaryId);
|
||||
final updatedDiary = await _downloadDiary(diaryId); // 下载日记的实现
|
||||
await _saveLocalDiary(updatedDiary); // 保存到本地的实现
|
||||
await Utils().isarUtil.insertADiary(updatedDiary); // 保存到本地的实现
|
||||
onDownload?.call();
|
||||
syncingDiaries.remove(diaryId);
|
||||
}
|
||||
// 本地存在该日记,但服务器版本较新,更新本地
|
||||
if (localLastModified != null && serverLastModified.compareTo(localLastModified) > 0) {
|
||||
syncingDiaries.add(diaryId);
|
||||
final oldDiary = localDiaries.firstWhere((element) => element.id == diaryId);
|
||||
final newDiary = await _downloadDiary(diaryId);
|
||||
await Utils().isarUtil.updateADiary(oldDiary: oldDiary, newDiary: newDiary);
|
||||
onDownload?.call();
|
||||
syncingDiaries.remove(diaryId);
|
||||
}
|
||||
@@ -176,6 +236,42 @@ class WebDavUtil {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> updateSingleDiary({
|
||||
required Diary oldDiary,
|
||||
required Diary newDiary,
|
||||
flutter.VoidCallback? onUpload,
|
||||
flutter.VoidCallback? onComplete,
|
||||
}) async {
|
||||
if (syncingDiaries.contains(newDiary.id)) {
|
||||
return; // 避免重复上传
|
||||
}
|
||||
syncingDiaries.add(newDiary.id);
|
||||
try {
|
||||
// 遍历删除日记资源文件
|
||||
final needToDeleteImage = oldDiary.imageName.where((element) => !newDiary.imageName.contains(element)).toList();
|
||||
final needToDeleteAudio = oldDiary.audioName.where((element) => !newDiary.audioName.contains(element)).toList();
|
||||
final needToDeleteVideo = oldDiary.videoName.where((element) => !newDiary.videoName.contains(element)).toList();
|
||||
final needToDeleteThumbnail =
|
||||
needToDeleteVideo.map((videoName) => 'thumbnail-${videoName.substring(6, 42)}.jpeg').toList();
|
||||
await _deleteFiles(needToDeleteImage, WebDavOptions.imagePath, 'image');
|
||||
await _deleteFiles(needToDeleteAudio, WebDavOptions.audioPath, 'audio');
|
||||
await _deleteFiles(needToDeleteVideo, WebDavOptions.videoPath, 'video');
|
||||
await _deleteFiles(needToDeleteThumbnail, WebDavOptions.videoPath, 'thumbnail');
|
||||
// 上传日记到服务器
|
||||
await _uploadDiary(newDiary); // 上传日记的实现
|
||||
// 更新服务器同步数据
|
||||
final serverSyncData = await fetchServerSyncData();
|
||||
serverSyncData[newDiary.id] = newDiary.lastModified.toIso8601String();
|
||||
await updateServerSyncData(serverSyncData);
|
||||
onUpload?.call();
|
||||
} catch (e) {
|
||||
Utils().logUtil.printInfo('Failed to upload diary: $e');
|
||||
} finally {
|
||||
syncingDiaries.remove(newDiary.id);
|
||||
onComplete?.call(); // 调用完成回调
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _uploadDiary(Diary diary) async {
|
||||
// 检查并上传分类
|
||||
if (diary.categoryId != null) {
|
||||
@@ -188,7 +284,7 @@ class WebDavUtil {
|
||||
// 上传日记 JSON 数据
|
||||
final diaryPath = '${WebDavOptions.diaryPath}/${diary.id}.json';
|
||||
final diaryData = jsonEncode(diary.toJson());
|
||||
|
||||
Utils().logUtil.printInfo(diaryData);
|
||||
try {
|
||||
_client!.setHeaders({
|
||||
'accept-charset': 'utf-8',
|
||||
@@ -201,18 +297,20 @@ class WebDavUtil {
|
||||
rethrow;
|
||||
}
|
||||
|
||||
// 上传资源文件
|
||||
await _uploadFiles(diary.imageName, WebDavOptions.imagePath, 'image');
|
||||
await _uploadFiles(diary.audioName, WebDavOptions.audioPath, 'audio');
|
||||
await _uploadFiles(diary.videoName, WebDavOptions.videoPath, 'video');
|
||||
// 上传资源文件,目标路径是资源文件夹下的日记id
|
||||
await _uploadFiles(diary.imageName, '${WebDavOptions.imagePath}/${diary.id}', 'image');
|
||||
await _uploadFiles(diary.audioName, '${WebDavOptions.audioPath}/${diary.id}', 'audio');
|
||||
await _uploadFiles(diary.videoName, '${WebDavOptions.videoPath}/${diary.id}', 'video');
|
||||
await _uploadFiles(diary.videoName, '${WebDavOptions.videoPath}/${diary.id}', 'thumbnail');
|
||||
}
|
||||
|
||||
Future<void> _uploadFiles(List<String> fileNames, String resourcePath, String type) async {
|
||||
// 检查服务器是否已经存在该文件
|
||||
await _client!.mkdirAll(resourcePath);
|
||||
final existingFiles = await _client!.readDir(resourcePath);
|
||||
|
||||
for (final fileName in fileNames) {
|
||||
for (var fileName in fileNames) {
|
||||
final filePath = Utils().fileUtil.getRealPath(type, fileName);
|
||||
fileName = type == 'thumbnail' ? 'thumbnail-${fileName.substring(6, 42)}.jpeg' : fileName;
|
||||
if (existingFiles.any((file) => file.name == fileName)) {
|
||||
Utils().logUtil.printInfo('$type file already exists: $fileName');
|
||||
continue;
|
||||
@@ -232,6 +330,18 @@ class WebDavUtil {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _deleteFiles(List<String> fileNames, String resourcePath, String type) async {
|
||||
for (final fileName in fileNames) {
|
||||
try {
|
||||
await _client!.remove('$resourcePath/$fileName');
|
||||
Utils().logUtil.printInfo('$type file deleted: $fileName');
|
||||
} catch (e) {
|
||||
Utils().logUtil.printInfo('Failed to delete $type file: $fileName, Error: $e');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<Diary> _downloadDiary(String diaryId) async {
|
||||
// 下载日记 JSON 数据
|
||||
final diaryPath = '${WebDavOptions.diaryPath}/$diaryId.json';
|
||||
@@ -250,7 +360,7 @@ class WebDavUtil {
|
||||
if (diary.categoryId != null) {
|
||||
try {
|
||||
final category = await _downloadCategory(diary.categoryId!);
|
||||
Utils().isarUtil.insertACategory(Category()
|
||||
await Utils().isarUtil.updateACategory(Category()
|
||||
..id = category['id']!
|
||||
..categoryName = category['name']!);
|
||||
} catch (e) {
|
||||
@@ -259,10 +369,11 @@ class WebDavUtil {
|
||||
}
|
||||
|
||||
// 下载资源文件
|
||||
diary.imageName = await _downloadFiles(diary.imageName, WebDavOptions.imagePath, 'image');
|
||||
diary.audioName = await _downloadFiles(diary.audioName, WebDavOptions.audioPath, 'audio');
|
||||
diary.videoName = await _downloadFiles(diary.videoName, WebDavOptions.videoPath, 'video');
|
||||
|
||||
diary.imageName = await _downloadFiles(diary.imageName, '${WebDavOptions.imagePath}/$diaryId', 'image');
|
||||
diary.audioName = await _downloadFiles(diary.audioName, '${WebDavOptions.audioPath}/$diaryId', 'audio');
|
||||
diary.videoName = await _downloadFiles(diary.videoName, '${WebDavOptions.videoPath}/$diaryId', 'video');
|
||||
// 下载视频缩略图
|
||||
await _downloadFiles(diary.videoName, '${WebDavOptions.videoPath}/$diaryId', 'thumbnail');
|
||||
return diary;
|
||||
}
|
||||
|
||||
@@ -270,7 +381,8 @@ class WebDavUtil {
|
||||
final localFileNames = <String>[];
|
||||
|
||||
for (final fileName in fileNames) {
|
||||
final serverFilePath = '$resourcePath/$fileName';
|
||||
final serverFilePath =
|
||||
type == 'thumbnail' ? '$resourcePath/thumbnail-${fileName.substring(6, 42)}.jpeg' : '$resourcePath/$fileName';
|
||||
final localFilePath = Utils().fileUtil.getRealPath(type, fileName);
|
||||
|
||||
try {
|
||||
@@ -287,11 +399,6 @@ class WebDavUtil {
|
||||
return localFileNames;
|
||||
}
|
||||
|
||||
Future<void> _saveLocalDiary(Diary diary) async {
|
||||
// 使用 Isar 或文件存储
|
||||
await Utils().isarUtil.insertADiary(diary);
|
||||
}
|
||||
|
||||
Future<void> _uploadCategory(String categoryId, String categoryName) async {
|
||||
final categoryPath = '${WebDavOptions.categoryPath}/$categoryId.json';
|
||||
final categoryData = jsonEncode({'id': categoryId, 'name': categoryName});
|
||||
|
||||
Reference in New Issue
Block a user