| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117 |
- import 'dart:convert';
- import 'dart:io';
- import '../models/update_info.dart';
- class UpdateException implements Exception {
- UpdateException(this.message);
- final String message;
- @override
- String toString() => message;
- }
- /// Checks the public Gogs releases API. This is a notification only; installing
- /// an update remains a user action from the release page.
- class UpdateService {
- static const repository = 'kkazakov/car-interface-android';
- static const _baseUrl = 'https://git.krasi.net';
- static const releasePageUrl = '$_baseUrl/$repository/releases';
- Future<UpdateInfo> check(String currentVersion) async {
- final uri = Uri.parse('$_baseUrl/api/v1/repos/$repository/releases');
- final client = HttpClient()..connectionTimeout = const Duration(seconds: 8);
- try {
- final request = await client.getUrl(uri);
- request.headers.set(
- HttpHeaders.userAgentHeader,
- 'CarDash/$currentVersion',
- );
- final response = await request.close().timeout(
- const Duration(seconds: 10),
- );
- if (response.statusCode != HttpStatus.ok) {
- throw UpdateException(
- 'Release server returned ${response.statusCode}.',
- );
- }
- final body = await response.transform(utf8.decoder).join();
- final decoded = jsonDecode(body);
- if (decoded is! List) {
- throw UpdateException('Unexpected release response.');
- }
- final releases = <_Release>[];
- for (final raw in decoded) {
- if (raw is! Map) continue;
- if (raw['draft'] == true || raw['prerelease'] == true) continue;
- final tag = (raw['tag_name'] as String?)?.trim() ?? '';
- final version = _normaliseVersion(tag);
- if (version == null) continue;
- releases.add(
- _Release(
- version: version,
- title: (raw['name'] as String?)?.trim() ?? tag,
- ),
- );
- }
- if (releases.isEmpty) {
- throw UpdateException('No published releases found.');
- }
- releases.sort((a, b) => compareVersions(b.version, a.version));
- final latest = releases.first;
- final available = compareVersions(latest.version, currentVersion) > 0;
- return UpdateInfo(
- currentVersion: currentVersion,
- latestVersion: latest.version,
- available: available,
- releasePageUrl: releasePageUrl,
- downloadUrl:
- '$_baseUrl/$repository/raw/main/releases/'
- 'cardash-android-${latest.version}.apk',
- );
- } on UpdateException {
- rethrow;
- } catch (error) {
- throw UpdateException('Could not check for updates: $error');
- } finally {
- client.close(force: true);
- }
- }
- static String? _normaliseVersion(String tag) {
- final value = tag.replaceFirst(RegExp(r'^[vV]'), '').trim();
- return RegExp(r'^\d+\.\d+\.\d+$').hasMatch(value) ? value : null;
- }
- }
- /// Compares dot-separated numeric versions. Returns <0 when [a] is older.
- int compareVersions(String a, String b) {
- List<int> parts(String value) {
- final clean = value.replaceFirst(RegExp(r'^[vV]'), '');
- final numbers = clean
- .split('.')
- .map((part) => int.tryParse(part) ?? 0)
- .toList();
- while (numbers.length < 3) {
- numbers.add(0);
- }
- return numbers;
- }
- final left = parts(a);
- final right = parts(b);
- for (var index = 0; index < 3; index++) {
- final compared = left[index].compareTo(right[index]);
- if (compared != 0) return compared;
- }
- return 0;
- }
- class _Release {
- const _Release({required this.version, required this.title});
- final String version;
- final String title;
- }
|