100 lines
3.0 KiB
Dart
100 lines
3.0 KiB
Dart
/// JSON backup document: products, trips, settings, and embedded photos.
|
|
library;
|
|
|
|
import 'dart:convert';
|
|
|
|
const int kBackupVersion = 1;
|
|
|
|
class BackupDocument {
|
|
const BackupDocument({
|
|
required this.version,
|
|
required this.exportedAt,
|
|
required this.darkMode,
|
|
required this.supermarkets,
|
|
required this.products,
|
|
required this.shops,
|
|
required this.shopItems,
|
|
required this.images,
|
|
});
|
|
|
|
final int version;
|
|
final String exportedAt;
|
|
final bool darkMode;
|
|
final List<Map<String, Object?>> supermarkets;
|
|
final List<Map<String, Object?>> products;
|
|
final List<Map<String, Object?>> shops;
|
|
final List<Map<String, Object?>> shopItems;
|
|
final Map<String, String> images;
|
|
|
|
Map<String, Object?> toJson() => {
|
|
'format': 'receipity-backup',
|
|
'version': version,
|
|
'exportedAt': exportedAt,
|
|
'darkMode': darkMode,
|
|
'supermarkets': supermarkets,
|
|
'products': products,
|
|
'shops': shops,
|
|
'shopItems': shopItems,
|
|
'images': images,
|
|
};
|
|
|
|
String encode() => const JsonEncoder.withIndent(' ').convert(toJson());
|
|
|
|
static BackupDocument parse(String json) {
|
|
final decoded = jsonDecode(json);
|
|
if (decoded is! Map<String, dynamic>) {
|
|
throw const FormatException('Backup is not a JSON object.');
|
|
}
|
|
if (decoded['format'] != 'receipity-backup') {
|
|
throw const FormatException('This file is not a Receipity backup.');
|
|
}
|
|
final version = decoded['version'];
|
|
if (version is! int || version < 1 || version > kBackupVersion) {
|
|
throw const FormatException('Unsupported backup version.');
|
|
}
|
|
return BackupDocument(
|
|
version: version,
|
|
exportedAt: decoded['exportedAt'] as String? ?? '',
|
|
darkMode: decoded['darkMode'] == true,
|
|
supermarkets: _maps(decoded['supermarkets']),
|
|
products: _maps(decoded['products']),
|
|
shops: _maps(decoded['shops']),
|
|
shopItems: _maps(decoded['shopItems']),
|
|
images: _strings(decoded['images']),
|
|
);
|
|
}
|
|
|
|
static List<Map<String, Object?>> _maps(Object? value) {
|
|
if (value == null) return [];
|
|
if (value is! List) {
|
|
throw const FormatException('Backup tables must be lists.');
|
|
}
|
|
return [
|
|
for (final item in value)
|
|
if (item is Map)
|
|
Map<String, Object?>.from(
|
|
item.map((key, val) => MapEntry(key.toString(), val)),
|
|
),
|
|
];
|
|
}
|
|
|
|
static Map<String, String> _strings(Object? value) {
|
|
if (value == null) return {};
|
|
if (value is! Map) return {};
|
|
return {
|
|
for (final entry in value.entries)
|
|
if (entry.value is String) entry.key.toString(): entry.value as String,
|
|
};
|
|
}
|
|
}
|
|
|
|
String? backupImageKey(String? absolutePath) {
|
|
if (absolutePath == null || absolutePath.isEmpty) return null;
|
|
final normalized = absolutePath.replaceAll('\\', '/');
|
|
final products = normalized.split('/products/');
|
|
if (products.length == 2) return 'products/${products.last}';
|
|
final receipts = normalized.split('/receipts/');
|
|
if (receipts.length == 2) return 'receipts/${receipts.last}';
|
|
return null;
|
|
}
|