refactor: storage

This commit is contained in:
2023-10-14 20:37:23 +03:00
parent c099cacd54
commit aaf7610e22
8 changed files with 177 additions and 151 deletions

View File

@@ -7,16 +7,15 @@ import 'trigger.dart';
import 'variable.dart';
class PluginBase extends ChangeNotifier {
final String id;
final IStorage<Map<String, dynamic>> storage = JsonStorage();
final List<Trigger> triggers = [];
final List<Alias> aliases = [];
final Map<String, Variable> variables = {};
final List<GameButtonSetData> buttonSets = [];
PluginBase(this.id);
Future<void> load() async {
await storage.init();
await Future.wait([
getAliases(),
getTriggers(),
@@ -31,13 +30,11 @@ class PluginBase extends ChangeNotifier {
List<Future<void>> additionalLoaders() => [];
Future<List<Trigger>> loadTriggers() async {
debugPrint('MUDProfile.loadTriggers: $id');
final triggers = await ProfileStorage.listProfileFiles(id, 'triggers');
debugPrint('$this loadTriggers');
final triggers = await storage.readDirectory('triggers');
final triggerFiles = <Map<String, dynamic>>[];
for (final trigger in triggers) {
debugPrint('MUDProfile.loadTriggers: $id/triggers/$trigger');
final triggerFile =
await ProfileStorage.readProfileFile(id, 'triggers/$trigger');
final triggerFile = await storage.readFile(trigger);
if (triggerFile != null) {
triggerFiles.add(triggerFile);
}
@@ -46,13 +43,11 @@ class PluginBase extends ChangeNotifier {
}
Future<List<Alias>> loadAliases() async {
debugPrint('MUDProfile.loadAliases: $id');
final aliases = await ProfileStorage.listProfileFiles(id, 'aliases');
debugPrint('$this loadAliases');
final aliases = await storage.readDirectory('aliases');
final aliasFiles = <Map<String, dynamic>>[];
for (final alias in aliases) {
debugPrint('MUDProfile.loadAliases: $id/aliases/$alias');
final aliasFile =
await ProfileStorage.readProfileFile(id, 'aliases/$alias');
final aliasFile = await storage.readFile(alias);
if (aliasFile != null) {
aliasFiles.add(aliasFile);
}
@@ -61,8 +56,8 @@ class PluginBase extends ChangeNotifier {
}
Future<List<Variable>> loadVariables() async {
debugPrint('MUDProfile.loadVariables: $id');
final vars = await ProfileStorage.readProfileFile(id, 'vars');
debugPrint('$this loadVariables');
final vars = await storage.readFile('vars');
if (vars == null) {
return [];
}
@@ -72,13 +67,11 @@ class PluginBase extends ChangeNotifier {
}
Future<List<GameButtonSetData>> loadButtonSets() async {
debugPrint('MUDProfile.loadButtonSets: $id');
final buttonSets = await ProfileStorage.listProfileFiles(id, 'button_sets');
debugPrint('$this loadButtonSets');
final buttonSets = await storage.readDirectory('button_sets');
final buttonSetFiles = <Map<String, dynamic>>[];
for (final buttonSet in buttonSets) {
debugPrint('MUDProfile.loadButtonSets: $id/buttonSets/$buttonSet');
final buttonSetFile =
await ProfileStorage.readProfileFile(id, 'button_sets/$buttonSet');
final buttonSetFile = await storage.readFile(buttonSet);
if (buttonSetFile != null) {
buttonSetFiles.add(buttonSetFile);
}
@@ -87,7 +80,7 @@ class PluginBase extends ChangeNotifier {
}
Future<void> saveAlias(Alias alias) async {
debugPrint('MUDProfile.saveAlias: $id/aliases/${alias.id}');
debugPrint('$this saveAlias: $alias');
notifyListeners();
final idx = aliases.indexWhere((a) => a.id == alias.id);
if (idx >= 0) {
@@ -95,22 +88,21 @@ class PluginBase extends ChangeNotifier {
} else {
aliases.add(alias);
}
return ProfileStorage.writeProfileFile(
id, 'aliases/${alias.id}', alias.toJson());
return storage.writeFile('aliases/${alias.id}', alias.toJson());
}
Future<void> deleteAlias(Alias alias) async {
debugPrint('MUDProfile.deleteAlias: $id/aliases/${alias.id}');
debugPrint('$this deleteAlias: $alias');
final idx = aliases.indexWhere((a) => a.id == alias.id);
if (idx >= 0) {
aliases.removeAt(idx);
}
notifyListeners();
return ProfileStorage.deleteProfileFile(id, 'aliases/${alias.id}');
return storage.deleteFile('aliases/${alias.id}');
}
Future<void> saveTrigger(Trigger trigger) async {
debugPrint('MUDProfile.saveTrigger: $id/triggers/${trigger.id}');
debugPrint('$this saveTrigger: $trigger');
final idx = triggers.indexWhere((a) => a.id == trigger.id);
if (idx >= 0) {
triggers[idx] = trigger;
@@ -118,22 +110,21 @@ class PluginBase extends ChangeNotifier {
triggers.add(trigger);
}
notifyListeners();
return ProfileStorage.writeProfileFile(
id, 'triggers/${trigger.id}', trigger.toJson());
return storage.writeFile('triggers/${trigger.id}', trigger.toJson());
}
Future<void> deleteTrigger(Trigger trigger) async {
debugPrint('MUDProfile.deleteTrigger: $id/triggers/${trigger.id}');
debugPrint('$this deleteTrigger: $trigger');
final idx = triggers.indexWhere((a) => a.id == trigger.id);
if (idx >= 0) {
triggers.removeAt(idx);
}
notifyListeners();
return ProfileStorage.deleteProfileFile(id, 'triggers/${trigger.id}');
return storage.deleteFile('triggers/${trigger.id}');
}
Future<void> saveButtonSet(GameButtonSetData buttonSet) async {
debugPrint('MUDProfile.saveButtonSet: $id/button_sets/${buttonSet.id}');
debugPrint('$this saveButtonSet: $buttonSet');
final idx = buttonSets.indexWhere((a) => a.id == buttonSet.id);
if (idx >= 0) {
buttonSets[idx] = buttonSet;
@@ -141,22 +132,21 @@ class PluginBase extends ChangeNotifier {
buttonSets.add(buttonSet);
}
notifyListeners();
return ProfileStorage.writeProfileFile(
id, 'button_sets/${buttonSet.id}', buttonSet.toJson());
return storage.writeFile('button_sets/${buttonSet.id}', buttonSet.toJson());
}
Future<void> deleteButtonSet(GameButtonSetData buttonSet) async {
debugPrint('MUDProfile.deleteButtonSet: $id/button_sets/${buttonSet.id}');
debugPrint('$this deleteButtonSet: $buttonSet');
final idx = buttonSets.indexWhere((a) => a.id == buttonSet.id);
if (idx >= 0) {
buttonSets.removeAt(idx);
}
notifyListeners();
return ProfileStorage.deleteProfileFile(id, 'button_sets/${buttonSet.id}');
return storage.deleteFile('button_sets/${buttonSet.id}');
}
Future<void> saveVariable(List<Variable> current, Variable update) async {
debugPrint('MUDProfile.saveVariable: $id/vars');
debugPrint('$this saveVariable: $update');
final existing = current.indexWhere(
(v) => v.name == update.name,
);
@@ -166,15 +156,14 @@ class PluginBase extends ChangeNotifier {
current.add(update);
}
notifyListeners();
return ProfileStorage.writeProfileFile(
id,
return storage.writeFile(
'vars',
{'vars': current.map((v) => v.toJson()).toList()},
);
}
Future<void> deleteVariable(List<Variable> current, Variable update) async {
debugPrint('MUDProfile.deleteVariable: $id/vars');
debugPrint('$this deleteVariable: $update');
final existing = current.indexWhere(
(v) => v.name == update.name,
);
@@ -182,8 +171,7 @@ class PluginBase extends ChangeNotifier {
current.removeAt(existing);
}
notifyListeners();
return ProfileStorage.writeProfileFile(
id,
return storage.writeFile(
'vars',
{'vars': current.map((v) => v.toJson()).toList()},
);
@@ -221,18 +209,22 @@ class PluginBase extends ChangeNotifier {
notifyListeners();
debugPrint('ButtonSets: ${buttonSets.length}');
}
@override
String toString() {
return 'PluginBase()';
}
}
class Plugin extends PluginBase {
final String profileId;
final String id;
Plugin(this.profileId, this.id) : _storage = PluginStorage(profileId, id);
@override
String get id => _id;
IStorage<Map<String, dynamic>> get storage => _storage;
final String _id;
Plugin(this.profileId, String id)
: _id = id,
super('$profileId/$id');
final IStorage<Map<String, dynamic>> _storage;
}

View File

@@ -10,6 +10,8 @@ import 'plugin.dart';
import 'settings.dart';
class MUDProfile extends PluginBase {
final String id;
String name;
String host;
int port;
@@ -21,8 +23,13 @@ class MUDProfile extends PluginBase {
Settings settings = Settings.empty();
KeyboardShortcuts keyboardShortcuts = KeyboardShortcuts.empty();
@override
IStorage<Map<String, dynamic>> get storage => _storage;
final IStorage<Map<String, dynamic>> _storage;
MUDProfile({
required String id,
required this.id,
required this.name,
required this.host,
required this.port,
@@ -30,7 +37,7 @@ class MUDProfile extends PluginBase {
this.username = '',
this.password = '',
this.authMethod = AuthMethod.none,
}) : super(id);
}) : _storage = ProfileStorage(id);
factory MUDProfile.empty() => MUDProfile(
id: uuid(),
@@ -96,8 +103,7 @@ class MUDProfile extends PluginBase {
Future<KeyboardShortcuts> loadKeyboardShortcuts() async {
debugPrint('MUDProfile.loadKeyboardShortcuts: $id');
final shortcuts =
await ProfileStorage.readProfileFile(id, 'keyboard_shortcuts');
final shortcuts = await storage.readFile('keyboard_shortcuts');
debugPrint('MUDProfile.loadKeyboardShortcuts: $shortcuts');
if (shortcuts == null) {
return KeyboardShortcuts.empty();
@@ -109,8 +115,7 @@ class MUDProfile extends PluginBase {
debugPrint('MUDProfile.saveKeyboardShortcuts: $id');
keyboardShortcuts = shortcuts;
notifyListeners();
return ProfileStorage.writeProfileFile(
id, 'keyboard_shortcuts', shortcuts.toJson());
return storage.writeFile('keyboard_shortcuts', shortcuts.toJson());
}
Future<void> getKeyboardShortcuts() async {
@@ -122,7 +127,7 @@ class MUDProfile extends PluginBase {
Future<Settings> loadSettings() async {
debugPrint('MUDProfile.loadSettings');
final settings = await ProfileStorage.readProfileFile(id, 'settings');
final settings = await storage.readFile('settings');
debugPrint('MUDProfile.loadSettings: $settings');
if (settings == null) {
return Settings.empty();
@@ -141,13 +146,12 @@ class MUDProfile extends PluginBase {
debugPrint('MUDProfile.saveSettings');
this.settings = settings;
notifyListeners();
return ProfileStorage.writeProfileFile(id, 'settings', settings.toJson());
return storage.writeFile('settings', settings.toJson());
}
static Future<void> save(MUDProfile profile) async {
debugPrint('MUDProfile.save: ${profile.id}');
return ProfileStorage.writeProfileFile(
profile.id, profile.id, (profile.toJson()));
Future<void> save() async {
debugPrint('MUDProfile.save: ${id}');
return storage.writeFile(id, toJson());
}
static final encKey = enc.Key.fromUtf8(pwdKey);
@@ -159,7 +163,6 @@ class MUDProfile extends PluginBase {
return '';
}
final encrypted = encrypter.encrypt(password, iv: iv);
// debugPrint('MUDProfile.encrypt: $password -> ${encrypted.base64}');
return '${encrypted.base64}:${iv.base64}';
}
@@ -171,10 +174,8 @@ class MUDProfile extends PluginBase {
final ivStr = password.substring(password.indexOf(':') + 1);
final iv = enc.IV.fromBase64(ivStr);
password = password.substring(0, password.indexOf(':'));
// debugPrint('MUDProfile.decrypt: $password');
final encrypted = enc.Encrypted.fromBase64(password);
final decrypted = encrypter.decrypt(encrypted, iv: iv);
// debugPrint('MUDProfile.decrypt: $decrypted');
return decrypted;
} catch (e, stack) {
debugPrint('MUDProfile.decrypt: $e$lf$stack');

View File

@@ -6,15 +6,30 @@ import 'package:path/path.dart' as path;
import 'platform_utils.dart';
class FileStorage {
static late final String base;
abstract class IStorage<T> {
Future<void> init();
Future<T?> readFile(String filename);
Future<void> writeFile(String filename, T data);
Future<void> deleteFile(String filename);
Future<List<String>> readDirectory(String directory);
Future<void> deleteDirectory(String directory);
}
static Future<void> init() async {
base = await PlatformUtils.getStorageBasePath();
debugPrint('Storage base: $base');
class FileStorage<T> implements IStorage<T> {
late String base;
final String _base;
FileStorage({String? base}) : _base = base ?? '';
@override
Future<void> init() async {
base = path.join(await PlatformUtils.getStorageBasePath(), _base);
debugPrint('init: $this');
}
static Future<String?> readFile(String filename) async {
@override
Future<T?> readFile(String filename) async {
debugPrint('Getting file: $filename');
final file = File(path.join(base, filename));
var exists = await file.exists();
@@ -22,120 +37,134 @@ class FileStorage {
debugPrint('File does not exist: $filename');
return null;
}
return file.readAsString();
return file.readAsString() as Future<T>;
}
static Future<void> writeFile(String filename, String data) async {
@override
Future<void> writeFile(String filename, T data) async {
debugPrint(
'Setting file: $filename, data: ${data.toString().length} bytes');
final file = File(path.join(base, filename));
await file.create(recursive: true);
await file.writeAsString(data);
if (T == String) {
await file.writeAsString(data as String);
return;
} else if (T == List<int>) {
await file.writeAsBytes(data as List<int>);
return;
}
throw Exception('Unsupported type: $T');
}
static Future<void> deleteFile(String filename) async {
@override
Future<void> deleteFile(String filename) async {
debugPrint('Deleting file: $filename');
final file = File(path.join(base, filename));
await file.delete();
}
static Future<List<String>> listDirectoryFiles(
String collection,
@override
Future<List<String>> readDirectory(
String directory,
) async {
final dir = Directory(path.join(base, collection));
final dir = Directory(path.join(base, directory));
var exists = await dir.exists();
if (!exists) {
debugPrint('Directory does not exist: $collection');
debugPrint('Directory does not exist: $dir');
return [];
}
// TODO use absolute paths?
final list = await dir.list().map((e) => path.basename(e.path)).toList();
debugPrint('Listing directory: $collection, $list');
debugPrint('Base: $base');
debugPrint('Listing directory: $dir, $list');
return list;
}
static Future<void> deleteDirectory(String collection) async {
debugPrint('Clearing collection: $collection');
final dir = Directory(path.join(base, collection));
@override
Future<void> deleteDirectory(String directory) async {
debugPrint('Clearing directory: $directory');
final dir = Directory(path.join(base, directory));
await dir.delete(recursive: true);
}
@override
String toString() => 'FileStorage(base: $base)';
}
class JsonStorage {
static const encoder = JsonEncoder.withIndent(' ');
static const decoder = JsonDecoder();
class JsonStorage implements IStorage<Map<String, dynamic>> {
JsonStorage({String? base}) : _storage = FileStorage(base: base);
static Future<Map<String, dynamic>?> readFile(String filename) async {
final data = await FileStorage.readFile('$filename.json');
final FileStorage<String> _storage;
final encoder = const JsonEncoder.withIndent(' ');
final decoder = const JsonDecoder();
@override
Future<void> init() {
return _storage.init();
}
String get base => _storage.base;
@override
Future<Map<String, dynamic>?> readFile(String filename) async {
final data = await _storage.readFile('$filename.json');
return data != null ? decoder.convert(data) : null;
}
static Future<void> writeFile(
String filename, Map<String, dynamic> data) async {
@override
Future<void> writeFile(String filename, Map<String, dynamic> data) async {
final output = encoder.convert(data);
await FileStorage.writeFile('$filename.json', output);
await _storage.writeFile('$filename.json', output);
}
static Future<void> deleteFile(String filename) async {
await FileStorage.deleteFile('$filename.json');
@override
Future<void> deleteFile(String filename) async {
await _storage.deleteFile('$filename.json');
}
static Future<List<Map<String, dynamic>?>> readDirectory(
String collection,
@override
Future<List<String>> readDirectory(
String directory,
) async {
final list = await FileStorage.listDirectoryFiles(collection);
return Future.wait(
list.map((f) => readFile('$collection/${path.withoutExtension(f)}')),
);
final list = await _storage.readDirectory(directory);
return list.map((f) => '$directory/${path.withoutExtension(f)}').toList();
}
static Future<void> deleteDirectory(String collection) async {
await FileStorage.deleteDirectory(collection);
@override
Future<void> deleteDirectory(String directory) async {
await _storage.deleteDirectory(directory);
}
@override
String toString() {
return 'JsonStorage(base: $base)';
}
}
class ProfileStorage {
static const encoder = JsonEncoder.withIndent(' ');
static const decoder = JsonDecoder();
class ProfileStorage extends JsonStorage {
final String profileId;
static Future<Map<String, dynamic>?> readProfileFile(
String profile, String filename) async {
return JsonStorage.readFile('profiles/$profile/$filename');
ProfileStorage(this.profileId) : super(base: 'profiles/$profileId');
ProfileStorage._(this.profileId, String base)
: super(base: 'profiles/$profileId/$base');
@override
Future<void> init() async {
await super.init();
}
static Future<void> writeProfileFile(
String profile, String filename, dynamic data) async {
await JsonStorage.writeFile('profiles/$profile/$filename', data);
}
static Future<void> deleteProfile(String profile) async {
await JsonStorage.deleteDirectory('profiles/$profile');
}
static Future<void> deleteProfileFile(String profile, String filename) async {
await JsonStorage.deleteFile('profiles/$profile/$filename');
}
static Future<List<String>> listAllProfiles() async {
final list = await FileStorage.listDirectoryFiles('profiles');
return list
.where((f) =>
Directory(path.join(FileStorage.base, 'profiles', f)).existsSync())
.map((f) => path.withoutExtension(f))
.toList();
}
static Future<List<String>> listProfileFiles(
String profile, [
String? directory,
]) async {
final dir = directory != null ? '$profile/$directory' : profile;
final list = await FileStorage.listDirectoryFiles('profiles/$dir');
return list.map((f) => path.withoutExtension(f)).toList();
}
static Future<void> deleteAllProfiles() async {
await JsonStorage.deleteDirectory('profiles');
}
@override
String toString() => 'ProfileStorage(profileId: $profileId, base: $base)';
}
class PluginStorage extends ProfileStorage {
final String pluginId;
PluginStorage(String profileId, this.pluginId)
: super._(profileId, 'plugins/$pluginId');
@override
String toString() =>
'PluginStorage(profileId: $profileId, pluginId: $pluginId, base: $base)';
}

View File

@@ -36,6 +36,7 @@ class GameStore extends ChangeNotifier {
late final List<MUDProfile> profiles = [];
MUDProfile? _currentProfile;
bool _clientReady = false;
final storage = ProfileStorage('');
String get commandSeparator => currentProfile.settings.commandSeparator;
@@ -49,6 +50,8 @@ class GameStore extends ChangeNotifier {
Future<GameStore> init() async {
debugPrint('GameStore.init');
await storage.init();
debugPrint('storage.init $storage');
fillStockProfiles();
return this;
}
@@ -366,11 +369,11 @@ class GameStore extends ChangeNotifier {
}
void loadProfiles() async {
final list = await ProfileStorage.listAllProfiles();
final list = await storage.readDirectory('.');
profiles.clear();
debugPrint('loading profiles: $list');
for (final name in list) {
final profile = await ProfileStorage.readProfileFile(name, name);
final profile = await storage.readFile(name);
profiles.add(MUDProfile.fromJson(profile!));
}
if (_currentProfile != null) {
@@ -380,20 +383,23 @@ class GameStore extends ChangeNotifier {
}
void fillStockProfiles() async {
final list = await ProfileStorage.listAllProfiles();
final list = await storage.readDirectory('.');
debugPrint('existing profiles: $list');
if (list.isEmpty) {
debugPrint('filling stock profiles');
for (final profile in profilePresets) {
await MUDProfile.save(profile);
await profile.save();
profiles.add(profile);
}
} else {
debugPrint('loading profiles: $list');
for (final name in list) {
final profile = await ProfileStorage.readProfileFile(name, name);
final profile = await storage.readFile('$name/$name');
if (profile == null) {
continue;
}
debugPrint('profile: $profile');
profiles.add(MUDProfile.fromJson(profile!));
profiles.add(MUDProfile.fromJson(profile));
}
}
debugPrint('profiles: ${profiles.map((e) => [e.name, e.password])}');

View File

@@ -5,14 +5,12 @@ import 'package:window_manager/window_manager.dart';
import 'core/platform_utils.dart';
import 'core/routes.dart';
import 'core/storage.dart';
import 'core/storage/shared_prefs.dart';
import 'core/store.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await getPrefs();
await FileStorage.init();
await gameStore.init();
if (PlatformUtils.isDesktop) {
await windowManager.ensureInitialized();

View File

@@ -67,7 +67,7 @@ class HomeScaffold extends StatelessWidget with GameStoreMixin {
arguments: store.currentProfile,
);
if (updated != null) {
await MUDProfile.save(updated as MUDProfile);
await (updated as MUDProfile).save();
store.loadProfiles();
}
},

View File

@@ -42,8 +42,7 @@ class SelectProfilePage extends StatelessWidget with GameStoreMixin {
arguments: profile,
);
if (updated != null) {
await MUDProfile.save(
updated as MUDProfile);
await (updated as MUDProfile).save();
store.loadProfiles();
}
},
@@ -66,3 +65,4 @@ class SelectProfilePage extends StatelessWidget with GameStoreMixin {
);
}
}

View File

@@ -390,7 +390,7 @@ packages:
path: "../script_runner"
relative: true
source: path
version: "0.3.1"
version: "0.3.2"
shared_preferences:
dependency: "direct main"
description: