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 {
|
void clean() async {
|
||||||
if (!kIsWeb) return;
|
if (!kIsWeb) return;
|
||||||
|
|
||||||
final storage = await getLocalStorage();
|
await store.deleteItem(widget.clientName);
|
||||||
await storage.deleteItem(widget.clientName);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _initWithStore() async {
|
void _initWithStore() async {
|
||||||
|
|
|
@ -175,7 +175,7 @@ class ThemeSwitcherWidgetState extends State<ThemeSwitcherWidget> {
|
||||||
BuildContext context;
|
BuildContext context;
|
||||||
|
|
||||||
Future loadSelection(MatrixState matrix) async {
|
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(
|
selectedTheme = Themes.values.firstWhere(
|
||||||
(e) => e.toString() == 'Themes.' + item,
|
(e) => e.toString() == 'Themes.' + item,
|
||||||
orElse: () => Themes.system);
|
orElse: () => Themes.system);
|
||||||
|
|
|
@ -21,8 +21,8 @@ final sentry = SentryClient(dsn: '8591d0d863b646feb4f3dda7e5dcab38');
|
||||||
void captureException(error, stackTrace) async {
|
void captureException(error, stackTrace) async {
|
||||||
debugPrint(error.toString());
|
debugPrint(error.toString());
|
||||||
debugPrint(stackTrace.toString());
|
debugPrint(stackTrace.toString());
|
||||||
final storage = await getLocalStorage();
|
final storage = Store();
|
||||||
if (storage.getItem('sentry') == true) {
|
if (await storage.getItem('sentry') == 'true') {
|
||||||
await sentry.captureException(
|
await sentry.captureException(
|
||||||
exception: error,
|
exception: error,
|
||||||
stackTrace: stackTrace,
|
stackTrace: stackTrace,
|
||||||
|
|
|
@ -1,27 +1,13 @@
|
||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'package:famedlysdk/famedlysdk.dart';
|
import 'package:famedlysdk/famedlysdk.dart';
|
||||||
import 'package:fluffychat/utils/platform_infos.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:flutter_secure_storage/flutter_secure_storage.dart';
|
||||||
import 'package:localstorage/localstorage.dart';
|
import 'package:localstorage/localstorage.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:core';
|
import 'dart:core';
|
||||||
import './database/shared.dart';
|
import './database/shared.dart';
|
||||||
import 'package:olm/olm.dart' as olm; // needed for migration
|
|
||||||
import 'package:random_string/random_string.dart';
|
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 {
|
Future<Database> getDatabase(Client client) async {
|
||||||
while (_generateDatabaseLock) {
|
while (_generateDatabaseLock) {
|
||||||
await Future.delayed(Duration(milliseconds: 50));
|
await Future.delayed(Duration(milliseconds: 50));
|
||||||
|
@ -31,9 +17,9 @@ Future<Database> getDatabase(Client client) async {
|
||||||
if (_db != null) return _db;
|
if (_db != null) return _db;
|
||||||
final store = Store();
|
final store = Store();
|
||||||
var password = await store.getItem('database-password');
|
var password = await store.getItem('database-password');
|
||||||
var needMigration = false;
|
var newPassword = false;
|
||||||
if (password == null || password.isEmpty) {
|
if (password == null || password.isEmpty) {
|
||||||
needMigration = true;
|
newPassword = true;
|
||||||
password = randomString(255);
|
password = randomString(255);
|
||||||
}
|
}
|
||||||
_db = await constructDb(
|
_db = await constructDb(
|
||||||
|
@ -41,11 +27,7 @@ Future<Database> getDatabase(Client client) async {
|
||||||
filename: 'moor.sqlite',
|
filename: 'moor.sqlite',
|
||||||
password: password,
|
password: password,
|
||||||
);
|
);
|
||||||
// Check if database is open:
|
if (newPassword) {
|
||||||
debugPrint((await _db.customSelect('SELECT 1').get()).toString());
|
|
||||||
if (needMigration) {
|
|
||||||
debugPrint('[Moor] Start migration');
|
|
||||||
await migrate(client.clientName, _db, store);
|
|
||||||
await store.setItem('database-password', password);
|
await store.setItem('database-password', password);
|
||||||
}
|
}
|
||||||
return _db;
|
return _db;
|
||||||
|
@ -57,239 +39,54 @@ Future<Database> getDatabase(Client client) async {
|
||||||
Database _db;
|
Database _db;
|
||||||
bool _generateDatabaseLock = false;
|
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 {
|
class Store {
|
||||||
final LocalStorage storage;
|
LocalStorage storage;
|
||||||
final FlutterSecureStorage secureStorage;
|
final FlutterSecureStorage secureStorage;
|
||||||
static final _mutex = AsyncMutex();
|
|
||||||
|
|
||||||
Store()
|
Store()
|
||||||
: storage = LocalStorage('LocalStorage'),
|
: secureStorage = PlatformInfos.isMobile ? FlutterSecureStorage() : null;
|
||||||
secureStorage = PlatformInfos.isMobile ? FlutterSecureStorage() : null;
|
|
||||||
|
|
||||||
Future<dynamic> getItem(String key) async {
|
Future<void> _setupLocalStorage() async {
|
||||||
if (!PlatformInfos.isMobile) {
|
if (storage == null) {
|
||||||
|
final directory = PlatformInfos.isBetaDesktop
|
||||||
|
? await getApplicationSupportDirectory()
|
||||||
|
: (PlatformInfos.isWeb
|
||||||
|
? null
|
||||||
|
: await getApplicationDocumentsDirectory());
|
||||||
|
storage = LocalStorage('LocalStorage', directory?.path);
|
||||||
await storage.ready;
|
await storage.ready;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String> getItem(String key) async {
|
||||||
|
if (!PlatformInfos.isMobile) {
|
||||||
|
await _setupLocalStorage();
|
||||||
try {
|
try {
|
||||||
return await storage.getItem(key);
|
return await storage.getItem(key).toString();
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await _mutex.lock();
|
|
||||||
return await secureStorage.read(key: key);
|
return await secureStorage.read(key: key);
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
return null;
|
return null;
|
||||||
} finally {
|
|
||||||
_mutex.unlock();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> setItem(String key, String value) async {
|
Future<void> setItem(String key, String value) async {
|
||||||
if (!PlatformInfos.isMobile) {
|
if (!PlatformInfos.isMobile) {
|
||||||
await storage.ready;
|
await _setupLocalStorage();
|
||||||
return await storage.setItem(key, value);
|
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);
|
return await secureStorage.write(key: key, value: value);
|
||||||
} finally {
|
|
||||||
_mutex.unlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Map<String, dynamic>> getAllItems() async {
|
Future<void> deleteItem(String key) async {
|
||||||
if (!PlatformInfos.isMobile) {
|
if (!PlatformInfos.isMobile) {
|
||||||
try {
|
await _setupLocalStorage();
|
||||||
final rawStorage = await getLocalstorage('LocalStorage');
|
return await storage.deleteItem(key);
|
||||||
return json.decode(rawStorage);
|
}
|
||||||
} catch (_) {
|
return await secureStorage.delete(key: key);
|
||||||
return {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
await _mutex.lock();
|
|
||||||
return await secureStorage.readAll();
|
|
||||||
} catch (_) {
|
|
||||||
return {};
|
|
||||||
} finally {
|
|
||||||
_mutex.unlock();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,14 +13,14 @@ abstract class SentryController {
|
||||||
confirmText: L10n.of(context).ok,
|
confirmText: L10n.of(context).ok,
|
||||||
cancelText: L10n.of(context).no,
|
cancelText: L10n.of(context).no,
|
||||||
);
|
);
|
||||||
final storage = await getLocalStorage();
|
final storage = Store();
|
||||||
await storage.setItem('sentry', enableSentry);
|
await storage.setItem('sentry', enableSentry.toString());
|
||||||
BotToast.showText(text: L10n.of(context).changesHaveBeenSaved);
|
BotToast.showText(text: L10n.of(context).changesHaveBeenSaved);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<bool> getSentryStatus() async {
|
static Future<bool> getSentryStatus() async {
|
||||||
final storage = await getLocalStorage();
|
final storage = Store();
|
||||||
return storage.getItem('sentry') as bool;
|
return await storage.getItem('sentry') == 'true';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -185,7 +185,7 @@ class _SettingsState extends State<Settings> {
|
||||||
|
|
||||||
void deleteWallpaperAction(BuildContext context) async {
|
void deleteWallpaperAction(BuildContext context) async {
|
||||||
Matrix.of(context).wallpaper = null;
|
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);
|
setState(() => null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -362,7 +362,7 @@ packages:
|
||||||
name: flutter_secure_storage
|
name: flutter_secure_storage
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.3.4"
|
version: "3.3.5"
|
||||||
flutter_slidable:
|
flutter_slidable:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
|
@ -41,7 +41,7 @@ dependencies:
|
||||||
path_provider: ^1.5.1
|
path_provider: ^1.5.1
|
||||||
webview_flutter: ^0.3.19+9
|
webview_flutter: ^0.3.19+9
|
||||||
share: ^0.6.3+5
|
share: ^0.6.3+5
|
||||||
flutter_secure_storage: ^3.3.4
|
flutter_secure_storage: ^3.3.5
|
||||||
http: ^0.12.0+4
|
http: ^0.12.0+4
|
||||||
universal_html: ^1.1.12
|
universal_html: ^1.1.12
|
||||||
receive_sharing_intent: ^1.3.3
|
receive_sharing_intent: ^1.3.3
|
||||||
|
|
Loading…
Reference in a new issue