Files
moodiary/lib/utils/file_util.dart
2025-04-13 21:36:41 +08:00

346 lines
11 KiB
Dart

import 'dart:io';
import 'package:archive/archive_io.dart';
import 'package:get/get.dart';
import 'package:isar/isar.dart';
import 'package:moodiary/common/models/isar/category.dart';
import 'package:moodiary/common/models/isar/diary.dart';
import 'package:moodiary/common/values/media_type.dart';
import 'package:moodiary/components/audio_player/audio_player_logic.dart';
import 'package:moodiary/persistence/isar.dart';
import 'package:moodiary/persistence/pref.dart';
import 'package:moodiary/src/rust/api/zip.dart';
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';
class FileUtil {
static final String _filePath = PrefUtil.getValue<String>('supportPath')!;
static final String _cachePath = PrefUtil.getValue<String>('cachePath')!;
// 删除文件
static Future<bool> deleteFile(String path) async {
final File file = File(path);
if (await file.exists()) {
await file.delete();
return true; // 返回删除成功
} else {
// 文件不存在,返回失败
return false;
}
}
static String getErrorLogFilePath() {
//打开文件
return join(_filePath, 'error.log');
}
//删除指定文件夹
static Future<void> deleteDir(String path) async {
final Directory directory = Directory(path);
if (await directory.exists()) {
await directory.delete(recursive: true);
}
}
static Future<void> initCreateDir() async {
await Future.wait([
createDir(join(_filePath, 'database')),
createDir(join(_filePath, 'image')),
createDir(join(_filePath, 'audio')),
createDir(join(_filePath, 'video')),
createDir(join(_filePath, 'font')),
]);
}
static Future<void> createDir(String path) async {
final Directory directory = Directory(path);
await directory.create(recursive: true);
}
//计算指定路径下文件的大小
static Future<Map<String, dynamic>> countSize() async {
final cacheDir = await getApplicationCacheDirectory();
var bytes = 0;
final fileList = cacheDir.listSync(recursive: true);
// 计算文件总大小(字节)
for (final file in fileList) {
if (file is File) {
bytes += file.lengthSync();
}
}
// 根据文件总大小选择合适的单位
return bytesToUnits(bytes);
}
static Map<String, dynamic> bytesToUnits(int bytes) {
// 根据文件总大小选择合适的单位
if (bytes < 1024) {
return {'size': bytes.toString(), 'unit': 'B', 'bytes': bytes};
} else if (bytes < 1024 * 1024) {
return {
'size': (bytes / 1024).toStringAsFixed(2),
'unit': 'KB',
'bytes': bytes,
};
} else if (bytes < 1024 * 1024 * 1024) {
return {
'size': (bytes / (1024 * 1024)).toStringAsFixed(2),
'unit': 'MB',
'bytes': bytes,
};
} else {
return {
'size': (bytes / (1024 * 1024 * 1024)).toStringAsFixed(2),
'unit': 'GB',
'bytes': bytes,
};
}
}
static Future<void> clearCache() async {
final cacheDir = await getApplicationCacheDirectory();
if (await cacheDir.exists()) {
await cacheDir.delete(recursive: true);
}
}
//文件导出
static Future<String> zipFile(Map<String, dynamic> params) async {
final zipPath = params['zipPath'] as String;
final dataPath = params['dataPath'] as String;
final zipEncoder = ZipFileEncoder();
final datetime = DateTime.now();
final fileName = join(
zipPath,
'心绪日记${datetime.toString().split(' ')[0]}备份.zip',
);
final outputStream = OutputFileStream(fileName);
zipEncoder.createWithStream(outputStream);
//备份照片
await zipEncoder.addDirectory(Directory(join(dataPath, 'image')));
//备份音频
await zipEncoder.addDirectory(Directory(join(dataPath, 'audio')));
//备份视频
await zipEncoder.addDirectory(Directory(join(dataPath, 'video')));
//备份字体
await zipEncoder.addDirectory(Directory(join(dataPath, 'font')));
//备份数据库
await IsarUtil.exportIsar(
dataPath,
zipPath,
'${datetime.millisecondsSinceEpoch}.isar',
);
await zipEncoder.addFile(
File(join(zipPath, '${datetime.millisecondsSinceEpoch}.isar')),
);
await zipEncoder.close();
return fileName;
}
static Future<String> zipFileUseRust(Map<String, dynamic> params) async {
final zipPath = params['zipPath'] as String;
final dataPath = params['dataPath'] as String;
final datetime = DateTime.now();
final filePath = join(
zipPath,
'心绪日记${datetime.toString().split(' ')[0]}备份.zip',
);
final zip = Zip(filePath: filePath);
await Future.wait([
zip.addDir(dirPath: join(dataPath, 'image'), basePath: 'image'),
zip.addDir(dirPath: join(dataPath, 'audio'), basePath: 'audio'),
zip.addDir(dirPath: join(dataPath, 'video'), basePath: 'video'),
zip.addDir(dirPath: join(dataPath, 'font'), basePath: 'font'),
IsarUtil.exportIsar(
dataPath,
zipPath,
'${datetime.millisecondsSinceEpoch}.isar',
),
zip.addFile(
filePath: join(zipPath, '${datetime.millisecondsSinceEpoch}.isar'),
zipPath: '${datetime.millisecondsSinceEpoch}.isar',
),
]);
await zip.finish();
zip.dispose();
return filePath;
}
//导入文件
static Future<void> extractFile(String path) async {
final inputStream = InputFileStream(path);
//删除图片文件夹
await deleteDir(join(_filePath, 'image'));
//删除音频文件夹
await deleteDir(join(_filePath, 'audio'));
//删除视频文件夹
await deleteDir(join(_filePath, 'video'));
//重新创建图片文件夹
await createDir(join(_filePath, 'image'));
//重新创建音频文件夹
await createDir(join(_filePath, 'audio'));
//重新创建视频文件夹
await createDir(join(_filePath, 'video'));
//删除字体文件夹
await deleteDir(join(_filePath, 'font'));
//重新创建字体文件夹
await createDir(join(_filePath, 'font'));
final archive = ZipDecoder().decodeStream(inputStream);
for (final file in archive.files) {
//如果是数据库
if (file.name.endsWith('.isar')) {
final outputStream = OutputFileStream(join(_cachePath, 'old.isar'));
file.writeContent(outputStream);
} else {
final outputStream = OutputFileStream(join(_filePath, file.name));
file.writeContent(outputStream);
}
}
//复制数据库
await IsarUtil.dataMigration(_cachePath);
}
static String getRealPath(String fileType, String fileName) {
if (fileType == 'thumbnail') {
final thumbnailName = 'thumbnail-${fileName.substring(6, 42)}.jpeg';
return join(_filePath, 'video', thumbnailName);
}
return join(_filePath, fileType, fileName);
}
// 媒体库
static Future<List<String>> getDirFilePath(String fileType) async {
final path = join(_filePath, fileType);
final List<String> filePaths = [];
final Directory directory = Directory(path);
if (await directory.exists()) {
await for (final entity in directory.list()) {
if (entity is File) {
filePaths.add(entity.path);
}
}
}
return filePaths;
}
static Future<List<String>> getDirFileName(String fileType) async {
final path = join(_filePath, fileType);
final List<String> fileNames = [];
final Directory directory = Directory(path);
if (await directory.exists()) {
await for (final entity in directory.list()) {
if (entity is File) {
fileNames.add(basename(entity.path));
}
}
}
return fileNames;
}
static Future<void> deleteMediaFiles(
Set<String> files,
String mediaType,
) async {
for (final name in files) {
final filePath = getRealPath(mediaType, name);
final file = File(filePath);
if (await file.exists()) {
await file.delete();
}
}
}
static String getCachePath(String fileName) {
return join(_cachePath, fileName);
}
static String getErrorLogPath() {
return join(_filePath, 'error.log');
}
static 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) => deleteFile(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');
}
static Future<void> cleanFile(String dir) async {
final isar = Isar.open(
schemas: [DiarySchema, CategorySchema],
directory: dir,
);
// 获取各类型的所有文件路径并转换为Set以提高查找效率
final imageFiles =
(await FileUtil.getDirFileName(MediaType.image.value)).toSet();
final audioFiles =
(await FileUtil.getDirFileName(MediaType.audio.value)).toSet();
final videoFiles =
(await FileUtil.getDirFileName(MediaType.video.value)).toSet();
// 用于存储日记中引用的文件名的Set
final usedImages = <String>{};
final usedAudios = <String>{};
final usedVideos = <String>{};
// 获取日记总数
final count = isar.diarys.count();
// 分批获取日记并收集引用的文件名
const batchSize = 50;
for (int i = 0; i < count; i += batchSize) {
final diaryList = await isar.diarys.where().findAllAsync(
offset: i,
limit: batchSize,
);
for (final diary in diaryList) {
usedImages.addAll(diary.imageName);
usedAudios.addAll(diary.audioName);
usedVideos.addAll(diary.videoName);
for (final name in diary.videoName) {
final thumbnailName = 'thumbnail-${name.substring(6, 42)}.jpeg';
usedVideos.add(thumbnailName);
}
}
}
// 计算需要删除的文件
final imagesToDelete = imageFiles.difference(usedImages);
final audiosToDelete = audioFiles.difference(usedAudios);
final videosToDelete = videoFiles.difference(usedVideos);
// delete controller when need
for (final path in audiosToDelete) {
if (Bind.isRegistered<AudioPlayerLogic>(tag: path)) {
Bind.delete<AudioPlayerLogic>(tag: path);
}
// 并行删除文件
await Future.wait([
FileUtil.deleteMediaFiles(imagesToDelete, MediaType.image.value),
FileUtil.deleteMediaFiles(audiosToDelete, MediaType.audio.value),
FileUtil.deleteMediaFiles(videosToDelete, MediaType.video.value),
]);
}
}
}