import 'dart:io'; import 'package:common_utils/common_utils.dart'; import 'package:external_path/external_path.dart'; import 'package:pj_supermarket_cloud/common/index.dart'; import 'package:sqflite/sqflite.dart'; abstract class AppFileTool { static const _tag = "app文件工具"; static const _baseDir = "pj_cashier"; static const _baseBackupDir = "backup"; static const _backupDatabaseDir = "database"; static const _backupLoggerDir = "logger"; static const _backupFaceDir = "face"; /// 检查并创建目录 static Future _checkAndcreateDir(String dir) async { Directory targetDir = Directory(dir); if (!await targetDir.exists()) { await targetDir.create(recursive: true); } return dir; } /// 获取外部路径 static Future getExternalPath({ bool backupExternalPath = true, }) async { final documentsDir = await ExternalPath.getExternalStoragePublicDirectory( ExternalPath.DIRECTORY_DOCUMENTS, ); final backupDir = "$documentsDir/$_baseDir"; if (backupExternalPath) { final sysSetting = await AppSettingTool.getSysSetting(); sysSetting.externalPath = documentsDir; await AppSettingTool.setSysSetting(sysSetting); } return backupDir; } /// 创建数据库文件地址 static Future getDatabasePath() async { String backupDir = await getExternalPath(backupExternalPath: false); String dir = "$backupDir/$_backupDatabaseDir"; dir = await _checkAndcreateDir(dir); return dir; } /// 创建数据库文件备份地址 static Future getDatabaseBackupPath() async { String backupDir = await getExternalPath(backupExternalPath: false); String dir = "$backupDir/$_baseBackupDir/$_backupDatabaseDir"; DateTime now = DateTime.now(); int year = now.year; int month = now.month; int day = now.day; dir = "$dir/$year-$month"; dir = await _checkAndcreateDir(dir); dir = "$dir/$day.db"; return dir; } /// 创建日志地址 static Future getLoggerPath() async { final backupDir = await getExternalPath(); return _checkAndcreateDir("$backupDir/$_backupLoggerDir"); } /// 创建日志备份地址 static Future getLoggerBackupPath() async { final backupDir = await getExternalPath(backupExternalPath: false); String dir = "$backupDir/$_baseBackupDir/$_backupLoggerDir"; DateTime now = DateTime.now(); int year = now.year; int month = now.month; int day = now.day; dir = "$dir/$year-$month"; dir = await _checkAndcreateDir(dir); dir = "$dir/$day.log"; return dir; } /// 创建一次性日志快照地址,避免手动导出覆盖当前仍在写入的实时日志文件。 static Future getLoggerSnapshotPath() async { final backupDir = await getExternalPath(backupExternalPath: false); String dir = "$backupDir/$_baseBackupDir/$_backupLoggerDir/manual"; dir = await _checkAndcreateDir(dir); final now = DateTime.now(); final stamp = now.toIso8601String().replaceAll(':', '-'); return "$dir/log-$stamp.log"; } /// 创建人脸地址 static Future getFacePath() async { final backupDir = await getExternalPath(); return _checkAndcreateDir("$backupDir/$_backupFaceDir"); } /// 创建人脸备份地址 static Future getFaceBackupPath() async { final backupDir = await getExternalPath(); return _checkAndcreateDir("$backupDir/$_baseBackupDir/$_backupFaceDir"); } /// 备份数据库并返回生成文件,页面据此展示真实结果和最近备份时间。 static Future backupSqlFilePath() async { final sourcePath = await getDatabasePath(); if (ObjectUtil.isEmpty(sourcePath)) { logger.e("|$_tag|备份sql文件失败,无效的sourcePath路径path:|$sourcePath|"); return null; } final databaseFilePath = "$sourcePath/${Constants.databaseName}"; if (!await File(databaseFilePath).exists()) { logger.w("|$_tag|备份sql文件失败,无效的sourcePath路径path|$databaseFilePath|"); return null; } String? backupPath = await getDatabaseBackupPath(); if (ObjectUtil.isEmpty(backupPath)) { logger.e("|$_tag|备份sql文件失败,无效的backupPath路径path:|$backupPath|"); return null; } try { await File(databaseFilePath).copy(backupPath!); return backupPath; } catch (e, stack) { logger.e("|$_tag|备份sql文件失败,意外的错误error|$e|stack|$stack|"); return null; } } // 保留旧接口,应用启动时仍可按原方式进行自动备份。 static Future backupSqlFile() async { return await backupSqlFilePath() != null; } /// 将当前实时日志复制为独立快照,不中断 Logger 正在持有的文件句柄。 static Future backupLogFile() async { final sourcePath = await getLoggerBackupPath(); final targetPath = await getLoggerSnapshotPath(); if (ObjectUtil.isEmpty(sourcePath) || ObjectUtil.isEmpty(targetPath)) { return null; } final source = File(sourcePath!); if (!await source.exists()) { logger.w("|$_tag|备份日志失败,实时日志不存在path|$sourcePath|"); return null; } try { await source.copy(targetPath!); return targetPath; } catch (e, stack) { logger.e("|$_tag|备份日志失败error|$e|stack|$stack|"); return null; } } /// 获取目录中最近生成文件的修改时间。 static Future getLatestModifiedTime(String? path) async { if (ObjectUtil.isEmpty(path)) return null; final entity = FileSystemEntity.typeSync(path!); if (entity == FileSystemEntityType.file) { return File(path).stat().then((value) => value.modified); } final directory = Directory(path); if (!await directory.exists()) return null; DateTime? latest; await for (final file in directory.list(recursive: true)) { if (file is! File) continue; final modified = (await file.stat()).modified; if (latest == null || modified.isAfter(latest)) latest = modified; } return latest; } // 如果没有数据库将最新的数据库移动为使用的数据库 static Future useLastBackDatabase() async { final databasePath = getDatabasesPath(); final databaseFilePath = "$databasePath/${Constants.databaseName}"; if (await File(databaseFilePath).exists()) { logger.w("|$_tag|替换数据库错误,数据库文件已存在path|$databaseFilePath|"); return; } final path = await getDatabaseBackupPath(); if (ObjectUtil.isEmpty(path)) { logger.w("|$_tag|替换数据库错误,无效的数据库备份path|$path|"); return; } final dir = Directory(path!).parent; if (!await dir.exists()) { logger.w("|$_tag|替换数据库错误,无效的数据库备份目录path|$dir|"); return; } final files = dir.listSync(); files.sort( (a, b) => b.statSync().modified.compareTo(a.statSync().modified), ); if (files.isEmpty) { logger.w("|$_tag|替换数据库错误,数据库备份目录为空path|$dir|"); return; } final lastFile = files.first; try { if (lastFile is File) { final sourcePath = lastFile.path; await File(sourcePath).copy(databaseFilePath); logger.i( "|$_tag|替换数据库成功,sourcePath|$sourcePath|targetPath|$databaseFilePath|", ); } } catch (e, stack) { logger.e("|$_tag|替换数据库错误,意外的错误error|$e|stack|$stack|"); } } // 获取文件后缀 static String getFileExtension(String url) { int lastDotIndex = url.lastIndexOf('.'); if (lastDotIndex == -1) { return ''; } int lastSlashIndex = url.lastIndexOf('/'); if (lastDotIndex > lastSlashIndex) { return url.substring(lastDotIndex); } return ''; } }