mnemo_cards/lib/features/packs/pack_updater.dart
2024-06-21 22:12:29 +03:00

156 lines
4.9 KiB
Dart

import 'dart:async';
import 'dart:developer';
import 'dart:typed_data';
import 'package:archive/archive_io.dart' as a;
import 'package:flutter/cupertino.dart';
import 'package:mnemo_cards/features/packs/packs_api.dart';
import 'package:mnemo_cards_common/mnemo_cards_common.dart';
import 'package:package_info_plus/package_info_plus.dart';
import '../../di/locator.dart';
import '../../features/packs/pack_cache_manager.dart';
import '../../features/packs/pack_holder.dart';
import 'preview_pack_holder.dart';
/// Updates cache and state holders for available packs
/// Does polling
class PackUpdater {
final PacksApi _packsApi;
final PackCacheManager _cacheManager;
final PackHolder _packHolder;
final PreviewPackHolder _previewPackHolder;
Completer? _updateCompleter;
PackUpdater(
this._packsApi,
this._cacheManager,
this._packHolder,
this._previewPackHolder,
);
Future<void> init() async {
final pollStream = Stream.periodic(const Duration(seconds: 600));
pollStream.listen((event) async {
await updateAvailablePacks();
try {
final r = await _packsApi.getPacksPreviews(null);
_previewPackHolder.setPreviewPacks(r, save: true);
} catch (e) {
log(e.toString());
}
});
}
Future<void> updateAvailablePacks() async {
if (_updateCompleter?.isCompleted ?? true) {
_updateCompleter = Completer();
}
unawaited(_updateAvailablePacks().whenComplete(() {
if (_updateCompleter?.isCompleted == false) {
_updateCompleter?.complete();
}
}));
return _updateCompleter!.future;
}
Future<void> _updateAvailablePacks() async {
final savedList = await _cacheManager.loadAllPacks();
final packData = Map.fromEntries(
savedList.map(
(e) => MapEntry(
e.id.toString(),
e.version,
),
),
);
final actions = await _packsApi.getPacksActions(packData);
Map<String, CardPackDto> updatedPacks = {};
bool shouldUpdateHolder = false;
for (final action in actions) {
switch (action.action) {
case PackAction.delete:
log('DELETE ${action.id}');
shouldUpdateHolder = true;
await _cacheManager.deletePack(action.id);
savedList.removeWhere((p) => p.id == action.id);
break;
case PackAction.update:
log('UPDATE ${action.id}');
shouldUpdateHolder = true;
try {
final updatedPack = await updatePackAndSaveImages(action.id);
updatedPacks[action.id] = updatedPack;
} catch (e) {
log('Error while download cards for ${action.id} $e');
}
break;
case PackAction.unknown:
log('Unknown action on update ${action.id}');
break;
}
}
if (shouldUpdateHolder) {
List<CardPackDto> packs = [];
for (final saved in savedList) {
if (updatedPacks[saved.id] != null) {
packs.add(updatedPacks[saved.id]!);
updatedPacks.remove(saved.id);
} else {
packs.add(saved);
}
}
packs.addAll(updatedPacks.values);
_packHolder.setPacks(packs);
}
}
Future<CardPackDto> updatePackAndSaveImages(String packId) async {
try {
log('Updating with cards ${packId}', name: 'updatePackWithCards');
final s = Stopwatch()..start();
final packDto = await _packsApi.getPackDto(packId);
print('get from api ${s.elapsedMilliseconds} mills');
if (packDto == null) {
throw Exception('Cant update pack $packId');
}
final imagesData = await _packsApi.packImagesArchive(packId);
print('get cards from api ${s.elapsedMilliseconds} mills');
final userId = locator.userManager.userId!;
final appVersion = (await PackageInfo.fromPlatform()).version;
final password = TokenGenerator.generateArchivePassword(
appVersion: appVersion,
packId: packId,
);
print('generate token ${s.elapsedMilliseconds} mills');
final imagesArchive = a.ZipDecoder().decodeBytes(
imagesData,
password: password,
);
print('deflate archive ${s.elapsedMilliseconds} mills');
Map<String, MemoryImage> images = {};
// todo optimize, make really async
Future<void> setImage(a.ArchiveFile file) async {
final fileData = file.content as List<int>;
final data = Uint8List.fromList(fileData);
images[file.name] = MemoryImage(data);
}
await Future.wait([
for (final file in imagesArchive) setImage(file),
]);
print('loaded images ${s.elapsedMilliseconds} mills');
_cacheManager.savePack(packDto, images: images);
print('saved pack ${s.elapsedMilliseconds} mills');
log('Pack ${packId} updated with cards');
return packDto;
} catch (e, s) {
log('Cant update pack', name: 'updatePackWithCards');
log(e.toString(), stackTrace: s);
rethrow;
}
}
}