|
|
|
// 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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|