11import 'dart:io' ;
22import 'dart:math' ;
33
4+ import 'package:background_downloader/background_downloader.dart' ;
45import 'package:collection/collection.dart' ;
56import 'package:flutter_rust_bridge/flutter_rust_bridge.dart' ;
67import 'package:kyber_collection/kyber_collection.dart' ;
@@ -40,11 +41,15 @@ class IncrementalUpdater {
4041
4142 DownloaderHandle ? d;
4243 try {
44+ _logger.fine ('Checking incremental update eligibility for $downloadUrl ' );
45+
4346 d = await downloaderCreate (
4447 id: 'check-${DateTime .now ().millisecondsSinceEpoch }' ,
4548 zipUrl: downloadUrl,
4649 outputDir: tmpDir.path,
4750 );
51+
52+ _logger.fine ('Fetching mod collection entries for eligibility check' );
4853 entries = await downloaderListEntries (d: d);
4954
5055 final collectionEntry = entries.firstWhereOrNull (
@@ -55,6 +60,7 @@ class IncrementalUpdater {
5560 return false ;
5661 }
5762
63+ _logger.fine ('Downloading collection manifest for eligibility check' );
5864 await downloaderDownloadEntryByName (
5965 d: d,
6066 entryName: collectionEntry.name,
@@ -115,6 +121,7 @@ class IncrementalUpdater {
115121 void Function (UpdatePhase phase)? onPhaseChanged,
116122 void Function (int current, int total)? onProgress,
117123 void Function (int bytesDownloaded, int totalBytes)? onDownloadProgress,
124+ CallbackTaskController ? controller,
118125 }) async {
119126 final uri = Uri .parse (downloadUrl);
120127
@@ -131,6 +138,7 @@ class IncrementalUpdater {
131138 String ? newDir;
132139
133140 try {
141+ controller? .throwIfCancelled ();
134142 onPhaseChanged? .call (.fetchingEntries);
135143
136144 onProgress? .call (1 , 1 );
@@ -161,6 +169,7 @@ class IncrementalUpdater {
161169 return false ;
162170 }
163171
172+ controller? .throwIfCancelled ();
164173 onPhaseChanged? .call (.parsingCollection);
165174
166175 await downloaderDownloadEntryByName (
@@ -179,6 +188,7 @@ class IncrementalUpdater {
179188 return false ;
180189 }
181190
191+ controller? .throwIfCancelled ();
182192 onPhaseChanged? .call (.comparingMods);
183193
184194 final installedMods = sl.get <ModService >().mods;
@@ -215,6 +225,7 @@ class IncrementalUpdater {
215225 )
216226 .fold <int >(0 , (sum, e) => sum + e.compressedSize);
217227
228+ controller? .throwIfCancelled ();
218229 onPhaseChanged? .call (.copyingExistingMods);
219230
220231 final random = String .fromCharCodes (
@@ -231,6 +242,7 @@ class IncrementalUpdater {
231242 _logger.info ('Creating collection in $newDir ' );
232243 await Directory (newDir).create (recursive: true );
233244
245+ controller? .throwIfCancelled ();
234246 onPhaseChanged? .call (.downloadingMissingMods);
235247
236248 d = await downloaderCreate (
@@ -248,6 +260,7 @@ class IncrementalUpdater {
248260
249261 try {
250262 for (var i = 0 ; i < missing.length; i++ ) {
263+ controller? .throwIfCancelled ();
251264 final mod = missing[i];
252265 final entryInfo = entries.firstWhereOrNull (
253266 (e) => e.name == mod.$1,
@@ -257,6 +270,7 @@ class IncrementalUpdater {
257270 );
258271 final streamSink = RustStreamSink <int >();
259272
273+ // TODO: use frb cancel token to allow cancelling mid-download
260274 final future = downloaderDownloadEntryByName (
261275 d: d,
262276 entryName: mod.$1,
@@ -286,6 +300,7 @@ class IncrementalUpdater {
286300 _logger.info ('All missing mods downloaded, copying existing mods' );
287301
288302 for (var i = 0 ; i < installed.length; i++ ) {
303+ controller? .throwIfCancelled ();
289304 final mod = installed[i];
290305 onProgress? .call (i + 1 , installed.length);
291306
@@ -314,6 +329,20 @@ class IncrementalUpdater {
314329 } catch (_) {}
315330
316331 return true ;
332+ } on CancelledException {
333+ _logger.info ('Incremental update cancelled' );
334+
335+ if (newDir != null ) {
336+ try {
337+ await Directory (newDir).delete (recursive: true );
338+ } catch (_) {
339+ _logger.warning (
340+ 'Failed to clean up partial output directory: $newDir ' ,
341+ );
342+ }
343+ }
344+
345+ rethrow ;
317346 } catch (e, s) {
318347 _logger.severe ('Incremental update failed' , e, s);
319348
0 commit comments