Save preferences to same directory to binary file on Windows

This commit is contained in:
2023-08-30 22:09:39 +08:00
parent 63b5e9f71e
commit 6f3a400ed8
12 changed files with 257 additions and 6 deletions

3
.gitmodules vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule "utils"]
path = utils
url = https://github.com/lifegpc/c-utils

11
lib/config/base.dart Normal file
View File

@@ -0,0 +1,11 @@
abstract interface class Config {
Future<bool> clear();
bool containsKey(String key);
Object? get(String key);
String? getString(String key);
Future<bool> setString(String key, String value);
int? getInt(String key);
Future<bool> setInt(String key, int value);
bool? getBool(String key);
Future<bool> setBool(String key, bool value);
}

View File

@@ -0,0 +1,43 @@
import 'base.dart';
import 'package:shared_preferences/shared_preferences.dart';
class SharedPreferencesConfig implements Config {
SharedPreferencesConfig(this._prefs);
final SharedPreferences _prefs;
@override
Future<bool> clear() {
return _prefs.clear();
}
@override
bool containsKey(String key) {
return _prefs.containsKey(key);
}
@override
Object? get(String key) {
return _prefs.get(key);
}
@override
String? getString(String key) {
return _prefs.getString(key);
}
@override
Future<bool> setString(String key, String value) {
return _prefs.setString(key, value);
}
@override
int? getInt(String key) {
return _prefs.getInt(key);
}
@override
Future<bool> setInt(String key, int value) {
return _prefs.setInt(key, value);
}
@override
bool? getBool(String key) {
return _prefs.getBool(key);
}
@override
Future<bool> setBool(String key, bool value) {
return _prefs.setBool(key, value);
}
}

108
lib/config/windows.dart Normal file
View File

@@ -0,0 +1,108 @@
import 'dart:convert' show json;
import 'package:file/file.dart';
import 'package:file/local.dart';
import 'package:logging/logging.dart';
import 'package:path/path.dart' as path;
import 'base.dart';
import '../globals.dart';
final _log = Logger("WindowsConfig");
class WindowsConfig implements Config {
WindowsConfig();
FileSystem fs = const LocalFileSystem();
Map<String, Object>? _cachedPreferences;
File? _filePath;
Future<File?> filePath() async {
if (_filePath != null) return _filePath;
final String? exe = await platformPath.getCurrentExe();
if (exe == null) return null;
final dir = path.dirname(exe);
final name = path.basenameWithoutExtension(exe);
return _filePath = fs.file(path.join(dir, "$name.json"));
}
Future<Map<String, Object>> reload() async {
Map<String, Object> preferences = <String, Object>{};
final File? localDataFile = await filePath();
if (localDataFile != null && localDataFile.existsSync()) {
final String stringMap = localDataFile.readAsStringSync();
if (stringMap.isNotEmpty) {
final Object? data = json.decode(stringMap);
if (data is Map) {
preferences = data.cast<String, Object>();
}
}
}
_cachedPreferences = preferences;
return preferences;
}
Future<Map<String, Object>> _readPreferences() async {
return _cachedPreferences ?? await reload();
}
Future<bool> _writePreferences(Map<String, Object> preferences) async {
try {
final File? localDataFile = await filePath();
if (localDataFile == null) {
_log.warning('Unable to determine where to write preferences.');
return false;
}
if (!localDataFile.existsSync()) {
localDataFile.createSync(recursive: true);
}
final String stringMap = json.encode(preferences);
localDataFile.writeAsStringSync(stringMap);
} catch (e) {
_log.severe('Error saving preferences to disk: ', e);
return false;
}
return true;
}
@override
Future<bool> clear() async {
final Map<String, Object> preferences = await _readPreferences();
preferences.clear();
return _writePreferences(preferences);
}
Future<bool> setValue(String key, Object value) async {
final Map<String, Object> preferences = await _readPreferences();
preferences[key] = value;
return _writePreferences(preferences);
}
@override
bool containsKey(String key) {
return _cachedPreferences?.containsKey(key) ?? false;
}
@override
Object? get(String key) => _cachedPreferences?[key];
@override
String? getString(String key) => _cachedPreferences?[key] as String?;
@override
Future<bool> setString(String key, String value) {
return setValue(key, value);
}
@override
int? getInt(String key) => _cachedPreferences?[key] as int?;
@override
Future<bool> setInt(String key, int value) {
return setValue(key, value);
}
@override
bool? getBool(String key) => _cachedPreferences?[key] as bool?;
@override
Future<bool> setBool(String key, bool value) {
return setValue(key, value);
}
}

View File

@@ -9,13 +9,18 @@ import 'package:path_provider/path_provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'api/client.dart';
import 'auth.dart';
import 'config/base.dart';
import 'config/shared_preferences.dart';
import 'config/windows.dart';
import 'platform/path.dart';
import 'utils.dart';
final dio = Dio()
..options.validateStatus = (int? _) {
return true;
}
..options.extra['withCredentials'] = true;
SharedPreferences? _prefs;
Config? _prefs;
EHApi? _api;
Future<void> prepareJar() async {
@@ -28,10 +33,20 @@ Future<void> prepareJar() async {
}
Future<void> preparePrefs() async {
_prefs = await SharedPreferences.getInstance();
if (isWindows) {
try {
var tmp = WindowsConfig();
tmp.reload();
_prefs = tmp;
return;
} catch (e) {
// Do nothing.
}
}
_prefs = SharedPreferencesConfig(await SharedPreferences.getInstance());
}
SharedPreferences get prefs {
Config get prefs {
if (_prefs == null) {
throw Exception('SharedPreferences not initialized');
}
@@ -77,3 +92,4 @@ EHApi get api {
}
final AuthInfo auth = AuthInfo();
final Path platformPath = Path();

View File

@@ -42,6 +42,7 @@ Future<void> initLogger() async {
}
void main() async {
if (!kIsWeb) WidgetsFlutterBinding.ensureInitialized();
bool? usePathUrl = const bool.fromEnvironment("usePathUrl");
if (usePathUrl == true && kIsWeb) {
usePathUrlStrategy();
@@ -49,7 +50,6 @@ void main() async {
if (!kIsWeb) await prepareJar();
await preparePrefs();
if (isDesktop) {
WidgetsFlutterBinding.ensureInitialized();
await windowManager.ensureInitialized();
}
await initLogger();

28
lib/platform/path.dart Normal file
View File

@@ -0,0 +1,28 @@
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:logging/logging.dart';
import '../utils.dart';
final Logger _log = Logger("platformPath");
class Path {
static const platform = MethodChannel("lifegpc.eh_downloader_flutter/path");
String? _currentExe;
bool _currentExeLoaded = false;
String? get currentExe => _currentExe;
Future<String?> getCurrentExe() async {
if (_currentExeLoaded) return _currentExe;
try {
final String result = await platform.invokeMethod("getCurrentExe");
_currentExe = result;
} on PlatformException catch (e) {
if (isWindows) {
_log.warning("Failed to get current exe path:", e);
}
}
_currentExeLoaded = true;
return _currentExe;
}
}

View File

@@ -234,7 +234,7 @@ packages:
source: hosted
version: "2.1.0"
file:
dependency: transitive
dependency: "direct main"
description:
name: file
sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d"
@@ -438,7 +438,7 @@ packages:
source: hosted
version: "2.1.0"
path:
dependency: transitive
dependency: "direct main"
description:
name: path
sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917"

View File

@@ -12,6 +12,7 @@ dependencies:
cryptography_flutter: ^2.3.0
dio: ^5.3.2
dio_cookie_manager: ^3.1.0+1
file: ^6.1.4
flutter:
sdk: flutter
flutter_hooks: ^0.20.0
@@ -21,6 +22,7 @@ dependencies:
intl: any
json_annotation: ^4.8.1
logging: ^1.2.0
path: ^1.8.3
path_provider: ^2.1.0
retrofit: ^4.0.1
shared_preferences: ^2.2.0

1
utils Submodule

Submodule utils added at 63992e1238

View File

@@ -1,6 +1,10 @@
cmake_minimum_required(VERSION 3.14)
project(runner LANGUAGES CXX)
set(ENABLE_ICONV OFF CACHE BOOL "Libiconv is not needed.")
add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/../../utils" "${CMAKE_BINARY_DIR}/utils")
include_directories("${CMAKE_CURRENT_SOURCE_DIR}/../../utils")
# Define the application target. To change its name, change BINARY_NAME in the
# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer
# work.
@@ -34,6 +38,7 @@ target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX")
# dependencies here.
target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app)
target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib")
target_link_libraries(${BINARY_NAME} PRIVATE utils)
target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}")
# Run the Flutter tool portions of the build. This must not be removed.

View File

@@ -1,9 +1,19 @@
#include "flutter_window.h"
#include <flutter/event_channel.h>
#include <flutter/event_sink.h>
#include <flutter/event_stream_handler_functions.h>
#include <flutter/method_channel.h>
#include <flutter/standard_method_codec.h>
#include <windows.h>
#include <optional>
#include "flutter/generated_plugin_registrant.h"
#include "wchar_util.h"
#define MAX_PATH_SIZE 32768
FlutterWindow::FlutterWindow(const flutter::DartProject& project)
: project_(project) {}
@@ -25,6 +35,30 @@ bool FlutterWindow::OnCreate() {
return false;
}
RegisterPlugins(flutter_controller_->engine());
flutter::MethodChannel<> channel(
flutter_controller_->engine()->messenger(), "lifegpc.eh_downloader_flutter/path",
&flutter::StandardMethodCodec::GetInstance());
channel.SetMethodCallHandler(
[](const flutter::MethodCall<>& call,
std::unique_ptr<flutter::MethodResult<>> result) {
if (call.method_name() == "getCurrentExe") {
std::string current;
wchar_t tmp[MAX_PATH_SIZE];
if (!GetModuleFileNameW(nullptr, tmp, MAX_PATH_SIZE)) {
result->Error("UNAVAILABLE", "Failed to get module file name.");
return;
}
if (!wchar_util::wstr_to_str(current, tmp, CP_UTF8)) {
result->Error("UNAVAILABLE", "Failed to convert module file name to UTF-8.");
return;
}
result->Success(current);
} else {
result->NotImplemented();
}
});
SetChildContent(flutter_controller_->view()->GetNativeWindow());
flutter_controller_->engine()->SetNextFrameCallback([&]() {