@@ -8,6 +8,7 @@ import 'package:flutter/material.dart';
88import 'package:http/http.dart' as http;
99import 'package:path/path.dart' as path;
1010
11+ import 'package:icarus/services/app_error_reporter.dart' ;
1112import 'package:icarus/services/windows_desktop_update_restart_service.dart' ;
1213
1314class WindowsDesktopUpdateController extends ChangeNotifier {
@@ -22,8 +23,8 @@ class WindowsDesktopUpdateController extends ChangeNotifier {
2223 _updater = updater ?? DesktopUpdater (),
2324 _httpClient = httpClient ?? http.Client (),
2425 _ownsHttpClient = httpClient == null ,
25- _restartService =
26- restartService ?? WindowsDesktopUpdateRestartService (updater: updater) {
26+ _restartService = restartService ??
27+ WindowsDesktopUpdateRestartService (updater: updater) {
2728 if (autoCheck) {
2829 unawaited (checkVersion ());
2930 }
@@ -107,6 +108,16 @@ class WindowsDesktopUpdateController extends ChangeNotifier {
107108 0 ,
108109 (int total, FileHashModel file) => total + file.length,
109110 );
111+ _reportInfoSafely (
112+ 'Desktop update detected.' ,
113+ source: 'WindowsDesktopUpdateController.checkVersion' ,
114+ error: < String , Object ? > {
115+ 'version' : versionResponse.version,
116+ 'changedFileCount' : files.length,
117+ 'downloadSizeBytes' : _downloadSizeBytes,
118+ 'updateUrl' : versionResponse.url,
119+ },
120+ );
110121 notifyListeners ();
111122 } catch (_) {
112123 // Leave the direct installer updater silent if the remote metadata fails.
@@ -131,6 +142,16 @@ class WindowsDesktopUpdateController extends ChangeNotifier {
131142 final installDirectory = await _resolveInstallDirectory ();
132143 final updateDirectory = Directory (path.join (installDirectory, 'update' ));
133144 await _resetUpdateDirectory (updateDirectory);
145+ _reportInfoSafely (
146+ 'Starting desktop update download.' ,
147+ source: 'WindowsDesktopUpdateController.downloadUpdate' ,
148+ error: < String , Object ? > {
149+ 'installDirectory' : installDirectory,
150+ 'updateDirectory' : updateDirectory.path,
151+ 'changedFileCount' : files.length,
152+ 'downloadSizeBytes' : _downloadSizeBytes,
153+ },
154+ );
134155
135156 _skipUpdate = false ;
136157 _isDownloading = true ;
@@ -151,7 +172,8 @@ class WindowsDesktopUpdateController extends ChangeNotifier {
151172 );
152173 completedBytes += file.length;
153174 _downloadedBytes = completedBytes;
154- _downloadProgress = _calculateProgress (_downloadedBytes, _downloadSizeBytes);
175+ _downloadProgress =
176+ _calculateProgress (_downloadedBytes, _downloadSizeBytes);
155177 notifyListeners ();
156178 }
157179
@@ -161,10 +183,32 @@ class WindowsDesktopUpdateController extends ChangeNotifier {
161183 );
162184
163185 _isDownloaded = true ;
164- } catch (_) {
186+ _reportInfoSafely (
187+ 'Desktop update download completed and verified.' ,
188+ source: 'WindowsDesktopUpdateController.downloadUpdate' ,
189+ error: < String , Object ? > {
190+ 'updateDirectory' : updateDirectory.path,
191+ 'changedFileCount' : files.length,
192+ 'downloadSizeBytes' : _downloadSizeBytes,
193+ },
194+ );
195+ } catch (error, stackTrace) {
165196 _isDownloaded = false ;
166- await _cleanupPartialDownload (updateDirectory);
167- rethrow ;
197+ try {
198+ await _cleanupPartialDownload (updateDirectory);
199+ } catch (cleanupError, cleanupStackTrace) {
200+ _reportInfoSafely (
201+ 'Failed to clean up a partial desktop update download.' ,
202+ source: 'WindowsDesktopUpdateController.downloadUpdate' ,
203+ error: < String , Object ? > {
204+ 'updateDirectory' : updateDirectory.path,
205+ 'downloadError' : error.toString (),
206+ 'cleanupError' : cleanupError.toString (),
207+ 'cleanupStackTrace' : cleanupStackTrace.toString (),
208+ },
209+ );
210+ }
211+ Error .throwWithStackTrace (error, stackTrace);
168212 } finally {
169213 _isDownloading = false ;
170214 notifyListeners ();
@@ -177,6 +221,16 @@ class WindowsDesktopUpdateController extends ChangeNotifier {
177221 throw StateError ('No desktop update is available to apply.' );
178222 }
179223
224+ _reportInfoSafely (
225+ 'Restart requested to apply downloaded desktop update.' ,
226+ source: 'WindowsDesktopUpdateController.restartApp' ,
227+ error: < String , Object ? > {
228+ 'version' : update.version,
229+ 'changedFileCount' : _requiredChangedFiles (update).length,
230+ 'updaterLogPath' :
231+ WindowsDesktopUpdateRestartService .resolveUpdaterLogPath (),
232+ },
233+ );
180234 await _restartService.restartIntoDownloadedUpdate (
181235 expectedFiles: _requiredChangedFiles (update),
182236 );
@@ -211,7 +265,8 @@ class WindowsDesktopUpdateController extends ChangeNotifier {
211265 await destination.delete ();
212266 }
213267 if (attempt < 2 ) {
214- await Future <void >.delayed (Duration (milliseconds: 400 * (attempt + 1 )));
268+ await Future <void >.delayed (
269+ Duration (milliseconds: 400 * (attempt + 1 )));
215270 }
216271 }
217272 }
@@ -242,27 +297,15 @@ class WindowsDesktopUpdateController extends ChangeNotifier {
242297 var fileBytesReceived = 0 ;
243298
244299 try {
245- await response.stream.listen (
246- (List <int > chunk) {
247- sink.add (chunk);
248- fileBytesReceived += chunk.length;
249- _downloadedBytes = completedBytes + fileBytesReceived;
250- _downloadProgress =
251- _calculateProgress (_downloadedBytes, totalBytes);
252- notifyListeners ();
253- },
254- onDone: () async {
255- await sink.close ();
256- },
257- onError: (Object error) async {
258- await sink.close ();
259- throw error;
260- },
261- cancelOnError: true ,
262- ).asFuture <void >();
263- } catch (_) {
300+ await for (final List <int > chunk in response.stream) {
301+ sink.add (chunk);
302+ fileBytesReceived += chunk.length;
303+ _downloadedBytes = completedBytes + fileBytesReceived;
304+ _downloadProgress = _calculateProgress (_downloadedBytes, totalBytes);
305+ notifyListeners ();
306+ }
307+ } finally {
264308 await sink.close ();
265- rethrow ;
266309 }
267310 }
268311
@@ -307,7 +350,9 @@ class WindowsDesktopUpdateController extends ChangeNotifier {
307350 }
308351
309352 Future <String > _resolveInstallDirectory () async {
310- final executablePath = (await _updater.getExecutablePath ())? .trim ();
353+ final executablePath =
354+ WindowsDesktopUpdateRestartService .normalizeExecutablePath (
355+ await _updater.getExecutablePath ());
311356 if (executablePath == null || executablePath.isEmpty) {
312357 throw const FileSystemException (
313358 'Unable to resolve the installed executable path.' ,
@@ -318,18 +363,25 @@ class WindowsDesktopUpdateController extends ChangeNotifier {
318363 }
319364
320365 Future <void > _resetUpdateDirectory (Directory updateDirectory) async {
321- if (await updateDirectory.exists ()) {
322- await updateDirectory.delete (recursive: true );
323- }
366+ await _deleteDirectoryIfExistsWithRetry (
367+ updateDirectory,
368+ source: 'WindowsDesktopUpdateController._resetUpdateDirectory' ,
369+ bestEffort: false ,
370+ );
324371 await updateDirectory.create (recursive: true );
325372 }
326373
327374 Future <void > _cleanupPartialDownload (Directory updateDirectory) async {
328- if (await updateDirectory.exists ()) {
329- await updateDirectory.delete (recursive: true );
375+ try {
376+ await _deleteDirectoryIfExistsWithRetry (
377+ updateDirectory,
378+ source: 'WindowsDesktopUpdateController._cleanupPartialDownload' ,
379+ bestEffort: true ,
380+ );
381+ } finally {
382+ _downloadedBytes = 0 ;
383+ _downloadProgress = 0 ;
330384 }
331- _downloadedBytes = 0 ;
332- _downloadProgress = 0 ;
333385 }
334386
335387 List <FileHashModel > _requiredChangedFiles (ItemModel update) {
@@ -380,4 +432,65 @@ class WindowsDesktopUpdateController extends ChangeNotifier {
380432 final hash = await Blake2b ().hash (bytes);
381433 return base64Encode (hash.bytes);
382434 }
435+
436+ Future <void > _deleteDirectoryIfExistsWithRetry (
437+ Directory directory, {
438+ required String source,
439+ required bool bestEffort,
440+ }) async {
441+ if (! await directory.exists ()) {
442+ return ;
443+ }
444+
445+ Object ? lastError;
446+ StackTrace ? lastStackTrace;
447+
448+ for (var attempt = 0 ; attempt < 5 ; attempt++ ) {
449+ try {
450+ await directory.delete (recursive: true );
451+ return ;
452+ } catch (error, stackTrace) {
453+ lastError = error;
454+ lastStackTrace = stackTrace;
455+ if (attempt == 4 ) {
456+ break ;
457+ }
458+
459+ await Future <void >.delayed (
460+ Duration (milliseconds: 150 * (attempt + 1 )),
461+ );
462+ }
463+ }
464+
465+ if (bestEffort) {
466+ _reportInfoSafely (
467+ 'Unable to delete the staged desktop update directory after retries.' ,
468+ source: source,
469+ error: < String , Object ? > {
470+ 'directory' : directory.path,
471+ 'error' : lastError.toString (),
472+ 'stackTrace' : lastStackTrace.toString (),
473+ },
474+ );
475+ return ;
476+ }
477+
478+ Error .throwWithStackTrace (lastError! , lastStackTrace! );
479+ }
480+
481+ void _reportInfoSafely (
482+ String message, {
483+ required String source,
484+ Object ? error,
485+ }) {
486+ try {
487+ AppErrorReporter .reportInfo (
488+ message,
489+ source: source,
490+ error: error,
491+ );
492+ } catch (_) {
493+ // Logging must never interrupt the update flow.
494+ }
495+ }
383496}
0 commit comments