prasule/lib/api/wallet_manager.dart

220 lines
6.5 KiB
Dart

// SPDX-FileCopyrightText: (C) 2024 Matyáš Caras
//
// SPDX-License-Identifier: AGPL-3.0-only
import 'dart:convert';
import 'dart:io';
import 'package:archive/archive_io.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_file_dialog/flutter_file_dialog.dart';
import 'package:intl/intl.dart';
import 'package:path_provider/path_provider.dart';
import 'package:prasule/api/wallet.dart';
import 'package:prasule/main.dart';
/// Used for [Wallet]-managing operations
class WalletManager {
/// Currently selected wallet
static Wallet? selectedWallet;
/// Path to the directory with wallet files
///
/// Saved beforehand so we don't have to use async everywhere
static late String walletPath;
/// Returns a list of all [Wallet]s
static List<Wallet> listWallets() {
final path = Directory(walletPath);
if (!path.existsSync()) {
path.createSync();
}
final wallets = <Wallet>[];
for (final w in path
.listSync()
.whereType<File>()
.where((e) => e.path.endsWith(".json"))
.map((e) => e.path.split("/").last)
.toList()) {
try {
wallets.add(loadWallet(w));
} catch (e) {
logger.e(e);
// TODO: do something with unreadable wallets
}
}
return wallets;
}
/// Deletes all [Wallet]s
static void deleteAllData() {
final path = Directory(walletPath);
if (!path.existsSync()) {
return;
}
for (final entry in path.listSync()) {
logger.d("Deleting ${entry.path}");
entry.deleteSync();
}
}
/// Creates a ZIP archive from all wallets
static Future<void> exportAllWallets() async {
if (kIsWeb) {
// TODO
return;
}
final archive = Archive();
for (final w in Directory(
"${(await getApplicationDocumentsDirectory()).path}/wallets",
).listSync()) {
if (w is! File) continue;
logger.i("Zipping ${w.path.split("/").last}");
final wf = w;
archive.addFile(
ArchiveFile.stream(
wf.path.split("/").last,
wf.lengthSync(),
InputFileStream(wf.path),
),
);
}
if (!await FlutterFileDialog.isPickDirectorySupported()) {
File(
"${(await getApplicationDocumentsDirectory()).path}/export_${DateFormat("dd_MM_yyyy").format(DateTime.now())}.zip",
).writeAsBytesSync(ZipEncoder().encode(archive) ?? []);
return;
}
final dir = await FlutterFileDialog.pickDirectory();
if (dir == null) return;
await FlutterFileDialog.saveFileToDirectory(
directory: dir,
data: Uint8List.fromList(ZipEncoder().encode(archive) ?? []),
fileName: "export_${DateFormat("dd_MM_yyyy").format(DateTime.now())}.zip",
mimeType: "application/zip",
);
}
/// Exports a single [Wallet]
static Future<void> exportWallet({Wallet? wallet, String? name}) async {
if (wallet == null && name == null) {
throw Exception("You need to specify either a wallet or a name");
}
final n = name ?? wallet!.name;
if (!await FlutterFileDialog.isPickDirectorySupported()) {
File("${(await getApplicationDocumentsDirectory()).path}/wallets/$n")
.copySync(
"${await getApplicationDocumentsDirectory()}/export_${n.replaceAll(RegExp('[|\\?*<":>+[]/\' ]+'), '_')}_${DateFormat("dd_MM_yyyy").format(DateTime.now())}.json",
);
return;
}
final dir = await FlutterFileDialog.pickDirectory();
if (dir == null) return;
await FlutterFileDialog.saveFileToDirectory(
directory: dir,
data:
File("${(await getApplicationDocumentsDirectory()).path}/wallets/$n")
.readAsBytesSync(),
fileName:
"export_${n.replaceAll(RegExp('[|\\?*<":>+[]/\' ]+'), '_')}_${DateFormat("dd_MM_yyyy").format(DateTime.now())}.json",
mimeType: "application/json",
);
}
/// Import a single wallet
static Future<void> importWallet({String? data}) async {
var d = data ?? "";
if (data == null) {
final filePath = await FlutterFileDialog.pickFile(
params: const OpenFileDialogParams(
mimeTypesFilter: ["application/json"],
fileExtensionsFilter: ["json"],
),
);
if (filePath == null) return;
d = File(filePath).readAsStringSync();
}
final w = Wallet.fromJson(jsonDecode(d) as Map<String, dynamic>);
if (WalletManager.exists(w.name)) {
throw Exception("Wallet already exists!");
}
WalletManager.saveWallet(
w,
);
}
/// Imports wallets from a ZIP archive
static Future<void> importArchive() async {
final filePath = await FlutterFileDialog.pickFile(
params: const OpenFileDialogParams(
mimeTypesFilter: ["application/zip"],
fileExtensionsFilter: ["zip"],
),
);
if (filePath == null) return;
if (kIsWeb) {
// TODO
return;
}
final temp = Directory("${(await getTemporaryDirectory()).path}/data");
if (temp.existsSync()) {
temp.deleteSync();
}
temp.createSync(recursive: true);
final archive = ZipDecoder().decodeBuffer(InputFileStream(filePath));
for (final file in archive.files) {
if (!file.isFile) {
logger.d(file.name);
continue;
}
file.writeContent(OutputFileStream("${temp.path}/${file.name}"));
}
for (final e in temp.listSync()) {
logger.d(e.path);
if (e is! File) continue;
await importWallet(data: e.readAsStringSync());
}
}
/// Loads and returns a single [Wallet] by name
static Wallet loadWallet(String name) {
final path = Directory(walletPath);
final wallet = File("${path.path}/$name");
if (!path.existsSync()) {
path.createSync();
}
if (!wallet.existsSync()) {
throw Exception("Wallet does not exist");
}
return Wallet.fromJson(
jsonDecode(wallet.readAsStringSync()) as Map<String, dynamic>,
);
}
/// Converts [Wallet] to JSON and saves it to AppData
static void saveWallet(Wallet w) {
final path = Directory(walletPath);
final wallet = File("${path.path}/${w.name}");
if (!path.existsSync()) {
path.createSync();
}
// if (!wallet.existsSync()) return false;
wallet.writeAsStringSync(jsonEncode(w.toJson()));
}
/// Deletes the corresponding [Wallet] file
static void deleteWallet(Wallet w) {
final path = Directory(walletPath);
File("${path.path}/${w.name}").deleteSync();
}
/// Checks if the wallet exists
static bool exists(String name) {
return File(
"$walletPath/$name",
).existsSync();
}
}