2024-05-22 20:48:44 +00:00
|
|
|
import 'dart:async';
|
|
|
|
|
import 'dart:developer';
|
|
|
|
|
import 'dart:typed_data';
|
|
|
|
|
|
2024-08-03 22:37:10 +00:00
|
|
|
import 'package:archive/archive_io.dart';
|
|
|
|
|
import 'package:flutter/widgets.dart';
|
2024-08-18 16:52:03 +00:00
|
|
|
import 'package:mnemo_cards/features/packs/loading_packs_holder.dart';
|
|
|
|
|
import 'package:mnemo_cards/features/packs/preview_packs_poller.dart';
|
2024-05-22 20:48:44 +00:00
|
|
|
import 'package:mnemo_cards_common/mnemo_cards_common.dart';
|
2024-06-21 19:12:29 +00:00
|
|
|
import 'package:package_info_plus/package_info_plus.dart';
|
2024-08-18 16:52:03 +00:00
|
|
|
import 'package:rxdart/rxdart.dart';
|
2024-08-03 22:37:10 +00:00
|
|
|
import '../analytics/analytics.dart';
|
|
|
|
|
import 'pack_cache_manager.dart';
|
|
|
|
|
import 'pack_holder.dart';
|
|
|
|
|
import 'packs_api.dart';
|
2024-05-22 20:48:44 +00:00
|
|
|
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;
|
2024-08-18 16:52:03 +00:00
|
|
|
final LoadingPacksHolder _loadingPacksHolder;
|
|
|
|
|
final PreviewPacksPoller _previewPacksPoller;
|
2024-05-22 20:48:44 +00:00
|
|
|
|
|
|
|
|
Completer? _updateCompleter;
|
|
|
|
|
|
|
|
|
|
PackUpdater(
|
|
|
|
|
this._packsApi,
|
|
|
|
|
this._cacheManager,
|
|
|
|
|
this._packHolder,
|
|
|
|
|
this._previewPackHolder,
|
2024-08-18 16:52:03 +00:00
|
|
|
this._loadingPacksHolder,
|
|
|
|
|
this._previewPacksPoller,
|
2024-05-22 20:48:44 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
Future<void> init() async {
|
2024-06-21 19:12:29 +00:00
|
|
|
final pollStream = Stream.periodic(const Duration(seconds: 600));
|
2024-08-18 16:52:03 +00:00
|
|
|
pollStream.startWith(0).listen((event) async {
|
2024-05-22 20:48:44 +00:00
|
|
|
await updateAvailablePacks();
|
2024-08-18 16:52:03 +00:00
|
|
|
await _previewPacksPoller.poll();
|
2024-05-22 20:48:44 +00:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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 {
|
2024-08-18 16:52:03 +00:00
|
|
|
final updatedPack = await _updatePackAndSaveImages(action.id);
|
2024-05-22 20:48:44 +00:00
|
|
|
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);
|
2024-08-18 16:52:03 +00:00
|
|
|
_packHolder.setPacksWithCards(packs.map((p) => p.id).toList());
|
2024-05-22 20:48:44 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-03 22:37:10 +00:00
|
|
|
Future<CardPackDto> updatePackAndSaveImages(
|
|
|
|
|
String packId, {
|
|
|
|
|
bool shouldUpdateHolder = false,
|
2024-08-18 16:52:03 +00:00
|
|
|
}) {
|
|
|
|
|
_previewPacksPoller.poll();
|
|
|
|
|
return _updatePackAndSaveImages(
|
|
|
|
|
packId,
|
|
|
|
|
shouldUpdateHolder: shouldUpdateHolder,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<CardPackDto> _updatePackAndSaveImages(
|
|
|
|
|
String packId, {
|
|
|
|
|
bool shouldUpdateHolder = false,
|
2024-08-03 22:37:10 +00:00
|
|
|
}) async {
|
2024-08-18 16:52:03 +00:00
|
|
|
final loadingDto = _previewPackHolder.state?.previewPacks
|
|
|
|
|
.firstWhereOrNull((p) => p.id == packId)
|
|
|
|
|
?.let(CardPackLoadingDto.fromPreviewDto) ??
|
|
|
|
|
CardPackLoadingDto.fromId(packId, message: 'Загружаем карточки...');
|
2024-05-22 20:48:44 +00:00
|
|
|
try {
|
2024-08-18 16:52:03 +00:00
|
|
|
_loadingPacksHolder.addLoadingPack(loadingDto);
|
2024-05-22 20:48:44 +00:00
|
|
|
log('Updating with cards ${packId}', name: 'updatePackWithCards');
|
2024-06-21 19:12:29 +00:00
|
|
|
final s = Stopwatch()..start();
|
2024-05-22 20:48:44 +00:00
|
|
|
final packDto = await _packsApi.getPackDto(packId);
|
2024-06-21 19:12:29 +00:00
|
|
|
print('get from api ${s.elapsedMilliseconds} mills');
|
2024-05-22 20:48:44 +00:00
|
|
|
if (packDto == null) {
|
|
|
|
|
throw Exception('Cant update pack $packId');
|
|
|
|
|
}
|
|
|
|
|
final imagesData = await _packsApi.packImagesArchive(packId);
|
2024-06-21 19:12:29 +00:00
|
|
|
print('get cards from api ${s.elapsedMilliseconds} mills');
|
2024-08-18 16:52:03 +00:00
|
|
|
_loadingPacksHolder.addLoadingPack(
|
|
|
|
|
loadingDto.copyWith(message: 'Распаковываем карточки...'),
|
|
|
|
|
);
|
2024-06-21 19:12:29 +00:00
|
|
|
final appVersion = (await PackageInfo.fromPlatform()).version;
|
|
|
|
|
final password = TokenGenerator.generateArchivePassword(
|
|
|
|
|
appVersion: appVersion,
|
|
|
|
|
packId: packId,
|
|
|
|
|
);
|
|
|
|
|
print('generate token ${s.elapsedMilliseconds} mills');
|
2024-08-03 22:37:10 +00:00
|
|
|
final imagesArchive = ZipDecoder().decodeBytes(
|
2024-06-21 19:12:29 +00:00
|
|
|
imagesData,
|
|
|
|
|
password: password,
|
|
|
|
|
);
|
|
|
|
|
print('deflate archive ${s.elapsedMilliseconds} mills');
|
2024-05-22 20:48:44 +00:00
|
|
|
Map<String, MemoryImage> images = {};
|
2024-06-21 19:12:29 +00:00
|
|
|
|
|
|
|
|
// todo optimize, make really async
|
2024-08-03 22:37:10 +00:00
|
|
|
Future<void> setImage(ArchiveFile file) async {
|
2024-05-22 20:48:44 +00:00
|
|
|
final fileData = file.content as List<int>;
|
2024-06-21 19:12:29 +00:00
|
|
|
final data = Uint8List.fromList(fileData);
|
|
|
|
|
images[file.name] = MemoryImage(data);
|
2024-05-22 20:48:44 +00:00
|
|
|
}
|
2024-06-21 19:12:29 +00:00
|
|
|
|
|
|
|
|
await Future.wait([
|
|
|
|
|
for (final file in imagesArchive) setImage(file),
|
|
|
|
|
]);
|
|
|
|
|
|
2024-08-18 16:52:03 +00:00
|
|
|
_loadingPacksHolder.addLoadingPack(
|
|
|
|
|
loadingDto.copyWith(message: 'Почти всё'),
|
|
|
|
|
);
|
2024-06-21 19:12:29 +00:00
|
|
|
print('loaded images ${s.elapsedMilliseconds} mills');
|
2024-08-18 16:52:03 +00:00
|
|
|
await _cacheManager.savePack(packDto, images: images);
|
2024-06-21 19:12:29 +00:00
|
|
|
print('saved pack ${s.elapsedMilliseconds} mills');
|
2024-05-22 20:48:44 +00:00
|
|
|
log('Pack ${packId} updated with cards');
|
2024-07-22 20:49:30 +00:00
|
|
|
if (shouldUpdateHolder) {
|
2024-08-18 16:52:03 +00:00
|
|
|
_packHolder.setPacksWithCards(
|
|
|
|
|
[
|
|
|
|
|
..._packHolder.state.packsWithCards,
|
|
|
|
|
packDto.id,
|
|
|
|
|
],
|
|
|
|
|
);
|
2024-07-22 20:49:30 +00:00
|
|
|
}
|
2024-05-22 20:48:44 +00:00
|
|
|
return packDto;
|
|
|
|
|
} catch (e, s) {
|
2024-08-03 22:37:10 +00:00
|
|
|
log('Cant update pack', error: e, stackTrace: s);
|
|
|
|
|
Analytics.crashlyticsError(
|
|
|
|
|
message: 'Cant update pack',
|
|
|
|
|
e: e,
|
|
|
|
|
s: s,
|
|
|
|
|
);
|
2024-05-22 20:48:44 +00:00
|
|
|
rethrow;
|
2024-08-18 16:52:03 +00:00
|
|
|
} finally {
|
|
|
|
|
_loadingPacksHolder.removeLoadingPack(packId);
|
2024-05-22 20:48:44 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|