You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
168 lines
5.1 KiB
168 lines
5.1 KiB
// file_utils.dart |
|
import 'dart:async'; |
|
import 'dart:convert'; |
|
import 'dart:io'; |
|
import 'package:flutter/material.dart'; |
|
import 'package:file_picker/file_picker.dart'; |
|
import 'package:path/path.dart' as path; |
|
import 'package:win_text_editor/framework/controllers/logger.dart'; |
|
|
|
class FileUtils { |
|
static Future<String?> pickFile(BuildContext context) async { |
|
try { |
|
final result = await FilePicker.platform.pickFiles(type: FileType.any, allowMultiple: false); |
|
return result?.files.single.path; |
|
} catch (e) { |
|
Logger().error('选择文件失败: ${e.toString()}'); |
|
if (context.mounted) { |
|
ScaffoldMessenger.of( |
|
context, |
|
).showSnackBar(SnackBar(content: Text('选择文件失败: ${e.toString()}'))); |
|
} |
|
return null; |
|
} |
|
} |
|
|
|
static Future<String?> readFileContent( |
|
BuildContext context, |
|
String filePath, { |
|
Duration timeout = const Duration(seconds: 30), |
|
}) async { |
|
try { |
|
final content = await File(filePath).readAsString().timeout( |
|
timeout, |
|
onTimeout: () { |
|
throw TimeoutException('文件加载超时,可能文件过大'); |
|
}, |
|
); |
|
return content; |
|
} on FormatException { |
|
if (context.mounted) { |
|
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('这不是可读的文本文件'))); |
|
} |
|
return null; |
|
} on FileSystemException catch (e) { |
|
if (context.mounted) { |
|
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('文件访问错误: ${e.message}'))); |
|
} |
|
return null; |
|
} catch (e) { |
|
Logger().error('读取文件失败: ${e.toString()}'); |
|
if (context.mounted) { |
|
ScaffoldMessenger.of( |
|
context, |
|
).showSnackBar(SnackBar(content: Text('读取失败: ${e.toString()}'))); |
|
} |
|
return null; |
|
} |
|
} |
|
|
|
static bool matchesFileType(String filePath, String fileType) { |
|
// 处理特殊情况 |
|
if (fileType == '*.*' || fileType == '*') return true; |
|
|
|
// 获取文件的实际名称和扩展名 |
|
final fileName = path.basename(filePath); |
|
final fileExt = path.extension(fileName).toLowerCase().replaceFirst('.', ''); |
|
|
|
// 分割通配符为文件名和扩展名部分 |
|
final parts = fileType.split('.'); |
|
final patternName = parts.length > 0 ? parts[0] : ''; |
|
final patternExt = parts.length > 1 ? parts.sublist(1).join('.') : ''; |
|
|
|
// 匹配文件名部分 |
|
bool nameMatches = true; |
|
if (patternName.isNotEmpty && patternName != '*') { |
|
// 如果模式不含通配符,则执行精确匹配 |
|
if (!patternName.contains('*') && !patternName.contains('?')) { |
|
nameMatches = fileName.startsWith('${patternName.toLowerCase()}.'); |
|
} else { |
|
nameMatches = matchesWildcard(fileName, patternName); |
|
} |
|
} |
|
|
|
// 匹配扩展名部分 |
|
bool extMatches = true; |
|
if (patternExt.isNotEmpty) { |
|
if (patternExt == '*') { |
|
extMatches = true; |
|
} else if (!patternExt.contains('*') && !patternExt.contains('?')) { |
|
// 如果扩展名模式不含通配符,执行精确匹配 |
|
extMatches = fileExt == patternExt.toLowerCase(); |
|
} else { |
|
extMatches = matchesWildcard(fileExt, patternExt.toLowerCase()); |
|
} |
|
} |
|
|
|
return nameMatches && extMatches; |
|
} |
|
|
|
static bool matchesWildcard(String input, String pattern) { |
|
// 处理特殊情况 |
|
if (pattern == '*') return true; |
|
|
|
// 动态规划实现通配符匹配 |
|
final m = input.length; |
|
final n = pattern.length; |
|
final dp = List.generate(m + 1, (_) => List.filled(n + 1, false)); |
|
|
|
// 空字符串匹配空模式 |
|
dp[0][0] = true; |
|
|
|
// 处理模式以 * 开头的情况 |
|
for (int j = 1; j <= n; j++) { |
|
if (pattern[j - 1] == '*') { |
|
dp[0][j] = dp[0][j - 1]; |
|
} |
|
} |
|
|
|
// 填充动态规划表 |
|
for (int i = 1; i <= m; i++) { |
|
for (int j = 1; j <= n; j++) { |
|
if (pattern[j - 1] == '*') { |
|
// * 可以匹配任意字符或空字符 |
|
dp[i][j] = dp[i][j - 1] || dp[i - 1][j]; |
|
} else if (pattern[j - 1] == '?' || input[i - 1] == pattern[j - 1]) { |
|
// ? 匹配单个字符,或者字符直接相等 |
|
dp[i][j] = dp[i - 1][j - 1]; |
|
} else { |
|
// 不匹配 |
|
dp[i][j] = false; |
|
} |
|
} |
|
} |
|
|
|
return dp[m][n]; |
|
} |
|
|
|
static Future<String> readFileWithAutoEncoding(File file) async { |
|
try { |
|
// 首先尝试 UTF-8 |
|
return await file.readAsString(); |
|
} on FileSystemException { |
|
// 如果 UTF-8 失败,尝试常见其他编码 |
|
final bytes = await file.readAsBytes(); |
|
|
|
// 常见编码尝试顺序 |
|
final encodingsToTry = |
|
[ |
|
Encoding.getByName('gbk'), |
|
Encoding.getByName('gb2312'), |
|
Encoding.getByName('big5'), |
|
latin1, |
|
].whereType<Encoding>(); |
|
|
|
for (final encoding in encodingsToTry) { |
|
try { |
|
Logger().debug("尝试字符集:${encoding.name}"); |
|
return encoding.decode(bytes); |
|
} catch (_) { |
|
continue; |
|
} |
|
} |
|
|
|
// 所有尝试都失败,返回原始字节 |
|
return String.fromCharCodes(bytes); |
|
} |
|
} |
|
}
|
|
|