import 'dart:async'; import 'dart:developer'; import 'dart:typed_data'; import 'package:archive/archive.dart'; 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:rxdart/rxdart.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 init() async { final pollStream = Stream.periodic(const Duration(seconds: 60)); pollStream.startWith(-1).listen((event) async { await updateAvailablePacks(); try { final r = await _packsApi.getPacksPreviews(null); _previewPackHolder.setPreviewPacks(r, save: true); } catch (e) { log(e.toString()); } }); } Future updateAvailablePacks() async { if (_updateCompleter?.isCompleted ?? true) { _updateCompleter = Completer(); } unawaited(_updateAvailablePacks().whenComplete(() { if (_updateCompleter?.isCompleted == false) { _updateCompleter?.complete(); } })); return _updateCompleter!.future; } Future _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 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 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 updatePackInfoWithCardsAndSave(String packId) async { try { // todo // final pack = await _updatePackInfoWithCards(packId); // await _cacheManager.savePack(pack, updateCards: true); return true; } catch (e) { log(e.toString()); } return false; } Future updatePackAndSaveImages(String packId) async { try { log('Updating with cards ${packId}', name: 'updatePackWithCards'); final packDto = await _packsApi.getPackDto(packId); if (packDto == null) { throw Exception('Cant update pack $packId'); } final imagesData = await _packsApi.packImagesArchive(packId); Archive imagesArchive = ZipDecoder().decodeBytes(imagesData); Map images = {}; for (final file in imagesArchive) { final fileData = file.content as List; images[file.name] = MemoryImage( Uint8List.fromList(fileData), ); } _cacheManager.savePack(packDto, images: images); log('Pack ${packId} updated with cards'); return packDto; } catch (e, s) { log('Cant update pack', name: 'updatePackWithCards'); log(e.toString(), stackTrace: s); rethrow; } } }