import 'package:appflowy/core/helpers/url_launcher.dart'; import 'package:appflowy/startup/tasks/device_info_task.dart'; import 'package:appflowy_backend/log.dart'; import 'package:auto_updater/auto_updater.dart'; import 'package:collection/collection.dart'; import 'package:http/http.dart' as http; import 'package:universal_platform/universal_platform.dart'; import 'package:xml/xml.dart' as xml; final versionChecker = VersionChecker(); /// Version checker class to handle update checks using appcast XML feeds class VersionChecker { factory VersionChecker() => _instance; VersionChecker._internal(); String? _feedUrl; static final VersionChecker _instance = VersionChecker._internal(); /// Sets the appcast XML feed URL void setFeedUrl(String url) { _feedUrl = url; if (UniversalPlatform.isWindows || UniversalPlatform.isMacOS) { autoUpdater.setFeedURL(url); // disable the auto update check autoUpdater.setScheduledCheckInterval(0); } } /// Checks for updates by fetching and parsing the appcast XML /// Returns a list of [AppcastItem] or throws an exception if the feed URL is not set Future checkForUpdateInformation() async { if (_feedUrl == null) { Log.error('Feed URL is not set'); return null; } try { final response = await http.get(Uri.parse(_feedUrl!)); if (response.statusCode != 200) { Log.info('Failed to fetch appcast XML: ${response.statusCode}'); return null; } // Parse XML content final document = xml.XmlDocument.parse(response.body); final items = document.findAllElements('item'); // Convert XML items to AppcastItem objects return items .map(_parseAppcastItem) .nonNulls .firstWhereOrNull((e) => e.os == ApplicationInfo.os); } catch (e) { Log.error('Failed to check for updates: $e'); } return null; } /// For Windows and macOS, calling this API will trigger the auto updater to check for updates /// For Linux, it will open the official website in the browser if there is a new version Future checkForUpdate() async { if (UniversalPlatform.isLinux) { // open the official website in the browser await afLaunchUrlString('https://appflowy.com/download'); } else { await autoUpdater.checkForUpdates(); } } AppcastItem? _parseAppcastItem(xml.XmlElement item) { final enclosure = item.findElements('enclosure').firstOrNull; return AppcastItem.fromJson({ 'title': item.findElements('title').firstOrNull?.innerText, 'versionString': item .findElements('sparkle:shortVersionString') .firstOrNull ?.innerText, 'displayVersionString': item .findElements('sparkle:shortVersionString') .firstOrNull ?.innerText, 'releaseNotesUrl': item.findElements('releaseNotesLink').firstOrNull?.innerText, 'pubDate': item.findElements('pubDate').firstOrNull?.innerText, 'fileURL': enclosure?.getAttribute('url') ?? '', 'os': enclosure?.getAttribute('sparkle:os') ?? '', 'criticalUpdate': enclosure?.getAttribute('sparkle:criticalUpdate') ?? false, }); } }