From cada21fc25669ee8a3c7c6cdb7d029b826a77872 Mon Sep 17 00:00:00 2001 From: franzap <_@franzap.com> Date: Tue, 18 Jun 2024 12:43:47 -0300 Subject: [PATCH] Move streaming hash computation to isolate (both memory and CPU efficient) Do not auto-dispose installationProgressProvider --- lib/models/app.dart | 48 +++++++++++++++++++++-------- lib/models/settings.dart | 1 - lib/navigation/mobile_scaffold.dart | 1 - pubspec.lock | 2 +- pubspec.yaml | 1 + 5 files changed, 37 insertions(+), 16 deletions(-) diff --git a/lib/models/app.dart b/lib/models/app.dart index 351a893..6329b7d 100644 --- a/lib/models/app.dart +++ b/lib/models/app.dart @@ -1,8 +1,10 @@ import 'dart:async'; import 'dart:io'; +import 'dart:isolate'; import 'dart:typed_data'; import 'package:android_package_manager/android_package_manager.dart'; +import 'package:async/async.dart'; import 'package:collection/collection.dart'; import 'package:convert/convert.dart'; import 'package:crypto/crypto.dart'; @@ -114,12 +116,10 @@ class App extends BaseApp with DataModelMixin { final dir = await getApplicationSupportDirectory(); final file = File(path.join(dir.path, hash)); - installOnDevice({String? downloadedFileHash}) async { + installOnDevice() async { notifier.state = DeviceInstallProgress(); - // Only check hash when passed to this function - // (when installing from local file, it has already been checked) - if (downloadedFileHash != null && downloadedFileHash != hash) { + if (await _isHashMismatch(file.path, hash)) { var e = 'Hash mismatch, '; if (size == await file.length()) { e += 'likely a malicious file.'; @@ -162,13 +162,9 @@ class App extends BaseApp with DataModelMixin { } final totalBytes = response.contentLength ?? size; - final digestOutputSink = AccumulatorSink(); - final digestInputSink = sha256.startChunkedConversion(digestOutputSink); - sub = response.stream.listen((chunk) { final data = Uint8List.fromList(chunk); sink.add(data); - digestInputSink.add(data); downloadedBytes += data.length; notifier.state = DownloadingInstallProgress(downloadedBytes / totalBytes); @@ -177,10 +173,8 @@ class App extends BaseApp with DataModelMixin { }, onDone: () async { await sub?.cancel(); await sink.close(); - digestInputSink.close(); client.close(); - final digest = digestOutputSink.events.single.toString(); - await installOnDevice(downloadedFileHash: digest); + await installOnDevice(); }); } } @@ -342,6 +336,33 @@ mixin AppAdapter on Adapter { } } +Future _isHashMismatch(String path, String hash) async { + return await Isolate.run(() async { + final file = File(path); + final fileStream = file.openRead(); + final reader = ChunkedStreamReader(fileStream); + final digestOutputSink = AccumulatorSink(); + final digestInputSink = sha256.startChunkedConversion(digestOutputSink); + String? digest; + + try { + while (true) { + final chunk = await reader.readChunk(1024 * 1024); + if (chunk.isEmpty) { + break; // EOF + } + digestInputSink.add(chunk); + } + } finally { + digestInputSink.close(); + digest = digestOutputSink.events.single.toString(); + digestOutputSink.close(); + reader.cancel(); + } + return digest != hash; + }); +} + // install support enum AppInstallStatus { @@ -376,5 +397,6 @@ class ErrorInstallProgress extends AppInstallProgress { final installedAppProvider = StateProvider>((_) => {}); -final installationProgressProvider = StateProvider.autoDispose - .family((_, arg) => IdleInstallProgress()); +final installationProgressProvider = + StateProvider.family( + (_, arg) => IdleInstallProgress()); diff --git a/lib/models/settings.dart b/lib/models/settings.dart index 56da4ea..5511556 100644 --- a/lib/models/settings.dart +++ b/lib/models/settings.dart @@ -27,6 +27,5 @@ mixin SettingsAdapter on Adapter { if (!existsId('_')) { Settings().saveLocal(); } - print('initing and db version is ${findOneLocalById('_')!.dbVersion} '); } } diff --git a/lib/navigation/mobile_scaffold.dart b/lib/navigation/mobile_scaffold.dart index f809821..4343196 100644 --- a/lib/navigation/mobile_scaffold.dart +++ b/lib/navigation/mobile_scaffold.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:zapstore/main.data.dart'; -import 'package:zapstore/models/app.dart'; import 'package:zapstore/utils/theme.dart'; import 'package:zapstore/widgets/drawer_container.dart'; diff --git a/pubspec.lock b/pubspec.lock index c678e71..cff378e 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -42,7 +42,7 @@ packages: source: hosted version: "2.5.0" async: - dependency: transitive + dependency: "direct main" description: name: async sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" diff --git a/pubspec.yaml b/pubspec.yaml index a35c7bf..1e376e1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -44,6 +44,7 @@ dependencies: system_info2: ^4.0.0 badges: ^3.1.2 convert: ^3.1.1 + async: ^2.11.0 dependency_overrides: purplebase: