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:
Christian Pauly 2020-10-26 11:19:02 +00:00
commit 489de95689
8 changed files with 37 additions and 241 deletions

View File

@ -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 {

View File

@ -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);

View File

@ -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,

View File

@ -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);
}
}

View File

@ -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';
}
}

View File

@ -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);
}

View File

@ -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:

View File

@ -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