snap_wish/lib/core/utils/file_utils.dart

455 lines
14 KiB
Dart

import 'dart:io';
import 'package:path/path.dart' as path;
import 'package:path_provider/path_provider.dart' as path_provider;
import '../errors/app_error.dart';
import 'logger.dart';
/// 文件工具类
/// 提供文件存储、路径管理、存储空间检查等功能
class FileUtils {
// 私有构造函数,防止实例化
FileUtils._();
/// 获取应用文档目录
static Future<Directory> getApplicationDocumentsDirectory() async {
try {
final directory = await path_provider.getApplicationDocumentsDirectory();
Logger.debug('获取应用文档目录: ${directory.path}');
return directory;
} catch (error, stackTrace) {
Logger.error('获取应用文档目录失败', error: error, stackTrace: stackTrace);
throw StorageError(
message: '获取应用文档目录失败: ${error.toString()}',
code: 'STORAGE_ERROR',
stackTrace: stackTrace,
);
}
}
/// 获取临时目录
static Future<Directory> getTemporaryDirectory() async {
try {
final directory = await path_provider.getTemporaryDirectory();
Logger.debug('获取临时目录: ${directory.path}');
return directory;
} catch (error, stackTrace) {
Logger.error('获取临时目录失败', error: error, stackTrace: stackTrace);
throw StorageError(
message: '获取临时目录失败: ${error.toString()}',
code: 'STORAGE_ERROR',
stackTrace: stackTrace,
);
}
}
/// 获取应用支持目录(用于存储应用数据)
static Future<Directory> getApplicationSupportDirectory() async {
try {
final directory = await path_provider.getApplicationSupportDirectory();
Logger.debug('获取应用支持目录: ${directory.path}');
return directory;
} catch (error, stackTrace) {
Logger.error('获取应用支持目录失败', error: error, stackTrace: stackTrace);
throw StorageError(
message: '获取应用支持目录失败: ${error.toString()}',
code: 'STORAGE_ERROR',
stackTrace: stackTrace,
);
}
}
/// 创建应用存储目录结构
/// 按照日期格式创建目录:/yyyy/MM/dd/
static Future<String> createStorageDirectory(DateTime date) async {
try {
final supportDir = await getApplicationSupportDirectory();
// 构建日期路径
final year = date.year.toString();
final month = date.month.toString().padLeft(2, '0');
final day = date.day.toString().padLeft(2, '0');
final storagePath = path.join(supportDir.path, 'images', year, month, day);
final storageDir = Directory(storagePath);
// 如果目录不存在,创建目录
if (!await storageDir.exists()) {
await storageDir.create(recursive: true);
Logger.debug('创建存储目录: $storagePath');
}
return storagePath;
} catch (error, stackTrace) {
Logger.error('创建存储目录失败', error: error, stackTrace: stackTrace);
throw StorageError(
message: '创建存储目录失败: ${error.toString()}',
code: 'STORAGE_ERROR',
stackTrace: stackTrace,
);
}
}
/// 创建缩略图存储目录
static Future<String> createThumbnailDirectory(DateTime date) async {
try {
final supportDir = await getApplicationSupportDirectory();
// 构建日期路径
final year = date.year.toString();
final month = date.month.toString().padLeft(2, '0');
final day = date.day.toString().padLeft(2, '0');
final thumbnailPath = path.join(supportDir.path, 'thumbnails', year, month, day);
final thumbnailDir = Directory(thumbnailPath);
// 如果目录不存在,创建目录
if (!await thumbnailDir.exists()) {
await thumbnailDir.create(recursive: true);
Logger.debug('创建缩略图目录: $thumbnailPath');
}
return thumbnailPath;
} catch (error, stackTrace) {
Logger.error('创建缩略图目录失败', error: error, stackTrace: stackTrace);
throw StorageError(
message: '创建缩略图目录失败: ${error.toString()}',
code: 'STORAGE_ERROR',
stackTrace: stackTrace,
);
}
}
/// 创建临时文件目录
static Future<String> createTempDirectory() async {
try {
final tempDir = await getTemporaryDirectory();
final inspoSnapTempPath = path.join(tempDir.path, 'insposnap_temp');
final tempDirectory = Directory(inspoSnapTempPath);
if (!await tempDirectory.exists()) {
await tempDirectory.create(recursive: true);
Logger.debug('创建临时目录: $inspoSnapTempPath');
}
return inspoSnapTempPath;
} catch (error, stackTrace) {
Logger.error('创建临时目录失败', error: error, stackTrace: stackTrace);
throw StorageError(
message: '创建临时目录失败: ${error.toString()}',
code: 'STORAGE_ERROR',
stackTrace: stackTrace,
);
}
}
/// 检查存储空间是否充足
static Future<bool> hasEnoughStorageSpace({int requiredBytes = 100 * 1024 * 1024}) async {
try {
final directory = await getApplicationSupportDirectory();
// final stat = await FileStat.stat(directory.path);
// 获取可用空间(这里简化处理,实际需要更复杂的计算)
// 在实际应用中,可能需要使用 platform-specific 代码来获取准确的可用空间
// 模拟检查:如果目录存在且有写入权限,则认为空间充足
final testFile = File(path.join(directory.path, '.space_test'));
try {
await testFile.writeAsString('test');
await testFile.delete();
Logger.debug('存储空间检查通过');
return true;
} catch (e) {
Logger.warning('存储空间可能不足或没有写入权限');
return false;
}
} catch (error, stackTrace) {
Logger.error('检查存储空间失败', error: error, stackTrace: stackTrace);
return false;
}
}
/// 获取目录大小(字节)
static Future<int> getDirectorySize(Directory directory) async {
try {
int totalSize = 0;
if (!await directory.exists()) {
return 0;
}
await for (final entity in directory.list(recursive: true, followLinks: false)) {
if (entity is File) {
try {
final stat = await entity.stat();
totalSize += stat.size;
} catch (e) {
// 忽略单个文件的错误,继续统计其他文件
Logger.warning('获取文件大小失败: ${entity.path}');
}
}
}
Logger.debug('目录大小: ${directory.path} = ${(totalSize / 1024 / 1024).toStringAsFixed(2)}MB');
return totalSize;
} catch (error, stackTrace) {
Logger.error('获取目录大小失败', error: error, stackTrace: stackTrace);
return 0;
}
}
/// 删除目录及其所有内容
static Future<void> deleteDirectory(Directory directory) async {
try {
if (await directory.exists()) {
await directory.delete(recursive: true);
Logger.debug('删除目录: ${directory.path}');
}
} catch (error, stackTrace) {
Logger.error('删除目录失败', error: error, stackTrace: stackTrace);
throw StorageError(
message: '删除目录失败: ${error.toString()}',
code: 'STORAGE_ERROR',
stackTrace: stackTrace,
);
}
}
/// 清空目录内容(保留目录本身)
static Future<void> clearDirectory(Directory directory) async {
try {
if (!await directory.exists()) {
return;
}
await for (final entity in directory.list(followLinks: false)) {
try {
if (entity is File) {
await entity.delete();
} else if (entity is Directory) {
await entity.delete(recursive: true);
}
} catch (e) {
// 忽略单个文件/目录的删除错误
Logger.warning('删除失败: ${entity.path}');
}
}
Logger.debug('清空目录: ${directory.path}');
} catch (error, stackTrace) {
Logger.error('清空目录失败', error: error, stackTrace: stackTrace);
throw StorageError(
message: '清空目录失败: ${error.toString()}',
code: 'STORAGE_ERROR',
stackTrace: stackTrace,
);
}
}
/// 复制文件
static Future<File> copyFile(String sourcePath, String targetPath) async {
try {
final sourceFile = File(sourcePath);
if (!await sourceFile.exists()) {
throw StorageError(
message: '源文件不存在: $sourcePath',
code: 'STORAGE_ERROR',
);
}
// 确保目标目录存在
final targetDir = Directory(path.dirname(targetPath));
if (!await targetDir.exists()) {
await targetDir.create(recursive: true);
}
final targetFile = await sourceFile.copy(targetPath);
Logger.debug('复制文件: $sourcePath -> $targetPath');
return targetFile;
} catch (error, stackTrace) {
Logger.error('复制文件失败', error: error, stackTrace: stackTrace);
throw StorageError(
message: '复制文件失败: ${error.toString()}',
code: 'STORAGE_ERROR',
stackTrace: stackTrace,
);
}
}
/// 移动文件
static Future<File> moveFile(String sourcePath, String targetPath) async {
try {
final sourceFile = File(sourcePath);
if (!await sourceFile.exists()) {
throw StorageError(
message: '源文件不存在: $sourcePath',
code: 'STORAGE_ERROR',
);
}
// 确保目标目录存在
final targetDir = Directory(path.dirname(targetPath));
if (!await targetDir.exists()) {
await targetDir.create(recursive: true);
}
final targetFile = await sourceFile.rename(targetPath);
Logger.debug('移动文件: $sourcePath -> $targetPath');
return targetFile;
} catch (error, stackTrace) {
Logger.error('移动文件失败', error: error, stackTrace: stackTrace);
throw StorageError(
message: '移动文件失败: ${error.toString()}',
code: 'STORAGE_ERROR',
stackTrace: stackTrace,
);
}
}
/// 获取文件扩展名
static String getFileExtension(String filePath) {
return path.extension(filePath).toLowerCase();
}
/// 获取文件名(不含路径)
static String getFileName(String filePath) {
return path.basename(filePath);
}
/// 获取文件名(不含扩展名)
static String getFileNameWithoutExtension(String filePath) {
return path.basenameWithoutExtension(filePath);
}
/// 生成唯一的文件名
static String generateUniqueFileName(String originalFileName) {
final timestamp = DateTime.now().millisecondsSinceEpoch;
final extension = getFileExtension(originalFileName);
final nameWithoutExt = getFileNameWithoutExtension(originalFileName);
return '${nameWithoutExt}_$timestamp$extension';
}
/// 检查文件是否存在
static Future<bool> fileExists(String filePath) async {
try {
final file = File(filePath);
return await file.exists();
} catch (error) {
Logger.error('检查文件存在失败', error: error);
return false;
}
}
/// 获取文件大小(字节)
static Future<int> getFileSize(String filePath) async {
try {
final file = File(filePath);
if (await file.exists()) {
final stat = await file.stat();
return stat.size;
}
return 0;
} catch (error) {
Logger.error('获取文件大小失败', error: error);
return 0;
}
}
/// 格式化文件大小
static String formatFileSize(int bytes) {
if (bytes <= 0) return '0 B';
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
int unitIndex = 0;
double size = bytes.toDouble();
while (size >= 1024 && unitIndex < units.length - 1) {
size /= 1024;
unitIndex++;
}
return '${size.toStringAsFixed(2)} ${units[unitIndex]}';
}
/// 清理临时文件
static Future<void> cleanTempFiles() async {
try {
final tempDir = Directory(await createTempDirectory());
await clearDirectory(tempDir);
Logger.info('临时文件清理完成');
} catch (error, stackTrace) {
Logger.error('清理临时文件失败', error: error, stackTrace: stackTrace);
}
}
/// 获取存储使用情况
static Future<Map<String, dynamic>> getStorageUsage() async {
try {
final supportDir = await getApplicationSupportDirectory();
final imagesDir = Directory(path.join(supportDir.path, 'images'));
final thumbnailsDir = Directory(path.join(supportDir.path, 'thumbnails'));
final imagesSize = await getDirectorySize(imagesDir);
final thumbnailsSize = await getDirectorySize(thumbnailsDir);
final totalSize = imagesSize + thumbnailsSize;
return {
'imagesSize': imagesSize,
'thumbnailsSize': thumbnailsSize,
'totalSize': totalSize,
'imagesSizeFormatted': formatFileSize(imagesSize),
'thumbnailsSizeFormatted': formatFileSize(thumbnailsSize),
'totalSizeFormatted': formatFileSize(totalSize),
};
} catch (error, stackTrace) {
Logger.error('获取存储使用情况失败', error: error, stackTrace: stackTrace);
return {
'imagesSize': 0,
'thumbnailsSize': 0,
'totalSize': 0,
'imagesSizeFormatted': '0 B',
'thumbnailsSizeFormatted': '0 B',
'totalSizeFormatted': '0 B',
};
}
}
}
/// 文件信息类
class FileInfo {
final String path;
final String name;
final String extension;
final int size;
final DateTime modifiedTime;
final bool exists;
FileInfo({
required this.path,
required this.name,
required this.extension,
required this.size,
required this.modifiedTime,
required this.exists,
});
factory FileInfo.fromFile(File file, [String? filePath]) {
final path = filePath ?? file.path;
final stat = file.statSync();
return FileInfo(
path: path,
name: file.uri.pathSegments.last,
extension: FileUtils.getFileExtension(path),
size: stat.size,
modifiedTime: stat.modified,
exists: stat.type != FileSystemEntityType.notFound,
);
}
String get sizeFormatted => FileUtils.formatFileSize(size);
@override
String toString() {
return 'FileInfo{path: $path, name: $name, size: $sizeFormatted, modified: $modifiedTime}';
}
}