Merge branch 'soru/fix-store' into 'main'
fix: Multiple related store things Closes #189 See merge request ChristianPauly/fluffychat-flutter!241
This commit is contained in:
commit
489de95689
|
@ -81,8 +81,7 @@ class MatrixState extends State<Matrix> {
|
|||
void clean() async {
|
||||
if (!kIsWeb) return;
|
||||
|
||||
final storage = await getLocalStorage();
|
||||
await storage.deleteItem(widget.clientName);
|
||||
await store.deleteItem(widget.clientName);
|
||||
}
|
||||
|
||||
void _initWithStore() async {
|
||||
|
|
|
@ -175,7 +175,7 @@ class ThemeSwitcherWidgetState extends State<ThemeSwitcherWidget> {
|
|||
BuildContext context;
|
||||
|
||||
Future loadSelection(MatrixState matrix) async {
|
||||
String item = await matrix.store.getItem('theme') ?? 'system';
|
||||
var item = await matrix.store.getItem('theme') ?? 'system';
|
||||
selectedTheme = Themes.values.firstWhere(
|
||||
(e) => e.toString() == 'Themes.' + item,
|
||||
orElse: () => Themes.system);
|
||||
|
|
|
@ -21,8 +21,8 @@ final sentry = SentryClient(dsn: '8591d0d863b646feb4f3dda7e5dcab38');
|
|||
void captureException(error, stackTrace) async {
|
||||
debugPrint(error.toString());
|
||||
debugPrint(stackTrace.toString());
|
||||
final storage = await getLocalStorage();
|
||||
if (storage.getItem('sentry') == true) {
|
||||
final storage = Store();
|
||||
if (await storage.getItem('sentry') == 'true') {
|
||||
await sentry.captureException(
|
||||
exception: error,
|
||||
stackTrace: stackTrace,
|
||||
|
|
|
@ -1,27 +1,13 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:famedlysdk/famedlysdk.dart';
|
||||
import 'package:fluffychat/utils/platform_infos.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:localstorage/localstorage.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'dart:async';
|
||||
import 'dart:core';
|
||||
import './database/shared.dart';
|
||||
import 'package:olm/olm.dart' as olm; // needed for migration
|
||||
import 'package:random_string/random_string.dart';
|
||||
|
||||
Future<LocalStorage> getLocalStorage() async {
|
||||
final directory = PlatformInfos.isBetaDesktop
|
||||
? await getApplicationSupportDirectory()
|
||||
: (PlatformInfos.isWeb ? null : await getApplicationDocumentsDirectory());
|
||||
final localStorage = LocalStorage('LocalStorage', directory?.path);
|
||||
await localStorage.ready;
|
||||
return localStorage;
|
||||
}
|
||||
|
||||
Future<Database> getDatabase(Client client) async {
|
||||
while (_generateDatabaseLock) {
|
||||
await Future.delayed(Duration(milliseconds: 50));
|
||||
|
@ -31,9 +17,9 @@ Future<Database> getDatabase(Client client) async {
|
|||
if (_db != null) return _db;
|
||||
final store = Store();
|
||||
var password = await store.getItem('database-password');
|
||||
var needMigration = false;
|
||||
var newPassword = false;
|
||||
if (password == null || password.isEmpty) {
|
||||
needMigration = true;
|
||||
newPassword = true;
|
||||
password = randomString(255);
|
||||
}
|
||||
_db = await constructDb(
|
||||
|
@ -41,11 +27,7 @@ Future<Database> getDatabase(Client client) async {
|
|||
filename: 'moor.sqlite',
|
||||
password: password,
|
||||
);
|
||||
// Check if database is open:
|
||||
debugPrint((await _db.customSelect('SELECT 1').get()).toString());
|
||||
if (needMigration) {
|
||||
debugPrint('[Moor] Start migration');
|
||||
await migrate(client.clientName, _db, store);
|
||||
if (newPassword) {
|
||||
await store.setItem('database-password', password);
|
||||
}
|
||||
return _db;
|
||||
|
@ -57,239 +39,54 @@ Future<Database> getDatabase(Client client) async {
|
|||
Database _db;
|
||||
bool _generateDatabaseLock = false;
|
||||
|
||||
Future<void> migrate(String clientName, Database db, Store store) async {
|
||||
debugPrint('[Store] attempting old migration to moor...');
|
||||
final oldKeys = await store.getAllItems();
|
||||
if (oldKeys == null || oldKeys.isEmpty) {
|
||||
debugPrint('[Store] empty store!');
|
||||
return; // we are done!
|
||||
}
|
||||
final credentialsStr = oldKeys[clientName];
|
||||
if (credentialsStr == null || credentialsStr.isEmpty) {
|
||||
debugPrint('[Store] no credentials found!');
|
||||
return; // no credentials
|
||||
}
|
||||
final Map<String, dynamic> credentials = json.decode(credentialsStr);
|
||||
if (!credentials.containsKey('homeserver') ||
|
||||
!credentials.containsKey('token') ||
|
||||
!credentials.containsKey('userID')) {
|
||||
debugPrint('[Store] invalid credentials!');
|
||||
return; // invalid old store, we are done, too!
|
||||
}
|
||||
var clientId = 0;
|
||||
final oldClient = await db.getClient(clientName);
|
||||
if (oldClient == null) {
|
||||
clientId = await db.insertClient(
|
||||
clientName,
|
||||
credentials['homeserver'],
|
||||
credentials['token'],
|
||||
credentials['userID'],
|
||||
credentials['deviceID'],
|
||||
credentials['deviceName'],
|
||||
null,
|
||||
credentials['olmAccount'],
|
||||
);
|
||||
} else {
|
||||
clientId = oldClient.clientId;
|
||||
await db.updateClient(
|
||||
credentials['homeserver'],
|
||||
credentials['token'],
|
||||
credentials['userID'],
|
||||
credentials['deviceID'],
|
||||
credentials['deviceName'],
|
||||
null,
|
||||
credentials['olmAccount'],
|
||||
clientId,
|
||||
);
|
||||
}
|
||||
await db.clearCache(clientId);
|
||||
debugPrint('[Store] Inserted/updated client, clientId = ${clientId}');
|
||||
await db.transaction(() async {
|
||||
// alright, we stored / updated the client and have the account ID, time to import everything else!
|
||||
// user_device_keys and user_device_keys_key
|
||||
debugPrint('[Store] Migrating user device keys...');
|
||||
final deviceKeysListString = oldKeys['${clientName}.user_device_keys'];
|
||||
if (deviceKeysListString != null && deviceKeysListString.isNotEmpty) {
|
||||
Map<String, dynamic> rawUserDeviceKeys =
|
||||
json.decode(deviceKeysListString);
|
||||
for (final entry in rawUserDeviceKeys.entries) {
|
||||
final map = entry.value;
|
||||
await db.storeUserDeviceKeysInfo(
|
||||
clientId, map['user_id'], map['outdated']);
|
||||
for (final rawKey in map['device_keys'].entries) {
|
||||
final jsonVaue = rawKey.value;
|
||||
await db.storeUserDeviceKey(
|
||||
clientId,
|
||||
jsonVaue['user_id'],
|
||||
jsonVaue['device_id'],
|
||||
json.encode(jsonVaue),
|
||||
jsonVaue['verified'],
|
||||
jsonVaue['blocked']);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (final entry in oldKeys.entries) {
|
||||
final key = entry.key;
|
||||
final value = entry.value;
|
||||
if (value == null || value.isEmpty) {
|
||||
continue;
|
||||
}
|
||||
// olm_sessions
|
||||
final olmSessionsMatch =
|
||||
RegExp(r'^\/clients\/([^\/]+)\/olm-sessions$').firstMatch(key);
|
||||
if (olmSessionsMatch != null) {
|
||||
if (olmSessionsMatch[1] != credentials['deviceID']) {
|
||||
continue;
|
||||
}
|
||||
debugPrint('[Store] migrating olm sessions...');
|
||||
final identityKey = json.decode(value);
|
||||
for (final olmKey in identityKey.entries) {
|
||||
final identKey = olmKey.key;
|
||||
final sessions = olmKey.value;
|
||||
for (final pickle in sessions) {
|
||||
var sess = olm.Session();
|
||||
sess.unpickle(credentials['userID'], pickle);
|
||||
await db.storeOlmSession(
|
||||
clientId, identKey, sess.session_id(), pickle, null);
|
||||
sess?.free();
|
||||
}
|
||||
}
|
||||
}
|
||||
// outbound_group_sessions
|
||||
final outboundGroupSessionsMatch = RegExp(
|
||||
r'^\/clients\/([^\/]+)\/rooms\/([^\/]+)\/outbound_group_session$')
|
||||
.firstMatch(key);
|
||||
if (outboundGroupSessionsMatch != null) {
|
||||
if (outboundGroupSessionsMatch[1] != credentials['deviceID']) {
|
||||
continue;
|
||||
}
|
||||
final pickle = value;
|
||||
final roomId = outboundGroupSessionsMatch[2];
|
||||
debugPrint(
|
||||
'[Store] Migrating outbound group sessions for room ${roomId}...');
|
||||
final devicesString = oldKeys[
|
||||
'/clients/${outboundGroupSessionsMatch[1]}/rooms/${roomId}/outbound_group_session_devices'];
|
||||
var devices = <String>[];
|
||||
if (devicesString != null) {
|
||||
devices = List<String>.from(json.decode(devicesString));
|
||||
}
|
||||
await db.storeOutboundGroupSession(
|
||||
clientId,
|
||||
roomId,
|
||||
pickle,
|
||||
json.encode(devices),
|
||||
DateTime.now().millisecondsSinceEpoch,
|
||||
0,
|
||||
);
|
||||
}
|
||||
// session_keys
|
||||
final sessionKeysMatch =
|
||||
RegExp(r'^\/clients\/([^\/]+)\/rooms\/([^\/]+)\/session_keys$')
|
||||
.firstMatch(key);
|
||||
if (sessionKeysMatch != null) {
|
||||
if (sessionKeysMatch[1] != credentials['deviceID']) {
|
||||
continue;
|
||||
}
|
||||
final roomId = sessionKeysMatch[2];
|
||||
debugPrint('[Store] Migrating session keys for room ${roomId}...');
|
||||
final map = json.decode(value);
|
||||
for (final entry in map.entries) {
|
||||
await db.storeInboundGroupSession(
|
||||
clientId,
|
||||
roomId,
|
||||
entry.key,
|
||||
entry.value['inboundGroupSession'],
|
||||
json.encode(entry.value['content']),
|
||||
json.encode(entry.value['indexes']),
|
||||
null,
|
||||
null);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// see https://github.com/mogol/flutter_secure_storage/issues/161#issuecomment-704578453
|
||||
class AsyncMutex {
|
||||
Completer<void> _completer;
|
||||
|
||||
Future<void> lock() async {
|
||||
while (_completer != null) {
|
||||
await _completer.future;
|
||||
}
|
||||
|
||||
_completer = Completer<void>();
|
||||
}
|
||||
|
||||
void unlock() {
|
||||
assert(_completer != null);
|
||||
final completer = _completer;
|
||||
_completer = null;
|
||||
completer.complete();
|
||||
}
|
||||
}
|
||||
|
||||
class Store {
|
||||
final LocalStorage storage;
|
||||
LocalStorage storage;
|
||||
final FlutterSecureStorage secureStorage;
|
||||
static final _mutex = AsyncMutex();
|
||||
|
||||
Store()
|
||||
: storage = LocalStorage('LocalStorage'),
|
||||
secureStorage = PlatformInfos.isMobile ? FlutterSecureStorage() : null;
|
||||
: secureStorage = PlatformInfos.isMobile ? FlutterSecureStorage() : null;
|
||||
|
||||
Future<dynamic> getItem(String key) async {
|
||||
if (!PlatformInfos.isMobile) {
|
||||
Future<void> _setupLocalStorage() async {
|
||||
if (storage == null) {
|
||||
final directory = PlatformInfos.isBetaDesktop
|
||||
? await getApplicationSupportDirectory()
|
||||
: (PlatformInfos.isWeb
|
||||
? null
|
||||
: await getApplicationDocumentsDirectory());
|
||||
storage = LocalStorage('LocalStorage', directory?.path);
|
||||
await storage.ready;
|
||||
}
|
||||
}
|
||||
|
||||
Future<String> getItem(String key) async {
|
||||
if (!PlatformInfos.isMobile) {
|
||||
await _setupLocalStorage();
|
||||
try {
|
||||
return await storage.getItem(key);
|
||||
return await storage.getItem(key).toString();
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
try {
|
||||
await _mutex.lock();
|
||||
return await secureStorage.read(key: key);
|
||||
} catch (_) {
|
||||
return null;
|
||||
} finally {
|
||||
_mutex.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> setItem(String key, String value) async {
|
||||
if (!PlatformInfos.isMobile) {
|
||||
await storage.ready;
|
||||
await _setupLocalStorage();
|
||||
return await storage.setItem(key, value);
|
||||
}
|
||||
if (value == null) {
|
||||
return await secureStorage.delete(key: key);
|
||||
} else {
|
||||
try {
|
||||
await _mutex.lock();
|
||||
return await secureStorage.write(key: key, value: value);
|
||||
} finally {
|
||||
_mutex.unlock();
|
||||
}
|
||||
}
|
||||
return await secureStorage.write(key: key, value: value);
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> getAllItems() async {
|
||||
Future<void> deleteItem(String key) async {
|
||||
if (!PlatformInfos.isMobile) {
|
||||
try {
|
||||
final rawStorage = await getLocalstorage('LocalStorage');
|
||||
return json.decode(rawStorage);
|
||||
} catch (_) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
try {
|
||||
await _mutex.lock();
|
||||
return await secureStorage.readAll();
|
||||
} catch (_) {
|
||||
return {};
|
||||
} finally {
|
||||
_mutex.unlock();
|
||||
await _setupLocalStorage();
|
||||
return await storage.deleteItem(key);
|
||||
}
|
||||
return await secureStorage.delete(key: key);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,14 +13,14 @@ abstract class SentryController {
|
|||
confirmText: L10n.of(context).ok,
|
||||
cancelText: L10n.of(context).no,
|
||||
);
|
||||
final storage = await getLocalStorage();
|
||||
await storage.setItem('sentry', enableSentry);
|
||||
final storage = Store();
|
||||
await storage.setItem('sentry', enableSentry.toString());
|
||||
BotToast.showText(text: L10n.of(context).changesHaveBeenSaved);
|
||||
return;
|
||||
}
|
||||
|
||||
static Future<bool> getSentryStatus() async {
|
||||
final storage = await getLocalStorage();
|
||||
return storage.getItem('sentry') as bool;
|
||||
final storage = Store();
|
||||
return await storage.getItem('sentry') == 'true';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -185,7 +185,7 @@ class _SettingsState extends State<Settings> {
|
|||
|
||||
void deleteWallpaperAction(BuildContext context) async {
|
||||
Matrix.of(context).wallpaper = null;
|
||||
await Matrix.of(context).store.setItem('chat.fluffy.wallpaper', null);
|
||||
await Matrix.of(context).store.deleteItem('chat.fluffy.wallpaper');
|
||||
setState(() => null);
|
||||
}
|
||||
|
||||
|
|
|
@ -362,7 +362,7 @@ packages:
|
|||
name: flutter_secure_storage
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.3.4"
|
||||
version: "3.3.5"
|
||||
flutter_slidable:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
|
@ -41,7 +41,7 @@ dependencies:
|
|||
path_provider: ^1.5.1
|
||||
webview_flutter: ^0.3.19+9
|
||||
share: ^0.6.3+5
|
||||
flutter_secure_storage: ^3.3.4
|
||||
flutter_secure_storage: ^3.3.5
|
||||
http: ^0.12.0+4
|
||||
universal_html: ^1.1.12
|
||||
receive_sharing_intent: ^1.3.3
|
||||
|
|
Loading…
Reference in a new issue