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.

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