502 lines
13 KiB
Dart
502 lines
13 KiB
Dart
import 'dart:io';
|
||
import 'package:path/path.dart' as path;
|
||
|
||
/// 文件工具类
|
||
/// 提供文件操作、目录管理和代码格式化功能
|
||
class FileUtils {
|
||
/// 解析路径(支持相对路径和绝对路径)
|
||
/// 如果是相对路径,相对于项目根目录(配置文件所在目录)
|
||
static String resolvePath(String filePath) {
|
||
// 如果是绝对路径,直接返回
|
||
if (path.isAbsolute(filePath)) {
|
||
return filePath;
|
||
}
|
||
|
||
// 相对路径:相对于当前工作目录
|
||
// 查找配置文件所在的目录作为项目根目录
|
||
final configFile = _findConfigFile();
|
||
if (configFile != null) {
|
||
final configDir = path.dirname(configFile);
|
||
return path.join(configDir, filePath);
|
||
}
|
||
|
||
// 如果找不到配置文件,使用当前工作目录
|
||
return path.join(Directory.current.path, filePath);
|
||
}
|
||
|
||
/// 查找配置文件
|
||
static String? _findConfigFile() {
|
||
var currentDir = Directory.current;
|
||
final maxDepth = 10;
|
||
var depth = 0;
|
||
|
||
while (depth < maxDepth) {
|
||
final configFile =
|
||
File(path.join(currentDir.path, 'generator_config.yaml'));
|
||
if (configFile.existsSync()) {
|
||
return configFile.path;
|
||
}
|
||
|
||
final parent = currentDir.parent;
|
||
if (parent.path == currentDir.path) {
|
||
break;
|
||
}
|
||
currentDir = parent;
|
||
depth++;
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
/// 确保目录存在
|
||
static Future<Directory> ensureDirectoryExists(String dirPath) async {
|
||
final resolvedPath = resolvePath(dirPath);
|
||
final directory = Directory(resolvedPath);
|
||
if (!await directory.exists()) {
|
||
await directory.create(recursive: true);
|
||
}
|
||
return directory;
|
||
}
|
||
|
||
/// 安全写入文件
|
||
static Future<void> safeWriteFile(String filePath, String content) async {
|
||
try {
|
||
final resolvedPath = resolvePath(filePath);
|
||
final file = File(resolvedPath);
|
||
final directory = file.parent;
|
||
|
||
// 确保目录存在
|
||
if (!await directory.exists()) {
|
||
await directory.create(recursive: true);
|
||
}
|
||
|
||
// 写入文件
|
||
await file.writeAsString(content);
|
||
} catch (e) {
|
||
throw FileSystemException('写入文件失败: $filePath', filePath);
|
||
}
|
||
}
|
||
|
||
/// 安全读取文件
|
||
static Future<String> safeReadFile(String filePath) async {
|
||
try {
|
||
final file = File(filePath);
|
||
if (!await file.exists()) {
|
||
throw FileSystemException('文件不存在: $filePath', filePath);
|
||
}
|
||
|
||
return await file.readAsString();
|
||
} catch (e) {
|
||
throw FileSystemException('读取文件失败: $filePath', filePath);
|
||
}
|
||
}
|
||
|
||
/// 检查文件是否存在
|
||
static Future<bool> fileExists(String filePath) async {
|
||
return await File(filePath).exists();
|
||
}
|
||
|
||
/// 检查目录是否存在
|
||
static Future<bool> directoryExists(String dirPath) async {
|
||
return await Directory(dirPath).exists();
|
||
}
|
||
|
||
/// 删除文件(如果存在)
|
||
static Future<void> deleteFileIfExists(String filePath) async {
|
||
final file = File(filePath);
|
||
if (await file.exists()) {
|
||
await file.delete();
|
||
}
|
||
}
|
||
|
||
/// 删除目录(如果存在)
|
||
static Future<void> deleteDirectoryIfExists(String dirPath) async {
|
||
final directory = Directory(dirPath);
|
||
if (await directory.exists()) {
|
||
await directory.delete(recursive: true);
|
||
}
|
||
}
|
||
|
||
/// 复制文件
|
||
static Future<void> copyFile(
|
||
String sourcePath, String destinationPath) async {
|
||
try {
|
||
final sourceFile = File(sourcePath);
|
||
final destinationFile = File(destinationPath);
|
||
|
||
if (!await sourceFile.exists()) {
|
||
throw FileSystemException('源文件不存在: $sourcePath', sourcePath);
|
||
}
|
||
|
||
// 确保目标目录存在
|
||
final destinationDir = destinationFile.parent;
|
||
if (!await destinationDir.exists()) {
|
||
await destinationDir.create(recursive: true);
|
||
}
|
||
|
||
await sourceFile.copy(destinationPath);
|
||
} catch (e) {
|
||
throw FileSystemException('复制文件失败: $sourcePath -> $destinationPath',
|
||
sourcePath, e is OSError ? e : null);
|
||
}
|
||
}
|
||
|
||
/// 移动文件
|
||
static Future<void> moveFile(
|
||
String sourcePath, String destinationPath) async {
|
||
try {
|
||
final sourceFile = File(sourcePath);
|
||
final destinationFile = File(destinationPath);
|
||
|
||
if (!await sourceFile.exists()) {
|
||
throw FileSystemException('源文件不存在: $sourcePath', sourcePath);
|
||
}
|
||
|
||
// 确保目标目录存在
|
||
final destinationDir = destinationFile.parent;
|
||
if (!await destinationDir.exists()) {
|
||
await destinationDir.create(recursive: true);
|
||
}
|
||
|
||
await sourceFile.rename(destinationPath);
|
||
} catch (e) {
|
||
throw FileSystemException('移动文件失败: $sourcePath -> $destinationPath',
|
||
sourcePath, e is OSError ? e : null);
|
||
}
|
||
}
|
||
|
||
/// 获取文件大小
|
||
static Future<int> getFileSize(String filePath) async {
|
||
try {
|
||
final file = File(filePath);
|
||
if (!await file.exists()) {
|
||
return 0;
|
||
}
|
||
return await file.length();
|
||
} catch (e) {
|
||
return 0;
|
||
}
|
||
}
|
||
|
||
/// 获取目录大小
|
||
static Future<int> getDirectorySize(String dirPath) async {
|
||
try {
|
||
final directory = Directory(dirPath);
|
||
if (!await directory.exists()) {
|
||
return 0;
|
||
}
|
||
|
||
int totalSize = 0;
|
||
await for (final entity in directory.list(recursive: true)) {
|
||
if (entity is File) {
|
||
totalSize += await entity.length();
|
||
}
|
||
}
|
||
return totalSize;
|
||
} catch (e) {
|
||
return 0;
|
||
}
|
||
}
|
||
|
||
/// 列出目录中的文件
|
||
static Future<List<String>> listFiles(String dirPath,
|
||
{String? extension}) async {
|
||
try {
|
||
final directory = Directory(dirPath);
|
||
if (!await directory.exists()) {
|
||
return [];
|
||
}
|
||
|
||
final files = <String>[];
|
||
await for (final entity in directory.list()) {
|
||
if (entity is File) {
|
||
if (extension == null || entity.path.endsWith(extension)) {
|
||
files.add(entity.path);
|
||
}
|
||
}
|
||
}
|
||
return files;
|
||
} catch (e) {
|
||
return [];
|
||
}
|
||
}
|
||
|
||
/// 列出目录中的子目录
|
||
static Future<List<String>> listDirectories(String dirPath) async {
|
||
try {
|
||
final directory = Directory(dirPath);
|
||
if (!await directory.exists()) {
|
||
return [];
|
||
}
|
||
|
||
final directories = <String>[];
|
||
await for (final entity in directory.list()) {
|
||
if (entity is Directory) {
|
||
directories.add(entity.path);
|
||
}
|
||
}
|
||
return directories;
|
||
} catch (e) {
|
||
return [];
|
||
}
|
||
}
|
||
|
||
/// 创建备份文件
|
||
static Future<String> createBackup(String filePath) async {
|
||
try {
|
||
final file = File(filePath);
|
||
if (!await file.exists()) {
|
||
throw FileSystemException('文件不存在: $filePath', filePath);
|
||
}
|
||
|
||
final timestamp = DateTime.now().toIso8601String().replaceAll(':', '-');
|
||
final backupPath = '${filePath}.backup.$timestamp';
|
||
|
||
await file.copy(backupPath);
|
||
return backupPath;
|
||
} catch (e) {
|
||
throw FileSystemException(
|
||
'创建备份失败: $filePath', filePath, e is OSError ? e : null);
|
||
}
|
||
}
|
||
|
||
/// 恢复备份文件
|
||
static Future<void> restoreBackup(
|
||
String backupPath, String originalPath) async {
|
||
try {
|
||
final backupFile = File(backupPath);
|
||
if (!await backupFile.exists()) {
|
||
throw FileSystemException('备份文件不存在: $backupPath', backupPath);
|
||
}
|
||
|
||
await backupFile.copy(originalPath);
|
||
} catch (e) {
|
||
throw FileSystemException('恢复备份失败: $backupPath -> $originalPath',
|
||
backupPath, e is OSError ? e : null);
|
||
}
|
||
}
|
||
|
||
/// 格式化文件路径
|
||
static String formatPath(String filePath) {
|
||
return path.normalize(filePath);
|
||
}
|
||
|
||
/// 获取文件名(不包括路径)
|
||
static String getFileName(String filePath) {
|
||
return path.basename(filePath);
|
||
}
|
||
|
||
/// 获取文件名(不包括扩展名)
|
||
static String getFileNameWithoutExtension(String filePath) {
|
||
return path.basenameWithoutExtension(filePath);
|
||
}
|
||
|
||
/// 获取文件扩展名
|
||
static String getFileExtension(String filePath) {
|
||
return path.extension(filePath);
|
||
}
|
||
|
||
/// 获取文件所在目录
|
||
static String getDirectoryPath(String filePath) {
|
||
return path.dirname(filePath);
|
||
}
|
||
|
||
/// 连接路径
|
||
static String joinPath(List<String> parts) {
|
||
return path.joinAll(parts);
|
||
}
|
||
|
||
/// 获取相对路径
|
||
static String getRelativePath(String filePath, String basePath) {
|
||
return path.relative(filePath, from: basePath);
|
||
}
|
||
|
||
/// 获取绝对路径
|
||
static String getAbsolutePath(String filePath) {
|
||
return path.absolute(filePath);
|
||
}
|
||
|
||
/// 检查路径是否为绝对路径
|
||
static bool isAbsolute(String filePath) {
|
||
return path.isAbsolute(filePath);
|
||
}
|
||
|
||
/// 清理文件名(移除不合法字符)
|
||
static String sanitizeFileName(String fileName) {
|
||
// 移除或替换不合法的文件名字符
|
||
return fileName
|
||
.replaceAll(RegExp(r'[<>:"/\\|?*]'), '_')
|
||
.replaceAll(RegExp(r'\s+'), '_')
|
||
.replaceAll(RegExp(r'_{2,}'), '_')
|
||
.replaceAll(RegExp(r'^_|_$'), '');
|
||
}
|
||
|
||
/// 生成唯一文件名
|
||
static Future<String> generateUniqueFileName(
|
||
String basePath, String fileName) async {
|
||
final extension = getFileExtension(fileName);
|
||
final nameWithoutExt = getFileNameWithoutExtension(fileName);
|
||
|
||
String uniqueName = fileName;
|
||
int counter = 1;
|
||
|
||
while (await File(path.join(basePath, uniqueName)).exists()) {
|
||
uniqueName = '${nameWithoutExt}_$counter$extension';
|
||
counter++;
|
||
}
|
||
|
||
return uniqueName;
|
||
}
|
||
|
||
/// 批量操作文件
|
||
static Future<void> batchOperation(
|
||
List<String> filePaths,
|
||
Future<void> Function(String filePath) operation,
|
||
) async {
|
||
for (final filePath in filePaths) {
|
||
try {
|
||
await operation(filePath);
|
||
} catch (e) {
|
||
print('批量操作失败: $filePath - $e');
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 查找文件
|
||
static Future<List<String>> findFiles(
|
||
String searchPath,
|
||
String pattern, {
|
||
bool recursive = false,
|
||
}) async {
|
||
try {
|
||
final directory = Directory(searchPath);
|
||
if (!await directory.exists()) {
|
||
return [];
|
||
}
|
||
|
||
final regex = RegExp(pattern);
|
||
final foundFiles = <String>[];
|
||
|
||
await for (final entity in directory.list(recursive: recursive)) {
|
||
if (entity is File) {
|
||
final fileName = getFileName(entity.path);
|
||
if (regex.hasMatch(fileName)) {
|
||
foundFiles.add(entity.path);
|
||
}
|
||
}
|
||
}
|
||
|
||
return foundFiles;
|
||
} catch (e) {
|
||
return [];
|
||
}
|
||
}
|
||
|
||
/// 获取文件修改时间
|
||
static Future<DateTime?> getFileModifiedTime(String filePath) async {
|
||
try {
|
||
final file = File(filePath);
|
||
if (!await file.exists()) {
|
||
return null;
|
||
}
|
||
|
||
final stat = await file.stat();
|
||
return stat.modified;
|
||
} catch (e) {
|
||
return null;
|
||
}
|
||
}
|
||
|
||
/// 比较文件修改时间
|
||
static Future<bool> isFileNewer(String filePath1, String filePath2) async {
|
||
final time1 = await getFileModifiedTime(filePath1);
|
||
final time2 = await getFileModifiedTime(filePath2);
|
||
|
||
if (time1 == null || time2 == null) {
|
||
return false;
|
||
}
|
||
|
||
return time1.isAfter(time2);
|
||
}
|
||
|
||
/// 计算文件哈希
|
||
static Future<String?> calculateFileHash(String filePath) async {
|
||
try {
|
||
final file = File(filePath);
|
||
if (!await file.exists()) {
|
||
return null;
|
||
}
|
||
|
||
final bytes = await file.readAsBytes();
|
||
return bytes.hashCode.toString();
|
||
} catch (e) {
|
||
return null;
|
||
}
|
||
}
|
||
|
||
/// 格式化文件大小
|
||
static String formatFileSize(int bytes) {
|
||
if (bytes < 1024) {
|
||
return '${bytes}B';
|
||
} else if (bytes < 1024 * 1024) {
|
||
return '${(bytes / 1024).toStringAsFixed(1)}KB';
|
||
} else if (bytes < 1024 * 1024 * 1024) {
|
||
return '${(bytes / (1024 * 1024)).toStringAsFixed(1)}MB';
|
||
} else {
|
||
return '${(bytes / (1024 * 1024 * 1024)).toStringAsFixed(1)}GB';
|
||
}
|
||
}
|
||
|
||
/// 创建临时文件
|
||
static Future<File> createTempFile(String prefix, {String? suffix}) async {
|
||
final tempDir = Directory.systemTemp;
|
||
final timestamp = DateTime.now().millisecondsSinceEpoch;
|
||
final fileName = '$prefix$timestamp${suffix ?? ''}';
|
||
|
||
return File(path.join(tempDir.path, fileName));
|
||
}
|
||
|
||
/// 清理临时文件
|
||
static Future<void> cleanupTempFiles(String pattern) async {
|
||
try {
|
||
final tempDir = Directory.systemTemp;
|
||
final regex = RegExp(pattern);
|
||
|
||
await for (final entity in tempDir.list()) {
|
||
if (entity is File) {
|
||
final fileName = getFileName(entity.path);
|
||
if (regex.hasMatch(fileName)) {
|
||
await entity.delete();
|
||
}
|
||
}
|
||
}
|
||
} catch (e) {
|
||
print('清理临时文件失败: $e');
|
||
}
|
||
}
|
||
|
||
/// 获取项目根目录下的generator目录路径(兼容旧版本)
|
||
static String getProjectRootGeneratorDir() {
|
||
final currentDir = Directory.current.path;
|
||
return joinPath([currentDir, 'generator']);
|
||
}
|
||
|
||
/// 安全地写入文件(兼容旧版本)
|
||
static Future<void> writeFile(String path, String content) async {
|
||
await safeWriteFile(path, content);
|
||
}
|
||
|
||
/// 获取目录中的文件列表(兼容旧版本)
|
||
static Future<List<FileSystemEntity>> listDirectory(String path) async {
|
||
try {
|
||
final directory = Directory(path);
|
||
if (!await directory.exists()) {
|
||
return [];
|
||
}
|
||
return await directory.list().toList();
|
||
} catch (e) {
|
||
return [];
|
||
}
|
||
}
|
||
}
|