migration(db): hive to sqlite migration#1059
Conversation
📝 WalkthroughWalkthroughReplaces Hive with SQLite/SQLCipher across the app: multiple store classes now extend Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
lib/main.dart (1)
57-63:⚠️ Potential issue | 🟠 MajorConfirm migration placement; address unprotected initialization operations.
The migration call placement is correct—properly sequenced after
Stores.init()(a dependency of the migrator) and before_doDbMigrate().However, the migrator's exception handling is incomplete. While individual box migrations (
_migrateOne()) are wrapped in try-catch, the following operations lack protection and could crash app startup:
Hive.initFlutter()(line 34)Hive.registerAdapters()(line 35)- Store flush operations (lines 82-88)
_archiveLegacyFiles()(line 100)If any of these throw an unhandled exception, app startup fails despite the migrated flag never being set (allowing retry on next launch). Consider wrapping the entire migration logic or at minimum these initialization and persistence steps in a try-catch block to ensure a corrupted legacy file doesn't block app launches.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/main.dart` around lines 57 - 63, Wrap the entire HiveToSqliteMigrator.migrateIfNeeded() implementation (or at minimum the calls to Hive.initFlutter(), Hive.registerAdapters(), the store flush loops in _migrateOne()/Store.flush(), and _archiveLegacyFiles()) in a top-level try-catch so any exception during initialization or persistence is caught; inside the catch log the error and abort the migration gracefully without setting the migrated flag, and ensure successful completion still sets the migrated flag and performs cleanup—update migrateIfNeeded(), _migrateOne(), and any code that calls _archiveLegacyFiles() to use this guarded flow so startup cannot crash from unprotected init/flush/archive steps.
🧹 Nitpick comments (7)
lib/view/page/server/connection_stats.dart (1)
276-280: Extract size-string formatting into one helper.The same bytes/KB/MB formatting is duplicated for both old and new sizes.
♻️ Proposed refactor
void _showCompactDialog() { final path = '${Paths.doc}${Pfs.seperator}connection_stats.db'; final file = File(path); final oldSize = file.existsSync() ? file.lengthSync() : 0; - final sizeStr = oldSize < 1000 - ? '$oldSize B' - : oldSize < 1000 * 1000 - ? '${(oldSize / 1000).toStringAsFixed(1)} KB' - : '${(oldSize / (1000 * 1000)).toStringAsFixed(1)} MB'; + String formatSize(int bytes) => bytes < 1000 + ? '$bytes B' + : bytes < 1000 * 1000 + ? '${(bytes / 1000).toStringAsFixed(1)} KB' + : '${(bytes / (1000 * 1000)).toStringAsFixed(1)} MB'; + final sizeStr = formatSize(oldSize); @@ await Stores.connectionStats.compact(); final newSize = file.existsSync() ? file.lengthSync() : 0; - final newSizeStr = newSize < 1000 - ? '$newSize B' - : newSize < 1000 * 1000 - ? '${(newSize / 1000).toStringAsFixed(1)} KB' - : '${(newSize / (1000 * 1000)).toStringAsFixed(1)} MB'; + final newSizeStr = formatSize(newSize);Also applies to: 294-298
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/view/page/server/connection_stats.dart` around lines 276 - 280, Extract the duplicated bytes→string logic into a single helper (e.g. String formatSize(int bytes)) in connection_stats.dart and replace both sizeStr assignments (the one using oldSize and the one using newSize) to call that helper; implement formatSize to return "X B" for bytes<1000, "Y.Z KB" for bytes<1000*1000, else "Y.Z MB" using toStringAsFixed(1), and update any references to sizeStr/new size formatting accordingly (keep the same thresholds and formatting behavior).lib/data/store/snippet.dart (1)
17-34: Unify deserialization logic betweenfetchandfetchOne.
fetch()and_fromObjcurrently duplicate parser logic; this can drift and cause inconsistent behavior.♻️ Proposed refactor
for (final key in keys()) { final s = get<Snippet>( key, - fromObj: (val) { - if (val is Snippet) return val; - if (val is Map<dynamic, dynamic>) { - final map = val.toStrDynMap; - if (map == null) return null; - try { - final snippet = Snippet.fromJson(map as Map<String, dynamic>); - put(snippet); - return snippet; - } catch (e) { - dprint('Parsing Snippet from JSON', e); - } - } - return null; - }, + fromObj: (val) { + final parsed = _fromObj(val); + if (parsed != null && val is Map<dynamic, dynamic>) { + put(parsed); // normalize legacy map payload + } + return parsed; + }, );Also applies to: 60-72
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/data/store/snippet.dart` around lines 17 - 34, The fromObj closure passed to get<Snippet> duplicates parsing logic used by fetch() and fetchOne(); extract the shared deserialization into a single helper (e.g., a private function like _parseSnippetFromObj(dynamic val) or a static Snippet.tryFrom(dynamic obj)) and call that helper from the fromObj closure and from fetch/fetchOne so behavior stays consistent; ensure the helper handles Map<dynamic,dynamic> -> toStrDynMap conversion, catches exceptions and logs via dprint, returns null on failure, and still calls put(snippet) when a Snippet is successfully constructed.lib/data/store/connection_stats.dart (1)
66-80: Reduce work in_buildServerStatsby avoiding extra list sorts.
sortedStatsis already newest-first, so latest success/failure timestamps can be found in one pass.⚡ Proposed refactor
- final successTimes = sortedStats.where((s) => s.result.isSuccess).map((s) => s.timestamp).toList(); - final failureTimes = sortedStats.where((s) => !s.result.isSuccess).map((s) => s.timestamp).toList(); - DateTime? lastSuccessTime; DateTime? lastFailureTime; - - if (successTimes.isNotEmpty) { - successTimes.sort((a, b) => b.compareTo(a)); - lastSuccessTime = successTimes.first; - } - - if (failureTimes.isNotEmpty) { - failureTimes.sort((a, b) => b.compareTo(a)); - lastFailureTime = failureTimes.first; + for (final stat in sortedStats) { + if (lastSuccessTime == null && stat.result.isSuccess) { + lastSuccessTime = stat.timestamp; + } + if (lastFailureTime == null && !stat.result.isSuccess) { + lastFailureTime = stat.timestamp; + } + if (lastSuccessTime != null && lastFailureTime != null) break; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/data/store/connection_stats.dart` around lines 66 - 80, sortedStats is already ordered newest-first, so avoid creating and sorting successTimes/failureTimes; in _buildServerStats iterate sortedStats once and set lastSuccessTime to the first s.timestamp where s.result.isSuccess and lastFailureTime to the first s.timestamp where !s.result.isSuccess (stop early once both are found), then remove the successTimes/failureTimes lists and the extra sorts to reduce work.lib/data/store/private_key.dart (1)
17-34: Consider extracting shared deserialization logic.The inline
fromObjinfetch()(lines 19-32) and the_fromObjhelper (lines 51-63) have nearly identical deserialization logic. The only difference is thatfetch()callsput(pki)to re-persist migrated data.You could extract the parsing logic and have
fetch()call the shared parser then conditionally persist:♻️ Optional refactor to reduce duplication
List<PrivateKeyInfo> fetch() { final ps = <PrivateKeyInfo>[]; for (final key in keys()) { final s = get<PrivateKeyInfo>( key, fromObj: (val) { - if (val is PrivateKeyInfo) return val; - if (val is Map<dynamic, dynamic>) { - final map = val.toStrDynMap; - if (map == null) return null; - try { - final pki = PrivateKeyInfo.fromJson(map as Map<String, dynamic>); - put(pki); - return pki; - } catch (e) { - dprint('Parsing PrivateKeyInfo from JSON', e); - } - } - return null; + final pki = _fromObj(val); + if (pki != null && val is Map) { + put(pki); // Re-persist migrated data + } + return pki; }, );Also applies to: 51-63
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/data/store/private_key.dart` around lines 17 - 34, The deserialization logic in the inline fromObj inside fetch() and the _fromObj helper is duplicated; extract the shared parsing steps into a single private parser function (e.g., _parsePrivateKeyInfo(Object? val) that performs the Map check, uses PrivateKeyInfo.fromJson and returns a PrivateKeyInfo? ) then have both fetch()'s fromObj and _fromObj call that parser; in fetch(), after calling the parser, call put(pki) when the parser returns a non-null PrivateKeyInfo to preserve the existing re-persist/migration behavior.lib/data/migration/hive_to_sqlite_migrator.dart (1)
281-301: Robust normalization with graceful fallback.The
_normalizeRawfunction handles primitives, enums, lists, maps, and objects withtoJson()methods. The try-catch around dynamictoJsoncalls ensures unknown object types don't crash the migration—they're simply skipped (returnnull).One note: objects without a
toJsonmethod will be silently dropped. Consider logging a warning for debugging purposes if unexpected types are encountered.♻️ Optional: Add debug logging for unknown types
try { final dynamic obj = raw; final jsonObj = obj.toJson(); return _normalizeRaw(jsonObj); } catch (_) { + Loggers.app.fine('Skipping non-serializable object: ${raw.runtimeType}'); return null; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/data/migration/hive_to_sqlite_migrator.dart` around lines 281 - 301, The _normalizeRaw function currently swallows unknown object types by returning null inside the catch block; update the catch to log a warning with the runtime type and/or a brief description before returning null so unexpected types are visible during migration; specifically modify _normalizeRaw's try-catch around the dynamic obj.toJson() call to use your logger (e.g., processLogger, migrationLogger, or existing logger in scope) to emit a warning including raw.runtimeType (and the caught error message if available), then return null as before.lib/data/store/server.dart (1)
20-37: Similar duplication pattern as PrivateKeyStore.The inline
fromObjinfetch()(lines 22-36) duplicates the logic in_fromObj(lines 132-144), with the same difference thatfetch()callsput(spi)to re-persist. Consider the same refactoring approach suggested forPrivateKeyStoreif you want to reduce duplication.Also applies to: 132-144
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/data/store/server.dart` around lines 20 - 37, The inline fromObj lambda inside fetch() duplicates the parsing logic in _fromObj; refactor fetch() to call the existing _fromObj(Map) or a shared helper that encapsulates Spi.fromJson and dprint, and then have fetch() call put(spi) only when the parsed spi is non-null to re-persist; specifically, replace the anonymous fromObj in the get<Spi>(...) call with a reference to the shared _fromObj (or extract a private helper used by both fetch() and _fromObj) and ensure fetch() still invokes put(spi) after obtaining a non-null Spi, keeping Spi.fromJson, put, and dprint usage unchanged.lib/data/model/app/bak/backup.dart (1)
115-119: Consider making_withTsstatic.The
_withTshelper doesn't access any instance state (this), so it could be astaticmethod for clarity.♻️ Optional refactor
- Map<String, Object?> _withTs(String tsKey, Map<String, Object?> data, int ts) { + static Map<String, Object?> _withTs(String tsKey, Map<String, Object?> data, int ts) { final map = <String, Object?>{...data}; map[tsKey] = ts; return map; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/data/model/app/bak/backup.dart` around lines 115 - 119, The helper method _withTs does not use instance state; make it static by adding the static keyword to its declaration (change "Map<String, Object?> _withTs(...)" to "static Map<String, Object?> _withTs(...)") in the class that contains it; update any internal call sites if they're using instance access to call it (prefer ClassName._withTs(...) or, if called from within the same class, call _withTs(...) which will resolve to the static method).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@lib/data/store/connection_stats.dart`:
- Line 14: Verify ShortId.generate in fl_lib to confirm its character set and
whether it can emit '_' and, if it can, fix the fragile concatenation/parsing in
connection_stats.dart: either (A) change the delimiter used when building the
key (the line creating key = '${stat.serverId}_${ShortId.generate()}') to a
character guaranteed not emitted by ShortId (or a binary/UUID separator), (B)
sanitize/encode the ShortId output to remove/escape '_' (update ShortId.generate
or wrap its output), or (C) avoid string concatenation entirely and store
serverId and shortId as separate fields (or use a fixed-length suffix) and
update any parsing that uses lastIndexOf('_') to the new scheme; update all
occurrences referenced (the key creation and any parsing that relies on
lastIndexOf('_') in the 198-207 and 209-221 ranges) so parsing is unambiguous.
In `@lib/data/store/setting.dart`:
- Around line 11-12: The SettingStore currently extends SqliteStore (class
SettingStore and constructor SettingStore._()) which violates the repo policy
requiring Hive-backed stores under lib/data/store; either change SettingStore to
use the existing Hive store base (e.g., extend the HiveStore/appropriate
Hive-backed base and implement Hive box access) so persistence uses Hive, or, if
you intend to switch to SQLite, update the ADR/policy in this PR to explicitly
allow SqliteStore for settings and adjust any store-layer docs; ensure the
constructor and any storage-specific methods are updated to reference the Hive
API (or document the ADR change) to resolve the policy conflict.
In `@lib/view/page/private_key/list.dart`:
- Around line 71-90: The async read idRsaFile.readAsString() can allow the
widget to unmount before you call context.showRoundDialog /
PrivateKeyEditPage.route.go; after awaiting the file read, check that the
BuildContext is still mounted (e.g., if (!context.mounted) return;) before using
context, and only then construct PrivateKeyInfo and call
context.showRoundDialog; ensure you still await the file into a local variable
first (store key = await idRsaFile.readAsString()), then guard with
context.mounted, then create PrivateKeyInfo(id: 'system', key: key) and proceed
to show the dialog and navigate.
In `@lib/view/page/setting/entry.dart`:
- Around line 145-157: Replace the hard-coded section titles with localized
strings from libL10n: instead of literal 'App', 'AI', 'SSH', and 'SFTP' pass the
corresponding libL10n properties to CenterGreyTitle where the children list uses
CenterGreyTitle and the builders (_buildApp, _buildAskAiConfig, _buildSSH,
_buildSFTP) are placed; follow the existing pattern used for libL10n.server to
add/consume libL10n.app, libL10n.ai (or appropriate key names), libL10n.ssh, and
libL10n.sftp so titles come from localization resources rather than hard-coded
text.
- Around line 128-139: The four TextEditingController instances
(_sshOpacityCtrl, _sshBlurCtrl, _textScalerCtrl, _serverLogoCtrl) created on the
state should be disposed to avoid leaks: add an override of dispose() in
_AppSettingsPageState that calls dispose() on each controller and then calls
super.dispose(); locate the state class _AppSettingsPageState and ensure the
dispose method disposes _sshOpacityCtrl, _sshBlurCtrl, _textScalerCtrl, and
_serverLogoCtrl in that order (or any order) before calling super.dispose().
---
Outside diff comments:
In `@lib/main.dart`:
- Around line 57-63: Wrap the entire HiveToSqliteMigrator.migrateIfNeeded()
implementation (or at minimum the calls to Hive.initFlutter(),
Hive.registerAdapters(), the store flush loops in _migrateOne()/Store.flush(),
and _archiveLegacyFiles()) in a top-level try-catch so any exception during
initialization or persistence is caught; inside the catch log the error and
abort the migration gracefully without setting the migrated flag, and ensure
successful completion still sets the migrated flag and performs cleanup—update
migrateIfNeeded(), _migrateOne(), and any code that calls _archiveLegacyFiles()
to use this guarded flow so startup cannot crash from unprotected
init/flush/archive steps.
---
Nitpick comments:
In `@lib/data/migration/hive_to_sqlite_migrator.dart`:
- Around line 281-301: The _normalizeRaw function currently swallows unknown
object types by returning null inside the catch block; update the catch to log a
warning with the runtime type and/or a brief description before returning null
so unexpected types are visible during migration; specifically modify
_normalizeRaw's try-catch around the dynamic obj.toJson() call to use your
logger (e.g., processLogger, migrationLogger, or existing logger in scope) to
emit a warning including raw.runtimeType (and the caught error message if
available), then return null as before.
In `@lib/data/model/app/bak/backup.dart`:
- Around line 115-119: The helper method _withTs does not use instance state;
make it static by adding the static keyword to its declaration (change
"Map<String, Object?> _withTs(...)" to "static Map<String, Object?>
_withTs(...)") in the class that contains it; update any internal call sites if
they're using instance access to call it (prefer ClassName._withTs(...) or, if
called from within the same class, call _withTs(...) which will resolve to the
static method).
In `@lib/data/store/connection_stats.dart`:
- Around line 66-80: sortedStats is already ordered newest-first, so avoid
creating and sorting successTimes/failureTimes; in _buildServerStats iterate
sortedStats once and set lastSuccessTime to the first s.timestamp where
s.result.isSuccess and lastFailureTime to the first s.timestamp where
!s.result.isSuccess (stop early once both are found), then remove the
successTimes/failureTimes lists and the extra sorts to reduce work.
In `@lib/data/store/private_key.dart`:
- Around line 17-34: The deserialization logic in the inline fromObj inside
fetch() and the _fromObj helper is duplicated; extract the shared parsing steps
into a single private parser function (e.g., _parsePrivateKeyInfo(Object? val)
that performs the Map check, uses PrivateKeyInfo.fromJson and returns a
PrivateKeyInfo? ) then have both fetch()'s fromObj and _fromObj call that
parser; in fetch(), after calling the parser, call put(pki) when the parser
returns a non-null PrivateKeyInfo to preserve the existing re-persist/migration
behavior.
In `@lib/data/store/server.dart`:
- Around line 20-37: The inline fromObj lambda inside fetch() duplicates the
parsing logic in _fromObj; refactor fetch() to call the existing _fromObj(Map)
or a shared helper that encapsulates Spi.fromJson and dprint, and then have
fetch() call put(spi) only when the parsed spi is non-null to re-persist;
specifically, replace the anonymous fromObj in the get<Spi>(...) call with a
reference to the shared _fromObj (or extract a private helper used by both
fetch() and _fromObj) and ensure fetch() still invokes put(spi) after obtaining
a non-null Spi, keeping Spi.fromJson, put, and dprint usage unchanged.
In `@lib/data/store/snippet.dart`:
- Around line 17-34: The fromObj closure passed to get<Snippet> duplicates
parsing logic used by fetch() and fetchOne(); extract the shared deserialization
into a single helper (e.g., a private function like _parseSnippetFromObj(dynamic
val) or a static Snippet.tryFrom(dynamic obj)) and call that helper from the
fromObj closure and from fetch/fetchOne so behavior stays consistent; ensure the
helper handles Map<dynamic,dynamic> -> toStrDynMap conversion, catches
exceptions and logs via dprint, returns null on failure, and still calls
put(snippet) when a Snippet is successfully constructed.
In `@lib/view/page/server/connection_stats.dart`:
- Around line 276-280: Extract the duplicated bytes→string logic into a single
helper (e.g. String formatSize(int bytes)) in connection_stats.dart and replace
both sizeStr assignments (the one using oldSize and the one using newSize) to
call that helper; implement formatSize to return "X B" for bytes<1000, "Y.Z KB"
for bytes<1000*1000, else "Y.Z MB" using toStringAsFixed(1), and update any
references to sizeStr/new size formatting accordingly (keep the same thresholds
and formatting behavior).
ℹ️ Review info
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
pubspec.lockis excluded by!**/*.lock
📒 Files selected for processing (26)
lib/core/utils/server.dartlib/data/migration/hive_to_sqlite_migrator.dartlib/data/model/app/bak/backup.dartlib/data/res/store.dartlib/data/store/connection_stats.dartlib/data/store/container.dartlib/data/store/history.dartlib/data/store/private_key.dartlib/data/store/server.dartlib/data/store/setting.dartlib/data/store/snippet.dartlib/main.dartlib/view/page/private_key/list.dartlib/view/page/server/connection_stats.dartlib/view/page/server/edit/actions.dartlib/view/page/setting/entries/ai.dartlib/view/page/setting/entries/app.dartlib/view/page/setting/entries/editor.dartlib/view/page/setting/entry.dartlinux/flutter/generated_plugin_registrant.cclinux/flutter/generated_plugins.cmakemacos/Flutter/GeneratedPluginRegistrant.swiftpackages/fl_libpubspec.yamlwindows/flutter/generated_plugin_registrant.ccwindows/flutter/generated_plugins.cmake
| class SettingStore extends SqliteStore { | ||
| SettingStore._() : super('setting'); |
There was a problem hiding this comment.
Store backend change conflicts with current repository policy.
This moves the settings store to SQLite, but the current store-layer guideline still requires Hive persistence. Please either align the implementation with policy or update the policy/ADR in this PR so they are not contradictory.
As per coding guidelines lib/data/store/**/*.dart: Persist state using Hive stores placed under lib/data/store/.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@lib/data/store/setting.dart` around lines 11 - 12, The SettingStore currently
extends SqliteStore (class SettingStore and constructor SettingStore._()) which
violates the repo policy requiring Hive-backed stores under lib/data/store;
either change SettingStore to use the existing Hive store base (e.g., extend the
HiveStore/appropriate Hive-backed base and implement Hive box access) so
persistence uses Hive, or, if you intend to switch to SQLite, update the
ADR/policy in this PR to explicitly allow SqliteStore for settings and adjust
any store-layer docs; ensure the constructor and any storage-specific methods
are updated to reference the Hive API (or document the ADR change) to resolve
the policy conflict.
| [ | ||
| const CenterGreyTitle('App'), | ||
| _buildApp(), | ||
| const CenterGreyTitle('AI'), | ||
| _buildAskAiConfig(), | ||
| ], | ||
| [CenterGreyTitle(libL10n.server), _buildServer()], | ||
| [const CenterGreyTitle('SSH'), _buildSSH(), const CenterGreyTitle('SFTP'), _buildSFTP()], | ||
| [CenterGreyTitle(libL10n.container), _buildContainer(), CenterGreyTitle(libL10n.editor), _buildEditor()], | ||
| [ | ||
| const CenterGreyTitle('SSH'), | ||
| _buildSSH(), | ||
| const CenterGreyTitle('SFTP'), | ||
| _buildSFTP(), | ||
| ], |
There was a problem hiding this comment.
Localize section titles instead of hard-coded strings.
'App', 'AI', 'SSH', and 'SFTP' should come from localization resources for consistency and translation coverage.
As per coding guidelines, "Prefer using libL10n strings before adding new ones to project l10n".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@lib/view/page/setting/entry.dart` around lines 145 - 157, Replace the
hard-coded section titles with localized strings from libL10n: instead of
literal 'App', 'AI', 'SSH', and 'SFTP' pass the corresponding libL10n properties
to CenterGreyTitle where the children list uses CenterGreyTitle and the builders
(_buildApp, _buildAskAiConfig, _buildSSH, _buildSFTP) are placed; follow the
existing pattern used for libL10n.server to add/consume libL10n.app, libL10n.ai
(or appropriate key names), libL10n.ssh, and libL10n.sftp so titles come from
localization resources rather than hard-coded text.
Deploying serverbox with
|
| Latest commit: |
f36874b
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://f91c3278.serverbox.pages.dev |
| Branch Preview URL: | https://fix-sqlite-migration-safety.serverbox.pages.dev |
There was a problem hiding this comment.
♻️ Duplicate comments (1)
lib/view/page/setting/entry.dart (1)
162-165:⚠️ Potential issue | 🟡 MinorLocalize SSH/SFTP section titles too.
'SSH'and'SFTP'are still hard-coded; please switch them to localization keys for full translation coverage consistency.As per coding guidelines, "Prefer using libL10n strings before adding new ones to project l10n".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/view/page/setting/entry.dart` around lines 162 - 165, The section titles are hard-coded; replace the literal 'SSH' and 'SFTP' passed to CenterGreyTitle with the corresponding localized strings from your libL10n (e.g., use AppLocalizations/GeneratedLocalizations getters) where these widgets are built alongside _buildSSH() and _buildSFTP(); if the ssh/sftp keys are not yet defined in the ARB/localization files, add them to libL10n and regenerate localization so CenterGreyTitle receives the localized string via the localization accessor instead of a hard-coded literal.
🧹 Nitpick comments (4)
lib/data/store/snippet.dart (1)
44-56: Redundant cast aftertoStrDynMap.The
toStrDynMapextension already returnsMap<String, dynamic>?, so the cast at line 50 is unnecessary.🔧 Suggested simplification
static Snippet? _fromObj(Object? val) { if (val is Snippet) return val; if (val is Map<dynamic, dynamic>) { final map = val.toStrDynMap; if (map == null) return null; try { - return Snippet.fromJson(map as Map<String, dynamic>); + return Snippet.fromJson(map); } catch (e) { dprint('Parsing Snippet from JSON', e); } } return null; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/data/store/snippet.dart` around lines 44 - 56, In _fromObj, remove the redundant cast: when val is Map<dynamic,dynamic> use the toStrDynMap extension (which already returns Map<String,dynamic>?) directly (assign to a variable like map) and pass that map into Snippet.fromJson without the "as Map<String, dynamic>" cast; keep the existing null check and catch block (dprint('Parsing Snippet from JSON', e)) unchanged so behavior stays the same.lib/data/migration/hive_to_sqlite_migrator.dart (1)
131-133: Clarify the null check for readability.The condition
normalized is! Objectis functionally equivalent tonormalized == nullin Dart (since all non-null values are Objects), but it's not immediately obvious. Consider using explicit null check for clarity.🔧 Suggested simplification
final normalized = normalize(box.get(rawKey)); - if (normalized is! Object) continue; + if (normalized == null) continue; target.set(rawKey, normalized);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/data/migration/hive_to_sqlite_migrator.dart` around lines 131 - 133, The null-check is unclear: replace the type-test `normalized is! Object` with an explicit null check to improve readability; in the block around normalize(box.get(rawKey)), change the condition to `if (normalized == null) continue;` and leave the subsequent `target.set(rawKey, normalized)` as-is so only non-null normalized values are written (refer to the normalize function and variables normalized, box.get, and target.set to locate the change).lib/data/store/server.dart (1)
116-128: Redundant cast aftertoStrDynMap.Same issue as in
SnippetStore._fromObj: the cast at line 122 is unnecessary sincetoStrDynMapalready returnsMap<String, dynamic>?.🔧 Suggested simplification
static Spi? _fromObj(Object? val) { if (val is Spi) return val; if (val is Map<dynamic, dynamic>) { final map = val.toStrDynMap; if (map == null) return null; try { - return Spi.fromJson(map as Map<String, dynamic>); + return Spi.fromJson(map); } catch (e) { dprint('Parsing Spi from JSON', e); } } return null; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/data/store/server.dart` around lines 116 - 128, The cast is redundant: in Spi._fromObj remove the unnecessary "as Map<String, dynamic>" when passing map to Spi.fromJson since toStrDynMap already returns Map<String, dynamic>?; update the _fromObj function to use the map variable directly (e.g., Spi.fromJson(map)) and keep the existing try/catch and dprint handling unchanged.lib/data/store/connection_stats.dart (1)
191-207: Redundant cast aftertoStrDynMap.Same pattern as other stores: the cast at line 200 is unnecessary since
toStrDynMapalready returnsMap<String, dynamic>?.🔧 Suggested simplification
ConnectionStat? _readStat(String key) { return get<ConnectionStat>( key, fromObj: (val) { if (val is ConnectionStat) return val; if (val is Map<dynamic, dynamic>) { final map = val.toStrDynMap; if (map == null) return null; try { - return ConnectionStat.fromJson(map as Map<String, dynamic>); + return ConnectionStat.fromJson(map); } catch (e) { dprint('Parsing ConnectionStat from JSON', e); } } return null; }, ); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/data/store/connection_stats.dart` around lines 191 - 207, In _readStat, remove the unnecessary cast of map to Map<String, dynamic> when calling ConnectionStat.fromJson: toStrDynMap already returns Map<String, dynamic>?, so use the map variable directly in ConnectionStat.fromJson(map) and adjust null checks accordingly (keep the try/catch and dprint behavior intact); this change affects the anonymous fromObj closure inside get<ConnectionStat> and ensures no redundant casting before calling ConnectionStat.fromJson.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@lib/view/page/setting/entry.dart`:
- Around line 162-165: The section titles are hard-coded; replace the literal
'SSH' and 'SFTP' passed to CenterGreyTitle with the corresponding localized
strings from your libL10n (e.g., use AppLocalizations/GeneratedLocalizations
getters) where these widgets are built alongside _buildSSH() and _buildSFTP();
if the ssh/sftp keys are not yet defined in the ARB/localization files, add them
to libL10n and regenerate localization so CenterGreyTitle receives the localized
string via the localization accessor instead of a hard-coded literal.
---
Nitpick comments:
In `@lib/data/migration/hive_to_sqlite_migrator.dart`:
- Around line 131-133: The null-check is unclear: replace the type-test
`normalized is! Object` with an explicit null check to improve readability; in
the block around normalize(box.get(rawKey)), change the condition to `if
(normalized == null) continue;` and leave the subsequent `target.set(rawKey,
normalized)` as-is so only non-null normalized values are written (refer to the
normalize function and variables normalized, box.get, and target.set to locate
the change).
In `@lib/data/store/connection_stats.dart`:
- Around line 191-207: In _readStat, remove the unnecessary cast of map to
Map<String, dynamic> when calling ConnectionStat.fromJson: toStrDynMap already
returns Map<String, dynamic>?, so use the map variable directly in
ConnectionStat.fromJson(map) and adjust null checks accordingly (keep the
try/catch and dprint behavior intact); this change affects the anonymous fromObj
closure inside get<ConnectionStat> and ensures no redundant casting before
calling ConnectionStat.fromJson.
In `@lib/data/store/server.dart`:
- Around line 116-128: The cast is redundant: in Spi._fromObj remove the
unnecessary "as Map<String, dynamic>" when passing map to Spi.fromJson since
toStrDynMap already returns Map<String, dynamic>?; update the _fromObj function
to use the map variable directly (e.g., Spi.fromJson(map)) and keep the existing
try/catch and dprint handling unchanged.
In `@lib/data/store/snippet.dart`:
- Around line 44-56: In _fromObj, remove the redundant cast: when val is
Map<dynamic,dynamic> use the toStrDynMap extension (which already returns
Map<String,dynamic>?) directly (assign to a variable like map) and pass that map
into Snippet.fromJson without the "as Map<String, dynamic>" cast; keep the
existing null check and catch block (dprint('Parsing Snippet from JSON', e))
unchanged so behavior stays the same.
ℹ️ Review info
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (11)
docs/src/content/docs/development/structure.mddocs/src/content/docs/zh/development/structure.mdlib/data/migration/hive_to_sqlite_migrator.dartlib/data/model/app/bak/backup.dartlib/data/store/connection_stats.dartlib/data/store/private_key.dartlib/data/store/server.dartlib/data/store/snippet.dartlib/view/page/private_key/list.dartlib/view/page/server/connection_stats.dartlib/view/page/setting/entry.dart
🚧 Files skipped from review as they are similar to previous changes (4)
- lib/data/model/app/bak/backup.dart
- lib/data/store/private_key.dart
- lib/view/page/private_key/list.dart
- lib/view/page/server/connection_stats.dart
|
|
||
| void _showCompactDialog() { | ||
| final path = '${Paths.doc}${Pfs.seperator}connection_stats_enc.hive'; | ||
| final path = '${Paths.doc}${Pfs.seperator}connection_stats.db'; |
There was a problem hiding this comment.
🟡 Compact dialog uses hardcoded .db file path that may not match SqliteStore's actual database file location
The compact dialog constructs a file path as connection_stats.db under Paths.doc, but the actual SQLite/SQLCipher database file created by SqliteStore may use a different naming convention, extension, or subdirectory. This would cause the dialog to always show 0 B for the database size (both before and after compaction), since File(path).existsSync() would return false.
Detailed Explanation
The old code referenced connection_stats_enc.hive, which matched the Hive naming convention. The new code changed it to connection_stats.db (lib/view/page/server/connection_stats.dart:273), but the actual file path depends entirely on how SqliteStore (from fl_lib) creates and names its database file.
For example, SqliteStore (likely built on drift + SQLCipher) might store files as:
connection_stats.sqliteconnection_stats.enc.db- Inside a subdirectory like
databases/connection_stats.db
The vacuum operation at lib/view/page/server/connection_stats.dart:289 works correctly regardless (it calls Stores.connectionStats.compact() which internally knows the right file), but the UI display of before/after file sizes will be incorrect.
Impact: The compact dialog will show incorrect file sizes (likely 0 B), providing misleading information to the user.
Was this helpful? React with 👍 or 👎 to provide feedback.
lib/data/store/server.dart
Outdated
| for (final id in keys()) { | ||
| final s = get<Spi>( | ||
| id, | ||
| fromObj: (val) { | ||
| if (val is Spi) return val; | ||
| if (val is Map<dynamic, dynamic>) { | ||
| final map = val.toStrDynMap; | ||
| if (map == null) return null; | ||
| try { | ||
| final spi = Spi.fromJson(map as Map<String, dynamic>); | ||
| put(spi); | ||
| return spi; | ||
| } catch (e) { | ||
| dprint('Parsing Spi from JSON', e); | ||
| } | ||
| } | ||
| return null; | ||
| }, | ||
| ); | ||
| final s = get<Spi>(id, fromObj: _fromObj); | ||
| if (s != null) { | ||
| put(s); | ||
| ss.add(s); | ||
| } | ||
| } | ||
| return ss; |
There was a problem hiding this comment.
🟡 ServerStore.fetch() creates unbounded duplicate entries for servers with empty IDs
In ServerStore.fetch(), every call unconditionally calls put(s) after deserializing each server. Because SQLite stores JSON (not typed Hive objects), _fromObj always goes through Spi.fromJson, which triggers Spi.parseId (server_private_info.dart:60-63). For any server with an empty id in the stored JSON, parseId generates a new random ShortId. Then put(s) writes this server under the new ID as key — while the original key (with empty id JSON) is never deleted.
Root cause and growth mechanics
In the old Hive code, fromObj checked if (val is Spi) return val; — the Hive adapter returned a typed Spi object directly, preserving the empty id without calling put. Only when the value was a raw Map did it call Spi.fromJson and put. With SQLite, stored values are always JSON Maps, so _fromObj always calls Spi.fromJson, which always triggers parseId.
The migrateIds() method (called every launch at main.dart:100) calls fetch(), which generates new IDs for legacy entries. But since those SPIs now have non-empty IDs, migrateId() returns null and the old key is never cleaned up:
String? migrateId() {
if (id.isNotEmpty) return null; // Always true after fromJson
...
}Each subsequent app launch adds +1 duplicate per affected server:
- Launch 1: old key + new key A
- Launch 2: old key + key A + new key B
- Launch N: old key + N extra entries
Impact: Unbounded store growth on every app launch. Affected users: those upgrading from very old versions (before server IDs were introduced) who never ran an intermediate version that migrated IDs in Hive.
Prompt for agents
In lib/data/store/server.dart, the fetch() method (lines 17-27) unconditionally calls put(s) for every deserialized Spi. This creates duplicate entries when Spi.parseId generates a new ShortId for servers with empty IDs stored in SQLite JSON.
Fix: Only call put(s) when the deserialized Spi's id matches the store key. If they differ (i.e., parseId generated a new id for an empty one), handle migration explicitly by deleting the old key before inserting the new one:
List<Spi> fetch() {
final List<Spi> ss = [];
for (final id in keys()) {
final s = get<Spi>(id, fromObj: _fromObj);
if (s != null) {
if (s.id != id) {
remove(id);
}
put(s);
ss.add(s);
}
}
return ss;
}
Note: Since keys() is iterated while the store is mutated (removing old keys), ensure keys() returns a snapshot (e.g., .toList()) before iterating. Apply the same pattern to SnippetStore.fetch() and PrivateKeyStore.fetch() for consistency, though those stores likely don't auto-generate IDs.
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
🧹 Nitpick comments (2)
lib/data/store/private_key.dart (2)
38-40: Consider removing the redundant_fromObjwrapper.
_fromObjjust delegates to_parsePrivateKeyInfowith no additional logic. You could use_parsePrivateKeyInfodirectly infetchOne.Proposed simplification
PrivateKeyInfo? fetchOne(String? id) { if (id == null) return null; - return get<PrivateKeyInfo>(id, fromObj: _fromObj); + return get<PrivateKeyInfo>(id, fromObj: _parsePrivateKeyInfo); } void delete(PrivateKeyInfo s) { remove(s.id); } - - static PrivateKeyInfo? _fromObj(Object? val) { - return _parsePrivateKeyInfo(val); - }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/data/store/private_key.dart` around lines 38 - 40, The helper _fromObj is redundant because it simply forwards to _parsePrivateKeyInfo; remove _fromObj and update call sites (notably fetchOne) to call _parsePrivateKeyInfo directly, ensuring any nullable handling and imports remain the same and running tests to confirm no other references to _fromObj exist.
19-22: Unconditionalput()may cause unnecessary writes.After the ID mismatch check and removal,
put(s)is called unconditionally on line 22. Whens.id == key, this re-writes existing data without changes. Iffetch()is called frequently or with many keys, this could impact performance.If this is intentional for data format normalization during migration, consider adding a brief comment to clarify.
Proposed optimization
final s = get<PrivateKeyInfo>(key, fromObj: _parsePrivateKeyInfo); if (s != null) { if (s.id != key) { remove(key); + put(s); // Re-insert with correct key } - put(s); ps.add(s); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/data/store/private_key.dart` around lines 19 - 22, The unconditional put(s) causes unnecessary rewrites — change the logic so put(s) is only invoked when needed (e.g., when s.id != key to perform the migration/rename) or when the record actually changed; specifically, modify the block using s, key, remove() and put() so that put(s) is conditional (for example: if (s.id != key) { remove(key); put(s); } ), or add a comment explaining that the unconditional write is intentional for format migration if you keep it.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@lib/data/store/private_key.dart`:
- Around line 38-40: The helper _fromObj is redundant because it simply forwards
to _parsePrivateKeyInfo; remove _fromObj and update call sites (notably
fetchOne) to call _parsePrivateKeyInfo directly, ensuring any nullable handling
and imports remain the same and running tests to confirm no other references to
_fromObj exist.
- Around line 19-22: The unconditional put(s) causes unnecessary rewrites —
change the logic so put(s) is only invoked when needed (e.g., when s.id != key
to perform the migration/rename) or when the record actually changed;
specifically, modify the block using s, key, remove() and put() so that put(s)
is conditional (for example: if (s.id != key) { remove(key); put(s); } ), or add
a comment explaining that the unconditional write is intentional for format
migration if you keep it.
ℹ️ Review info
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
lib/data/store/private_key.dartlib/data/store/server.dartlib/data/store/snippet.dart
There was a problem hiding this comment.
🧹 Nitpick comments (1)
lib/data/store/private_key.dart (1)
19-22: Potential silent data loss on ID collision during cleanup.If two entries are stored with different keys but have the same
s.id, the second processed entry will overwrite the first. This may be intentional for deduplication, but silent data loss could be surprising. Consider logging when an overwrite occurs or verifying uniqueness before putting.🔧 Optional: Add logging for visibility
if (s.id != key) { + dprint('Migrating key mismatch: stored at "$key" but id is "${s.id}"'); remove(key); put(s); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/data/store/private_key.dart` around lines 19 - 22, The current cleanup branch in private_key.dart (the if block that checks s.id != key and then calls remove(key) and put(s)) can silently overwrite an existing entry that shares s.id; before calling put(s) check whether an entry already exists for s.id (or for the key you intend to write) and if so either skip the overwrite or surface it via logging; specifically, locate the block referencing s.id, key, remove(...) and put(...), query the store for an existing entry with that id, and if one exists and the stored key differs, emit a warning (or merge/skip) instead of blindly remove(key)/put(s) so you avoid silent data loss.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@lib/data/store/private_key.dart`:
- Around line 19-22: The current cleanup branch in private_key.dart (the if
block that checks s.id != key and then calls remove(key) and put(s)) can
silently overwrite an existing entry that shares s.id; before calling put(s)
check whether an entry already exists for s.id (or for the key you intend to
write) and if so either skip the overwrite or surface it via logging;
specifically, locate the block referencing s.id, key, remove(...) and put(...),
query the store for an existing entry with that id, and if one exists and the
stored key differs, emit a warning (or merge/skip) instead of blindly
remove(key)/put(s) so you avoid silent data loss.
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
lib/data/migration/hive_to_sqlite_migrator.dart (1)
161-181: Consider whether strict encrypted-only enforcement is necessary for this migration scenario.At lines 161–181, the code attempts decrypted open first, then falls back to plain only if that fails. While encrypted should be preferred (more recent), the fallback is a reasonable recovery pattern for a one-time migration tool where an encrypted box file might be corrupted but a plain variant exists as backup.
The concern that both valid variants could coexist is unlikely here: file existence checks (lines 120–121) and the lack of any code maintaining dual variants suggest boxes exist in either encrypted or plain state, not both. The fallback only triggers if the encrypted open fails (exception), not if both are readable—in which case you already have an error condition (corrupted or mixed storage).
For production resilience, the current behavior prioritizes completing the migration. If you prefer stricter enforcement that rejects the migration when encrypted-box decryption fails (even if a plain backup exists), consider whether that trade-off is worth potential migration failures in recovery scenarios.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/data/migration/hive_to_sqlite_migrator.dart` around lines 161 - 181, The current logic tries encrypted first then silently falls back to plain (using hasEnc/hasPlain, Hive.openBox, openErrors and Loggers.app.warning), but we should make this behavior configurable so callers can choose strict encrypted-only migration; add a boolean option (e.g. enforceEncrypted or migrationOptions.enforceEncrypted) to the migrator and update the open sequence: if hasEnc is true attempt Hive.openBox('${boxName}_enc', ... ) as now, and if it fails and enforceEncrypted is true propagate/throw the error (or return a failing result) instead of falling back; if enforceEncrypted is false, keep the existing fallback to plain (Hive.openBox(boxName,...)) and still record openErrors and Loggers.app.warning; update any calling sites to pass the new option or default it to false.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@pubspec.yaml`:
- Around line 33-34: Update dependencies in pubspec.yaml: bump drift to ^2.31.0
and replace the EOL sqlcipher_flutter_libs entry with the maintained sqlite3
family (use a compatible sqlite3 package/version, e.g., sqlite3 v3.x or the
appropriate sqlite3_flutter_libs/ffi wrapper your app uses). Ensure you update
any imports or initialization that referenced sqlcipher_flutter_libs to the new
sqlite3 package and run pub get and your test suite to confirm compatibility
(look for usages of the drift dependency and any initialization code that relied
on sqlcipher_flutter_libs).
---
Nitpick comments:
In `@lib/data/migration/hive_to_sqlite_migrator.dart`:
- Around line 161-181: The current logic tries encrypted first then silently
falls back to plain (using hasEnc/hasPlain, Hive.openBox, openErrors and
Loggers.app.warning), but we should make this behavior configurable so callers
can choose strict encrypted-only migration; add a boolean option (e.g.
enforceEncrypted or migrationOptions.enforceEncrypted) to the migrator and
update the open sequence: if hasEnc is true attempt
Hive.openBox('${boxName}_enc', ... ) as now, and if it fails and
enforceEncrypted is true propagate/throw the error (or return a failing result)
instead of falling back; if enforceEncrypted is false, keep the existing
fallback to plain (Hive.openBox(boxName,...)) and still record openErrors and
Loggers.app.warning; update any calling sites to pass the new option or default
it to false.
ℹ️ Review info
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
pubspec.lockis excluded by!**/*.lock
📒 Files selected for processing (2)
lib/data/migration/hive_to_sqlite_migrator.dartpubspec.yaml
| drift: ^2.29.0 | ||
| sqlcipher_flutter_libs: ^0.6.8 |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check drift package information from pub.dev API
echo "=== Drift Package Info ==="
curl -s https://pub.dev/api/packages/drift | jq -r '.latest | "\(.version) (published: \(.published))"' 2>/dev/null || echo "API call failed"
# Check sqlcipher_flutter_libs package information from pub.dev API
echo "=== SQLCipher Flutter Libs Package Info ==="
curl -s https://pub.dev/api/packages/sqlcipher_flutter_libs | jq -r '.latest | "\(.version) (published: \(.published))"' 2>/dev/null || echo "API call failed"
# Try to get all versions to see if specified versions exist
echo "=== Drift Available Versions ==="
curl -s https://pub.dev/api/packages/drift | jq -r '.versions[] | select(.version | test("2\\.29")) | .version' 2>/dev/null || echo "Could not fetch versions"
echo "=== SQLCipher Flutter Libs Available Versions ==="
curl -s https://pub.dev/api/packages/sqlcipher_flutter_libs | jq -r '.versions[] | select(.version | test("0\\.6")) | .version' 2>/dev/null || echo "Could not fetch versions"Repository: lollipopkit/flutter_server_box
Length of output: 387
🌐 Web query:
drift package pub.dev version 2.29.0 security advisories
💡 Result:
No security advisories specific to the Dart/Flutter drift package (including version 2.29.0) appear to be published:
- The upstream GitHub repository simolus3/drift shows “There aren’t any published security advisories.” [1]
- Pub’s guidance for package vulnerabilities is to use/search GitHub Security Advisories for the package/repo (which, for
drift, currently yields none). [2]
Related note (ecosystem/tooling, not drift itself): there is a recent CVE affecting dart pub / flutter pub package extraction in certain SDK versions, but it’s a Pub client issue rather than a drift package advisory. [3]
Sources: [1] [2] [3]
🌐 Web query:
sqlcipher_flutter_libs package pub.dev version 0.6.8 security advisories
💡 Result:
- No published security advisories found specifically for
sqlcipher_flutter_libsv0.6.8 in the upstream GitHub repo that hosts the package (simolus3/sqlite3.dart)—GitHub shows “There aren’t any published security advisories.” [1] - What changed in v0.6.8: the package updated SQLCipher to 4.10.0. [2]
- Security-relevant note for SQLCipher 4.10.0: Zetetic states the release includes “enhancements and security updates” and that the bundled SQLite 3.50.4 contains several security fixes (they advise upgrading if your app allows untrusted schema modification). [3]
- Status note: the package is now marked obsolete/EOL on pub.dev (newer versions “no longer do anything” and users are told to migrate to
sqlite3v3.x). [4]
Sources:
[1] GitHub security overview for simolus3/sqlite3.dart (github.com)
[2] sqlcipher_flutter_libs changelog (0.6.8) (pub.dev)
[3] Zetetic “SQLCipher 4.10.0 Release” (zetetic.net)
[4] pub.dev package page (EOL/obsolete notice) (pub.dev)
Citations:
- 1: https://github.com/simolus3/sqlite3.dart/security
- 2: https://pub.dev/packages/sqlcipher_flutter_libs/changelog?utm_source=openai
- 3: https://www.zetetic.net/blog/2025/08/04/sqlcipher-4.10.0-release/?utm_source=openai
- 4: https://pub.dev/packages/sqlcipher_flutter_libs
Consider upgrading drift to v2.31.0 and sqlcipher_flutter_libs to a maintained alternative.
Both package versions are valid and free from published security advisories. However, sqlcipher_flutter_libs is marked as end-of-life (EOL) on pub.dev, with users directed to migrate to sqlite3 v3.x for continued support. Additionally, drift has a newer stable version (2.31.0, released Feb 3, 2026) available; consider upgrading to the latest to ensure compatibility with current Flutter/Dart updates.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@pubspec.yaml` around lines 33 - 34, Update dependencies in pubspec.yaml: bump
drift to ^2.31.0 and replace the EOL sqlcipher_flutter_libs entry with the
maintained sqlite3 family (use a compatible sqlite3 package/version, e.g.,
sqlite3 v3.x or the appropriate sqlite3_flutter_libs/ffi wrapper your app uses).
Ensure you update any imports or initialization that referenced
sqlcipher_flutter_libs to the new sqlite3 package and run pub get and your test
suite to confirm compatibility (look for usages of the drift dependency and any
initialization code that relied on sqlcipher_flutter_libs).
Summary
SqliteStoreIssue
Closes #1037
What Changed
SettingStore/ServerStore/ContainerStore/PrivateKeyStore/SnippetStore/HistoryStore/ConnectionStatsStorefromHiveStoretoSqliteStoreboxusage in related pages and utils with store APIs (get/set/remove/keys/fetchOne)Mergeable.mergeStorewith store timestampsHiveToSqliteMigratorand called it during app initdriftandsqlcipher_flutter_libsdependencies and regenerated platform plugin registrantsMigration Safety Fixes (for #1037)
hive_key/flutter.hive_key) and sync it back to secure storageVerification
flutter analyze lib/data/migration/hive_to_sqlite_migrator.dartflutter test(194 tests, all passed)Summary by CodeRabbit
New Features
Improvements
Chores
Documentation