156 lines
4.9 KiB
Dart
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;
|
|
}
|
|
}
|
|
}
|