Commit a33cbb71d09d06c04957c021e8bb03dc82632bc0

Authored by 权海
1 parent cd2e364f

feat(ui):优化ohos日志

... ... @@ -7,7 +7,7 @@ import 'package:path_provider/path_provider.dart';
import 'app_logger.dart';
/// Persists a compact, privacy-safe timeline of the OHOS health data pipeline.
/// Persists a privacy-safe timeline of the OHOS health data pipeline.
///
/// Writes are queued and intentionally fire-and-forget so health callbacks,
/// calculation, and push delivery are never blocked by file I/O. Call
... ... @@ -18,9 +18,10 @@ class OhosHealthFlowLogStore {
static final OhosHealthFlowLogStore instance = OhosHealthFlowLogStore();
static const fileName = 'ohos_health_flow_events.json';
static const _retention = Duration(days: 7);
static const _maxEventCount = 1000;
static const _fileNamePrefix = 'ohos_health_flow_events_';
static const _fileNameSuffix = '.json';
static const _legacyFileName = 'ohos_health_flow_events.json';
static const _retainedDays = 7;
final Directory? _rootDirectory;
Future<void> _writeQueue = Future<void>.value();
... ... @@ -49,18 +50,15 @@ class OhosHealthFlowLogStore {
if (details != null && details.isNotEmpty)
'details': _sanitizeMap(details),
};
final file = await _file();
await file.parent.create(recursive: true);
final cutoff = now.subtract(_retention);
final events = (await _readEvents(file))
.where((item) => _isWithinRetention(item, cutoff))
.toList()
..add(event);
final retained = events.length > _maxEventCount
? events.sublist(events.length - _maxEventCount)
: events;
final root = await _root();
await root.create(recursive: true);
await _migrateLegacyFile(root, now);
await _deleteExpiredDailyFiles(root, now);
final file = _fileFor(root, now);
final events = await _readEvents(file);
events.add(event);
await file.writeAsString(
const JsonEncoder.withIndent(' ').convert(retained),
const JsonEncoder.withIndent(' ').convert(events),
flush: true,
);
} catch (error, stackTrace) {
... ... @@ -77,7 +75,7 @@ class OhosHealthFlowLogStore {
/// Returns the complete file after all previously queued writes finish.
Future<Uint8List> readBytes() async {
await _writeQueue;
final file = await _file();
final file = _fileFor(await _root(), DateTime.now());
if (!await file.exists()) {
return Uint8List.fromList(
utf8.encode('[{"message":"No OHOS health flow logs found"}]'),
... ... @@ -86,11 +84,79 @@ class OhosHealthFlowLogStore {
return file.readAsBytes();
}
Future<File> _file() async {
final root = _rootDirectory ?? await getApplicationDocumentsDirectory();
return File('${root.path}/$fileName');
Future<Directory> _root() {
return _rootDirectory == null
? getApplicationDocumentsDirectory()
: Future<Directory>.value(_rootDirectory);
}
File _fileFor(Directory root, DateTime date) {
return File('${root.path}/${_fileNameFor(date)}');
}
static String _fileNameFor(DateTime date) {
final day = '${date.year.toString().padLeft(4, '0')}'
'${date.month.toString().padLeft(2, '0')}'
'${date.day.toString().padLeft(2, '0')}';
return '$_fileNamePrefix$day$_fileNameSuffix';
}
Future<void> _deleteExpiredDailyFiles(Directory root, DateTime now) async {
final earliestRetainedDay = DateTime(now.year, now.month, now.day)
.subtract(const Duration(days: _retainedDays - 1));
await for (final entity in root.list(followLinks: false)) {
if (entity is! File) continue;
final date = _dateFromFileName(entity.uri.pathSegments.last);
if (date != null && date.isBefore(earliestRetainedDay)) {
await entity.delete();
}
}
}
Future<void> _migrateLegacyFile(Directory root, DateTime now) async {
final legacyFile = File('${root.path}/$_legacyFileName');
if (!await legacyFile.exists()) return;
final earliestRetainedDay = DateTime(now.year, now.month, now.day)
.subtract(const Duration(days: _retainedDays - 1));
final eventsByDay = <String, List<Map<String, Object?>>>{};
for (final event in await _readEvents(legacyFile)) {
final timestamp = event['timestamp'];
final date = timestamp is String ? DateTime.tryParse(timestamp) : null;
if (date == null || _dayOf(date).isBefore(earliestRetainedDay)) continue;
eventsByDay.putIfAbsent(_fileNameFor(date), () => []).add(event);
}
try {
for (final entry in eventsByDay.entries) {
final file = File('${root.path}/${entry.key}');
final events = await _readEvents(file);
events.addAll(entry.value);
await file.writeAsString(
const JsonEncoder.withIndent(' ').convert(events),
flush: true,
);
}
await legacyFile.delete();
} catch (_) {
// Keep the legacy file for the next write if migration cannot finish.
}
}
static DateTime? _dateFromFileName(String fileName) {
final match =
RegExp(r'^ohos_health_flow_events_(\d{8})\.json$').firstMatch(fileName);
if (match == null) return null;
final day = match.group(1)!;
final date = DateTime.tryParse(
'${day.substring(0, 4)}-${day.substring(4, 6)}-${day.substring(6, 8)}',
);
return date != null && _fileNameFor(date) == fileName ? date : null;
}
static DateTime _dayOf(DateTime date) =>
DateTime(date.year, date.month, date.day);
Future<List<Map<String, Object?>>> _readEvents(File file) async {
if (!await file.exists()) return <Map<String, Object?>>[];
try {
... ... @@ -127,14 +193,4 @@ class OhosHealthFlowLogStore {
static String _limit(String value) =>
value.length <= 2000 ? value : '${value.substring(0, 2000)}…';
static bool _isWithinRetention(
Map<String, Object?> event,
DateTime cutoff,
) {
final timestamp = event['timestamp'];
if (timestamp is! String) return false;
final recordedAt = DateTime.tryParse(timestamp);
return recordedAt != null && !recordedAt.isBefore(cutoff);
}
}
... ...