import 'dart:async'; import 'dart:developer'; import 'dart:typed_data'; import 'package:archive/archive_io.dart'; import 'package:flutter/widgets.dart'; import 'package:mnemo_cards_common/mnemo_cards_common.dart'; import 'package:package_info_plus/package_info_plus.dart'; import '../analytics/analytics.dart'; import 'pack_cache_manager.dart'; import 'pack_holder.dart'; import 'packs_api.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: 600)); pollStream.listen((event) async { await updateAvailablePacks(); await _updatePreviewPacks(); }); } Future _updatePreviewPacks() async { 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 updatePackAndSaveImages( String packId, { bool shouldUpdateHolder = false, }) 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 appVersion = (await PackageInfo.fromPlatform()).version; final password = TokenGenerator.generateArchivePassword( appVersion: appVersion, packId: packId, ); print('generate token ${s.elapsedMilliseconds} mills'); final imagesArchive = ZipDecoder().decodeBytes( imagesData, password: password, ); print('deflate archive ${s.elapsedMilliseconds} mills'); Map images = {}; // todo optimize, make really async Future setImage(ArchiveFile file) async { final fileData = file.content as List; 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'); if (shouldUpdateHolder) { _packHolder.setPacks([ ..._packHolder.state?.packsWithCards ?? [], packDto, ]); } return packDto; } catch (e, s) { log('Cant update pack', error: e, stackTrace: s); Analytics.crashlyticsError( message: 'Cant update pack', e: e, s: s, ); rethrow; } } }