| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369 |
- import 'dart:async';
- import 'dart:io';
- import 'package:archive/archive.dart';
- import 'package:path_provider/path_provider.dart';
- import 'package:path/path.dart' as path;
- import 'log/logger.dart';
- /// GZIP文件管理器
- /// 用于创建和分享GZIP压缩文件,不需要密码保护
- class GzipManager {
- static const String TAG = 'GzipManager';
- static final GzipManager _instance = GzipManager._internal();
- factory GzipManager() => _instance;
- GzipManager._internal();
- // 防重复点击机制
- DateTime? _lastGenerateTime;
- String? _lastGeneratedFilePath;
- static const Duration _throttleDuration = Duration(minutes: 1);
- /// 生成GZIP压缩文件并分享
- ///
- /// [filePaths] 要压缩的文件路径列表
- /// [gzipFileName] gzip文件名,默认为 "nomo_logs_时间戳.tar.gz"
- /// [deleteExisting] 是否删除已存在的同名文件,默认为true
- ///
- /// 返回生成的gzip文件路径,如果失败返回null
- Future<String?> generateAndShareGzip({
- required List<String> filePaths,
- String? gzipFileName,
- bool deleteExisting = true,
- }) async {
- try {
- // 检查防重复点击
- if (_shouldUseLastGeneratedFile()) {
- if (_lastGeneratedFilePath != null &&
- await File(_lastGeneratedFilePath!).exists()) {
- // await _shareFile(_lastGeneratedFilePath!);
- return _lastGeneratedFilePath;
- }
- }
- // 验证文件路径
- final validFilePaths = await _validateFilePaths(filePaths);
- if (validFilePaths.isEmpty) {
- throw Exception('no valid file paths');
- }
- // 生成gzip文件
- final gzipFilePath = await _generateGzip(
- filePaths: validFilePaths,
- gzipFileName: gzipFileName,
- deleteExisting: deleteExisting,
- );
- if (gzipFilePath != null) {
- // 更新防重复点击状态
- _lastGenerateTime = DateTime.now();
- _lastGeneratedFilePath = gzipFilePath;
- // 分享文件
- // await _shareFile(gzipFilePath);
- return gzipFilePath;
- }
- return null;
- } catch (e) {
- log(TAG, 'generate gzip file failed: $e');
- return null;
- }
- }
- /// 检查是否应该使用上次生成的文件
- bool _shouldUseLastGeneratedFile() {
- if (_lastGenerateTime == null) return false;
- final timeDiff = DateTime.now().difference(_lastGenerateTime!);
- return timeDiff < _throttleDuration;
- }
- /// 验证文件路径
- Future<List<String>> _validateFilePaths(List<String> filePaths) async {
- final validPaths = <String>[];
- for (final filePath in filePaths) {
- final file = File(filePath);
- if (await file.exists()) {
- validPaths.add(filePath);
- } else {
- log(TAG, 'file not exists: $filePath');
- }
- }
- return validPaths;
- }
- /// 生成GZIP压缩文件
- Future<String?> _generateGzip({
- required List<String> filePaths,
- String? gzipFileName,
- bool deleteExisting = true,
- }) async {
- try {
- // 获取临时目录
- final tempDir = await getTemporaryDirectory();
- // 生成gzip文件名
- final timestamp = DateTime.now().millisecondsSinceEpoch;
- final fileName = gzipFileName ?? 'nomo_logs_$timestamp.tar.gz';
- final gzipFilePath = path.join(tempDir.path, fileName);
- // 删除已存在的文件
- if (deleteExisting) {
- final existingFile = File(gzipFilePath);
- if (await existingFile.exists()) {
- await existingFile.delete();
- }
- }
- // 创建GZIP压缩文件
- return await _createGzipFile(
- filePaths: filePaths,
- gzipFilePath: gzipFilePath,
- );
- } catch (e) {
- log(TAG, 'generate gzip file failed: $e');
- return null;
- }
- }
- /// 创建GZIP压缩文件
- Future<String?> _createGzipFile({
- required List<String> filePaths,
- required String gzipFilePath,
- }) async {
- try {
- // 创建TAR归档
- final tarArchive = Archive();
- // 添加文件到TAR归档
- for (final filePath in filePaths) {
- final file = File(filePath);
- final fileName = path.basename(filePath);
- final fileBytes = await file.readAsBytes();
- // 创建TAR文件条目
- final tarFile = ArchiveFile(fileName, fileBytes.length, fileBytes);
- tarArchive.addFile(tarFile);
- }
- // 将TAR归档转换为字节
- final tarData = TarEncoder().encode(tarArchive);
- // 使用GZIP压缩TAR数据
- final gzipData = GZipEncoder().encode(tarData);
- final gzipFile = File(gzipFilePath);
- await gzipFile.writeAsBytes(gzipData);
- log(TAG, 'gzip file generated successfully: $gzipFilePath');
- return gzipFilePath;
- } catch (e) {
- log(TAG, 'create gzip file failed: $e');
- rethrow;
- }
- }
- /// 解压GZIP文件
- ///
- /// [gzipFilePath] gzip文件路径
- /// [outputDir] 输出目录,默认为临时目录
- Future<List<String>> extractGzip({
- required String gzipFilePath,
- String? outputDir,
- }) async {
- try {
- final gzipFile = File(gzipFilePath);
- if (!await gzipFile.exists()) {
- throw Exception('gzip文件不存在: $gzipFilePath');
- }
- // 读取gzip文件
- var gzipBytes = await gzipFile.readAsBytes();
- log(TAG, 'read gzip file size: ${gzipBytes.length} bytes');
- // 解压GZIP
- log(TAG, 'start to decompress gzip file...');
- final tarBytes = GZipDecoder().decodeBytes(gzipBytes);
- log(
- TAG,
- 'gzip decompressed successfully, tar size: ${tarBytes.length} bytes',
- );
- // 解压TAR
- log(TAG, 'start to extract tar archive...');
- final archive = TarDecoder().decodeBytes(tarBytes);
- log(TAG, 'tar extracted successfully, contains ${archive.length} files');
- // 确定输出目录
- final outputDirectory = outputDir ?? (await getTemporaryDirectory()).path;
- final extractedFiles = <String>[];
- // 提取文件
- for (final file in archive) {
- if (file.isFile) {
- // 写入解压后的文件
- final outputPath = path.join(outputDirectory, file.name);
- final outputFile = File(outputPath);
- await outputFile.writeAsBytes(file.content);
- extractedFiles.add(outputPath);
- log(TAG, 'extract file: ${file.name} -> $outputPath');
- }
- }
- log(TAG, 'successfully extracted ${extractedFiles.length} files');
- return extractedFiles;
- } catch (e) {
- log(TAG, 'extract gzip file failed: $e');
- log(TAG, 'error type: ${e.runtimeType}');
- rethrow;
- }
- }
- /// 清除缓存的文件
- Future<void> clearCache() async {
- try {
- if (_lastGeneratedFilePath != null) {
- final file = File(_lastGeneratedFilePath!);
- if (await file.exists()) {
- await file.delete();
- }
- }
- _lastGeneratedFilePath = null;
- _lastGenerateTime = null;
- } catch (e) {
- log(TAG, 'clear cache failed: $e');
- }
- }
- /// 获取上次生成的文件路径
- String? get lastGeneratedFilePath => _lastGeneratedFilePath;
- /// 获取上次生成的时间
- DateTime? get lastGenerateTime => _lastGenerateTime;
- /// 检查是否在防重复点击时间窗口内
- bool get isInThrottleWindow => _shouldUseLastGeneratedFile();
- /// 获取文件大小信息
- Future<Map<String, dynamic>> getFileInfo(String filePath) async {
- try {
- final file = File(filePath);
- if (await file.exists()) {
- final stat = await file.stat();
- return {
- 'path': filePath,
- 'name': path.basename(filePath),
- 'size': stat.size,
- 'modified': stat.modified,
- 'created': stat.changed,
- };
- }
- return {};
- } catch (e) {
- log(TAG, 'get file info failed: $e');
- return {};
- }
- }
- /// 批量获取文件信息
- Future<List<Map<String, dynamic>>> getFilesInfo(
- List<String> filePaths,
- ) async {
- final filesInfo = <Map<String, dynamic>>[];
- for (final filePath in filePaths) {
- final info = await getFileInfo(filePath);
- if (info.isNotEmpty) {
- filesInfo.add(info);
- }
- }
- return filesInfo;
- }
- /// 检查文件是否为GZIP格式
- Future<bool> isGzipFile(String filePath) async {
- try {
- final file = File(filePath);
- if (!await file.exists()) {
- return false;
- }
- // 读取文件头部字节
- final bytes = await file.openRead(0, 2).first;
- if (bytes.length < 2) {
- return false;
- }
- // GZIP文件头标识:0x1f 0x8b
- return bytes[0] == 0x1f && bytes[1] == 0x8b;
- } catch (e) {
- log(TAG, 'check gzip file format failed: $e');
- return false;
- }
- }
- /// 获取GZIP文件中的文件列表
- Future<List<String>> getGzipFileList(String gzipFilePath) async {
- try {
- final gzipFile = File(gzipFilePath);
- if (!await gzipFile.exists()) {
- throw Exception('gzip file not exists: $gzipFilePath');
- }
- // 读取并解压GZIP
- var gzipBytes = await gzipFile.readAsBytes();
- final tarBytes = GZipDecoder().decodeBytes(gzipBytes);
- // 解析TAR归档
- final archive = TarDecoder().decodeBytes(tarBytes);
- final fileList = <String>[];
- for (final file in archive) {
- if (file.isFile) {
- fileList.add(file.name);
- }
- }
- return fileList;
- } catch (e) {
- log(TAG, 'get gzip file list failed: $e');
- rethrow;
- }
- }
- /// 获取压缩比信息
- Future<Map<String, dynamic>> getCompressionInfo(String gzipFilePath) async {
- try {
- final gzipFile = File(gzipFilePath);
- if (!await gzipFile.exists()) {
- return {};
- }
- final gzipSize = await gzipFile.length();
- // 解压获取原始大小
- var gzipBytes = await gzipFile.readAsBytes();
- final tarBytes = GZipDecoder().decodeBytes(gzipBytes);
- final originalSize = tarBytes.length;
- final compressionRatio = originalSize > 0
- ? ((1 - gzipSize / originalSize) * 100).toStringAsFixed(2)
- : '0.00';
- return {
- 'originalSize': originalSize,
- 'compressedSize': gzipSize,
- 'compressionRatio': '$compressionRatio%',
- 'spaceSaved': originalSize - gzipSize,
- };
- } catch (e) {
- log(TAG, 'get compression info failed: $e');
- return {};
- }
- }
- }
|