Files
zapstore/lib/services/package_manager/installed_packages_snapshot.dart
T
alltheseasandClaude Opus 4.5 958841eebc fix: address code review feedback
1. main.dart: Add database migration from Documents to Support directory
   - Migrates existing zapstore.db to new location on first run
   - Preserves existing users' data after directory change

2. installed_packages_snapshot.dart: Use atomic file replace
   - Remove explicit delete before rename (rename atomically replaces)
   - Add try/catch to preserve temp file on rename failure

3. polls_section.dart: Fix useEffect cleanup for dynamic controllers
   - Capture controller snapshot in useEffect
   - Properly dispose controllers added via "Add Option"

4. polls_section.dart: Fix filter to include votes at exact end time
   - Changed from isBefore to !isAfter for inclusive filtering

5. polls_utils.dart: Extract shared utility functions
   - canCreatePoll, filterValidResponses, calculateVoteCounts
   - Marked @visibleForTesting for test access

6. FEAT-001-package-manager.md: Add markdown language specifiers
   - Fix MD040 lint warnings for code blocks

7. polls_section_test.dart: Use production utils instead of duplicates
   - Import and test actual canCreatePoll from polls_utils
   - Add test for votes at exact poll end time

Signed-off-by: alltheseas <alltheseas@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-26 11:31:44 -06:00

100 lines
3.3 KiB
Dart

import 'dart:convert';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:path/path.dart' as path;
import 'package:path_provider/path_provider.dart';
import 'package:zapstore/services/package_manager/package_manager.dart';
class InstalledPackagesSnapshot {
static const _fileName = 'installed_packages_snapshot.json';
static Future<File> _file() async {
final dir = await getApplicationSupportDirectory();
return File(path.join(dir.path, _fileName));
}
static Future<void> save(Map<String, PackageInfo> installed) async {
try {
final file = await _file();
final tmp = File('${file.path}.tmp');
final list = installed.values
.map(
(p) => <String, dynamic>{
'appId': p.appId,
'name': p.name,
'version': p.version,
'versionCode': p.versionCode,
'signatureHash': p.signatureHash,
'canInstallSilently': p.canInstallSilently,
},
)
.toList(growable: false);
final payload = jsonEncode({
'v': 1,
'savedAt': DateTime.now().millisecondsSinceEpoch,
'installed': list,
});
await tmp.writeAsString(payload, flush: true);
// Atomic replace: rename() on Linux/Android atomically replaces destination
try {
await tmp.rename(file.path);
} catch (renameError) {
// Rename failed - try to preserve tmp file for recovery
if (kDebugMode) {
debugPrint(
'[InstalledPackagesSnapshot] Atomic rename failed: $renameError');
debugPrint('[InstalledPackagesSnapshot] Temp file preserved at: ${tmp.path}');
}
// Don't delete tmp - leave it for manual recovery if needed
rethrow;
}
} catch (e) {
// Best-effort snapshot only.
if (kDebugMode) {
debugPrint('[InstalledPackagesSnapshot] Save failed: $e');
}
}
}
static Future<Map<String, PackageInfo>> load() async {
try {
final file = await _file();
if (!await file.exists()) return {};
final raw = await file.readAsString();
final decoded = jsonDecode(raw);
if (decoded is! Map) return {};
final version = decoded['v'];
if (version != null && version is! int && kDebugMode) {
debugPrint('[InstalledPackagesSnapshot] Unknown schema: $version');
}
final installed = decoded['installed'];
if (installed is! List) return {};
final result = <String, PackageInfo>{};
for (final item in installed) {
if (item is! Map) continue;
final map = Map<String, dynamic>.from(item);
final appId = map['appId'] as String?;
if (appId == null || appId.isEmpty) continue;
result[appId] = PackageInfo(
appId: appId,
name: map['name'] as String?,
version: (map['version'] as String?) ?? '0.0.0',
versionCode: map['versionCode'] as int?,
signatureHash: (map['signatureHash'] as String?) ?? '',
installTime: null,
canInstallSilently: (map['canInstallSilently'] as bool?) ?? false,
);
}
return result;
} catch (e) {
if (kDebugMode) {
debugPrint('[InstalledPackagesSnapshot] Load failed: $e');
}
return {};
}
}
}