181 lines
5.3 KiB
Dart
181 lines
5.3 KiB
Dart
import 'dart:convert';
|
|
import 'package:hive/hive.dart';
|
|
|
|
/// Hive модель для кэшированного mindmap-файла
|
|
class CachedMindmap {
|
|
final String path;
|
|
final String content;
|
|
final String? sha;
|
|
final DateTime lastSync;
|
|
final DateTime lastModified;
|
|
final String branch;
|
|
|
|
CachedMindmap({
|
|
required this.path,
|
|
required this.content,
|
|
this.sha,
|
|
required this.lastSync,
|
|
required this.lastModified,
|
|
this.branch = 'main',
|
|
});
|
|
|
|
Map<String, dynamic> toJson() => {
|
|
'path': path,
|
|
'content': content,
|
|
'sha': sha,
|
|
'lastSync': lastSync.toIso8601String(),
|
|
'lastModified': lastModified.toIso8601String(),
|
|
'branch': branch,
|
|
};
|
|
|
|
factory CachedMindmap.fromJson(Map<String, dynamic> json) {
|
|
return CachedMindmap(
|
|
path: json['path'] ?? '',
|
|
content: json['content'] ?? '',
|
|
sha: json['sha'],
|
|
lastSync:
|
|
DateTime.parse(json['lastSync'] ?? DateTime.now().toIso8601String()),
|
|
lastModified: DateTime.parse(
|
|
json['lastModified'] ?? DateTime.now().toIso8601String()),
|
|
branch: json['branch'] ?? 'main',
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Оффлайн репозиторий для mindmaps
|
|
/// Хранит кэш файлов из Gitea и очередь изменений для синхронизации
|
|
class OfflineMindmapRepository {
|
|
static const String _boxName = 'mindmaps_cache';
|
|
static const String _queueBoxName = 'mindmaps_queue';
|
|
static const String _metaKey = '_meta_sync_state';
|
|
|
|
Box<String>? _box;
|
|
Box<String>? _queueBox;
|
|
|
|
Future<void> initialize() async {
|
|
_box = await Hive.openBox<String>(_boxName);
|
|
_queueBox = await Hive.openBox<String>(_queueBoxName);
|
|
}
|
|
|
|
bool get isInitialized => _box != null && _box!.isOpen;
|
|
|
|
// ==================== CACHE ====================
|
|
|
|
/// Сохранить файл в кэш
|
|
Future<void> cacheMindmap(CachedMindmap mindmap) async {
|
|
if (!isInitialized) throw Exception('Repository not initialized');
|
|
await _box!.put(mindmap.path, jsonEncode(mindmap.toJson()));
|
|
}
|
|
|
|
/// Получить файл из кэша
|
|
CachedMindmap? getMindmap(String path) {
|
|
if (!isInitialized) return null;
|
|
final raw = _box!.get(path);
|
|
if (raw == null) return null;
|
|
try {
|
|
return CachedMindmap.fromJson(jsonDecode(raw));
|
|
} catch (_) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// Получить все кэшированные mindmaps
|
|
List<CachedMindmap> getAllMindmaps() {
|
|
if (!isInitialized) return [];
|
|
return _box!.values
|
|
.map((raw) {
|
|
try {
|
|
return CachedMindmap.fromJson(jsonDecode(raw));
|
|
} catch (_) {
|
|
return null;
|
|
}
|
|
})
|
|
.whereType<CachedMindmap>()
|
|
.toList();
|
|
}
|
|
|
|
/// Удалить файл из кэша
|
|
Future<void> deleteMindmap(String path) async {
|
|
if (!isInitialized) return;
|
|
await _box!.delete(path);
|
|
}
|
|
|
|
/// Очистить весь кэш
|
|
Future<void> clearCache() async {
|
|
if (!isInitialized) return;
|
|
await _box!.clear();
|
|
}
|
|
|
|
// ==================== SYNC QUEUE ====================
|
|
|
|
/// Добавить изменение в очередь (для offline → online sync)
|
|
Future<void> queueChange({
|
|
required String path,
|
|
required String content,
|
|
required String operation, // 'create' | 'update' | 'delete'
|
|
String? commitMessage,
|
|
}) async {
|
|
if (!isInitialized) throw Exception('Repository not initialized');
|
|
final entry = {
|
|
'path': path,
|
|
'content': content,
|
|
'operation': operation,
|
|
'commitMessage': commitMessage ?? 'Mobile sync: $path',
|
|
'timestamp': DateTime.now().toIso8601String(),
|
|
};
|
|
await _queueBox!.put(
|
|
'${DateTime.now().millisecondsSinceEpoch}_$path', jsonEncode(entry));
|
|
}
|
|
|
|
/// Получить все изменения из очереди
|
|
List<Map<String, dynamic>> getPendingChanges() {
|
|
if (!isInitialized) return [];
|
|
return _queueBox!.values
|
|
.map((raw) {
|
|
try {
|
|
return jsonDecode(raw) as Map<String, dynamic>;
|
|
} catch (_) {
|
|
return null;
|
|
}
|
|
})
|
|
.whereType<Map<String, dynamic>>()
|
|
.toList()
|
|
..sort((a, b) => (a['timestamp'] ?? '').compareTo(b['timestamp'] ?? ''));
|
|
}
|
|
|
|
/// Удалить выполненное изменение из очереди
|
|
Future<void> removeQueuedChange(String key) async {
|
|
if (!isInitialized) return;
|
|
await _queueBox!.delete(key);
|
|
}
|
|
|
|
/// Очистить очередь
|
|
Future<void> clearQueue() async {
|
|
if (!isInitialized) return;
|
|
await _queueBox!.clear();
|
|
}
|
|
|
|
bool get hasPendingChanges => (_queueBox?.length ?? 0) > 0;
|
|
|
|
// ==================== SYNC STATE ====================
|
|
|
|
/// Сохранить время последней успешной синхронизации
|
|
Future<void> setLastSync(DateTime time) async {
|
|
if (!isInitialized) return;
|
|
await _box!.put(_metaKey, jsonEncode({'lastSync': time.toIso8601String()}));
|
|
}
|
|
|
|
/// Получить время последней синхронизации
|
|
DateTime? getLastSync() {
|
|
if (!isInitialized) return null;
|
|
final raw = _box!.get(_metaKey);
|
|
if (raw == null) return null;
|
|
try {
|
|
final data = jsonDecode(raw) as Map<String, dynamic>;
|
|
return DateTime.tryParse(data['lastSync'] ?? '');
|
|
} catch (_) {
|
|
return null;
|
|
}
|
|
}
|
|
}
|