From 6f3a400ed834dde07cb05127ce6151139baf0b80 Mon Sep 17 00:00:00 2001 From: lifegpc Date: Wed, 30 Aug 2023 22:09:39 +0800 Subject: [PATCH] Save preferences to same directory to binary file on Windows --- .gitmodules | 3 + lib/config/base.dart | 11 +++ lib/config/shared_preferences.dart | 43 ++++++++++++ lib/config/windows.dart | 108 +++++++++++++++++++++++++++++ lib/globals.dart | 22 +++++- lib/main.dart | 2 +- lib/platform/path.dart | 28 ++++++++ pubspec.lock | 4 +- pubspec.yaml | 2 + utils | 1 + windows/runner/CMakeLists.txt | 5 ++ windows/runner/flutter_window.cpp | 34 +++++++++ 12 files changed, 257 insertions(+), 6 deletions(-) create mode 100644 .gitmodules create mode 100644 lib/config/base.dart create mode 100644 lib/config/shared_preferences.dart create mode 100644 lib/config/windows.dart create mode 100644 lib/platform/path.dart create mode 160000 utils diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..2676665 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "utils"] + path = utils + url = https://github.com/lifegpc/c-utils diff --git a/lib/config/base.dart b/lib/config/base.dart new file mode 100644 index 0000000..30c7574 --- /dev/null +++ b/lib/config/base.dart @@ -0,0 +1,11 @@ +abstract interface class Config { + Future clear(); + bool containsKey(String key); + Object? get(String key); + String? getString(String key); + Future setString(String key, String value); + int? getInt(String key); + Future setInt(String key, int value); + bool? getBool(String key); + Future setBool(String key, bool value); +} diff --git a/lib/config/shared_preferences.dart b/lib/config/shared_preferences.dart new file mode 100644 index 0000000..b580ee0 --- /dev/null +++ b/lib/config/shared_preferences.dart @@ -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 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 setString(String key, String value) { + return _prefs.setString(key, value); + } + @override + int? getInt(String key) { + return _prefs.getInt(key); + } + @override + Future setInt(String key, int value) { + return _prefs.setInt(key, value); + } + @override + bool? getBool(String key) { + return _prefs.getBool(key); + } + @override + Future setBool(String key, bool value) { + return _prefs.setBool(key, value); + } +} diff --git a/lib/config/windows.dart b/lib/config/windows.dart new file mode 100644 index 0000000..dd12849 --- /dev/null +++ b/lib/config/windows.dart @@ -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? _cachedPreferences; + File? _filePath; + Future 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> reload() async { + Map preferences = {}; + 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(); + } + } + } + _cachedPreferences = preferences; + return preferences; + } + + Future> _readPreferences() async { + return _cachedPreferences ?? await reload(); + } + + Future _writePreferences(Map 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 clear() async { + final Map preferences = await _readPreferences(); + preferences.clear(); + return _writePreferences(preferences); + } + + Future setValue(String key, Object value) async { + final Map 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 setString(String key, String value) { + return setValue(key, value); + } + + @override + int? getInt(String key) => _cachedPreferences?[key] as int?; + + @override + Future setInt(String key, int value) { + return setValue(key, value); + } + + @override + bool? getBool(String key) => _cachedPreferences?[key] as bool?; + + @override + Future setBool(String key, bool value) { + return setValue(key, value); + } +} diff --git a/lib/globals.dart b/lib/globals.dart index 327cdae..831545d 100644 --- a/lib/globals.dart +++ b/lib/globals.dart @@ -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 prepareJar() async { @@ -28,10 +33,20 @@ Future prepareJar() async { } Future 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(); diff --git a/lib/main.dart b/lib/main.dart index e66e624..ae5135b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -42,6 +42,7 @@ Future 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(); diff --git a/lib/platform/path.dart b/lib/platform/path.dart new file mode 100644 index 0000000..279fd3e --- /dev/null +++ b/lib/platform/path.dart @@ -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 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; + } +} diff --git a/pubspec.lock b/pubspec.lock index 85f94cf..5cee718 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -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" diff --git a/pubspec.yaml b/pubspec.yaml index 4f1a1ab..9ce7c2b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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 diff --git a/utils b/utils new file mode 160000 index 0000000..63992e1 --- /dev/null +++ b/utils @@ -0,0 +1 @@ +Subproject commit 63992e1238955f750b160c6619cd01d10cc5bc17 diff --git a/windows/runner/CMakeLists.txt b/windows/runner/CMakeLists.txt index 394917c..24483a2 100644 --- a/windows/runner/CMakeLists.txt +++ b/windows/runner/CMakeLists.txt @@ -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. diff --git a/windows/runner/flutter_window.cpp b/windows/runner/flutter_window.cpp index b25e363..869c001 100644 --- a/windows/runner/flutter_window.cpp +++ b/windows/runner/flutter_window.cpp @@ -1,9 +1,19 @@ #include "flutter_window.h" +#include +#include +#include +#include +#include +#include #include #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> 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([&]() {