import 'dart:io'; import 'dart:convert'; import 'package:crypto/crypto.dart'; import 'package:dio/io.dart'; import 'package:path/path.dart' as path; import 'package:dio/dio.dart'; import 'developer/ix_developer_tools.dart'; class FileStreamUtils { static const int defaultBufferSize = 64 * 1024; static final Dio dio = Dio(); static setProxy(String proxy) { dio.httpClientAdapter = IOHttpClientAdapter( createHttpClient: () { final client = HttpClient(); client.findProxy = (uri) => proxy; client.badCertificateCallback = (X509Certificate cert, String host, int port) => true; return client; }, ); } /// 读取文件(支持本地文件和网络URL) /// [path] 文件路径或URL /// [encoding] 文件编码,默认UTF8 /// [onProgress] 读取进度回调 static Future readTextFile({ required String path, Encoding encoding = utf8, void Function(double progress)? onProgress, }) async { if (path.startsWith('http://') || path.startsWith('https://')) { return _readFromUrl( url: path, encoding: encoding, onProgress: onProgress, ); } else { return _readFromFile( filePath: path, encoding: encoding, onProgress: onProgress, ); } } /// 从URL读取 static Future _readFromUrl({ required String url, required Encoding encoding, void Function(double progress)? onProgress, }) async { try { // 添加talker日志 dio.interceptors.add(SimpleNoSignApiMonitorInterceptor()); final response = await dio.get>( url, options: Options( responseType: ResponseType.bytes, followRedirects: true, ), onReceiveProgress: (received, total) { if (total != -1 && onProgress != null) { onProgress(received / total); } }, ); if (response.data == null) { throw Exception('No data received'); } final bytes = response.data!; final content = encoding.decode(bytes); final md5Hash = md5.convert(bytes).toString(); return FileReadResult( content: content, md5Hash: md5Hash, fileName: path.basename(url), fileSize: bytes.length, ); } catch (e) { throw Exception('Error downloading file: $e'); } } /// 从本地文件读取 static Future _readFromFile({ required String filePath, required Encoding encoding, void Function(double progress)? onProgress, }) async { try { final file = File(filePath); if (!await file.exists()) { throw FileSystemException('File not found', filePath); } final fileSize = await file.length(); var bytesRead = 0; final List allBytes = []; final StringBuffer content = StringBuffer(); final stream = file.openRead(); await for (var data in stream) { allBytes.addAll(data); content.write(encoding.decode(data)); bytesRead += data.length; if (onProgress != null) { onProgress(bytesRead / fileSize); } } final md5Hash = md5.convert(allBytes).toString(); return FileReadResult( content: content.toString(), md5Hash: md5Hash, fileName: path.basename(filePath), fileSize: fileSize, ); } catch (e) { throw FileSystemException('Error reading file: $e', filePath); } } /// 验证文件或URL的MD5 static Future verifyMd5({ required String path, required String expectedMd5, void Function(double progress)? onProgress, }) async { try { final result = await readTextFile(path: path, onProgress: onProgress); return result.md5Hash.toLowerCase() == expectedMd5.toLowerCase(); } catch (e) { return false; } } /// 获取文件或URL的MD5 static Future getMd5({ required String path, void Function(double progress)? onProgress, }) async { try { final result = await readTextFile(path: path, onProgress: onProgress); return result.md5Hash; } catch (e) { return null; } } } /// 文件读取结果 class FileReadResult { final String content; // 文件内容 final String md5Hash; // MD5值 final String fileName; // 文件名 final int fileSize; // 文件大小 FileReadResult({ required this.content, required this.md5Hash, required this.fileName, required this.fileSize, }); @override String toString() { return 'FileReadResult{fileName: $fileName, fileSize: $fileSize, md5Hash: $md5Hash}'; } }