Skip to content

Commit

Permalink
refactor(account_repository): migrate to neon_storage
Browse files Browse the repository at this point in the history
Signed-off-by: Nikolas Rimikis <[email protected]>
  • Loading branch information
Leptopoda committed Sep 29, 2024
1 parent fef7677 commit b7d8a42
Show file tree
Hide file tree
Showing 10 changed files with 309 additions and 145 deletions.
12 changes: 6 additions & 6 deletions packages/neon_framework/lib/neon.dart
Original file line number Diff line number Diff line change
Expand Up @@ -52,19 +52,19 @@ Future<void> runNeon({
tz.setLocalLocation(location);
}

await NeonStorage().init();
final accountStorage = AccountStorage();
await NeonStorage().init(
dataTables: [
accountStorage,
],
);

final packageInfo = await PackageInfo.fromPlatform();

final globalOptions = GlobalOptions(
packageInfo,
);

final accountStorage = AccountStorage(
accountsPersistence: NeonStorage().singleValueStore(StorageKeys.accounts),
lastAccountPersistence: NeonStorage().singleValueStore(StorageKeys.lastUsedAccount),
);

final accountRepository = AccountRepository(
userAgent: buildUserAgent(packageInfo, theme.branding.name),
httpClient: httpClient ?? http.Client(),
Expand Down
10 changes: 7 additions & 3 deletions packages/neon_framework/lib/src/storage/storage_manager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'dart:async';

import 'package:meta/meta.dart';
import 'package:neon_framework/src/storage/storage.dart';
import 'package:neon_storage/neon_sqlite.dart';

/// Neon storage that manages the storage backend.
///
Expand Down Expand Up @@ -33,13 +34,16 @@ class NeonStorage {
/// Sets the individual storages.
///
/// Required to be called before accessing any individual one.
Future<void> init() async {
Future<void> init({
Iterable<Table>? cacheTables,
Iterable<Table>? dataTables,
}) async {
if (_initialized) {
return;
}

_neonCache = NeonCacheDB();
_neonData = NeonDataDB();
_neonCache = NeonCacheDB(tables: cacheTables);
_neonData = NeonDataDB(tables: dataTables);

await _neonCache.init();
await _neonData.init();
Expand Down
11 changes: 6 additions & 5 deletions packages/neon_framework/lib/src/utils/push_utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,13 @@ class PushUtils {
}
},
);
await NeonStorage().init();

final accountStorage = AccountStorage();
await NeonStorage().init(
dataTables: [
accountStorage,
],
);
final keypair = loadRSAKeypair();
for (final message in Uri(query: utf8.decode(messages)).queryParameters.values) {
final data = json.decode(message) as Map<String, dynamic>;
Expand All @@ -115,10 +120,6 @@ class PushUtils {
} else {
final localizations = await appLocalizationsFromSystem();
final packageInfo = await PackageInfo.fromPlatform();
final accountStorage = AccountStorage(
accountsPersistence: NeonStorage().singleValueStore(StorageKeys.accounts),
lastAccountPersistence: NeonStorage().singleValueStore(StorageKeys.lastUsedAccount),
);
final accountRepository = AccountRepository(
userAgent: buildUserAgent(packageInfo),
httpClient: http.Client(),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import 'dart:async';
import 'dart:convert';

import 'package:account_repository/src/models/models.dart';
import 'package:account_repository/src/utils/utils.dart';
import 'package:built_collection/built_collection.dart';
import 'package:equatable/equatable.dart';
import 'package:http/http.dart' as http;
import 'package:logging/logging.dart';
import 'package:meta/meta.dart';
import 'package:neon_framework/storage.dart';
import 'package:neon_storage/neon_sqlite.dart';
import 'package:nextcloud/core.dart' as core;
import 'package:nextcloud/nextcloud.dart';
import 'package:rxdart/rxdart.dart';
import 'package:sqflite_common/sqlite_api.dart';

part 'account_storage.dart';

Expand Down Expand Up @@ -272,8 +273,8 @@ class AccountRepository {

_accounts.add((active: active, accounts: accounts));
await Future.wait([
_storage.saveCredentials(accounts.values.map((e) => e.credentials)),
_storage.saveLastAccount(active),
_storage.addCredentials(account.credentials),
_storage.saveLastAccount(account.credentials),
]);
}

Expand All @@ -293,13 +294,16 @@ class AccountRepository {
}

var active = value.active;
if (active == accountID) {
if (value.active == accountID) {
active = accounts.keys.firstOrNull;
}

await Future.wait([
_storage.saveCredentials(accounts.values.map((e) => e.credentials)),
_storage.saveLastAccount(active),
_storage.removeCredentials(account!.credentials),
if (active != null)
_storage.saveLastAccount(
accountByID(active)!.credentials,
),
]);

_accounts.add((active: active, accounts: accounts));
Expand Down Expand Up @@ -329,6 +333,7 @@ class AccountRepository {
}

_accounts.add((active: accountID, accounts: value.accounts));
await _storage.saveLastAccount(accountID);
final active = value.accounts[accountID]!;
await _storage.saveLastAccount(active.credentials);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,52 +3,205 @@ part of 'account_repository.dart';
/// {@template account_repository_storage}
/// Storage for the [AccountRepository].
/// {@endtemplate}
@immutable
class AccountStorage {
class AccountStorage with Table {
/// {@macro account_repository_storage}
const AccountStorage({
required this.accountsPersistence,
required this.lastAccountPersistence,
});
AccountStorage();

/// The store for the account list.
final SingleValueStore accountsPersistence;
@override
String get name => 'account_credentials';

/// The store for the last used account.
final SingleValueStore lastAccountPersistence;
@override
int get version => 1;

/// Column for the [Credentials.serverURL].
@protected
static const String serverURL = 'server_urlL';

/// Column for the [Credentials.username].
@protected
static const String loginName = 'login_name';

/// Column for the [Credentials.appPassword].
@protected
static const String appPassword = 'app_password';

/// Column to store the active account.
@protected
static const String active = 'active';

@override
void onCreate(Batch db, int version) {
db.execute('''
CREATE TABLE "$name" (
"$serverURL" TEXT NOT NULL,
"$loginName" TEXT NOT NULL,
"$appPassword" TEXT,
"$active" BOOLEAN NOT NULL DEFAULT 0,
PRIMARY KEY("$serverURL", "$loginName")
);
''');
}

static final Logger _log = Logger('AccountStorage');

Database get _database => controller.database;

/// Gets a list of logged in credentials from storage.
///
/// It is not checked whether the stored information is still valid.
Future<BuiltList<Credentials>> readCredentials() async {
if (accountsPersistence.hasValue()) {
return accountsPersistence
.getStringList()!
.map((a) => Credentials.fromJson(json.decode(a) as Map<String, dynamic>))
.toBuiltList();
try {
final results = await _database.query(
name,
columns: [
serverURL,
loginName,
appPassword,
],
);

final credentials = ListBuilder<Credentials>();

for (final result in results) {
credentials.add(
Credentials((b) {
b
..serverURL = Uri.parse(result[serverURL]! as String)
..username = result[loginName]! as String
..appPassword = result[appPassword] as String?;
}),
);
}

return credentials.build();
} on DatabaseException catch (error, stackTrace) {
_log.warning(
'Error loading cookies.',
error,
stackTrace,
);
}

return BuiltList();
}

/// Saves the given [credentials] to the storage.
Future<void> saveCredentials(Iterable<Credentials> credentials) async {
final values = credentials.map((a) => json.encode(a.toJson())).toBuiltList();
/// Persists the given [credentials] on disk.
Future<void> addCredentials(Credentials credentials) async {
try {
// UPSERT is only available since SQLite 3.24.0 (June 4, 2018).
// Using a manual solution from https://stackoverflow.com/a/38463024
final batch = _database.batch()
..update(
name,
{
serverURL: credentials.serverURL.toString(),
loginName: credentials.username,
appPassword: credentials.appPassword,
},
where: '$serverURL = ? AND $loginName = ?',
whereArgs: [
credentials.serverURL.toString(),
credentials.username,
],
)
..rawInsert(
'''
INSERT INTO $name ($serverURL, $loginName, $appPassword)
SELECT ?, ?, ?
WHERE (SELECT changes() = 0)
''',
[
credentials.serverURL.toString(),
credentials.username,
credentials.appPassword,
],
);
await batch.commit(noResult: true);
} on DatabaseException catch (error, stackTrace) {
_log.warning(
'Error loading cookies.',
error,
stackTrace,
);
}
}

await accountsPersistence.setStringList(values);
/// Removes the given [credentials] from storage.
Future<void> removeCredentials(Credentials credentials) async {
try {
await _database.delete(
name,
where: '$serverURL = ? AND $loginName = ?',
whereArgs: [
credentials.serverURL.toString(),
credentials.username,
],
);
} on DatabaseException catch (error, stackTrace) {
_log.warning(
'Error loading cookies.',
error,
stackTrace,
);
}
}

/// Retrieves the id of the last used account.
Future<String?> readLastAccount() async {
return lastAccountPersistence.getString();
try {
final results = await _database.query(
name,
where: '$active = 1',
columns: [
serverURL,
loginName,
],
);

if (results.isNotEmpty) {
final result = results.single;
return Credentials((b) {
b
..serverURL = Uri.parse(result[serverURL]! as String)
..username = result[loginName]! as String;
}).id;
}
} on DatabaseException catch (error, stackTrace) {
_log.warning(
'Error loading cookies.',
error,
stackTrace,
);
}

return null;
}

/// Sets the last used account to the given [accountID].
Future<void> saveLastAccount(String? accountID) async {
if (accountID == null) {
await lastAccountPersistence.remove();
} else {
await lastAccountPersistence.setString(accountID);
/// Sets the last used account to the given [credentials].
Future<void> saveLastAccount(Credentials credentials) async {
try {
final batch = _database.batch()
..update(
name,
{active: 0},
where: '$active = 1',
)
..update(
name,
{active: 1},
where: '$serverURL = ? AND $loginName = ?',
whereArgs: [
credentials.serverURL.toString(),
credentials.username,
],
);
await batch.commit(noResult: true);
} on DatabaseException catch (error, stackTrace) {
_log.warning(
'Error loading cookies.',
error,
stackTrace,
);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import 'package:account_repository/src/models/models.dart';
import 'package:cookie_store/cookie_store.dart';
import 'package:http/http.dart' as http;
import 'package:neon_framework/storage.dart';
import 'package:neon_http_client/neon_http_client.dart';
import 'package:neon_storage/neon_storage.dart';
import 'package:nextcloud/nextcloud.dart';

/// Builds a [NextcloudClient] authenticated with the given [credentials].
Expand Down
Loading

0 comments on commit b7d8a42

Please sign in to comment.