mirror of
https://github.com/lifegpc/eh_downloader_flutter.git
synced 2026-07-08 01:30:28 +08:00
Add download zip page
This commit is contained in:
@@ -162,6 +162,14 @@ abstract class _EHApi {
|
|||||||
@GET('/tag/rows')
|
@GET('/tag/rows')
|
||||||
Future<ApiResult<List<Tag>>> getRowTags(
|
Future<ApiResult<List<Tag>>> getRowTags(
|
||||||
{@CancelRequest() CancelToken? cancel});
|
{@CancelRequest() CancelToken? cancel});
|
||||||
|
|
||||||
|
@GET('/export/gallery/zip/{gid}')
|
||||||
|
@DioResponseType(ResponseType.stream)
|
||||||
|
Future<HttpResponse> exportGalleryZip(@Path("gid") int gid,
|
||||||
|
{@Query("jpn_title") bool? jpnTitle,
|
||||||
|
@Query("max_length") int? maxLength,
|
||||||
|
@Query("export_ad") bool? exportAd,
|
||||||
|
@CancelRequest() CancelToken? cancel});
|
||||||
}
|
}
|
||||||
|
|
||||||
class EHApi extends __EHApi {
|
class EHApi extends __EHApi {
|
||||||
|
|||||||
@@ -647,6 +647,47 @@ class __EHApi implements _EHApi {
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<HttpResponse<dynamic>> exportGalleryZip(
|
||||||
|
int gid, {
|
||||||
|
bool? jpnTitle,
|
||||||
|
int? maxLength,
|
||||||
|
bool? exportAd,
|
||||||
|
CancelToken? cancel,
|
||||||
|
}) async {
|
||||||
|
const _extra = <String, dynamic>{};
|
||||||
|
final queryParameters = <String, dynamic>{
|
||||||
|
r'jpn_title': jpnTitle,
|
||||||
|
r'max_length': maxLength,
|
||||||
|
r'export_ad': exportAd,
|
||||||
|
};
|
||||||
|
queryParameters.removeWhere((k, v) => v == null);
|
||||||
|
final _headers = <String, dynamic>{};
|
||||||
|
final Map<String, dynamic>? _data = null;
|
||||||
|
final _result =
|
||||||
|
await _dio.fetch(_setStreamType<HttpResponse<dynamic>>(Options(
|
||||||
|
method: 'GET',
|
||||||
|
headers: _headers,
|
||||||
|
extra: _extra,
|
||||||
|
responseType: ResponseType.stream,
|
||||||
|
)
|
||||||
|
.compose(
|
||||||
|
_dio.options,
|
||||||
|
'/export/gallery/zip/${gid}',
|
||||||
|
queryParameters: queryParameters,
|
||||||
|
data: _data,
|
||||||
|
cancelToken: cancel,
|
||||||
|
)
|
||||||
|
.copyWith(
|
||||||
|
baseUrl: _combineBaseUrls(
|
||||||
|
_dio.options.baseUrl,
|
||||||
|
baseUrl,
|
||||||
|
))));
|
||||||
|
final value = _result.data;
|
||||||
|
final httpResponse = HttpResponse(value, _result);
|
||||||
|
return httpResponse;
|
||||||
|
}
|
||||||
|
|
||||||
RequestOptions _setStreamType<T>(RequestOptions requestOptions) {
|
RequestOptions _setStreamType<T>(RequestOptions requestOptions) {
|
||||||
if (T != dynamic &&
|
if (T != dynamic &&
|
||||||
!(requestOptions.responseType == ResponseType.bytes ||
|
!(requestOptions.responseType == ResponseType.bytes ||
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
import '../api/gallery.dart';
|
import '../api/gallery.dart';
|
||||||
import 'thumbnail.dart';
|
import 'thumbnail.dart';
|
||||||
|
|
||||||
@@ -40,7 +41,9 @@ class GalleryBasicInfo extends StatelessWidget {
|
|||||||
onPressed: null,
|
onPressed: null,
|
||||||
child: Text(AppLocalizations.of(context)!.read)),
|
child: Text(AppLocalizations.of(context)!.read)),
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: null,
|
onPressed: () {
|
||||||
|
context.push('/dialog/download/zip/${gMeta.gid}');
|
||||||
|
},
|
||||||
child: Text(AppLocalizations.of(context)!.download)),
|
child: Text(AppLocalizations.of(context)!.download)),
|
||||||
]))
|
]))
|
||||||
]));
|
]));
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import '../api/gallery.dart';
|
import '../api/gallery.dart';
|
||||||
import '../main.dart';
|
import '../main.dart';
|
||||||
@@ -115,7 +116,17 @@ class GalleryInfoDesktop extends StatelessWidget {
|
|||||||
const VerticalDivider(indent: 10, endIndent: 10),
|
const VerticalDivider(indent: 10, endIndent: 10),
|
||||||
Expanded(child: TagsPanel(gData.tags)),
|
Expanded(child: TagsPanel(gData.tags)),
|
||||||
const VerticalDivider(indent: 10, endIndent: 10),
|
const VerticalDivider(indent: 10, endIndent: 10),
|
||||||
SizedBox(width: 150, child: Column(children: [])),
|
SizedBox(
|
||||||
|
width: 150,
|
||||||
|
child: Column(children: [
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
context.push(
|
||||||
|
'/dialog/download/zip/${gData.meta.gid}');
|
||||||
|
},
|
||||||
|
child: Text(
|
||||||
|
AppLocalizations.of(context)!.download)),
|
||||||
|
])),
|
||||||
])),
|
])),
|
||||||
],
|
],
|
||||||
))
|
))
|
||||||
|
|||||||
40
lib/dialog/dialog_page.dart
Normal file
40
lib/dialog/dialog_page.dart
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class DialogPage<T> extends Page<T> {
|
||||||
|
final Offset? anchorPoint;
|
||||||
|
final Color? barrierColor;
|
||||||
|
final bool barrierDismissible;
|
||||||
|
final String? barrierLabel;
|
||||||
|
final bool useSafeArea;
|
||||||
|
final CapturedThemes? themes;
|
||||||
|
final WidgetBuilder builder;
|
||||||
|
|
||||||
|
const DialogPage({
|
||||||
|
required this.builder,
|
||||||
|
this.anchorPoint,
|
||||||
|
this.barrierColor = Colors.black87,
|
||||||
|
this.barrierDismissible = true,
|
||||||
|
this.barrierLabel,
|
||||||
|
this.useSafeArea = true,
|
||||||
|
this.themes,
|
||||||
|
super.key,
|
||||||
|
super.name,
|
||||||
|
super.arguments,
|
||||||
|
super.restorationId,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Route<T> createRoute(BuildContext context) => DialogRoute<T>(
|
||||||
|
context: context,
|
||||||
|
settings: this,
|
||||||
|
builder: (context) => Dialog(
|
||||||
|
child: builder(context),
|
||||||
|
),
|
||||||
|
anchorPoint: anchorPoint,
|
||||||
|
barrierColor: barrierColor,
|
||||||
|
barrierDismissible: barrierDismissible,
|
||||||
|
barrierLabel: barrierLabel,
|
||||||
|
useSafeArea: useSafeArea,
|
||||||
|
themes: themes,
|
||||||
|
);
|
||||||
|
}
|
||||||
117
lib/dialog/download_zip_page.dart
Normal file
117
lib/dialog/download_zip_page.dart
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
|
import '../globals.dart';
|
||||||
|
import '../utils/download_zip.dart';
|
||||||
|
|
||||||
|
final _log = Logger("DownloadZipPage");
|
||||||
|
|
||||||
|
class DownloadZipPage extends StatefulWidget {
|
||||||
|
const DownloadZipPage(this.gid, {Key? key}) : super(key: key);
|
||||||
|
final int gid;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<DownloadZipPage> createState() => _DownloadZipPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DownloadZipPage extends State<DownloadZipPage> {
|
||||||
|
bool useTitleJpn = false;
|
||||||
|
bool exportAd = false;
|
||||||
|
String maxLength = "";
|
||||||
|
final _formKey = GlobalKey<FormState>();
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
useTitleJpn = prefs.getBool("useTitleJpn") ?? false;
|
||||||
|
exportAd = prefs.getBool("exportAd") ?? false;
|
||||||
|
maxLength = prefs.getInt("maxZipFilenameLength")?.toString() ?? "";
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final i18n = AppLocalizations.of(context)!;
|
||||||
|
final maxWidth = MediaQuery.of(context).size.width;
|
||||||
|
return Container(
|
||||||
|
padding: maxWidth < 400
|
||||||
|
? const EdgeInsets.symmetric(vertical: 20, horizontal: 5)
|
||||||
|
: const EdgeInsets.all(20),
|
||||||
|
width: maxWidth < 810 ? null : 800,
|
||||||
|
decoration: BoxDecoration(borderRadius: BorderRadius.circular(10)),
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: Form(
|
||||||
|
key: _formKey,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
i18n.downloadAsZip,
|
||||||
|
style: Theme.of(context).textTheme.headlineSmall,
|
||||||
|
))),
|
||||||
|
IconButton(
|
||||||
|
onPressed: () => context.canPop()
|
||||||
|
? context.pop()
|
||||||
|
: context.go("/gallery/${widget.gid}"),
|
||||||
|
icon: const Icon(Icons.close),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
CheckboxMenuButton(
|
||||||
|
value: useTitleJpn,
|
||||||
|
onChanged: (u) {
|
||||||
|
if (u != null) {
|
||||||
|
setState(() {
|
||||||
|
useTitleJpn = u!;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Text(i18n.useTitleJpn)),
|
||||||
|
CheckboxMenuButton(
|
||||||
|
value: exportAd,
|
||||||
|
onChanged: (u) {
|
||||||
|
if (u != null) {
|
||||||
|
setState(() {
|
||||||
|
exportAd = u!;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Text(i18n.exportAd)),
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||||
|
child: TextFormField(
|
||||||
|
initialValue: maxLength,
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
inputFormatters: [
|
||||||
|
FilteringTextInputFormatter.digitsOnly
|
||||||
|
],
|
||||||
|
onChanged: (v) {
|
||||||
|
setState(() {
|
||||||
|
maxLength = v;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
decoration: InputDecoration(
|
||||||
|
border: const OutlineInputBorder(),
|
||||||
|
labelText: i18n.maxZipFilenameLength),
|
||||||
|
)),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
downloadZip(widget.gid,
|
||||||
|
jpnTitle: useTitleJpn,
|
||||||
|
exportAd: exportAd,
|
||||||
|
maxLength: int.tryParse(maxLength))
|
||||||
|
.catchError((err) {
|
||||||
|
_log.warning("Failed to download zip:", err);
|
||||||
|
});
|
||||||
|
context.canPop()
|
||||||
|
? context.pop()
|
||||||
|
: context.go("/gallery/${widget.gid}");
|
||||||
|
},
|
||||||
|
child: Text(i18n.download))
|
||||||
|
],
|
||||||
|
))));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -79,5 +79,8 @@
|
|||||||
"type": "String"
|
"type": "String"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"downloadAsZip": "Download as ZIP file",
|
||||||
|
"exportAd": "Export pages which marked as ads",
|
||||||
|
"maxZipFilenameLength": "Maximum length of filenames in Zip files"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,5 +79,8 @@
|
|||||||
"type": "String"
|
"type": "String"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"downloadAsZip": "下载为ZIP文件",
|
||||||
|
"exportAd": "导出标记为广告的页面",
|
||||||
|
"maxZipFilenameLength": "Zip文件中文件名的最大长度"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import 'package:logging/logging.dart';
|
|||||||
import 'package:window_manager/window_manager.dart';
|
import 'package:window_manager/window_manager.dart';
|
||||||
import 'api/client.dart';
|
import 'api/client.dart';
|
||||||
import 'create_root_user.dart';
|
import 'create_root_user.dart';
|
||||||
|
import 'dialog/dialog_page.dart';
|
||||||
|
import 'dialog/download_zip_page.dart';
|
||||||
import 'galleries.dart';
|
import 'galleries.dart';
|
||||||
import 'gallery.dart';
|
import 'gallery.dart';
|
||||||
import 'globals.dart';
|
import 'globals.dart';
|
||||||
@@ -87,7 +89,23 @@ final _router = GoRouter(
|
|||||||
GoRoute(
|
GoRoute(
|
||||||
path: "/gallery",
|
path: "/gallery",
|
||||||
redirect: (context, state) => "/galleries",
|
redirect: (context, state) => "/galleries",
|
||||||
)
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/dialog/download/zip/:gid',
|
||||||
|
pageBuilder: (context, state) => DialogPage(
|
||||||
|
key: state.pageKey,
|
||||||
|
builder: (context) {
|
||||||
|
return DownloadZipPage(int.parse(state.pathParameters["gid"]!));
|
||||||
|
}),
|
||||||
|
redirect: (context, state) {
|
||||||
|
try {
|
||||||
|
int.parse(state.pathParameters["gid"]!);
|
||||||
|
return null;
|
||||||
|
} catch (e) {
|
||||||
|
_routerLog.warning("Failed to parse gid:", e);
|
||||||
|
return "/";
|
||||||
|
}
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -39,4 +39,27 @@ class Path {
|
|||||||
return _safChannel.invokeMethod(
|
return _safChannel.invokeMethod(
|
||||||
"saveFile", [filenameWithoutExtension, dir, mimeType, bytes]);
|
"saveFile", [filenameWithoutExtension, dir, mimeType, bytes]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<SAFFile> openFile(String filenameWithoutExtension, String mimeType,
|
||||||
|
{String dir = ""}) async {
|
||||||
|
final fd = await _safChannel.invokeMethod<int>(
|
||||||
|
"openFile", [filenameWithoutExtension, dir, mimeType]);
|
||||||
|
return SAFFile(fd!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SAFFile {
|
||||||
|
SAFFile(this._fd);
|
||||||
|
final int _fd;
|
||||||
|
bool _disposed = false;
|
||||||
|
Future<int> write(Uint8List data) async {
|
||||||
|
if (_disposed) throw Exception("File already closed");
|
||||||
|
return await Path._safChannel.invokeMethod("writeFile", [_fd, data]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> dispose() async {
|
||||||
|
if (_disposed) return;
|
||||||
|
_disposed = true;
|
||||||
|
await Path._safChannel.invokeMethod("closeFile", [_fd]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
36
lib/utils/download_zip.dart
Normal file
36
lib/utils/download_zip.dart
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import '../globals.dart';
|
||||||
|
import '../platform/save_file.dart';
|
||||||
|
|
||||||
|
Future<void> downloadZip(int gid,
|
||||||
|
{bool? jpnTitle, int? maxLength, bool? exportAd}) async {
|
||||||
|
final cancel = CancelToken();
|
||||||
|
final re = await api.exportGalleryZip(gid,
|
||||||
|
jpnTitle: jpnTitle,
|
||||||
|
maxLength: maxLength,
|
||||||
|
exportAd: exportAd,
|
||||||
|
cancel: cancel);
|
||||||
|
final data = re.data as ResponseBody;
|
||||||
|
if (data.statusCode != 200) {
|
||||||
|
throw Exception("${data.statusCode} ${data.statusMessage}.");
|
||||||
|
}
|
||||||
|
final fileName = re.response.headers.value("content-disposition");
|
||||||
|
final filenameWithoutExtension = fileName?.substring(
|
||||||
|
fileName.indexOf("filename=\"") + 10, fileName.lastIndexOf(".")) ??
|
||||||
|
"$gid";
|
||||||
|
try {
|
||||||
|
final f = await platformPath.openFile(
|
||||||
|
Uri.decodeComponent(filenameWithoutExtension), "application/zip");
|
||||||
|
try {
|
||||||
|
await data.stream.forEach((data) {
|
||||||
|
f.write(data);
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
await f.dispose();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
cancel.cancel();
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
cmake_minimum_required(VERSION 3.14)
|
cmake_minimum_required(VERSION 3.14)
|
||||||
project(runner LANGUAGES CXX)
|
project(runner)
|
||||||
|
|
||||||
set(ENABLE_ICONV OFF CACHE BOOL "Libiconv is not needed.")
|
set(ENABLE_ICONV OFF CACHE BOOL "Libiconv is not needed.")
|
||||||
add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/../../utils" "${CMAKE_BINARY_DIR}/utils")
|
add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/../../utils" "${CMAKE_BINARY_DIR}/utils")
|
||||||
|
|||||||
@@ -10,6 +10,10 @@
|
|||||||
|
|
||||||
#include "flutter/generated_plugin_registrant.h"
|
#include "flutter/generated_plugin_registrant.h"
|
||||||
|
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <io.h>
|
||||||
|
#include "err.h"
|
||||||
|
#include "fileop.h"
|
||||||
#include "wchar_util.h"
|
#include "wchar_util.h"
|
||||||
|
|
||||||
#define MAX_PATH_SIZE 32768
|
#define MAX_PATH_SIZE 32768
|
||||||
@@ -19,6 +23,22 @@ FlutterWindow::FlutterWindow(const flutter::DartProject& project)
|
|||||||
|
|
||||||
FlutterWindow::~FlutterWindow() {}
|
FlutterWindow::~FlutterWindow() {}
|
||||||
|
|
||||||
|
void updateDataFromMimeType(std::wstring& defExt, std::wstring& filter, std::string mimeType) {
|
||||||
|
if (mimeType == "image/jpeg") {
|
||||||
|
filter.append(std::wstring(L"JPEG File(*.jpg)\0*.jpg\0", 23));
|
||||||
|
defExt = L"jpg";
|
||||||
|
} else if (mimeType == "image/png") {
|
||||||
|
filter.append(std::wstring(L"PNG File(*.png)\0*.png\0", 22));
|
||||||
|
defExt = L"png";
|
||||||
|
} else if (mimeType == "image/gif") {
|
||||||
|
filter.append(std::wstring(L"GIF File(*.gif)\0*.gif\0", 22));
|
||||||
|
defExt = L"gif";
|
||||||
|
} else if (mimeType == "application/zip") {
|
||||||
|
filter.append(std::wstring(L"ZIP File(*.zip)\0*.zip\0", 22));
|
||||||
|
defExt = L"zip";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool FlutterWindow::OnCreate() {
|
bool FlutterWindow::OnCreate() {
|
||||||
if (!Win32Window::OnCreate()) {
|
if (!Win32Window::OnCreate()) {
|
||||||
return false;
|
return false;
|
||||||
@@ -87,16 +107,7 @@ bool FlutterWindow::OnCreate() {
|
|||||||
ofn.hwndOwner = Win32Window::GetHandle();
|
ofn.hwndOwner = Win32Window::GetHandle();
|
||||||
std::wstring filter;
|
std::wstring filter;
|
||||||
std::wstring defExt;
|
std::wstring defExt;
|
||||||
if (*mimeType == "image/jpeg") {
|
updateDataFromMimeType(defExt, filter, *mimeType);
|
||||||
filter.append(std::wstring(L"JPEG File(*.jpg)\0*.jpg\0", 23));
|
|
||||||
defExt = L"jpg";
|
|
||||||
} else if (*mimeType == "image/png") {
|
|
||||||
filter.append(std::wstring(L"PNG File(*.png)\0*.png\0", 22));
|
|
||||||
defExt = L"png";
|
|
||||||
} else if (*mimeType == "image/gif") {
|
|
||||||
filter.append(std::wstring(L"GIF File(*.gif)\0*.gif\0", 22));
|
|
||||||
defExt = L"gif";
|
|
||||||
};
|
|
||||||
filter.append(std::wstring(L"All Files\0*.*\0\0", 15));
|
filter.append(std::wstring(L"All Files\0*.*\0\0", 15));
|
||||||
ofn.lpstrFilter = filter.c_str();
|
ofn.lpstrFilter = filter.c_str();
|
||||||
ofn.lpstrDefExt = defExt.empty() ? nullptr : defExt.c_str();
|
ofn.lpstrDefExt = defExt.empty() ? nullptr : defExt.c_str();
|
||||||
@@ -119,6 +130,88 @@ bool FlutterWindow::OnCreate() {
|
|||||||
fwrite(data->data(), sizeof(uint8_t), data->size(), f);
|
fwrite(data->data(), sizeof(uint8_t), data->size(), f);
|
||||||
fclose(f);
|
fclose(f);
|
||||||
result->Success();
|
result->Success();
|
||||||
|
} else if (call.method_name() == "closeFile") {
|
||||||
|
auto args = std::get_if<flutter::EncodableList>(call.arguments());
|
||||||
|
auto fd = std::get_if<int>(&args->at(0));
|
||||||
|
if (!fd) {
|
||||||
|
result->Error("INVALID_ARGUMENT", "Invalid arguments.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fileop::close(*fd);
|
||||||
|
result->Success();
|
||||||
|
} else if (call.method_name() == "openFile") {
|
||||||
|
auto args = std::get_if<flutter::EncodableList>(call.arguments());
|
||||||
|
auto fileName = std::get_if<std::string>(&args->at(0));
|
||||||
|
auto dir = std::get_if<std::string>(&args->at(1));
|
||||||
|
auto mimeType = std::get_if<std::string>(&args->at(2));
|
||||||
|
if (!fileName || !dir || !mimeType) {
|
||||||
|
result->Error("INVALID_ARGUMENT", "Invalid arguments.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::wstring wFileName;
|
||||||
|
if (!wchar_util::str_to_wstr(wFileName, *fileName, CP_UTF8)) {
|
||||||
|
result->Error("ERROR", "Failed to convert file name to wstring.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::wstring wDir;
|
||||||
|
if (!dir->empty() && !wchar_util::str_to_wstr(wDir, *dir, CP_UTF8)) {
|
||||||
|
result->Error("ERROR", "Failed to convert dir to wstring.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
OPENFILENAMEW ofn;
|
||||||
|
ZeroMemory(&ofn, sizeof(ofn));
|
||||||
|
ofn.lStructSize = sizeof(ofn);
|
||||||
|
ofn.hwndOwner = Win32Window::GetHandle();
|
||||||
|
std::wstring filter;
|
||||||
|
std::wstring defExt;
|
||||||
|
updateDataFromMimeType(defExt, filter, *mimeType);
|
||||||
|
filter.append(std::wstring(L"All Files\0*.*\0\0", 15));
|
||||||
|
ofn.lpstrFilter = filter.c_str();
|
||||||
|
ofn.lpstrDefExt = defExt.empty() ? nullptr : defExt.c_str();
|
||||||
|
wchar_t wFileNameBuf[MAX_PATH_SIZE];
|
||||||
|
memcpy(wFileNameBuf, wFileName.c_str(), (wFileName.size() + 1) * sizeof(wchar_t));
|
||||||
|
ofn.lpstrFile = wFileNameBuf;
|
||||||
|
ofn.nMaxFile = MAX_PATH_SIZE;
|
||||||
|
ofn.lpstrInitialDir = wDir.empty() ? nullptr : wDir.c_str();
|
||||||
|
ofn.Flags = OFN_DONTADDTORECENT | OFN_NONETWORKBUTTON | OFN_NOREADONLYRETURN | OFN_OVERWRITEPROMPT;
|
||||||
|
if (!GetSaveFileNameW(&ofn)) {
|
||||||
|
result->Error("ERROR", "Failed to get file name.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::string fn;
|
||||||
|
if (!wchar_util::wstr_to_str(fn, wFileNameBuf, CP_UTF8)) {
|
||||||
|
result->Error("ERROR", "Failed to convert file name to UTF-8.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int fd = 0;
|
||||||
|
int e = fileop::open(fn, fd, _O_WRONLY | _O_BINARY | O_CREAT);
|
||||||
|
if (e) {
|
||||||
|
std::string errmsg;
|
||||||
|
if (!err::get_errno_message(errmsg, e)) {
|
||||||
|
errmsg = "Unknown error.";
|
||||||
|
}
|
||||||
|
result->Error("ERROR", "Failed to open file: " + errmsg);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
result->Success(fd);
|
||||||
|
} else if (call.method_name() == "writeFile") {
|
||||||
|
auto args = std::get_if<flutter::EncodableList>(call.arguments());
|
||||||
|
auto fd = std::get_if<int>(&args->at(0));
|
||||||
|
auto data = std::get_if<std::vector<uint8_t>>(&args->at(1));
|
||||||
|
if (!fd || !data) {
|
||||||
|
result->Error("INVALID_ARGUMENT", "Invalid arguments.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int num = _write(*fd, data->data(), (unsigned int)data->size());
|
||||||
|
if (num == -1) {
|
||||||
|
std::string errmsg;
|
||||||
|
if (!err::get_errno_message(errmsg, errno)) {
|
||||||
|
errmsg = "Unknown error.";
|
||||||
|
}
|
||||||
|
result->Error("ERROR", "Failed to write file:" + errmsg);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
result->Success(num);
|
||||||
} else {
|
} else {
|
||||||
result->NotImplemented();
|
result->NotImplemented();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user