mirror of
https://github.com/GopeedLab/gopeed.git
synced 2026-11-10 02:20:28 +00:00
feat: task separate proxy (#727)
This commit is contained in:
22 files changed
+447
-67
No files matched your search
@@ -7,11 +7,13 @@ class Request {
|
||||
String url;
|
||||
Object? extra;
|
||||
Map<String, String>? labels = {};
|
||||
RequestProxy? proxy;
|
||||
|
||||
Request({
|
||||
required this.url,
|
||||
this.extra,
|
||||
this.labels,
|
||||
this.proxy,
|
||||
});
|
||||
|
||||
factory Request.fromJson(Map<String, dynamic> json) =>
|
||||
@@ -45,3 +47,24 @@ class ReqExtraBt {
|
||||
|
||||
Map<String, dynamic> toJson() => _$ReqExtraBtToJson(this);
|
||||
}
|
||||
|
||||
enum RequestProxyMode {
|
||||
follow,
|
||||
none,
|
||||
custom,
|
||||
}
|
||||
|
||||
@JsonSerializable()
|
||||
class RequestProxy {
|
||||
RequestProxyMode mode = RequestProxyMode.follow;
|
||||
String scheme = 'http';
|
||||
String host = '';
|
||||
String usr = '';
|
||||
String pwd = '';
|
||||
|
||||
RequestProxy();
|
||||
|
||||
factory RequestProxy.fromJson(Map<String, dynamic> json) =>
|
||||
_$RequestProxyFromJson(json);
|
||||
Map<String, dynamic> toJson() => _$RequestProxyToJson(this);
|
||||
}
|
||||
@@ -12,6 +12,9 @@ Request _$RequestFromJson(Map<String, dynamic> json) => Request(
|
||||
labels: (json['labels'] as Map<String, dynamic>?)?.map(
|
||||
(k, e) => MapEntry(k, e as String),
|
||||
),
|
||||
proxy: json['proxy'] == null
|
||||
? null
|
||||
: RequestProxy.fromJson(json['proxy'] as Map<String, dynamic>),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$RequestToJson(Request instance) {
|
||||
@@ -27,6 +30,7 @@ Map<String, dynamic> _$RequestToJson(Request instance) {
|
||||
|
||||
writeNotNull('extra', instance.extra);
|
||||
writeNotNull('labels', instance.labels);
|
||||
writeNotNull('proxy', instance.proxy?.toJson());
|
||||
return val;
|
||||
}
|
||||
|
||||
@@ -50,3 +54,25 @@ Map<String, dynamic> _$ReqExtraBtToJson(ReqExtraBt instance) =>
|
||||
<String, dynamic>{
|
||||
'trackers': instance.trackers,
|
||||
};
|
||||
|
||||
RequestProxy _$RequestProxyFromJson(Map<String, dynamic> json) => RequestProxy()
|
||||
..mode = $enumDecode(_$RequestProxyModeEnumMap, json['mode'])
|
||||
..scheme = json['scheme'] as String
|
||||
..host = json['host'] as String
|
||||
..usr = json['usr'] as String
|
||||
..pwd = json['pwd'] as String;
|
||||
|
||||
Map<String, dynamic> _$RequestProxyToJson(RequestProxy instance) =>
|
||||
<String, dynamic>{
|
||||
'mode': _$RequestProxyModeEnumMap[instance.mode]!,
|
||||
'scheme': instance.scheme,
|
||||
'host': instance.host,
|
||||
'usr': instance.usr,
|
||||
'pwd': instance.pwd,
|
||||
};
|
||||
|
||||
const _$RequestProxyModeEnumMap = {
|
||||
RequestProxyMode.follow: 'follow',
|
||||
RequestProxyMode.none: 'none',
|
||||
RequestProxyMode.custom: 'custom',
|
||||
};
|
||||
@@ -8,7 +8,7 @@ part of 'resource.dart';
|
||||
|
||||
Resource _$ResourceFromJson(Map<String, dynamic> json) => Resource(
|
||||
name: json['name'] as String? ?? "",
|
||||
size: json['size'] as int? ?? 0,
|
||||
size: (json['size'] as num?)?.toInt() ?? 0,
|
||||
range: json['range'] as bool? ?? false,
|
||||
files: (json['files'] as List<dynamic>)
|
||||
.map((e) => FileInfo.fromJson(e as Map<String, dynamic>))
|
||||
@@ -27,7 +27,7 @@ Map<String, dynamic> _$ResourceToJson(Resource instance) => <String, dynamic>{
|
||||
FileInfo _$FileInfoFromJson(Map<String, dynamic> json) => FileInfo(
|
||||
path: json['path'] as String? ?? "",
|
||||
name: json['name'] as String,
|
||||
size: json['size'] as int? ?? 0,
|
||||
size: (json['size'] as num?)?.toInt() ?? 0,
|
||||
req: json['req'] == null
|
||||
? null
|
||||
: Request.fromJson(json['req'] as Map<String, dynamic>),
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:gopeed/api/model/request.dart';
|
||||
|
||||
class CreateController extends GetxController
|
||||
with GetSingleTickerProviderStateMixin {
|
||||
@@ -13,6 +14,7 @@ class CreateController extends GetxController
|
||||
final isConfirming = false.obs;
|
||||
final showAdvanced = false.obs;
|
||||
final directDownload = false.obs;
|
||||
final proxyConfig = Rx<RequestProxy?>(null);
|
||||
late TabController advancedTabController;
|
||||
final oldUrl = "".obs;
|
||||
final fileDataUri = "".obs;
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:gopeed/api/model/downloader_config.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:rounded_loading_button_plus/rounded_loading_button.dart';
|
||||
|
||||
@@ -32,6 +33,10 @@ class CreateView extends GetView<CreateController> {
|
||||
final _connectionsController = TextEditingController();
|
||||
final _pathController = TextEditingController();
|
||||
final _confirmController = RoundedLoadingButtonController();
|
||||
final _proxyIpController = TextEditingController();
|
||||
final _proxyPortController = TextEditingController();
|
||||
final _proxyUsrController = TextEditingController();
|
||||
final _proxyPwdController = TextEditingController();
|
||||
final _httpUaController = TextEditingController();
|
||||
final _httpCookieController = TextEditingController();
|
||||
final _httpRefererController = TextEditingController();
|
||||
@@ -261,7 +266,164 @@ class CreateView extends GetView<CreateController> {
|
||||
() => Visibility(
|
||||
visible: controller.showAdvanced.value,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Transform.translate(
|
||||
offset: const Offset(-40, 0),
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.wifi_2_bar,
|
||||
color: Colors.grey,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 15,
|
||||
),
|
||||
SizedBox(
|
||||
width: 150,
|
||||
child: DropdownButton<
|
||||
RequestProxyMode>(
|
||||
hint: Text('proxy'.tr),
|
||||
isExpanded: true,
|
||||
value: controller
|
||||
.proxyConfig.value?.mode,
|
||||
onChanged: (value) async {
|
||||
if (value != null) {
|
||||
controller.proxyConfig
|
||||
.value = RequestProxy()
|
||||
..mode = value;
|
||||
}
|
||||
},
|
||||
items: [
|
||||
DropdownMenuItem<
|
||||
RequestProxyMode>(
|
||||
value:
|
||||
RequestProxyMode.follow,
|
||||
child: Text(
|
||||
'followSettings'.tr),
|
||||
),
|
||||
DropdownMenuItem<
|
||||
RequestProxyMode>(
|
||||
value:
|
||||
RequestProxyMode.none,
|
||||
child: Text('noProxy'.tr),
|
||||
),
|
||||
DropdownMenuItem<
|
||||
RequestProxyMode>(
|
||||
value:
|
||||
RequestProxyMode.custom,
|
||||
child:
|
||||
Text('customProxy'.tr),
|
||||
),
|
||||
],
|
||||
))
|
||||
],
|
||||
),
|
||||
),
|
||||
...(controller.proxyConfig.value?.mode ==
|
||||
RequestProxyMode.custom
|
||||
? [
|
||||
SizedBox(
|
||||
width: 150,
|
||||
child: DropdownButtonFormField<
|
||||
String>(
|
||||
value: controller
|
||||
.proxyConfig.value?.scheme,
|
||||
onChanged: (value) async {
|
||||
if (value != null) {}
|
||||
},
|
||||
items: const [
|
||||
DropdownMenuItem<String>(
|
||||
value: 'http',
|
||||
child: Text('HTTP'),
|
||||
),
|
||||
DropdownMenuItem<String>(
|
||||
value: 'https',
|
||||
child: Text('HTTPS'),
|
||||
),
|
||||
DropdownMenuItem<String>(
|
||||
value: 'socks5',
|
||||
child: Text('SOCKS5'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Row(children: [
|
||||
Flexible(
|
||||
child: TextFormField(
|
||||
controller:
|
||||
_proxyIpController,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'server'.tr,
|
||||
contentPadding:
|
||||
const EdgeInsets.all(
|
||||
0.0),
|
||||
),
|
||||
),
|
||||
),
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(
|
||||
left: 10)),
|
||||
Flexible(
|
||||
child: TextFormField(
|
||||
controller:
|
||||
_proxyPortController,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'port'.tr,
|
||||
contentPadding:
|
||||
const EdgeInsets.all(
|
||||
0.0),
|
||||
),
|
||||
keyboardType:
|
||||
TextInputType.number,
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter
|
||||
.digitsOnly,
|
||||
NumericalRangeFormatter(
|
||||
min: 0, max: 65535),
|
||||
],
|
||||
),
|
||||
),
|
||||
]),
|
||||
Row(children: [
|
||||
Flexible(
|
||||
child: TextFormField(
|
||||
controller:
|
||||
_proxyUsrController,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'username'.tr,
|
||||
contentPadding:
|
||||
const EdgeInsets.all(
|
||||
0.0),
|
||||
),
|
||||
),
|
||||
),
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(
|
||||
left: 10)),
|
||||
Flexible(
|
||||
child: TextFormField(
|
||||
controller:
|
||||
_proxyPwdController,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'password'.tr,
|
||||
contentPadding:
|
||||
const EdgeInsets.all(
|
||||
0.0),
|
||||
),
|
||||
),
|
||||
),
|
||||
])
|
||||
]
|
||||
: const []),
|
||||
],
|
||||
),
|
||||
const Divider(),
|
||||
TabBar(
|
||||
controller: controller.advancedTabController,
|
||||
tabs: const [
|
||||
@@ -416,18 +578,21 @@ class CreateView extends GetView<CreateController> {
|
||||
if (isDirect) {
|
||||
await Future.wait(urls.map((url) {
|
||||
return createTask(CreateTask(
|
||||
req: Request(url: url, extra: parseReqExtra(url)),
|
||||
req: Request(
|
||||
url: url, extra: parseReqExtra(url), proxy: parseProxy()),
|
||||
opt: Options(
|
||||
name: isMultiLine ? "" : _renameController.text,
|
||||
path: _pathController.text,
|
||||
selectFiles: [],
|
||||
extra: parseReqOptsExtra())));
|
||||
name: isMultiLine ? "" : _renameController.text,
|
||||
path: _pathController.text,
|
||||
selectFiles: [],
|
||||
extra: parseReqOptsExtra(),
|
||||
)));
|
||||
}));
|
||||
Get.rootDelegate.offNamed(Routes.TASK);
|
||||
} else {
|
||||
final rr = await resolve(Request(
|
||||
url: submitUrl,
|
||||
extra: parseReqExtra(_urlController.text),
|
||||
proxy: parseProxy(),
|
||||
));
|
||||
await _showResolveDialog(rr);
|
||||
}
|
||||
@@ -440,6 +605,18 @@ class CreateView extends GetView<CreateController> {
|
||||
}
|
||||
}
|
||||
|
||||
RequestProxy? parseProxy() {
|
||||
if (controller.proxyConfig.value?.mode == RequestProxyMode.custom) {
|
||||
return RequestProxy()
|
||||
..mode = RequestProxyMode.custom
|
||||
..scheme = _proxyIpController.text
|
||||
..host = "${_proxyIpController.text}:${_proxyPortController.text}"
|
||||
..usr = _proxyUsrController.text
|
||||
..pwd = _proxyPwdController.text;
|
||||
}
|
||||
return controller.proxyConfig.value;
|
||||
}
|
||||
|
||||
Object? parseReqExtra(String url) {
|
||||
Object? reqExtra;
|
||||
if (controller.showAdvanced.value) {
|
||||
@@ -543,7 +720,7 @@ class CreateView extends GetView<CreateController> {
|
||||
controller.selectedIndexes.map((index) {
|
||||
final file = rr.res.files[index];
|
||||
return createTask(CreateTask(
|
||||
req: file.req!,
|
||||
req: file.req!..proxy = parseProxy(),
|
||||
opt: Options(
|
||||
name: file.name,
|
||||
path: path.join(_pathController.text,
|
||||
|
||||
@@ -847,7 +847,7 @@ class SettingView extends GetView<SettingController> {
|
||||
// advanced config log items start
|
||||
buildLogsDir() {
|
||||
return ListTile(
|
||||
title: Text("日志目录"),
|
||||
title: Text("logDirectory".tr),
|
||||
subtitle: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
@@ -981,7 +981,7 @@ class SettingView extends GetView<SettingController> {
|
||||
: null,
|
||||
]),
|
||||
)),
|
||||
const Text('开发者'),
|
||||
Text('developer'.tr),
|
||||
Card(
|
||||
child: Column(
|
||||
children: _addDivider([
|
||||
|
||||
@@ -17,6 +17,7 @@ const enUS = {
|
||||
'create': 'Create Task',
|
||||
'directDownload': 'Direct Download',
|
||||
'advancedOptions': 'Advanced Options',
|
||||
'followSettings': 'Follow Settings',
|
||||
'downloadLink': 'Download Link',
|
||||
'downloadLinkValid': 'Please enter the download link',
|
||||
'downloadLinkHit':
|
||||
@@ -60,6 +61,8 @@ const enUS = {
|
||||
'set': 'SET',
|
||||
'portInUse': 'Port [@port] is in use, please change the port',
|
||||
'effectAfterRestart': 'Effect after restart',
|
||||
'developer': 'Developer',
|
||||
'logDirectory': 'Log Directory',
|
||||
'show': 'Show',
|
||||
'startAll': 'Start All',
|
||||
'pauseAll': 'Pause All',
|
||||
@@ -96,7 +99,7 @@ const enUS = {
|
||||
'browserExtension': 'Browser Extension',
|
||||
'launchAtStartup': 'Launch at Startup',
|
||||
'seedConfig': 'Seed Config',
|
||||
'seedKeep': 'Keep seeding',
|
||||
'seedKeep': 'Keep seeding until manually stopped',
|
||||
'seedRatio': 'Seed ratio',
|
||||
'seedTime': 'Seed time (minutes)',
|
||||
'taskDetail': 'Task Detail',
|
||||
|
||||
@@ -17,6 +17,7 @@ const zhCN = {
|
||||
'create': '创建任务',
|
||||
'directDownload': '直接下载',
|
||||
'advancedOptions': '高级选项',
|
||||
'followSettings': '跟随设置',
|
||||
'downloadLink': '下载链接',
|
||||
'downloadLinkValid': '请输入下载链接',
|
||||
'downloadLinkHit': '请输入下载链接,支持 HTTP/HTTPS/MAGNET@append',
|
||||
@@ -58,6 +59,8 @@ const zhCN = {
|
||||
'set': '已设置',
|
||||
'portInUse': '端口[@port]已被占用,请更换端口',
|
||||
'effectAfterRestart': '此配置项将在重启应用后生效',
|
||||
'developer': '开发者',
|
||||
'logDirectory': '日志目录',
|
||||
'show': '显示',
|
||||
'startAll': '全部开始',
|
||||
'pauseAll': '全部暂停',
|
||||
@@ -93,7 +96,7 @@ const zhCN = {
|
||||
'browserExtension': '浏览器扩展',
|
||||
'launchAtStartup': '开机自动运行',
|
||||
'seedConfig': '做种设置',
|
||||
'seedKeep': '持续做种',
|
||||
'seedKeep': '持续做种直到手动停止',
|
||||
'seedRatio': '做种分享率',
|
||||
'seedTime': '做种时间(分钟)',
|
||||
'taskDetail': '任务详情',
|
||||
|
||||
@@ -17,6 +17,7 @@ const zhTW = {
|
||||
'create': '建立任務',
|
||||
'directDownload': '直接下載',
|
||||
'advancedOptions': '進階選項',
|
||||
'followSettings': '跟隨設定',
|
||||
'downloadLink': '下載連結',
|
||||
'downloadLinkValid': '請輸入下載連結',
|
||||
'downloadLinkHit': '請輸入下載連結,支援 HTTP/HTTPS/MAGNET@append',
|
||||
@@ -58,6 +59,8 @@ const zhTW = {
|
||||
'set': '已設定',
|
||||
'portInUse': '連接埠 [@port] 已被使用,請更改連接埠',
|
||||
'effectAfterRestart': '重新啟動後生效',
|
||||
'developer': '開發者',
|
||||
'logDirectory': '日誌目錄',
|
||||
'show': '顯示',
|
||||
'startAll': '全部開始',
|
||||
'pauseAll': '全部暫停',
|
||||
@@ -93,15 +96,9 @@ const zhTW = {
|
||||
'browserExtension': '瀏覽器擴充功能',
|
||||
'launchAtStartup': '開機自動執行',
|
||||
'seedConfig': '做種設定',
|
||||
'seedKeep': '持續做種',
|
||||
'seedKeep': '持續做種直到手動停止',
|
||||
'seedRatio': '做種分享率',
|
||||
'seedTime': '做種時間(分鐘)',
|
||||
/*
|
||||
'taskDetail': '任务详情',
|
||||
'taskName': '任务名称',
|
||||
'taskUrl': '任务链接',
|
||||
'downloadPath': '下载路径',
|
||||
*/
|
||||
'taskDetail': '任務詳情',
|
||||
'taskName': '任務名稱',
|
||||
'taskUrl': '任務連結',
|
||||
|
||||
Reference in new issue
Block a user