diff --git a/.project b/.project new file mode 100644 index 00000000..44924512 --- /dev/null +++ b/.project @@ -0,0 +1,11 @@ + + + upgrader + + + + + + + + diff --git a/.settings/org.eclipse.core.resources.prefs b/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 00000000..99f26c02 --- /dev/null +++ b/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +encoding/=UTF-8 diff --git a/.settings/org.eclipse.core.runtime.prefs b/.settings/org.eclipse.core.runtime.prefs new file mode 100644 index 00000000..5a0ad22d --- /dev/null +++ b/.settings/org.eclipse.core.runtime.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +line.separator=\n diff --git a/example/macos/Flutter/GeneratedPluginRegistrant.swift b/example/macos/Flutter/GeneratedPluginRegistrant.swift index 92513f68..a84f33af 100644 --- a/example/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/example/macos/Flutter/GeneratedPluginRegistrant.swift @@ -12,7 +12,7 @@ import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) - FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin")) + FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) } diff --git a/lib/src/appcast.dart b/lib/src/appcast.dart index 3df79e2b..99062da4 100644 --- a/lib/src/appcast.dart +++ b/lib/src/appcast.dart @@ -40,6 +40,7 @@ class Appcast { List? items; String? osVersionString; + String? deviceAbi; /// Returns the latest critical item in the Appcast. AppcastItem? bestCriticalItem() { @@ -51,7 +52,8 @@ class Appcast { items!.forEach((AppcastItem item) { if (item.hostSupportsItem( osVersion: osVersionString, - currentPlatform: upgraderOS.current) && + currentPlatform: upgraderOS.current, + deviceAbi: deviceAbi) && item.isCriticalUpdate) { if (bestItem == null) { bestItem = item; @@ -81,7 +83,9 @@ class Appcast { AppcastItem? bestItem; items!.forEach((AppcastItem item) { if (item.hostSupportsItem( - osVersion: osVersionString, currentPlatform: upgraderOS.current)) { + osVersion: osVersionString, + currentPlatform: upgraderOS.current, + deviceAbi: deviceAbi)) { if (bestItem == null) { bestItem = item; } else { @@ -116,6 +120,7 @@ class Appcast { /// Parse the Appcast from XML string. Future?> parseAppcastItems(String contents) async { osVersionString = await upgraderDevice.getOsVersionString(upgraderOS); + deviceAbi = await upgraderDevice.getPreferredAbi(upgraderOS); return parseItemsFromXMLString(contents); } @@ -144,6 +149,7 @@ class Appcast { String? maximumSystemVersion; String? minimumSystemVersion; String? osString; + String? abi; String? releaseNotesLink; final tags = []; String? newVersion; @@ -168,6 +174,9 @@ class Appcast { } else if (attribute.name.toString() == AppcastConstants.AttributeURL) { fileURL = attribute.value; + } else if (attribute.name.toString() == + AppcastConstants.AttributeAbi) { + abi = attribute.value; } }); } else if (name == AppcastConstants.ElementMaximumSystemVersion) { @@ -209,6 +218,7 @@ class Appcast { maximumSystemVersion: maximumSystemVersion, minimumSystemVersion: minimumSystemVersion, osString: osString, + abi: abi, releaseNotesURL: releaseNotesLink, tags: tags, fileURL: fileURL, @@ -237,6 +247,7 @@ class AppcastItem { final int? contentLength; final String? versionString; final String? osString; + final String? abi; final String? displayVersionString; final String? infoURL; final List? tags; @@ -252,6 +263,7 @@ class AppcastItem { this.contentLength, this.versionString, this.osString, + this.abi, this.displayVersionString, this.infoURL, this.tags, @@ -264,7 +276,8 @@ class AppcastItem { : tags!.contains(AppcastConstants.ElementCriticalUpdate); /// Does the host support this item? If so is [osVersion] supported? - bool hostSupportsItem({String? osVersion, required String currentPlatform}) { + bool hostSupportsItem( + {String? osVersion, required String currentPlatform, String? deviceAbi}) { assert(currentPlatform.isNotEmpty); bool supported = true; if (osString != null && osString!.isNotEmpty) { @@ -273,6 +286,11 @@ class AppcastItem { supported = platformEnum.toLowerCase() == currentPlatform.toLowerCase(); } + // check ABI if present + if (supported && deviceAbi != null) { + supported = (abi == null || deviceAbi == abi); + } + if (supported && osVersion != null && osVersion.isNotEmpty) { Version osVersionValue; try { @@ -316,6 +334,7 @@ class AppcastConstants { 'sparkle:shortVersionString'; static const String AttributeVersion = 'sparkle:version'; static const String AttributeOsType = 'sparkle:os'; + static const String AttributeAbi = 'abi'; static const String ElementCriticalUpdate = 'sparkle:criticalUpdate'; static const String ElementDeltas = 'sparkle:deltas'; diff --git a/lib/src/upgrader_device.dart b/lib/src/upgrader_device.dart index ab2155c2..70953c94 100644 --- a/lib/src/upgrader_device.dart +++ b/lib/src/upgrader_device.dart @@ -48,14 +48,30 @@ class UpgraderDevice { return osVersionString; } + + /// Returns preferred ABI supported by the OS, if any. Currently, Android only. + Future getPreferredAbi(UpgraderOS upgraderOS) async { + final deviceInfo = DeviceInfoPlugin(); + if (upgraderOS.isAndroid) { + final androidInfo = await deviceInfo.androidInfo; + final abiList = androidInfo.supportedAbis; + return abiList.isNotEmpty ? abiList.first : null; + } else { + return null; + } + } } class MockUpgraderDevice extends UpgraderDevice { - MockUpgraderDevice({this.osVersionString = ''}); + MockUpgraderDevice({this.osVersionString = '', this.deviceAbi}); final String osVersionString; + final String? deviceAbi; @override Future getOsVersionString(UpgraderOS upgraderOS) async => osVersionString; + + @override + Future getPreferredAbi(UpgraderOS upgraderOS) async => deviceAbi; } diff --git a/pubspec.yaml b/pubspec.yaml index 1709ab1c..0bceece7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -24,7 +24,7 @@ dependencies: os_detect: ^2.0.1 # From Flutter Community: Provides an API for querying information about an application package. - package_info_plus: ^4.0.1 + package_info_plus: ^8.0.0 # From Flutter Team: Wraps NSUserDefaults (on iOS) and SharedPreferences (on Android). shared_preferences: ^2.1.1 @@ -44,6 +44,6 @@ dev_dependencies: # From Dart Team: Mock library for Dart inspired by Mockito. mockito: ^5.4.0 - flutter_lints: ^2.0.1 + flutter_lints: ^3.0.2 flutter: diff --git a/test/appcast_test.dart b/test/appcast_test.dart index aba07156..2a15661e 100644 --- a/test/appcast_test.dart +++ b/test/appcast_test.dart @@ -191,6 +191,33 @@ void main() { final bestItem = appcast.bestItem(); expect(bestItem, isNull); }); + test('Appcast ABI', () async { + var testFile = await getTestFile(filePath: 'test/testappcast-abi.xml'); + // no ABI + var appcast = TestAppcast( + client: setupMockClient(), + upgraderOS: MockUpgraderOS(android: true), + upgraderDevice: MockUpgraderDevice()); + await appcast.parseAppcastItemsFromFile(testFile); + var bestItem = appcast.bestItem(); + expect(bestItem, isNotNull); // nothing specified so anything goes + // wrong ABI + appcast = TestAppcast( + client: setupMockClient(), + upgraderOS: MockUpgraderOS(android: true), + upgraderDevice: MockUpgraderDevice(deviceAbi: 'bogus-abi')); + await appcast.parseAppcastItemsFromFile(testFile); + bestItem = appcast.bestItem(); + expect(bestItem, isNull); + // right ABI + appcast = TestAppcast( + client: setupMockClient(), + upgraderOS: MockUpgraderOS(android: true), + upgraderDevice: MockUpgraderDevice(deviceAbi: 'arm64-v8a')); + await appcast.parseAppcastItemsFromFile(testFile); + bestItem = appcast.bestItem(); + expect(bestItem!.abi, 'arm64-v8a'); + }); } void validateItems(List items, Appcast appcast) { diff --git a/test/device_test.dart b/test/device_test.dart index 656ddb8c..0be67dae 100644 --- a/test/device_test.dart +++ b/test/device_test.dart @@ -26,11 +26,16 @@ void main() { final device = UpgraderDevice(); expect(await device.getOsVersionString(MockUpgraderOS(android: true)), '1.2.3'); + expect(await device.getPreferredAbi(MockUpgraderOS(android: true)), + 'arm64-v8a'); // Verify invalid OS version deviceInfo = _androidInfo(baseOS: '.'); expect( await device.getOsVersionString(MockUpgraderOS(android: true)), isNull); + // only Android supports ABI + expect( + await device.getPreferredAbi(MockUpgraderOS(android: false)), isNull); }); test('testing UpgraderDevice macOS', () async { @@ -78,7 +83,7 @@ Map _androidInfo({required String baseOS}) { 'product': 'a', 'supported32BitAbis': ['a'], 'supported64BitAbis': ['a'], - 'supportedAbis': ['a'], + 'supportedAbis': ['arm64-v8a'], // also used in test 'tags': 'a', 'type': 'a', 'isPhysicalDevice': false, diff --git a/test/testappcast-abi.xml b/test/testappcast-abi.xml new file mode 100644 index 00000000..2e5cf66b --- /dev/null +++ b/test/testappcast-abi.xml @@ -0,0 +1,13 @@ + + + + Releases + + + + + + + +