Skip to content

Commit 87c912a

Browse files
store: Add ensureFreshRealmMetadata
A helper which ensures realm metadata (name and icon) are up-to-date for each account.
1 parent b8e0143 commit 87c912a

File tree

2 files changed

+142
-0
lines changed

2 files changed

+142
-0
lines changed

lib/model/store.dart

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,64 @@ abstract class GlobalStore extends ChangeNotifier {
345345
));
346346
}
347347

348+
/// Ensure the realm metadata of each account is up-to-date.
349+
///
350+
/// Fetches server settings for each account and then calls [updateRealmData]
351+
/// to update the realm metadata (name and icon).
352+
///
353+
/// The updation for some accounts may be skipped if:
354+
/// - The per account store for the account is already loaded or is loading.
355+
/// - The version of corresponding realm server is unsupported.
356+
///
357+
/// Returns immediately; the fetches and updates are done asynchronously.
358+
void ensureFreshRealmMetadata() {
359+
for (final accountId in accountIds) {
360+
// Avoid updating realm metadata if per account store has already
361+
// loaded or is being loaded. It should be updated with data from
362+
// the initial snapshot or realm update events.
363+
if (_perAccountStoresLoading.containsKey(accountId)) continue;
364+
if (_perAccountStores.containsKey(accountId)) continue;
365+
366+
final account = getAccount(accountId);
367+
if (account == null) continue;
368+
369+
// Fetch the server settings and update the realm data without awaiting.
370+
// This allows fetching server settings of all the accounts parallelly.
371+
unawaited(() async {
372+
final GetServerSettingsResult serverSettings;
373+
final connection = apiConnection(
374+
realmUrl: account.realmUrl,
375+
zulipFeatureLevel: null);
376+
try {
377+
serverSettings = await getServerSettings(connection);
378+
final zulipVersionData = ZulipVersionData.fromServerSettings(serverSettings);
379+
if (zulipVersionData.isUnsupported) {
380+
throw ServerVersionUnsupportedException(zulipVersionData);
381+
}
382+
} on MalformedServerResponseException catch (e) {
383+
final zulipVersionData = ZulipVersionData.fromMalformedServerResponseException(e);
384+
if (zulipVersionData != null && zulipVersionData.isUnsupported) {
385+
throw ServerVersionUnsupportedException(zulipVersionData);
386+
}
387+
rethrow;
388+
} finally {
389+
connection.close();
390+
}
391+
392+
// Account got logged out while fetching server settings.
393+
if (getAccount(accountId) == null) return;
394+
395+
if (_perAccountStoresLoading.containsKey(accountId)) return;
396+
if (_perAccountStores.containsKey(accountId)) return;
397+
398+
await updateRealmData(
399+
accountId,
400+
realmName: serverSettings.realmName,
401+
realmIcon: serverSettings.realmIcon);
402+
}());
403+
}
404+
}
405+
348406
/// Update an account in the underlying data store.
349407
Future<void> doUpdateAccount(int accountId, AccountsCompanion data);
350408

test/model/store_test.dart

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,90 @@ void main() {
391391
realmIcon: Value(Uri.parse('/image-b.png'))));
392392
});
393393

394+
group('GlobalStore.ensureFreshRealmMetadata', () {
395+
test('smoke; populates/updates realm data', () => awaitFakeAsync((async) async {
396+
final account1 = eg.selfAccount.copyWith(
397+
realmUrl: Uri.parse('https://realm1.example.com'),
398+
realmName: const Value.absent(), // account without realm metadata
399+
realmIcon: const Value.absent());
400+
final account2 = eg.otherAccount.copyWith(
401+
realmUrl: Uri.parse('https://realm2.example.com'),
402+
realmName: Value('Old realm 2 name'), // account with old realm metadata
403+
realmIcon: Value(Uri.parse('/old-realm-2-image.png')));
404+
405+
final globalStore = eg.globalStore(accounts: [account1, account2]);
406+
globalStore.useCachedApiConnections = true;
407+
408+
final connection1 = globalStore.apiConnection(
409+
realmUrl: account1.realmUrl, zulipFeatureLevel: null);
410+
final serverSettings1 = eg.serverSettings(
411+
realmUrl: account1.realmUrl,
412+
realmName: 'Realm 1 name',
413+
realmIcon: Uri.parse('/realm-1-image.png'));
414+
connection1.prepare(json: serverSettings1.toJson());
415+
416+
final connection2 = globalStore.apiConnection(
417+
realmUrl: account2.realmUrl, zulipFeatureLevel: null);
418+
final serverSettings2 = eg.serverSettings(
419+
realmUrl: account2.realmUrl,
420+
realmName: 'New realm 2 name',
421+
realmIcon: Uri.parse('/new-realm-2-image.png'));
422+
connection2.prepare(json: serverSettings2.toJson());
423+
424+
globalStore.ensureFreshRealmMetadata();
425+
async.elapse(Duration.zero);
426+
427+
check(globalStore.getAccount(account1.id)).isNotNull()
428+
..realmName.equals('Realm 1 name')
429+
..realmIcon.equals(Uri.parse('/realm-1-image.png'));
430+
check(globalStore.getAccount(account2.id)).isNotNull()
431+
..realmName.equals('New realm 2 name')
432+
..realmIcon.equals(Uri.parse('/new-realm-2-image.png'));
433+
}));
434+
435+
test('ignores per account store loaded account', () => awaitFakeAsync((async) async {
436+
final account1 = eg.selfAccount.copyWith(
437+
realmUrl: Uri.parse('https://realm1.example.com'),
438+
realmName: Value('Old realm 1 name'),
439+
realmIcon: Value(Uri.parse('/old-realm-1-image.png')));
440+
final account2 = eg.otherAccount.copyWith(
441+
realmUrl: Uri.parse('https://realm2.example.com'),
442+
realmName: Value('Old realm 2 name'),
443+
realmIcon: Value(Uri.parse('/old-realm-2-image.png')));
444+
445+
final globalStore = eg.globalStore();
446+
await globalStore.add(account1, eg.initialSnapshot(
447+
realmName: account1.realmName,
448+
realmIconUrl: account1.realmIcon,
449+
realmUsers: [eg.selfUser]));
450+
await globalStore.add(account2, eg.initialSnapshot(
451+
realmName: account2.realmName,
452+
realmIconUrl: account2.realmIcon,
453+
realmUsers: [eg.otherUser]));
454+
globalStore.useCachedApiConnections = true;
455+
456+
final connection1 = globalStore.apiConnection(
457+
realmUrl: account1.realmUrl, zulipFeatureLevel: null);
458+
final serverSettings1 = eg.serverSettings(
459+
realmUrl: account1.realmUrl,
460+
realmName: 'New realm 1 name',
461+
realmIcon: Uri.parse('/new-realm-1-image.png'));
462+
connection1.prepare(json: serverSettings1.toJson());
463+
464+
await globalStore.perAccount(account2.id);
465+
466+
globalStore.ensureFreshRealmMetadata();
467+
async.elapse(Duration.zero);
468+
469+
check(globalStore.getAccount(account1.id)).isNotNull()
470+
..realmName.equals('New realm 1 name')
471+
..realmIcon.equals(Uri.parse('/new-realm-1-image.png'));
472+
check(globalStore.getAccount(account2.id)).isNotNull()
473+
..realmName.equals('Old realm 2 name')
474+
..realmIcon.equals(Uri.parse('/old-realm-2-image.png'));
475+
}));
476+
});
477+
394478
group('GlobalStore.removeAccount', () {
395479
void checkGlobalStore(GlobalStore store, int accountId, {
396480
required bool expectAccount,

0 commit comments

Comments
 (0)