From 652ef8a427bffe1777fc9733a0b6b425ad4e9ac5 Mon Sep 17 00:00:00 2001 From: shenlong <139912620+shenlong-tanwen@users.noreply.github.com> Date: Fri, 14 Aug 2026 18:23:12 +0530 Subject: [PATCH] refactor: server capabilities (#30663) Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com> --- .../models/server_capability.model.dart | 23 ++++++++++++++++ .../domain/services/sync_stream.service.dart | 5 ++-- .../lib/domain/utils/migrate_cloud_ids.dart | 5 ++-- .../repositories/sync_api.repository.dart | 26 ++++++------------- .../server_info/server_version.model.dart | 4 --- .../actions/edit_asset.action.dart | 6 ++--- .../sync_status_and_actions.dart | 3 ++- 7 files changed, 41 insertions(+), 31 deletions(-) create mode 100644 mobile/lib/domain/models/server_capability.model.dart diff --git a/mobile/lib/domain/models/server_capability.model.dart b/mobile/lib/domain/models/server_capability.model.dart new file mode 100644 index 0000000000..ece9850594 --- /dev/null +++ b/mobile/lib/domain/models/server_capability.model.dart @@ -0,0 +1,23 @@ +import 'package:immich_mobile/utils/semver.dart'; + +enum ServerCapability { + // Feature Support + cloudIdMetadata(SemVer(major: 2, minor: 4, patch: 0)), + bulkCloudIdMetadata(SemVer(major: 2, minor: 5, patch: 0)), + assetEdits(SemVer(major: 2, minor: 6, patch: 0)), + assetFacesV2(SemVer(major: 2, minor: 6, patch: 0)), + syncV2(SemVer(major: 3, minor: 0, patch: 0)), + assetOcr(SemVer(major: 3, minor: 0, patch: 0)), + + // Migrations + assetPayloadChange20260128(SemVer(major: 2, minor: 5, patch: 0)), + assetPayloadChange20260597(SemVer(major: 2, minor: 7, patch: 6)); + + const ServerCapability(this.minVersion); + + final SemVer minVersion; +} + +extension ServerCapabilitySupport on SemVer { + bool supports(ServerCapability capability) => this >= capability.minVersion; +} diff --git a/mobile/lib/domain/services/sync_stream.service.dart b/mobile/lib/domain/services/sync_stream.service.dart index 0eb3ff2f99..71228ba2ba 100644 --- a/mobile/lib/domain/services/sync_stream.service.dart +++ b/mobile/lib/domain/services/sync_stream.service.dart @@ -4,6 +4,7 @@ import 'dart:async'; import 'dart:convert'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; +import 'package:immich_mobile/domain/models/server_capability.model.dart'; import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/domain/models/sync_event.model.dart'; import 'package:immich_mobile/entities/store.entity.dart'; @@ -122,7 +123,7 @@ class SyncStreamService { } if (!migrations.contains(SyncMigrationTask.v20260128_ResetAssetV1.name) && - semVer >= const SemVer(major: 2, minor: 5, patch: 0)) { + semVer.supports(.assetPayloadChange20260128)) { _logger.info("Running pre-sync task: v20260128_ResetAssetV1"); await _syncApiRepository.deleteSyncAck([ SyncEntityType.assetV1, @@ -139,7 +140,7 @@ class SyncStreamService { } if (!migrations.contains(SyncMigrationTask.v20260597_ResetAssetV1AssetV2.name) && - semVer > const SemVer(major: 2, minor: 7, patch: 5)) { + semVer.supports(.assetPayloadChange20260597)) { _logger.info("Running pre-sync task: v20260597_ResetAssetV1AssetV2"); await _syncApiRepository.deleteSyncAck([SyncEntityType.assetV1, SyncEntityType.assetV2]); migrations.add(SyncMigrationTask.v20260597_ResetAssetV1AssetV2.name); diff --git a/mobile/lib/domain/utils/migrate_cloud_ids.dart b/mobile/lib/domain/utils/migrate_cloud_ids.dart index efef6e8327..e1d5fe451e 100644 --- a/mobile/lib/domain/utils/migrate_cloud_ids.dart +++ b/mobile/lib/domain/utils/migrate_cloud_ids.dart @@ -5,6 +5,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/constants.dart'; import 'package:immich_mobile/domain/models/asset/asset_metadata.model.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; +import 'package:immich_mobile/domain/models/server_capability.model.dart'; import 'package:immich_mobile/extensions/platform_extensions.dart'; import 'package:immich_mobile/infrastructure/entities/local_asset.entity.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; @@ -31,12 +32,12 @@ Future syncCloudIds(ProviderContainer ref) async { await _populateCloudIds(db); final serverInfo = await ref.read(serverInfoProvider.notifier).getServerInfo(); - final canUpdateMetadata = serverInfo.serverVersion.isAtLeast(major: 2, minor: 4); + final canUpdateMetadata = serverInfo.serverVersion.supports(.cloudIdMetadata); if (!canUpdateMetadata) { logger.fine('Server version does not support asset metadata updates. Skipping cloudId migration.'); return; } - final canBulkUpdateMetadata = serverInfo.serverVersion.isAtLeast(major: 2, minor: 5); + final canBulkUpdateMetadata = serverInfo.serverVersion.supports(.bulkCloudIdMetadata); // Wait for remote sync to complete, so we have up-to-date asset metadata entries try { diff --git a/mobile/lib/infrastructure/repositories/sync_api.repository.dart b/mobile/lib/infrastructure/repositories/sync_api.repository.dart index 303859da2f..4e27da1a3d 100644 --- a/mobile/lib/infrastructure/repositories/sync_api.repository.dart +++ b/mobile/lib/infrastructure/repositories/sync_api.repository.dart @@ -3,6 +3,7 @@ import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:immich_mobile/constants/constants.dart'; +import 'package:immich_mobile/domain/models/server_capability.model.dart'; import 'package:immich_mobile/domain/models/sync_event.model.dart'; import 'package:immich_mobile/infrastructure/repositories/network.repository.dart'; import 'package:immich_mobile/services/api.service.dart'; @@ -44,25 +45,16 @@ class SyncApiRepository { types: [ SyncRequestType.authUsersV1, SyncRequestType.usersV1, - serverVersion >= const SemVer(major: 3, minor: 0, patch: 0) - ? SyncRequestType.assetsV2 - : SyncRequestType.assetsV1, + serverVersion.supports(.syncV2) ? SyncRequestType.assetsV2 : SyncRequestType.assetsV1, SyncRequestType.assetExifsV1, - if (serverVersion >= const SemVer(major: 2, minor: 6, patch: 0)) SyncRequestType.assetEditsV1, + if (serverVersion.supports(.assetEdits)) SyncRequestType.assetEditsV1, SyncRequestType.assetMetadataV1, SyncRequestType.partnersV1, - serverVersion >= const SemVer(major: 3, minor: 0, patch: 0) - ? SyncRequestType.partnerAssetsV2 - : SyncRequestType.partnerAssetsV1, + serverVersion.supports(.syncV2) ? SyncRequestType.partnerAssetsV2 : SyncRequestType.partnerAssetsV1, SyncRequestType.partnerAssetExifsV1, - if (serverVersion < const SemVer(major: 3, minor: 0, patch: 0)) - SyncRequestType.albumsV1 - else - SyncRequestType.albumsV2, + serverVersion.supports(.syncV2) ? SyncRequestType.albumsV2 : SyncRequestType.albumsV1, SyncRequestType.albumUsersV1, - serverVersion >= const SemVer(major: 3, minor: 0, patch: 0) - ? SyncRequestType.albumAssetsV2 - : SyncRequestType.albumAssetsV1, + serverVersion.supports(.syncV2) ? SyncRequestType.albumAssetsV2 : SyncRequestType.albumAssetsV1, SyncRequestType.albumAssetExifsV1, SyncRequestType.albumToAssetsV1, SyncRequestType.memoriesV1, @@ -71,10 +63,8 @@ class SyncApiRepository { SyncRequestType.partnerStacksV1, SyncRequestType.userMetadataV1, SyncRequestType.peopleV1, - serverVersion >= const SemVer(major: 2, minor: 6, patch: 0) - ? SyncRequestType.assetFacesV2 - : SyncRequestType.assetFacesV1, - if (serverVersion >= const SemVer(major: 3, minor: 0, patch: 0)) SyncRequestType.assetOcrV1, + serverVersion.supports(.assetFacesV2) ? SyncRequestType.assetFacesV2 : SyncRequestType.assetFacesV1, + if (serverVersion.supports(.assetOcr)) SyncRequestType.assetOcrV1, ], ).toJson(), ); diff --git a/mobile/lib/models/server_info/server_version.model.dart b/mobile/lib/models/server_info/server_version.model.dart index 40f35a3cd0..cf16d4db0b 100644 --- a/mobile/lib/models/server_info/server_version.model.dart +++ b/mobile/lib/models/server_info/server_version.model.dart @@ -6,8 +6,4 @@ class ServerVersion extends SemVer { ServerVersion.fromDto(ServerVersionResponseDto dto) : super(major: dto.major, minor: dto.minor, patch: dto.patch_, prerelease: dto.prerelease); - - bool isAtLeast({int major = 0, int minor = 0, int patch = 0, int? prerelease}) { - return this >= SemVer(major: major, minor: minor, patch: patch, prerelease: prerelease); - } } diff --git a/mobile/lib/presentation/actions/edit_asset.action.dart b/mobile/lib/presentation/actions/edit_asset.action.dart index 9b11032df9..ca0fa0bad0 100644 --- a/mobile/lib/presentation/actions/edit_asset.action.dart +++ b/mobile/lib/presentation/actions/edit_asset.action.dart @@ -7,6 +7,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/enums.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/domain/models/asset_edit.model.dart'; +import 'package:immich_mobile/domain/models/server_capability.model.dart'; import 'package:immich_mobile/generated/translations.g.dart'; import 'package:immich_mobile/presentation/actions/action.dart'; import 'package:immich_mobile/presentation/pages/edit/editor.provider.dart'; @@ -16,12 +17,9 @@ import 'package:immich_mobile/providers/server_info.provider.dart'; import 'package:immich_mobile/providers/websocket.provider.dart'; import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/utils/error_handler.dart'; -import 'package:immich_mobile/utils/semver.dart'; - -const _minimumServerVersion = SemVer(major: 2, minor: 6, patch: 0); final _stateProvider = Provider.family.autoDispose((ref, source) { - final isSupported = ref.watch(serverInfoProvider.select((state) => state.serverVersion >= _minimumServerVersion)); + final isSupported = ref.watch(serverInfoProvider.select((state) => state.serverVersion.supports(.assetEdits))); if (!isSupported) { return null; } diff --git a/mobile/lib/widgets/settings/beta_sync_settings/sync_status_and_actions.dart b/mobile/lib/widgets/settings/beta_sync_settings/sync_status_and_actions.dart index d2edef832e..69d86b4c36 100644 --- a/mobile/lib/widgets/settings/beta_sync_settings/sync_status_and_actions.dart +++ b/mobile/lib/widgets/settings/beta_sync_settings/sync_status_and_actions.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/server_capability.model.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/platform_extensions.dart'; import 'package:immich_mobile/generated/translations.g.dart'; @@ -150,7 +151,7 @@ class SyncStatusAndActions extends HookConsumerWidget { unawaited(ref.read(backgroundSyncProvider).syncRemote()); }, ), - if (CurrentPlatform.isIOS && serverVersion.isAtLeast(major: 2, minor: 5)) + if (CurrentPlatform.isIOS && serverVersion.supports(.cloudIdMetadata)) SettingListTile( title: 'Sync Cloud Ids', leading: const Icon(Icons.cloud_circle_rounded),