mnemo_cards/lib/features/packs/pack_updater.dart

221 lines
6.6 KiB
Dart
Raw Normal View History

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';
2024-08-24 21:09:58 +00:00
import 'package:flutter/foundation.dart';
2024-08-03 22:37:10 +00:00
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();
}
2024-08-24 21:09:58 +00:00
try {
print('Updating packs started');
await _updateAvailablePacks();
} catch (e, s) {
log('Error when updating packs', error: e, stackTrace: s);
print('Error when updating pack: ${e}');
} finally {
print('Updating packs complete');
2024-05-22 20:48:44 +00:00
if (_updateCompleter?.isCompleted == false) {
_updateCompleter?.complete();
}
2024-08-24 21:09:58 +00:00
}
2024-05-22 20:48:44 +00:00
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;
2024-08-24 21:09:58 +00:00
final images = await compute(
_decodeArchive,
_Envelope(
data: imagesData,
packId: packId,
appVersion: appVersion,
),
2024-06-21 19:12:29 +00:00
);
2024-08-18 16:52:03 +00:00
_loadingPacksHolder.addLoadingPack(
loadingDto.copyWith(message: 'Почти всё'),
);
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
}
}
}
2024-08-24 21:09:58 +00:00
Map<String, List<int>> _decodeArchive(_Envelope envelope) {
final s = Stopwatch()..start();
final password = TokenGenerator.generateArchivePassword(
appVersion: envelope.appVersion,
packId: envelope.packId,
);
print('generate token ${s.elapsedMilliseconds} mills');
final imagesArchive = ZipDecoder().decodeBytes(
envelope.data,
password: password,
);
print('deflate archive ${s.elapsedMilliseconds} mills');
final images = Map<String, List<int>>();
for (final file in imagesArchive) {
images[file.name] = file.content as List<int>;
}
print('loaded images ${s.elapsedMilliseconds} mills');
return images;
}
class _Envelope {
final List<int> data;
final String packId;
final String appVersion;
_Envelope({
required this.data,
required this.packId,
required this.appVersion,
});
}