Skip to content

refactor(nextcloud): Rework version checks #1157

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Nov 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions packages/neon/neon/lib/src/blocs/apps.dart
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ abstract interface class AppsBlocStates {
BehaviorSubject<void> get openNotifications;

/// A collection of unsupported apps and their minimum required version.
BehaviorSubject<Map<String, String?>> get appVersions;
BehaviorSubject<Map<String, VersionCheck>> get appVersionChecks;
}

/// The Bloc responsible for managing the [AppImplementation]s.
Expand Down Expand Up @@ -131,12 +131,12 @@ class AppsBloc extends InteractiveBloc implements AppsBlocEvents, AppsBlocStates
return;
}

final notSupported = <String, String?>{};
final notSupported = <String, VersionCheck>{};

try {
final coreCheck = _account.client.core.isSupported(capabilities.requireData);
final coreCheck = _account.client.core.getVersionCheck(capabilities.requireData);
if (!coreCheck.isSupported) {
notSupported['core'] = coreCheck.minimumVersion.toString();
notSupported['core'] = coreCheck;
}
} catch (e, s) {
debugPrint(e.toString());
Expand All @@ -145,14 +145,14 @@ class AppsBloc extends InteractiveBloc implements AppsBlocEvents, AppsBlocStates

for (final app in apps.requireData) {
try {
final check = await app.isSupported(_account, capabilities.requireData);
final check = await app.getVersionCheck(_account, capabilities.requireData);

if (check == null) {
continue;
}

if (!check.isSupported) {
notSupported[app.id] = check.minimumVersion;
notSupported[app.id] = check;
}
} catch (e, s) {
debugPrint(e.toString());
Expand All @@ -161,7 +161,7 @@ class AppsBloc extends InteractiveBloc implements AppsBlocEvents, AppsBlocStates
}

if (notSupported.isNotEmpty) {
appVersions.add(notSupported);
appVersionChecks.add(notSupported);
}
}

Expand Down Expand Up @@ -190,7 +190,7 @@ class AppsBloc extends InteractiveBloc implements AppsBlocEvents, AppsBlocStates
unawaited(notificationsAppImplementation.close());
unawaited(activeApp.close());
unawaited(openNotifications.close());
unawaited(appVersions.close());
unawaited(appVersionChecks.close());

super.dispose();
}
Expand All @@ -209,7 +209,7 @@ class AppsBloc extends InteractiveBloc implements AppsBlocEvents, AppsBlocStates
BehaviorSubject<void> openNotifications = BehaviorSubject();

@override
BehaviorSubject<Map<String, String?>> appVersions = BehaviorSubject();
BehaviorSubject<Map<String, VersionCheck>> appVersionChecks = BehaviorSubject();

@override
Future<void> refresh() async {
Expand Down
4 changes: 2 additions & 2 deletions packages/neon/neon/lib/src/models/app_implementation.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import 'package:neon/src/settings/models/storage.dart';
import 'package:neon/src/utils/provider.dart';
import 'package:neon/src/widgets/drawer_destination.dart';
import 'package:nextcloud/core.dart' as core;
import 'package:nextcloud/nextcloud.dart' show VersionSupported;
import 'package:nextcloud/nextcloud.dart' show VersionCheck;
import 'package:provider/provider.dart';
import 'package:rxdart/rxdart.dart';
import 'package:vector_graphics/vector_graphics.dart';
Expand Down Expand Up @@ -61,7 +61,7 @@ abstract class AppImplementation<T extends Bloc, R extends NextcloudAppOptions>
/// A value of `null` means that it can not be known if the app is supported.
/// This is the case for apps that depend on the server version like files and we assume that the app is supported.
/// The server support is handled differently.
FutureOr<VersionSupported<String>?> isSupported(
FutureOr<VersionCheck?> getVersionCheck(
final Account account,
final core.OcsGetCapabilitiesResponseApplicationJson_Ocs_Data capabilities,
) =>
Expand Down
17 changes: 7 additions & 10 deletions packages/neon/neon/lib/src/pages/home.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import 'package:neon/src/widgets/drawer.dart';
import 'package:neon/src/widgets/error.dart';
import 'package:neon/src/widgets/unified_search_results.dart';
import 'package:nextcloud/core.dart' as core;
import 'package:nextcloud/nextcloud.dart';
import 'package:provider/provider.dart';

/// The home page of Neon.
Expand All @@ -35,7 +36,7 @@ class _HomePageState extends State<HomePage> {
late global_options.GlobalOptions _globalOptions;
late AccountsBloc _accountsBloc;
late AppsBloc _appsBloc;
late StreamSubscription<Map<String, String?>> _versionCheckSubscription;
late StreamSubscription<Map<String, VersionCheck>> _versionCheckSubscription;

@override
void initState() {
Expand All @@ -45,23 +46,19 @@ class _HomePageState extends State<HomePage> {
_account = _accountsBloc.activeAccount.value!;
_appsBloc = _accountsBloc.activeAppsBloc;

_versionCheckSubscription = _appsBloc.appVersions.listen((final values) {
_versionCheckSubscription = _appsBloc.appVersionChecks.listen((final values) {
if (!mounted) {
return;
}

final l10n = NeonLocalizations.of(context);

final buffer = StringBuffer()..writeln();

for (final error in values.entries) {
final appId = error.key;
final minVersion = error.value;
final appName = l10n.appImplementationName(appId);
for (final entry in values.entries) {
final versionCheck = entry.value;
final appName = l10n.appImplementationName(entry.key);

if (appName.isNotEmpty && minVersion != null) {
buffer.writeln('- $appName $minVersion');
}
buffer.writeln('- $appName >=${versionCheck.minimumVersion} <${versionCheck.maximumMajor + 1}.0.0');
}

final message = l10n.errorUnsupportedAppVersions(buffer.toString());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ class _LoginCheckServerStatusPageState extends State<LoginCheckServerStatusPage>
child: ResultBuilder.behaviorSubject(
subject: bloc.state,
builder: (final context, final state) {
final success = state.hasData && state.requireData.isSupported && !state.requireData.maintenance;
final success =
state.hasData && state.requireData.versionCheck.isSupported && !state.requireData.maintenance;

return Column(
mainAxisAlignment: MainAxisAlignment.center,
Expand Down Expand Up @@ -121,7 +122,7 @@ class _LoginCheckServerStatusPageState extends State<LoginCheckServerStatusPage>
);
}

if (result.requireData.isSupported) {
if (result.requireData.versionCheck.isSupported) {
return NeonValidationTile(
title: NeonLocalizations.of(context).loginSupportedServerVersion(result.requireData.versionstring),
state: ValidationState.success,
Expand Down
1 change: 1 addition & 0 deletions packages/neon/neon/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ dependencies:
universal_io: ^2.0.0
url_launcher: ^6.1.0
vector_graphics: ^1.0.0
version: ^3.0.0
window_manager: ^0.3.0
xml: ^6.0.0

Expand Down
4 changes: 2 additions & 2 deletions packages/neon/neon_news/lib/neon_news.dart
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,9 @@ class NewsApp extends AppImplementation<NewsBloc, NewsAppSpecificOptions> {
BehaviorSubject<int> getUnreadCounter(final NewsBloc bloc) => bloc.unreadCounter;

@override
Future<VersionSupported<String>> isSupported(
Future<VersionCheck> getVersionCheck(
final Account account,
final core.OcsGetCapabilitiesResponseApplicationJson_Ocs_Data capabilities,
) =>
account.client.news.isSupported();
account.client.news.getVersionCheck();
}
8 changes: 3 additions & 5 deletions packages/neon/neon_notes/lib/neon_notes.dart
Original file line number Diff line number Diff line change
Expand Up @@ -95,11 +95,9 @@ class NotesApp extends AppImplementation<NotesBloc, NotesAppSpecificOptions> {
final RouteBase route = $notesAppRoute;

@override
VersionSupported<String> isSupported(
VersionCheck getVersionCheck(
final Account account,
final core.OcsGetCapabilitiesResponseApplicationJson_Ocs_Data capabilities,
) {
final result = account.client.notes.isSupported(capabilities);
return (isSupported: result.isSupported, minimumVersion: result.minimumVersion.toString());
}
) =>
account.client.notes.getVersionCheck(capabilities);
}
44 changes: 42 additions & 2 deletions packages/nextcloud/lib/src/helpers/common.dart
Original file line number Diff line number Diff line change
@@ -1,2 +1,42 @@
/// The result of a version check.
typedef VersionSupported<T> = ({bool isSupported, T minimumVersion});
import 'package:meta/meta.dart';
import 'package:version/version.dart';

/// Holds the [versions], [minimumVersion] and [maximumMajor] of an app.
@immutable
class VersionCheck {
/// Creates a new [VersionCheck].
///
/// If the [maximumMajor] is `null` the compatibility of the major of the [minimumVersion] is checked.
VersionCheck({
required this.versions,
required this.minimumVersion,
required final int? maximumMajor,
}) : maximumMajor = maximumMajor ?? minimumVersion.major;

/// Current version of the app.
final List<Version>? versions;

/// Minimum version of the app.
final Version minimumVersion;

/// Maximum major version of the app.
late final int maximumMajor;

/// Whether the [versions] is allowed by the [minimumVersion] and [maximumMajor].
///
/// If [versions] is `null` or empty it is assumed that the app is supported.
/// Only one of the [versions] has to be supported to return `true`.
bool get isSupported {
if (versions == null || versions!.isEmpty) {
return true;
}

for (final version in versions!) {
if (version >= minimumVersion && version.major <= maximumMajor) {
return true;
}
}

return false;
}
}
36 changes: 23 additions & 13 deletions packages/nextcloud/lib/src/helpers/core.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,36 @@

import 'package:nextcloud/src/api/core.openapi.dart' as core;
import 'package:nextcloud/src/helpers/common.dart';
import 'package:version/version.dart';

/// Version of core/Server supported
const supportedVersion = 27;
/// Minimum version of core/Server supported
final minVersion = Version(27, 0, 0);

extension CoreVersionSupported on core.Client {
extension CoreVersionCheck on core.Client {
/// Check if the core/Server version is supported by this client
///
/// Also returns the supported version number
VersionSupported<int> isSupported(
final core.OcsGetCapabilitiesResponseApplicationJson_Ocs_Data capabilities,
) =>
(
isSupported: capabilities.version.major == supportedVersion,
minimumVersion: supportedVersion,
);
/// Also returns the minimum supported version
VersionCheck getVersionCheck(final core.OcsGetCapabilitiesResponseApplicationJson_Ocs_Data capabilities) {
final version = Version(
capabilities.version.major,
capabilities.version.minor,
capabilities.version.micro,
);
return VersionCheck(
versions: [version],
minimumVersion: minVersion,
maximumMajor: null,
);
}
}

extension CoreStatusVersionSupported on core.Status {
extension CoreStatusVersionCheck on core.Status {
/// Check if the core/Server version is supported
bool get isSupported => version.startsWith('$supportedVersion.');
VersionCheck get versionCheck => VersionCheck(
versions: [Version.parse(version)],
minimumVersion: minVersion,
maximumMajor: null,
);
}

enum ShareType {
Expand Down
17 changes: 10 additions & 7 deletions packages/nextcloud/lib/src/helpers/news.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,22 @@

import 'package:nextcloud/src/api/news.openapi.dart' as news;
import 'package:nextcloud/src/helpers/common.dart';
import 'package:version/version.dart';

/// API version of the news app supported
const supportedVersion = 'v1-3';
/// Minimum API version of the news app supported
final minVersion = Version(1, 3, 0);

extension NewsVersionSupported on news.Client {
extension NewsVersionCheck on news.Client {
/// Check if the news app version is supported by this client
///
/// Also returns the supported API version number
Future<VersionSupported<String>> isSupported() async {
Future<VersionCheck> getVersionCheck() async {
final response = await getSupportedApiVersions();
return (
isSupported: response.body.apiLevels!.contains(supportedVersion),
minimumVersion: supportedVersion,
final versions = response.body.apiLevels;
return VersionCheck(
versions: versions?.map((final version) => Version.parse(version.substring(1).replaceAll('-', '.'))).toList(),
minimumVersion: minVersion,
maximumMajor: null,
);
}
}
Expand Down
25 changes: 11 additions & 14 deletions packages/nextcloud/lib/src/helpers/notes.dart
Original file line number Diff line number Diff line change
@@ -1,25 +1,22 @@
import 'package:collection/collection.dart';
import 'package:nextcloud/src/api/core.openapi.dart' as core;
import 'package:nextcloud/src/api/notes.openapi.dart' as notes;
import 'package:nextcloud/src/helpers/common.dart';
import 'package:version/version.dart';

/// API version of the notes app supported
const supportedVersion = 1;
/// Minimum API version of the notes app supported
final minVersion = Version(1, 3, 0);

// ignore: public_member_api_docs
extension NotesVersionSupported on notes.Client {
extension NotesVersionCheck on notes.Client {
/// Check if the notes app version is supported by this client
///
/// Also returns the supported API version number
VersionSupported<int> isSupported(
final core.OcsGetCapabilitiesResponseApplicationJson_Ocs_Data capabilities,
) =>
(
isSupported: capabilities.capabilities.notesCapabilities?.notes.apiVersion
?.map(Version.parse)
.firstWhereOrNull((final version) => version.major == supportedVersion) !=
null,
minimumVersion: supportedVersion,
);
VersionCheck getVersionCheck(final core.OcsGetCapabilitiesResponseApplicationJson_Ocs_Data capabilities) {
final versions = capabilities.capabilities.notesCapabilities?.notes.apiVersion;
return VersionCheck(
versions: versions?.map(Version.parse).toList(),
minimumVersion: minVersion,
maximumMajor: null,
);
}
}
15 changes: 8 additions & 7 deletions packages/nextcloud/lib/src/helpers/spreed.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,20 @@ import 'package:nextcloud/src/api/core.openapi.dart' as core;
import 'package:nextcloud/src/api/spreed.openapi.dart' as spreed;
import 'package:version/version.dart';

/// The version of the spreed app that is supported.
const supportedVersion = 17;
/// The minimum version of the spreed app that is supported.
final minVersion = Version(17, 0, 0);

/// Extension for checking whether spreed is supported.
extension SpreedVersionSupported on spreed.Client {
extension SpreedVersionCheck on spreed.Client {
/// Checks whether the spreed app installed on the server is supported by this client.
///
/// Also returns the supported version number.
VersionSupported<int> isSupported(final core.OcsGetCapabilitiesResponseApplicationJson_Ocs_Data capabilities) {
VersionCheck getVersionCheck(final core.OcsGetCapabilitiesResponseApplicationJson_Ocs_Data capabilities) {
final version = capabilities.capabilities.spreedPublicCapabilities?.spreedPublicCapabilities0?.spreed.version;
return (
isSupported: version != null && Version.parse(version).major == supportedVersion,
minimumVersion: supportedVersion,
return VersionCheck(
versions: version != null ? [Version.parse(version)] : null,
minimumVersion: minVersion,
maximumMajor: null,
);
}
}
Expand Down
Loading