Fix latest release queries (remote and local), load users once

This commit is contained in:
franzap
2024-10-15 10:39:38 -03:00
parent 4d42f5a95e
commit 6d8efa4420
5 changed files with 102 additions and 52 deletions
+60 -35
View File
@@ -43,7 +43,7 @@ class App extends BaseApp with DataModelMixin<App> {
Map<String, dynamic> toJson() => super.toMap();
Release? get latestRelease {
return releases.ordered.firstOrNull;
return releases.toList().sortedByLatest.firstOrNull;
}
FileMetadata? get latestMetadata {
@@ -168,48 +168,68 @@ class App extends BaseApp with DataModelMixin<App> {
mixin AppAdapter on Adapter<App> {
Future<List<App>> fetchAppModels(Map<String, dynamic> params) async {
final apps = await super.findAll(
params: {
...params,
'#f': ['android-arm64-v8a'],
},
);
// Find all appid@version ($3) as we need to pick one tag to query on
// (filters by kind ($1) and pubkey ($2) done locally)
final latestReleaseIdentifiers =
apps.map((app) => app.linkedReplaceableEvents.firstOrNull?.$3).nonNulls;
final releases = await ref.releases.findAll(
params: {'#d': latestReleaseIdentifiers},
);
final byRelease = params.remove('by-release');
// TODO: Deprecated, will be removed
// Some developers without access to the latest zapstore-cli
// have not published their apps with latest release identifiers
// so load as usual
final deprecatedApps =
apps.where((app) => app.linkedReplaceableEvents.isEmpty);
final deprecatedReleases = await ref.releases.findAll(
params: {
'#a':
deprecatedApps.map((app) => app.getReplaceableEventLink().formatted)
},
);
late final List<App> apps;
late final List<Release> releases;
if (byRelease != null) {
// This param fetches releases with params and then apps
releases = await ref.releases.findAll(params: params);
final appIdentifiers =
releases.map((r) => r.linkedReplaceableEvents.first.$3).nonNulls;
apps = await super.findAll(
params: {
'#d': appIdentifiers,
'#f': ['android-arm64-v8a'],
},
);
} else {
apps = await super.findAll(
params: {
...params,
'#f': ['android-arm64-v8a'],
},
);
// Find all appid@version ($3) as we need to pick one tag to query on
// (filters by kind ($1) and pubkey ($2) done locally)
final latestReleaseIdentifiers = apps
.map((app) => app.linkedReplaceableEvents.firstOrNull?.$3)
.nonNulls;
releases = await ref.releases.findAll(
params: {'#d': latestReleaseIdentifiers},
);
final groupedDeprecatedReleases =
deprecatedReleases.groupListsBy((r) => r.app.value!);
for (final e in groupedDeprecatedReleases.entries) {
final mostRecentRelease =
e.value.sorted((a, b) => b.createdAt!.compareTo(a.createdAt!)).first;
releases.add(mostRecentRelease);
// TODO: Deprecated, will be removed
// Some developers without access to the latest zapstore-cli
// have not published their apps with latest release identifiers
// so load as usual
final deprecatedApps =
apps.where((app) => app.linkedReplaceableEvents.isEmpty);
final deprecatedReleases = await ref.releases.findAll(
params: {
'#a': deprecatedApps
.map((app) => app.getReplaceableEventLink().formatted)
},
);
final groupedDeprecatedReleases =
deprecatedReleases.groupListsBy((r) => r.app.value!);
for (final e in groupedDeprecatedReleases.entries) {
final mostRecentRelease = e.value.sortedByLatest.first;
releases.add(mostRecentRelease);
}
// End deprecated
}
// End deprecated
final metadataIds =
releases.map((r) => r.linkedEvents).nonNulls.expand((_) => _);
// For now load users only once
final userIds = {
for (final app in apps) app.signer.id,
for (final app in apps) app.developer.id
for (final app in apps)
if (!app.signer.isPresent) app.signer.id,
for (final app in apps)
if (!app.developer.isPresent) app.developer.id,
}.nonNulls;
// Metadata and users probably go to separate relays
@@ -335,6 +355,11 @@ Future<bool> _isHashMismatch(String path, String hash) async {
});
}
extension AppExt on Iterable<App> {
List<App> get sortedByLatest =>
sorted((a, b) => b.createdAt!.compareTo(a.createdAt!));
}
// class
sealed class AppInstallProgress {}
+3 -2
View File
@@ -1,9 +1,10 @@
import 'package:collection/collection.dart';
import 'package:flutter_data/flutter_data.dart';
import 'package:purplebase/purplebase.dart';
// NOTE: Very important to use const in relay args to preserve equality in Riverpod families
const kAppRelays = ['ws://10.0.2.2:3000'];
// const kAppRelays = ['wss://relay.zap.store'];
// const kAppRelays = ['ws://10.0.2.2:3000'];
const kAppRelays = ['wss://relay.zap.store'];
const kSocialRelays = ['wss://relay.primal.net', 'wss://relay.nostr.band'];
mixin NostrAdapter<T extends DataModelMixin<T>> on Adapter<T> {
+3 -3
View File
@@ -46,7 +46,7 @@ mixin ReleaseAdapter on Adapter<Release> {
}
}
extension HasManyReleaseX on HasMany<Release> {
List<Release> get ordered =>
toList().sorted((a, b) => b.createdAt!.compareTo(a.createdAt!));
extension ReleaseExt on Iterable<Release> {
List<Release> get sortedByLatest =>
sorted((a, b) => b.createdAt!.compareTo(a.createdAt!));
}
+7
View File
@@ -175,6 +175,13 @@ final dataLibrariesInitializer = FutureProvider<void>((ref) async {
// In this initial phase, load there more or less fixed curation sets here
await ref.appCurationSets.findAll();
await ref.apps.findAll(
params: {
'by-release': true,
'limit': 10,
},
);
// Handle deep links
final appLinksSub = appLinks.uriLinkStream.listen((uri) async {
if (uri.scheme == "zapstore") {
+29 -12
View File
@@ -1,12 +1,12 @@
import 'dart:async';
import 'package:async_button_builder/async_button_builder.dart';
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:zapstore/main.data.dart';
import 'package:zapstore/models/app.dart';
import 'package:zapstore/models/release.dart';
import 'package:zapstore/widgets/app_card.dart';
class LatestReleasesContainer extends HookConsumerWidget {
@@ -53,6 +53,8 @@ class LatestReleasesContainer extends HookConsumerWidget {
if (state.isLoading)
for (final _ in List.generate(3, (_) => _)) AppCard(app: null),
if (state.hasValue)
// NOTE: Since we're showing apps but it's really a list of releases
// apps will appear repeated, to convert to set
for (final app in state.value!) AppCard(app: app),
if (state.hasValue)
AsyncButtonBuilder(
@@ -96,31 +98,46 @@ class LatestReleasesAppNotifier extends AutoDisposeAsyncNotifier<List<App>> {
@override
Future<List<App>> build() async {
// TODO: Should be ref.watching a pool state change (from purplebase)
final timer = Timer.periodic(Duration(minutes: 1), (_) => fetch());
final timer = Timer.periodic(Duration(minutes: 10), (_) => fetch());
ref.onDispose(timer.cancel);
// Trigger fetch ONLY on the most recent 10 (next=false)
await fetch();
// Do not fetch here as first 10 are preloaded
// await fetch();
return localFetch();
}
List<App> localFetch() {
final apps = ref.apps
// Find all releases that are the latest of each, and sort chronologically
final releases = ref.releases
.findAllLocal()
.sorted((a, b) => b.createdAt!.compareTo(a.createdAt!))
.where((r) => r.app.value?.latestMetadata != null)
.sortedByLatest;
// Set timestamp of oldest, to prepare for next query
if (releases.isNotEmpty) {
oldestTimestamp = releases.last.createdAtMs;
}
// Return only (first 10 * page) releases that have an associated app
// (it should be the case, but keep as we migrate
// from older event format)
return releases
.map((r) => r.app.value)
.nonNulls
.toSet()
.take(page * 10)
.toList();
if (apps.isNotEmpty) {
oldestTimestamp = apps.last.createdAtMs;
}
return apps;
}
Future<void> fetch({bool next = false}) async {
if (next) {
page++;
}
await ref.apps
.findAll(params: {'limit': 10, 'until': next ? oldestTimestamp : null});
await ref.apps.findAll(
params: {
'by-release': true,
'limit': 10,
'until': next ? oldestTimestamp : null
},
);
update((_) => localFetch());
}
}