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 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 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 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 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 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 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 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 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 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 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 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 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 fileExists(String filePath) async { try { final file = File(filePath); return await file.exists(); } catch (error) { Logger.error('检查文件存在失败', error: error); return false; } } /// 获取文件大小(字节) static Future 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 cleanTempFiles() async { try { final tempDir = Directory(await createTempDirectory()); await clearDirectory(tempDir); Logger.info('临时文件清理完成'); } catch (error, stackTrace) { Logger.error('清理临时文件失败', error: error, stackTrace: stackTrace); } } /// 获取存储使用情况 static Future> 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}'; } }