Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
82d316af83 | ||
|
|
150953a033 | ||
|
|
f30ad6641f | ||
|
|
7019f18635 | ||
|
|
c0338c3d8e | ||
|
|
fe55f1bd54 | ||
|
|
48f8c60672 | ||
|
|
cc97cedf0f | ||
|
|
a7d855f65a | ||
|
|
f0f16b3c80 | ||
|
|
3459b474db | ||
|
|
d28fcef0a7 | ||
|
|
d694c08635 | ||
|
|
a7c61a668e | ||
|
|
09aea55b49 | ||
|
|
c0cd2d7255 | ||
|
|
b00f4a1ed0 | ||
|
|
06766eea56 | ||
|
|
0f0c8f27bd | ||
|
|
45cf62f88d | ||
|
|
6e9d3499d9 | ||
|
|
b6d60f6bdf | ||
|
|
e1684e755a | ||
|
|
e63cfca82a | ||
|
|
85ef8d3be4 | ||
|
|
bf2ccdb5a7 | ||
|
|
a75bfd6118 | ||
|
|
a5d8713ff7 | ||
|
|
81518d794f | ||
|
|
7c2b81caf7 | ||
|
|
2221332243 | ||
|
|
dc407434d0 |
No files matched your search
@@ -1,20 +1,20 @@
|
|||||||
## Donate
|
## Donate
|
||||||
|
|
||||||
Thank you for your support! 🙏
|
🙏 Thank you for considering a donation to Gopeed project! Your support helps us continue to develop and maintain our software.
|
||||||
|
|
||||||
<div style="text-align: center;">
|
<div style="text-align: center;">
|
||||||
<table>
|
<table>
|
||||||
<tr>
|
<tr>
|
||||||
<th>微信</th>
|
<th>微信</th>
|
||||||
<th>支付宝</th>
|
<th>支付宝</th>
|
||||||
<th>PayPal</th>
|
|
||||||
<th>Ko-fi</th>
|
<th>Ko-fi</th>
|
||||||
|
<th>PayPal</th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><img src="/.donate/qr/wechatpay.jpg" alt="wechatpay" width="300" /></td>
|
<td><img src="/.donate/qr/wechatpay.jpg" alt="wechatpay" width="300" /></td>
|
||||||
<td><img src="/.donate/qr/alipay.jpg" alt="alipay" width="300" /></td>
|
<td><img src="/.donate/qr/alipay.jpg" alt="alipay" width="300" /></td>
|
||||||
<td><a href="https://paypal.me/monkeyWie" target="_blank">Click Me</a></td>
|
|
||||||
<td><a href="https://ko-fi.com/gopeed" target="_blank">Click Me</a></td>
|
<td><a href="https://ko-fi.com/gopeed" target="_blank">Click Me</a></td>
|
||||||
|
<td><a href="https://paypal.me/monkeyWie" target="_blank">Click Me</a></td>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -24,4 +24,4 @@ Thank you for your support! 🙏
|
|||||||
- **ETH:** 0x928a7fd2398c941Ac6713704e606Fa414595e3A0
|
- **ETH:** 0x928a7fd2398c941Ac6713704e606Fa414595e3A0
|
||||||
- **Matic:** 0x928a7fd2398c941Ac6713704e606Fa414595e3A0
|
- **Matic:** 0x928a7fd2398c941Ac6713704e606Fa414595e3A0
|
||||||
- **BNB:** 0x928a7fd2398c941Ac6713704e606Fa414595e3A0
|
- **BNB:** 0x928a7fd2398c941Ac6713704e606Fa414595e3A0
|
||||||
- **USDT(ERC20)**: 0x928a7fd2398c941Ac6713704e606Fa414595e3A0
|
- **USDT(ERC20)**: 0x928a7fd2398c941Ac6713704e606Fa414595e3A0
|
||||||
@@ -4,7 +4,7 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
env:
|
env:
|
||||||
GO_VERSION: 1.19
|
GO_VERSION: "1.20"
|
||||||
FLUTTER_VERSION: 3.10.6
|
FLUTTER_VERSION: 3.10.6
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
@@ -39,7 +39,7 @@ jobs:
|
|||||||
VERSION: ${{ needs.get-release.outputs.tag_name }}
|
VERSION: ${{ needs.get-release.outputs.tag_name }}
|
||||||
run: |
|
run: |
|
||||||
Invoke-WebRequest -Uri "https://github.com/jrsoftware/issrc/raw/main/Files/Languages/Unofficial/ChineseSimplified.isl" -OutFile "ChineseSimplified.isl"
|
Invoke-WebRequest -Uri "https://github.com/jrsoftware/issrc/raw/main/Files/Languages/Unofficial/ChineseSimplified.isl" -OutFile "ChineseSimplified.isl"
|
||||||
mv ChineseSimplified.isl "C:\Program Files (x86)\Inno Setup 6\Languages\"
|
mv ChineseSimplified.isl "C:\Program Files (x86)\Inno Setup 6\Languages\"
|
||||||
|
|
||||||
go build -tags nosqlite -ldflags="-w -s" -buildmode=c-shared -o ui/flutter/windows/libgopeed.dll github.com/GopeedLab/gopeed/bind/desktop
|
go build -tags nosqlite -ldflags="-w -s" -buildmode=c-shared -o ui/flutter/windows/libgopeed.dll github.com/GopeedLab/gopeed/bind/desktop
|
||||||
cd ui/flutter
|
cd ui/flutter
|
||||||
@@ -90,7 +90,7 @@ jobs:
|
|||||||
SolidCompression=yes
|
SolidCompression=yes
|
||||||
WizardStyle=modern
|
WizardStyle=modern
|
||||||
LanguageDetectionMethod=uilanguage
|
LanguageDetectionMethod=uilanguage
|
||||||
ShowLanguageDialog=yes
|
ShowLanguageDialog=yes
|
||||||
|
|
||||||
[Languages]
|
[Languages]
|
||||||
Name: "english"; MessagesFile: "compiler:Default.isl"
|
Name: "english"; MessagesFile: "compiler:Default.isl"
|
||||||
@@ -133,9 +133,8 @@ jobs:
|
|||||||
go-version: ${{ env.GO_VERSION }}
|
go-version: ${{ env.GO_VERSION }}
|
||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
docker pull techknowlogick/xgo:go-1.19.x
|
|
||||||
go install src.techknowlogick.com/xgo@latest
|
go install src.techknowlogick.com/xgo@latest
|
||||||
xgo --targets=darwin/arm64 -tags="nosqlite" -ldflags="-w -s" -buildmode=c-shared -pkg=bind/desktop -out=libgopeed .
|
xgo -go go-$GO_VERSION.x --targets=darwin/arm64 -tags="nosqlite" -ldflags="-w -s" -buildmode=c-shared -pkg=bind/desktop -out=libgopeed .
|
||||||
mv libgopeed-*.dylib libgopeed.dylib
|
mv libgopeed-*.dylib libgopeed.dylib
|
||||||
- uses: actions/upload-artifact@v3
|
- uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
@@ -166,7 +165,7 @@ jobs:
|
|||||||
VERSION: ${{ needs.get-release.outputs.tag_name }}
|
VERSION: ${{ needs.get-release.outputs.tag_name }}
|
||||||
run: |
|
run: |
|
||||||
PROJECT_DIR=$(pwd)
|
PROJECT_DIR=$(pwd)
|
||||||
|
|
||||||
# amd64 lib
|
# amd64 lib
|
||||||
go build -tags nosqlite -ldflags="-w -s" -buildmode=c-shared -o ui/flutter/lib-amd64/libgopeed.dylib github.com/GopeedLab/gopeed/bind/desktop
|
go build -tags nosqlite -ldflags="-w -s" -buildmode=c-shared -o ui/flutter/lib-amd64/libgopeed.dylib github.com/GopeedLab/gopeed/bind/desktop
|
||||||
# universal binary
|
# universal binary
|
||||||
@@ -221,13 +220,13 @@ jobs:
|
|||||||
mkdir -p debian/gui
|
mkdir -p debian/gui
|
||||||
cp assets/icon/icon_1024.png debian/gui/gopeed.png
|
cp assets/icon/icon_1024.png debian/gui/gopeed.png
|
||||||
cat>debian/debian.yaml<<EOF
|
cat>debian/debian.yaml<<EOF
|
||||||
flutter_app:
|
flutter_app:
|
||||||
command: gopeed
|
command: gopeed
|
||||||
arch: x64
|
arch: x64
|
||||||
parent: /usr/local/lib
|
parent: /usr/local/lib
|
||||||
control:
|
control:
|
||||||
Package: gopeed
|
Package: gopeed
|
||||||
Version: ${VERSION:1}
|
Version: ${VERSION:1}
|
||||||
Architecture: amd64
|
Architecture: amd64
|
||||||
Essential: no
|
Essential: no
|
||||||
Priority: optional
|
Priority: optional
|
||||||
@@ -238,7 +237,7 @@ jobs:
|
|||||||
|
|
||||||
cat>debian/gui/gopeed.desktop<<EOF
|
cat>debian/gui/gopeed.desktop<<EOF
|
||||||
[Desktop Entry]
|
[Desktop Entry]
|
||||||
Version=${VERSION:1}
|
Version=${VERSION:1}
|
||||||
Name=Gopeed
|
Name=Gopeed
|
||||||
GenericName=Gopeed
|
GenericName=Gopeed
|
||||||
Comment=High speed downloader that supports all platforms.
|
Comment=High speed downloader that supports all platforms.
|
||||||
@@ -294,6 +293,38 @@ jobs:
|
|||||||
upload_url: ${{ needs.get-release.outputs.upload_url }}
|
upload_url: ${{ needs.get-release.outputs.upload_url }}
|
||||||
asset_path: ui/flutter/dist/*
|
asset_path: ui/flutter/dist/*
|
||||||
overwrite: true
|
overwrite: true
|
||||||
|
build-ios:
|
||||||
|
runs-on: macos-latest
|
||||||
|
needs: [get-release]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: actions/setup-go@v4
|
||||||
|
with:
|
||||||
|
go-version: ${{ env.GO_VERSION }}
|
||||||
|
- uses: subosito/flutter-action@v2
|
||||||
|
with:
|
||||||
|
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||||
|
- name: Build
|
||||||
|
env:
|
||||||
|
VERSION: ${{ needs.get-release.outputs.tag_name }}
|
||||||
|
run: |
|
||||||
|
go install golang.org/x/mobile/cmd/gomobile@latest
|
||||||
|
go get golang.org/x/mobile/bind
|
||||||
|
gomobile init
|
||||||
|
gomobile bind -tags nosqlite -ldflags="-w -s" -o ui/flutter/ios/Frameworks/Libgopeed.xcframework -target=ios github.com/GopeedLab/gopeed/bind/mobile
|
||||||
|
cd ui/flutter
|
||||||
|
flutter build ios --no-codesign
|
||||||
|
mkdir Payload
|
||||||
|
cp -r build/ios/iphoneos/Runner.app Payload
|
||||||
|
zip -r -y Payload.zip Payload/Runner.app
|
||||||
|
mkdir dist
|
||||||
|
mv Payload.zip dist/Gopeed-$VERSION-ios.ipa
|
||||||
|
- name: Upload
|
||||||
|
uses: shogo82148/actions-upload-release-asset@v1
|
||||||
|
with:
|
||||||
|
upload_url: ${{ needs.get-release.outputs.upload_url }}
|
||||||
|
asset_path: ui/flutter/dist/*
|
||||||
|
overwrite: true
|
||||||
build-web:
|
build-web:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [get-release]
|
needs: [get-release]
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ on:
|
|||||||
- "Dockerfile"
|
- "Dockerfile"
|
||||||
|
|
||||||
env:
|
env:
|
||||||
GO_VERSION: 1.19
|
GO_VERSION: "1.20"
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ on:
|
|||||||
- "go.sum"
|
- "go.sum"
|
||||||
|
|
||||||
env:
|
env:
|
||||||
GO_VERSION: 1.19
|
GO_VERSION: "1.20"
|
||||||
FLUTTER_VERSION: 3.10.6
|
FLUTTER_VERSION: 3.10.6
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
name: 'translator'
|
|
||||||
|
|
||||||
on:
|
|
||||||
issues:
|
|
||||||
types: [opened, edited]
|
|
||||||
issue_comment:
|
|
||||||
types: [created, edited]
|
|
||||||
discussion:
|
|
||||||
types: [created, edited]
|
|
||||||
discussion_comment:
|
|
||||||
types: [created, edited]
|
|
||||||
pull_request_target:
|
|
||||||
types: [opened, edited]
|
|
||||||
pull_request_review_comment:
|
|
||||||
types: [created, edited]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
translate:
|
|
||||||
permissions:
|
|
||||||
issues: write
|
|
||||||
discussions: write
|
|
||||||
pull-requests: write
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: lizheming/[email protected]
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
with:
|
|
||||||
IS_MODIFY_TITLE: true
|
|
||||||
APPEND_TRANSLATION: true
|
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
# name: 'translator'
|
||||||
|
|
||||||
|
# on:
|
||||||
|
# issues:
|
||||||
|
# types: [opened, edited]
|
||||||
|
# issue_comment:
|
||||||
|
# types: [created, edited]
|
||||||
|
# discussion:
|
||||||
|
# types: [created, edited]
|
||||||
|
# discussion_comment:
|
||||||
|
# types: [created, edited]
|
||||||
|
# pull_request_target:
|
||||||
|
# types: [opened, edited]
|
||||||
|
# pull_request_review_comment:
|
||||||
|
# types: [created, edited]
|
||||||
|
|
||||||
|
# jobs:
|
||||||
|
# translate:
|
||||||
|
# permissions:
|
||||||
|
# issues: write
|
||||||
|
# discussions: write
|
||||||
|
# pull-requests: write
|
||||||
|
# runs-on: ubuntu-latest
|
||||||
|
# steps:
|
||||||
|
# - uses: lizheming/[email protected]
|
||||||
|
# env:
|
||||||
|
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
# with:
|
||||||
|
# IS_MODIFY_TITLE: true
|
||||||
|
# APPEND_TRANSLATION: true
|
||||||
@@ -7,6 +7,8 @@
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
[](https://ko-fi.com/R6R6IJGN6)
|
||||||
|
|
||||||
[English](/README.md) | [中文](/README_zh-CN.md) | [日本語](/README_ja-JP.md)
|
[English](/README.md) | [中文](/README_zh-CN.md) | [日本語](/README_ja-JP.md)
|
||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
@@ -23,7 +25,7 @@ Visit ✈ [Official Website](https://gopeed.com) | 📖 [Develop Docs](https://d
|
|||||||
- [x] macos
|
- [x] macos
|
||||||
- [x] linux
|
- [x] linux
|
||||||
- [x] android
|
- [x] android
|
||||||
- [ ] ios
|
- [x] ios
|
||||||
- [x] web
|
- [x] web
|
||||||
- [x] docker
|
- [x] docker
|
||||||
|
|
||||||
@@ -74,7 +76,7 @@ This project is divided into two parts, the front end uses `flutter`, the back e
|
|||||||
### Environment
|
### Environment
|
||||||
|
|
||||||
1. Golang 1.19+
|
1. Golang 1.19+
|
||||||
2. Flutter 3.7+
|
2. Flutter 3.10+
|
||||||
|
|
||||||
### Clone
|
### Clone
|
||||||
|
|
||||||
@@ -138,6 +140,14 @@ cd ui/flutter
|
|||||||
flutter build apk
|
flutter build apk
|
||||||
```
|
```
|
||||||
|
|
||||||
|
- ios
|
||||||
|
|
||||||
|
```bash
|
||||||
|
gomobile bind -tags nosqlite -ldflags="-w -s" -o ui/flutter/ios/Frameworks/Libgopeed.xcframework -target=ios github.com/GopeedLab/gopeed/bind/mobile
|
||||||
|
cd ui/flutter
|
||||||
|
flutter build ios --no-codesign
|
||||||
|
```
|
||||||
|
|
||||||
#### Web
|
#### Web
|
||||||
|
|
||||||
command:
|
command:
|
||||||
@@ -152,7 +162,7 @@ go build -tags nosqlite,web -ldflags="-s -w" -o bin/ github.com/GopeedLab/gopeed
|
|||||||
go run cmd/web/main.go
|
go run cmd/web/main.go
|
||||||
```
|
```
|
||||||
|
|
||||||
## Thanks
|
## Credits
|
||||||
|
|
||||||
### Contributors
|
### Contributors
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,8 @@
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
[](https://ko-fi.com/R6R6IJGN6)
|
||||||
|
|
||||||
[English](/README.md) | [中文](/README_zh-CN.md) | [日本語](/README_ja-JP.md)
|
[English](/README.md) | [中文](/README_zh-CN.md) | [日本語](/README_ja-JP.md)
|
||||||
|
|
||||||
## はじめに
|
## はじめに
|
||||||
@@ -23,7 +25,7 @@ Gopeed (正式名 Go Speed) は `Golang` + `Flutter` によって開発された
|
|||||||
- [x] macos
|
- [x] macos
|
||||||
- [x] linux
|
- [x] linux
|
||||||
- [x] android
|
- [x] android
|
||||||
- [ ] ios
|
- [x] ios
|
||||||
- [x] web
|
- [x] web
|
||||||
- [x] docker
|
- [x] docker
|
||||||
|
|
||||||
@@ -74,7 +76,7 @@ docker コンテナが起動しているときは、`http://localhost:9999` か
|
|||||||
### 環境
|
### 環境
|
||||||
|
|
||||||
1. Go 言語 1.19+
|
1. Go 言語 1.19+
|
||||||
2. Flutter 3.7+
|
2. Flutter 3.10+
|
||||||
|
|
||||||
### クローン
|
### クローン
|
||||||
|
|
||||||
@@ -138,6 +140,14 @@ cd ui/flutter
|
|||||||
flutter build apk
|
flutter build apk
|
||||||
```
|
```
|
||||||
|
|
||||||
|
- ios
|
||||||
|
|
||||||
|
```bash
|
||||||
|
gomobile bind -tags nosqlite -ldflags="-w -s" -o ui/flutter/ios/Frameworks/Libgopeed.xcframework -target=ios github.com/GopeedLab/gopeed/bind/mobile
|
||||||
|
cd ui/flutter
|
||||||
|
flutter build ios --no-codesign
|
||||||
|
```
|
||||||
|
|
||||||
#### Web
|
#### Web
|
||||||
|
|
||||||
コマンド:
|
コマンド:
|
||||||
|
|||||||
@@ -7,6 +7,8 @@
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
[](https://ko-fi.com/R6R6IJGN6)
|
||||||
|
|
||||||
[English](/README.md) | [中文](/README_zh-CN.md) | [日本語](/README_ja-JP.md)
|
[English](/README.md) | [中文](/README_zh-CN.md) | [日本語](/README_ja-JP.md)
|
||||||
|
|
||||||
## 介绍
|
## 介绍
|
||||||
@@ -23,7 +25,7 @@ Gopeed(全称 Go Speed),是一款由`Golang`+`Flutter`开发的高速下
|
|||||||
- [x] macos
|
- [x] macos
|
||||||
- [x] linux
|
- [x] linux
|
||||||
- [x] android
|
- [x] android
|
||||||
- [ ] ios
|
- [x] ios
|
||||||
- [x] web
|
- [x] web
|
||||||
- [x] docker
|
- [x] docker
|
||||||
|
|
||||||
@@ -74,7 +76,7 @@ docker-compose up -d
|
|||||||
### 环境要求
|
### 环境要求
|
||||||
|
|
||||||
1. Golang 1.19+
|
1. Golang 1.19+
|
||||||
2. Flutter 3.7+
|
2. Flutter 3.10+
|
||||||
|
|
||||||
### 克隆项目
|
### 克隆项目
|
||||||
|
|
||||||
@@ -138,6 +140,14 @@ cd ui/flutter
|
|||||||
flutter build apk
|
flutter build apk
|
||||||
```
|
```
|
||||||
|
|
||||||
|
- ios
|
||||||
|
|
||||||
|
```bash
|
||||||
|
gomobile bind -tags nosqlite -ldflags="-w -s" -o ui/flutter/ios/Frameworks/Libgopeed.xcframework -target=ios github.com/GopeedLab/gopeed/bind/mobile
|
||||||
|
cd ui/flutter
|
||||||
|
flutter build ios --no-codesign
|
||||||
|
```
|
||||||
|
|
||||||
#### Web 端
|
#### Web 端
|
||||||
|
|
||||||
构建命令:
|
构建命令:
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
type args struct {
|
||||||
|
Address *string `json:"address"`
|
||||||
|
Port *int `json:"port"`
|
||||||
|
Username *string `json:"username"`
|
||||||
|
Password *string `json:"password"`
|
||||||
|
ApiToken *string `json:"apiToken"`
|
||||||
|
|
||||||
|
configPath *string
|
||||||
|
}
|
||||||
|
|
||||||
|
func parse() *args {
|
||||||
|
var cliArgs args
|
||||||
|
cliArgs.Address = flag.String("A", "0.0.0.0", "Bind Address")
|
||||||
|
cliArgs.Port = flag.Int("P", 9999, "Bind Port")
|
||||||
|
cliArgs.Username = flag.String("u", "gopeed", "HTTP Basic Auth Username")
|
||||||
|
cliArgs.Password = flag.String("p", "", "HTTP Basic Auth Password")
|
||||||
|
cliArgs.ApiToken = flag.String("T", "", "API token, that can only be used when basic authentication is enabled.")
|
||||||
|
cliArgs.configPath = flag.String("c", "./config.json", "Config file path")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
fmt.Println("parse")
|
||||||
|
|
||||||
|
// args priority: config file > cli args
|
||||||
|
cfgArgs := loadConfig(*cliArgs.configPath)
|
||||||
|
if cfgArgs.Address == nil {
|
||||||
|
cfgArgs.Address = cliArgs.Address
|
||||||
|
}
|
||||||
|
if cfgArgs.Port == nil {
|
||||||
|
cfgArgs.Port = cliArgs.Port
|
||||||
|
}
|
||||||
|
if cfgArgs.Username == nil {
|
||||||
|
cfgArgs.Username = cliArgs.Username
|
||||||
|
}
|
||||||
|
if cfgArgs.Password == nil {
|
||||||
|
cfgArgs.Password = cliArgs.Password
|
||||||
|
}
|
||||||
|
if cfgArgs.ApiToken == nil {
|
||||||
|
cfgArgs.ApiToken = cliArgs.ApiToken
|
||||||
|
}
|
||||||
|
return cfgArgs
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadConfig(path string) *args {
|
||||||
|
var args args
|
||||||
|
|
||||||
|
if !filepath.IsAbs(path) {
|
||||||
|
dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
|
||||||
|
if err != nil {
|
||||||
|
return &args
|
||||||
|
}
|
||||||
|
path = filepath.Join(dir, path)
|
||||||
|
}
|
||||||
|
file, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return &args
|
||||||
|
}
|
||||||
|
fmt.Println("config file read failed, reason:" + err.Error())
|
||||||
|
return &args
|
||||||
|
}
|
||||||
|
if err = json.Unmarshal(file, &args); err != nil {
|
||||||
|
fmt.Println("config file parse failed, reason:" + err.Error())
|
||||||
|
return &args
|
||||||
|
}
|
||||||
|
fmt.Printf("config file loaded: %s\n", path)
|
||||||
|
return &args
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"embed"
|
"embed"
|
||||||
|
"fmt"
|
||||||
"github.com/GopeedLab/gopeed/cmd"
|
"github.com/GopeedLab/gopeed/cmd"
|
||||||
"github.com/GopeedLab/gopeed/pkg/rest/model"
|
"github.com/GopeedLab/gopeed/pkg/rest/model"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
@@ -18,12 +19,28 @@ func main() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
args := parse()
|
||||||
|
var webBasicAuth *model.WebBasicAuth
|
||||||
|
if isNotBlank(args.Username) && isNotBlank(args.Password) {
|
||||||
|
webBasicAuth = &model.WebBasicAuth{
|
||||||
|
Username: *args.Username,
|
||||||
|
Password: *args.Password,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
cfg := &model.StartConfig{
|
cfg := &model.StartConfig{
|
||||||
Network: "tcp",
|
Network: "tcp",
|
||||||
Address: "0.0.0.0:9999",
|
Address: fmt.Sprintf("%s:%d", *args.Address, *args.Port),
|
||||||
Storage: model.StorageBolt,
|
Storage: model.StorageBolt,
|
||||||
WebEnable: true,
|
ApiToken: *args.ApiToken,
|
||||||
WebFS: sub,
|
WebEnable: true,
|
||||||
|
WebFS: sub,
|
||||||
|
WebBasicAuth: webBasicAuth,
|
||||||
}
|
}
|
||||||
cmd.Start(cfg)
|
cmd.Start(cfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isNotBlank(str *string) bool {
|
||||||
|
return str != nil && *str != ""
|
||||||
|
}
|
||||||
@@ -1,20 +1,18 @@
|
|||||||
module github.com/GopeedLab/gopeed
|
module github.com/GopeedLab/gopeed
|
||||||
|
|
||||||
go 1.19
|
go 1.20
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/anacrolix/missinggo/v2 v2.7.2-0.20230527121029-a582b4f397b9
|
github.com/anacrolix/missinggo/v2 v2.7.2-0.20230527121029-a582b4f397b9
|
||||||
github.com/anacrolix/torrent v1.52.3
|
github.com/anacrolix/torrent v1.52.5
|
||||||
github.com/gorilla/handlers v1.5.1
|
github.com/gorilla/handlers v1.5.1
|
||||||
github.com/gorilla/mux v1.8.0
|
github.com/gorilla/mux v1.8.0
|
||||||
github.com/matoous/go-nanoid/v2 v2.0.0
|
github.com/matoous/go-nanoid/v2 v2.0.0
|
||||||
github.com/virtuald/go-paniclog v0.0.0-20190812204905-43a7fa316459
|
github.com/virtuald/go-paniclog v0.0.0-20190812204905-43a7fa316459
|
||||||
go.etcd.io/bbolt v1.3.6
|
go.etcd.io/bbolt v1.3.7-0.20231005155708-db45d0d6b1f6
|
||||||
golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde
|
golang.org/x/sync v0.3.0
|
||||||
)
|
)
|
||||||
|
|
||||||
replace github.com/anacrolix/torrent => github.com/monkeyWie/torrent v1.52.4-alpha
|
|
||||||
|
|
||||||
require (
|
require (
|
||||||
crawshaw.io/sqlite v0.3.3-0.20220618202545-d1964889ea3c // indirect
|
crawshaw.io/sqlite v0.3.3-0.20220618202545-d1964889ea3c // indirect
|
||||||
github.com/RoaringBitmap/roaring v1.2.3 // indirect
|
github.com/RoaringBitmap/roaring v1.2.3 // indirect
|
||||||
@@ -24,7 +22,7 @@ require (
|
|||||||
github.com/anacrolix/dht/v2 v2.19.2-0.20221121215055-066ad8494444 // indirect
|
github.com/anacrolix/dht/v2 v2.19.2-0.20221121215055-066ad8494444 // indirect
|
||||||
github.com/anacrolix/envpprof v1.2.1 // indirect
|
github.com/anacrolix/envpprof v1.2.1 // indirect
|
||||||
github.com/anacrolix/generics v0.0.0-20230428105757-683593396d68 // indirect
|
github.com/anacrolix/generics v0.0.0-20230428105757-683593396d68 // indirect
|
||||||
github.com/anacrolix/go-libutp v1.2.0 // indirect
|
github.com/anacrolix/go-libutp v1.3.1 // indirect
|
||||||
github.com/anacrolix/log v0.14.0 // indirect
|
github.com/anacrolix/log v0.14.0 // indirect
|
||||||
github.com/anacrolix/missinggo v1.3.0 // indirect
|
github.com/anacrolix/missinggo v1.3.0 // indirect
|
||||||
github.com/anacrolix/missinggo/perf v1.0.0 // indirect
|
github.com/anacrolix/missinggo/perf v1.0.0 // indirect
|
||||||
@@ -76,6 +74,6 @@ require (
|
|||||||
golang.org/x/crypto v0.5.0 // indirect
|
golang.org/x/crypto v0.5.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20220613132600-b0d781184e0d // indirect
|
golang.org/x/exp v0.0.0-20220613132600-b0d781184e0d // indirect
|
||||||
golang.org/x/net v0.7.0 // indirect
|
golang.org/x/net v0.7.0 // indirect
|
||||||
golang.org/x/sys v0.5.0 // indirect
|
golang.org/x/sys v0.13.0 // indirect
|
||||||
golang.org/x/time v0.0.0-20220609170525-579cf78fd858 // indirect
|
golang.org/x/time v0.0.0-20220609170525-579cf78fd858 // indirect
|
||||||
)
|
)
|
||||||
@@ -35,12 +35,12 @@ github.com/anacrolix/envpprof v1.2.1 h1:25TJe6t/i0AfzzldiGFKCpD+s+dk8lONBcacJZB2
|
|||||||
github.com/anacrolix/envpprof v1.2.1/go.mod h1:My7T5oSqVfEn4MD4Meczkw/f5lSIndGAKu/0SM/rkf4=
|
github.com/anacrolix/envpprof v1.2.1/go.mod h1:My7T5oSqVfEn4MD4Meczkw/f5lSIndGAKu/0SM/rkf4=
|
||||||
github.com/anacrolix/generics v0.0.0-20230428105757-683593396d68 h1:fyXlBfnlFzZSFckJ8QLb2lfmWfY++4RiUnae7ZMuv0A=
|
github.com/anacrolix/generics v0.0.0-20230428105757-683593396d68 h1:fyXlBfnlFzZSFckJ8QLb2lfmWfY++4RiUnae7ZMuv0A=
|
||||||
github.com/anacrolix/generics v0.0.0-20230428105757-683593396d68/go.mod h1:ff2rHB/joTV03aMSSn/AZNnaIpUw0h3njetGsaXcMy8=
|
github.com/anacrolix/generics v0.0.0-20230428105757-683593396d68/go.mod h1:ff2rHB/joTV03aMSSn/AZNnaIpUw0h3njetGsaXcMy8=
|
||||||
github.com/anacrolix/go-libutp v1.2.0 h1:sjxoB+/ARiKUR7IK/6wLWyADIBqGmu1fm0xo+8Yy7u0=
|
github.com/anacrolix/go-libutp v1.3.1 h1:idJzreNLl+hNjGC3ZnUOjujEaryeOGgkwHLqSGoige0=
|
||||||
github.com/anacrolix/go-libutp v1.2.0/go.mod h1:RrJ3KcaDcf9Jqp33YL5V/5CBEc6xMc7aJL8wXfuWL50=
|
github.com/anacrolix/go-libutp v1.3.1/go.mod h1:heF41EC8kN0qCLMokLBVkB8NXiLwx3t8R8810MTNI5o=
|
||||||
github.com/anacrolix/log v0.3.0/go.mod h1:lWvLTqzAnCWPJA08T2HCstZi0L1y2Wyvm3FJgwU9jwU=
|
github.com/anacrolix/log v0.3.0/go.mod h1:lWvLTqzAnCWPJA08T2HCstZi0L1y2Wyvm3FJgwU9jwU=
|
||||||
github.com/anacrolix/log v0.6.0/go.mod h1:lWvLTqzAnCWPJA08T2HCstZi0L1y2Wyvm3FJgwU9jwU=
|
github.com/anacrolix/log v0.6.0/go.mod h1:lWvLTqzAnCWPJA08T2HCstZi0L1y2Wyvm3FJgwU9jwU=
|
||||||
github.com/anacrolix/log v0.10.0/go.mod h1:s5yBP/j046fm9odtUTbHOfDUq/zh1W8OkPpJtnX0oQI=
|
|
||||||
github.com/anacrolix/log v0.10.1-0.20220123034749-3920702c17f8/go.mod h1:GmnE2c0nvz8pOIPUSC9Rawgefy1sDXqposC2wgtBZE4=
|
github.com/anacrolix/log v0.10.1-0.20220123034749-3920702c17f8/go.mod h1:GmnE2c0nvz8pOIPUSC9Rawgefy1sDXqposC2wgtBZE4=
|
||||||
|
github.com/anacrolix/log v0.13.1/go.mod h1:D4+CvN8SnruK6zIFS/xPoRJmtvtnxs+CSfDQ+BFxZ68=
|
||||||
github.com/anacrolix/log v0.14.0 h1:mYhTSemILe/Z8tIxbGdTIWWpPspI8W/fhZHpoFbDaL0=
|
github.com/anacrolix/log v0.14.0 h1:mYhTSemILe/Z8tIxbGdTIWWpPspI8W/fhZHpoFbDaL0=
|
||||||
github.com/anacrolix/log v0.14.0/go.mod h1:1OmJESOtxQGNMlUO5rcv96Vpp9mfMqXXbe2RdinFLdY=
|
github.com/anacrolix/log v0.14.0/go.mod h1:1OmJESOtxQGNMlUO5rcv96Vpp9mfMqXXbe2RdinFLdY=
|
||||||
github.com/anacrolix/lsan v0.0.0-20211126052245-807000409a62 h1:P04VG6Td13FHMgS5ZBcJX23NPC/fiC4cp9bXwYujdYM=
|
github.com/anacrolix/lsan v0.0.0-20211126052245-807000409a62 h1:P04VG6Td13FHMgS5ZBcJX23NPC/fiC4cp9bXwYujdYM=
|
||||||
@@ -73,6 +73,8 @@ github.com/anacrolix/sync v0.4.0/go.mod h1:BbecHL6jDSExojhNtgTFSBcdGerzNc64tz3DC
|
|||||||
github.com/anacrolix/tagflag v0.0.0-20180109131632-2146c8d41bf0/go.mod h1:1m2U/K6ZT+JZG0+bdMK6qauP49QT4wE5pmhJXOKKCHw=
|
github.com/anacrolix/tagflag v0.0.0-20180109131632-2146c8d41bf0/go.mod h1:1m2U/K6ZT+JZG0+bdMK6qauP49QT4wE5pmhJXOKKCHw=
|
||||||
github.com/anacrolix/tagflag v1.0.0/go.mod h1:1m2U/K6ZT+JZG0+bdMK6qauP49QT4wE5pmhJXOKKCHw=
|
github.com/anacrolix/tagflag v1.0.0/go.mod h1:1m2U/K6ZT+JZG0+bdMK6qauP49QT4wE5pmhJXOKKCHw=
|
||||||
github.com/anacrolix/tagflag v1.1.0/go.mod h1:Scxs9CV10NQatSmbyjqmqmeQNwGzlNe0CMUMIxqHIG8=
|
github.com/anacrolix/tagflag v1.1.0/go.mod h1:Scxs9CV10NQatSmbyjqmqmeQNwGzlNe0CMUMIxqHIG8=
|
||||||
|
github.com/anacrolix/torrent v1.52.5 h1:jWowdx+EU6zFVfBwmnL0d3H4J6vTFEGOrHI35YdfIT8=
|
||||||
|
github.com/anacrolix/torrent v1.52.5/go.mod h1:CcM8oPMYye5J42cSqJrmUpqwRFgSsJQ1jCEHwygqnqQ=
|
||||||
github.com/anacrolix/upnp v0.1.3-0.20220123035249-922794e51c96 h1:QAVZ3pN/J4/UziniAhJR2OZ9Ox5kOY2053tBbbqUPYA=
|
github.com/anacrolix/upnp v0.1.3-0.20220123035249-922794e51c96 h1:QAVZ3pN/J4/UziniAhJR2OZ9Ox5kOY2053tBbbqUPYA=
|
||||||
github.com/anacrolix/upnp v0.1.3-0.20220123035249-922794e51c96/go.mod h1:Wa6n8cYIdaG35x15aH3Zy6d03f7P728QfdcDeD/IEOs=
|
github.com/anacrolix/upnp v0.1.3-0.20220123035249-922794e51c96/go.mod h1:Wa6n8cYIdaG35x15aH3Zy6d03f7P728QfdcDeD/IEOs=
|
||||||
github.com/anacrolix/utp v0.1.0 h1:FOpQOmIwYsnENnz7tAGohA+r6iXpRjrq8ssKSre2Cp4=
|
github.com/anacrolix/utp v0.1.0 h1:FOpQOmIwYsnENnz7tAGohA+r6iXpRjrq8ssKSre2Cp4=
|
||||||
@@ -210,8 +212,6 @@ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJ
|
|||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
github.com/monkeyWie/torrent v1.52.4-alpha h1:iir8NMyLkv6X/D4skGUx8Htb8q57cPk9A6bYlRfrrqA=
|
|
||||||
github.com/monkeyWie/torrent v1.52.4-alpha/go.mod h1:yBUcBuIOlttGEgAW/6gamzQGD+k2+wUh65fOPPzxvC4=
|
|
||||||
github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg=
|
github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg=
|
||||||
github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM=
|
github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM=
|
||||||
github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw=
|
github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw=
|
||||||
@@ -324,8 +324,8 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
|||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
github.com/tidwall/btree v1.6.0 h1:LDZfKfQIBHGHWSwckhXI0RPSXzlo+KYdjK7FWSqOzzg=
|
github.com/tidwall/btree v1.6.0 h1:LDZfKfQIBHGHWSwckhXI0RPSXzlo+KYdjK7FWSqOzzg=
|
||||||
github.com/tidwall/btree v1.6.0/go.mod h1:twD9XRA5jj9VUQGELzDO4HPQTNJsoWWfYEL+EUQ2cKY=
|
github.com/tidwall/btree v1.6.0/go.mod h1:twD9XRA5jj9VUQGELzDO4HPQTNJsoWWfYEL+EUQ2cKY=
|
||||||
github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
|
github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
|
||||||
@@ -337,8 +337,8 @@ github.com/willf/bitset v1.1.9/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPyS
|
|||||||
github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
|
github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU=
|
go.etcd.io/bbolt v1.3.7-0.20231005155708-db45d0d6b1f6 h1:NkHVT1GBVV9hUj1iWglFxAuNlvLHELFn71U2H7aOIog=
|
||||||
go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
|
go.etcd.io/bbolt v1.3.7-0.20231005155708-db45d0d6b1f6/go.mod h1:tDI2vlr15+D9cyN3odhFrZJi3oJj8HlHaOEFPrUiGSY=
|
||||||
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
||||||
go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
||||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
@@ -365,7 +365,6 @@ golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTk
|
|||||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
golang.org/x/net v0.0.0-20180524181706-dfa909b99c79/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
@@ -404,8 +403,8 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
|
|||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde h1:ejfdSekXMDxDLbRrJMwUk6KnSLZ2McaUCVcIKM+N6jc=
|
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
|
||||||
golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
@@ -422,7 +421,6 @@ golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@@ -435,12 +433,14 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
|
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
||||||
|
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
|
golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
|
||||||
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
@@ -448,6 +448,7 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/time v0.0.0-20220609170525-579cf78fd858 h1:Dpdu/EMxGMFgq0CeYMh4fazTD2vtlZRYE7wyynxJb9U=
|
golang.org/x/time v0.0.0-20220609170525-579cf78fd858 h1:Dpdu/EMxGMFgq0CeYMh4fazTD2vtlZRYE7wyynxJb9U=
|
||||||
golang.org/x/time v0.0.0-20220609170525-579cf78fd858/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20220609170525-579cf78fd858/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package bt
|
package bt
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"github.com/GopeedLab/gopeed/internal/controller"
|
"github.com/GopeedLab/gopeed/internal/controller"
|
||||||
"github.com/GopeedLab/gopeed/internal/fetcher"
|
"github.com/GopeedLab/gopeed/internal/fetcher"
|
||||||
"github.com/GopeedLab/gopeed/pkg/base"
|
"github.com/GopeedLab/gopeed/pkg/base"
|
||||||
@@ -223,6 +224,15 @@ func (f *Fetcher) addTorrent(req *base.Request) (err error) {
|
|||||||
schema := util.ParseSchema(req.URL)
|
schema := util.ParseSchema(req.URL)
|
||||||
if schema == "MAGNET" {
|
if schema == "MAGNET" {
|
||||||
f.torrent, err = client.AddMagnet(req.URL)
|
f.torrent, err = client.AddMagnet(req.URL)
|
||||||
|
} else if schema == "APPLICATION/X-BITTORRENT" {
|
||||||
|
_, data := util.ParseDataUri(req.URL)
|
||||||
|
buf := bytes.NewBuffer(data)
|
||||||
|
var metaInfo *metainfo.MetaInfo
|
||||||
|
metaInfo, err = metainfo.Load(buf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
f.torrent, err = client.AddTorrent(metaInfo)
|
||||||
} else {
|
} else {
|
||||||
f.torrent, err = client.AddTorrentFromFile(req.URL)
|
f.torrent, err = client.AddTorrentFromFile(req.URL)
|
||||||
}
|
}
|
||||||
@@ -265,7 +275,7 @@ func (f *Fetcher) addTorrent(req *base.Request) (err error) {
|
|||||||
type FetcherBuilder struct {
|
type FetcherBuilder struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
var schemes = []string{"FILE", "MAGNET"}
|
var schemes = []string{"FILE", "MAGNET", "APPLICATION/X-BITTORRENT"}
|
||||||
|
|
||||||
func (fb *FetcherBuilder) Schemes() []string {
|
func (fb *FetcherBuilder) Schemes() []string {
|
||||||
return schemes
|
return schemes
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
package bt
|
package bt
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"github.com/GopeedLab/gopeed/internal/controller"
|
"github.com/GopeedLab/gopeed/internal/controller"
|
||||||
"github.com/GopeedLab/gopeed/internal/fetcher"
|
"github.com/GopeedLab/gopeed/internal/fetcher"
|
||||||
"github.com/GopeedLab/gopeed/internal/test"
|
"github.com/GopeedLab/gopeed/internal/test"
|
||||||
"github.com/GopeedLab/gopeed/pkg/base"
|
"github.com/GopeedLab/gopeed/pkg/base"
|
||||||
"github.com/GopeedLab/gopeed/pkg/protocol/bt"
|
"github.com/GopeedLab/gopeed/pkg/protocol/bt"
|
||||||
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
@@ -15,6 +17,39 @@ func TestFetcher_Resolve_Torrent(t *testing.T) {
|
|||||||
doResolve(t, buildFetcher())
|
doResolve(t, buildFetcher())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFetcher_Resolve_DataUri_Torrent(t *testing.T) {
|
||||||
|
fetcher := buildFetcher()
|
||||||
|
buf, err := os.ReadFile("./testdata/ubuntu-22.04-live-server-amd64.iso.torrent")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
// convert to data uri
|
||||||
|
dataUri := "data:application/x-bittorrent;base64," + base64.StdEncoding.EncodeToString(buf)
|
||||||
|
err = fetcher.Resolve(&base.Request{
|
||||||
|
URL: dataUri,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
want := &base.Resource{
|
||||||
|
Name: "ubuntu-22.04-live-server-amd64.iso",
|
||||||
|
Size: 1466714112,
|
||||||
|
Range: true,
|
||||||
|
RootDir: "ubuntu-22.04-live-server-amd64.iso",
|
||||||
|
Files: []*base.FileInfo{
|
||||||
|
{
|
||||||
|
Name: "ubuntu-22.04-live-server-amd64.iso",
|
||||||
|
Size: 1466714112,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Hash: "8a55cfbd5ca5d11507364765936c4f9e55b253ed",
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(want, fetcher.Meta().Res) {
|
||||||
|
t.Errorf("Resolve() got = %v, want %v", fetcher.Meta().Res, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestFetcher_Config(t *testing.T) {
|
func TestFetcher_Config(t *testing.T) {
|
||||||
doResolve(t, buildConfigFetcher())
|
doResolve(t, buildConfigFetcher())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type RequestError struct {
|
type RequestError struct {
|
||||||
@@ -85,13 +84,14 @@ func (f *Fetcher) Resolve(req *base.Request) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
client := buildClient()
|
client := buildClient()
|
||||||
// 只访问一个字节,测试资源是否支持Range请求
|
// send Range request to check whether the server supports breakpoint continuation
|
||||||
|
// just test one byte, Range: bytes=0-0
|
||||||
httpReq.Header.Set(base.HttpHeaderRange, fmt.Sprintf(base.HttpHeaderRangeFormat, 0, 0))
|
httpReq.Header.Set(base.HttpHeaderRange, fmt.Sprintf(base.HttpHeaderRangeFormat, 0, 0))
|
||||||
httpResp, err := client.Do(httpReq)
|
httpResp, err := client.Do(httpReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// 拿到响应头就关闭,不用加defer
|
// close response body immediately
|
||||||
httpResp.Body.Close()
|
httpResp.Body.Close()
|
||||||
res := &base.Resource{
|
res := &base.Resource{
|
||||||
Range: false,
|
Range: false,
|
||||||
@@ -395,8 +395,7 @@ func buildClient() *http.Client {
|
|||||||
// Cookie handle
|
// Cookie handle
|
||||||
jar, _ := cookiejar.New(nil)
|
jar, _ := cookiejar.New(nil)
|
||||||
return &http.Client{
|
return &http.Client{
|
||||||
Jar: jar,
|
Jar: jar,
|
||||||
Timeout: time.Second * 60,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -429,6 +428,9 @@ func buildRequest(ctx context.Context, req *base.Request) (httpReq *http.Request
|
|||||||
body = bytes.NewBufferString(extra.Body)
|
body = bytes.NewBufferString(extra.Body)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if _, ok := headers[base.HttpHeaderUserAgent]; !ok {
|
||||||
|
headers[base.HttpHeaderUserAgent] = []string{base.AgentName}
|
||||||
|
}
|
||||||
|
|
||||||
if ctx != nil {
|
if ctx != nil {
|
||||||
httpReq, err = http.NewRequestWithContext(ctx, method, url.String(), body)
|
httpReq, err = http.NewRequestWithContext(ctx, method, url.String(), body)
|
||||||
|
|||||||
@@ -19,6 +19,11 @@ const (
|
|||||||
HttpHeaderContentLength = "Content-Length"
|
HttpHeaderContentLength = "Content-Length"
|
||||||
HttpHeaderContentRange = "Content-Range"
|
HttpHeaderContentRange = "Content-Range"
|
||||||
HttpHeaderContentDisposition = "Content-Disposition"
|
HttpHeaderContentDisposition = "Content-Disposition"
|
||||||
|
HttpHeaderUserAgent = "User-Agent"
|
||||||
|
|
||||||
HttpHeaderRangeFormat = "bytes=%d-%d"
|
HttpHeaderRangeFormat = "bytes=%d-%d"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
AgentName = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36"
|
||||||
|
)
|
||||||
@@ -47,8 +47,9 @@ type Downloader struct {
|
|||||||
tasks []*Task
|
tasks []*Task
|
||||||
listener Listener
|
listener Listener
|
||||||
|
|
||||||
lock *sync.Mutex
|
lock *sync.Mutex
|
||||||
closed atomic.Bool
|
fetcherMapLock *sync.RWMutex
|
||||||
|
closed atomic.Bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDownloader(cfg *DownloaderConfig) *Downloader {
|
func NewDownloader(cfg *DownloaderConfig) *Downloader {
|
||||||
@@ -63,8 +64,9 @@ func NewDownloader(cfg *DownloaderConfig) *Downloader {
|
|||||||
fetchBuilders: make(map[string]fetcher.FetcherBuilder),
|
fetchBuilders: make(map[string]fetcher.FetcherBuilder),
|
||||||
fetcherMap: make(map[string]fetcher.Fetcher),
|
fetcherMap: make(map[string]fetcher.Fetcher),
|
||||||
|
|
||||||
storage: cfg.Storage,
|
storage: cfg.Storage,
|
||||||
lock: &sync.Mutex{},
|
lock: &sync.Mutex{},
|
||||||
|
fetcherMapLock: &sync.RWMutex{},
|
||||||
}
|
}
|
||||||
for _, f := range cfg.FetchBuilders {
|
for _, f := range cfg.FetchBuilders {
|
||||||
for _, p := range f.Schemes() {
|
for _, p := range f.Schemes() {
|
||||||
@@ -169,9 +171,6 @@ func (d *Downloader) Setup() error {
|
|||||||
func (d *Downloader) parseFb(url string) (fetcher.FetcherBuilder, error) {
|
func (d *Downloader) parseFb(url string) (fetcher.FetcherBuilder, error) {
|
||||||
schema := util.ParseSchema(url)
|
schema := util.ParseSchema(url)
|
||||||
fetchBuilder, ok := d.fetchBuilders[schema]
|
fetchBuilder, ok := d.fetchBuilders[schema]
|
||||||
if !ok {
|
|
||||||
fetchBuilder, ok = d.fetchBuilders[util.FileSchema]
|
|
||||||
}
|
|
||||||
if ok {
|
if ok {
|
||||||
return fetchBuilder, nil
|
return fetchBuilder, nil
|
||||||
}
|
}
|
||||||
@@ -201,7 +200,9 @@ func (d *Downloader) Resolve(req *base.Request) (rr *ResolveResult, err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
d.fetcherMapLock.Lock()
|
||||||
d.fetcherMap[rrId] = fetcher
|
d.fetcherMap[rrId] = fetcher
|
||||||
|
d.fetcherMapLock.Unlock()
|
||||||
rr = &ResolveResult{
|
rr = &ResolveResult{
|
||||||
ID: rrId,
|
ID: rrId,
|
||||||
Res: fetcher.Meta().Res,
|
Res: fetcher.Meta().Res,
|
||||||
@@ -221,11 +222,17 @@ func (d *Downloader) Create(rrId string, opts *base.Options) (taskId string, err
|
|||||||
if opts == nil {
|
if opts == nil {
|
||||||
opts = &base.Options{}
|
opts = &base.Options{}
|
||||||
}
|
}
|
||||||
|
d.fetcherMapLock.RLock()
|
||||||
fetcher, ok := d.fetcherMap[rrId]
|
fetcher, ok := d.fetcherMap[rrId]
|
||||||
|
d.fetcherMapLock.RUnlock()
|
||||||
if !ok {
|
if !ok {
|
||||||
return "", errors.New("invalid resource id")
|
return "", errors.New("invalid resource id")
|
||||||
}
|
}
|
||||||
defer delete(d.fetcherMap, rrId)
|
defer func() {
|
||||||
|
d.fetcherMapLock.Lock()
|
||||||
|
delete(d.fetcherMap, rrId)
|
||||||
|
d.fetcherMapLock.Unlock()
|
||||||
|
}()
|
||||||
|
|
||||||
meta := fetcher.Meta()
|
meta := fetcher.Meta()
|
||||||
meta.Opts = opts
|
meta.Opts = opts
|
||||||
@@ -521,7 +528,9 @@ func (d *Downloader) watch(task *Task) {
|
|||||||
d.emit(EventKeyDone, task)
|
d.emit(EventKeyDone, task)
|
||||||
}
|
}
|
||||||
d.emit(EventKeyFinally, task, err)
|
d.emit(EventKeyFinally, task, err)
|
||||||
d.notifyRunning()
|
if task.Status == EventKeyDone {
|
||||||
|
d.notifyRunning()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Downloader) restoreFetcher(task *Task) error {
|
func (d *Downloader) restoreFetcher(task *Task) error {
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package model
|
package model
|
||||||
|
|
||||||
import "io/fs"
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"io/fs"
|
||||||
|
)
|
||||||
|
|
||||||
type Storage string
|
type Storage string
|
||||||
|
|
||||||
@@ -17,8 +20,9 @@ type StartConfig struct {
|
|||||||
StorageDir string `json:"storageDir"`
|
StorageDir string `json:"storageDir"`
|
||||||
ApiToken string `json:"apiToken"`
|
ApiToken string `json:"apiToken"`
|
||||||
|
|
||||||
WebEnable bool
|
WebEnable bool
|
||||||
WebFS fs.FS
|
WebFS fs.FS
|
||||||
|
WebBasicAuth *WebBasicAuth
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *StartConfig) Init() *StartConfig {
|
func (cfg *StartConfig) Init() *StartConfig {
|
||||||
@@ -39,3 +43,14 @@ func (cfg *StartConfig) Init() *StartConfig {
|
|||||||
}
|
}
|
||||||
return cfg
|
return cfg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type WebBasicAuth struct {
|
||||||
|
Username string
|
||||||
|
Password string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Authorization returns the value of the Authorization header to be used in HTTP requests.
|
||||||
|
func (cfg *WebBasicAuth) Authorization() string {
|
||||||
|
userId := cfg.Username + ":" + cfg.Password
|
||||||
|
return "Basic " + base64.StdEncoding.EncodeToString([]byte(userId))
|
||||||
|
}
|
||||||
@@ -98,14 +98,21 @@ func BuildServer(startCfg *model.StartConfig) (*http.Server, net.Listener, error
|
|||||||
r.PathPrefix("/").Handler(http.FileServer(http.FS(startCfg.WebFS)))
|
r.PathPrefix("/").Handler(http.FileServer(http.FS(startCfg.WebFS)))
|
||||||
}
|
}
|
||||||
|
|
||||||
if startCfg.ApiToken != "" {
|
if startCfg.ApiToken != "" || (startCfg.WebEnable && startCfg.WebBasicAuth != nil) {
|
||||||
r.Use(func(h http.Handler) http.Handler {
|
r.Use(func(h http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Header.Get("X-Api-Token") != startCfg.ApiToken {
|
if startCfg.ApiToken != "" && r.Header.Get("X-Api-Token") == startCfg.ApiToken {
|
||||||
WriteJson(w, model.NewErrorResult("invalid token", model.CodeUnauthorized))
|
h.ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
h.ServeHTTP(w, r)
|
if startCfg.WebEnable && startCfg.WebBasicAuth != nil {
|
||||||
|
if r.Header.Get("Authorization") == startCfg.WebBasicAuth.Authorization() {
|
||||||
|
h.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Header().Set("WWW-Authenticate", "Basic realm=\"gopeed web\"")
|
||||||
|
}
|
||||||
|
WriteStatusJson(w, http.StatusUnauthorized, model.NewErrorResult("unauthorized", model.CodeUnauthorized))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -127,7 +134,11 @@ func ReadJson(r *http.Request, w http.ResponseWriter, v any) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func WriteJson(w http.ResponseWriter, v any) {
|
func WriteJson(w http.ResponseWriter, v any) {
|
||||||
|
WriteStatusJson(w, http.StatusOK, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func WriteStatusJson(w http.ResponseWriter, statusCode int, v any) {
|
||||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(statusCode)
|
||||||
json.NewEncoder(w).Encode(v)
|
json.NewEncoder(w).Encode(v)
|
||||||
}
|
}
|
||||||
@@ -273,7 +273,7 @@ func TestDoProxy(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAuthorization(t *testing.T) {
|
func TestApiToken(t *testing.T) {
|
||||||
var cfg = &model.StartConfig{}
|
var cfg = &model.StartConfig{}
|
||||||
cfg.Init()
|
cfg.Init()
|
||||||
cfg.ApiToken = "123456"
|
cfg.ApiToken = "123456"
|
||||||
@@ -285,16 +285,57 @@ func TestAuthorization(t *testing.T) {
|
|||||||
Stop()
|
Stop()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
code, _ := doHttpRequest[any](http.MethodGet, "/api/v1/config", nil, nil)
|
status, _ := doHttpRequest0(http.MethodGet, "/api/v1/config", nil, nil)
|
||||||
checkCode(code, model.CodeUnauthorized)
|
if status != http.StatusUnauthorized {
|
||||||
|
t.Errorf("TestApiToken() got = %v, want %v", status, http.StatusUnauthorized)
|
||||||
|
}
|
||||||
|
|
||||||
code, _ = doHttpRequest[any](http.MethodGet, "/api/v1/config", map[string]string{
|
status, _ = doHttpRequest0(http.MethodGet, "/api/v1/config", map[string]string{
|
||||||
"X-Api-Token": cfg.ApiToken,
|
"X-Api-Token": cfg.ApiToken,
|
||||||
}, nil)
|
}, nil)
|
||||||
checkCode(code, model.CodeOk)
|
if status != http.StatusOK {
|
||||||
|
t.Errorf("TestApiToken() got = %v, want %v", status, http.StatusOK)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAuthorization(t *testing.T) {
|
||||||
|
var cfg = &model.StartConfig{}
|
||||||
|
cfg.Init()
|
||||||
|
cfg.ApiToken = "123456"
|
||||||
|
cfg.WebEnable = true
|
||||||
|
cfg.WebBasicAuth = &model.WebBasicAuth{
|
||||||
|
Username: "admin",
|
||||||
|
Password: "123456",
|
||||||
|
}
|
||||||
|
fileListener := doStart(cfg)
|
||||||
|
defer func() {
|
||||||
|
if err := fileListener.Close(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
Stop()
|
||||||
|
}()
|
||||||
|
|
||||||
|
status, _ := doHttpRequest0(http.MethodGet, "/api/v1/config", nil, nil)
|
||||||
|
if status != http.StatusUnauthorized {
|
||||||
|
t.Errorf("TestAuthorization() got = %v, want %v", status, http.StatusUnauthorized)
|
||||||
|
}
|
||||||
|
|
||||||
|
status, _ = doHttpRequest0(http.MethodGet, "/api/v1/config", map[string]string{
|
||||||
|
"Authorization": cfg.WebBasicAuth.Authorization(),
|
||||||
|
}, nil)
|
||||||
|
if status != http.StatusOK {
|
||||||
|
t.Errorf("TestAuthorization() got = %v, want %v", status, http.StatusOK)
|
||||||
|
}
|
||||||
|
|
||||||
|
status, _ = doHttpRequest0(http.MethodGet, "/api/v1/config", map[string]string{
|
||||||
|
"X-Api-Token": cfg.ApiToken,
|
||||||
|
}, nil)
|
||||||
|
if status != http.StatusOK {
|
||||||
|
t.Errorf("TestAuthorization() got = %v, want %v", status, http.StatusOK)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func doTest(handler func()) {
|
func doTest(handler func()) {
|
||||||
testFunc := func(storage model.Storage) {
|
testFunc := func(storage model.Storage) {
|
||||||
var cfg = &model.StartConfig{}
|
var cfg = &model.StartConfig{}
|
||||||
|
|||||||
@@ -88,3 +88,13 @@ func CreateDirIfNotExist(dir string) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsExistsFile check file exists and is a file
|
||||||
|
func IsExistsFile(path string) bool {
|
||||||
|
info, err := os.Stat(path)
|
||||||
|
// 判断路径是否存在,并且是一个文件
|
||||||
|
if err == nil && !info.IsDir() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
@@ -199,3 +199,43 @@ func doCheckDuplicateAndRename(t *testing.T, exitsPaths []string, path string, e
|
|||||||
t.Errorf("CheckDuplicateAndRename() = %v, want %v", got, except)
|
t.Errorf("CheckDuplicateAndRename() = %v, want %v", got, except)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIsExistsFile(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
path string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "exist",
|
||||||
|
args: args{
|
||||||
|
path: "./path.go",
|
||||||
|
},
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "not exist",
|
||||||
|
args: args{
|
||||||
|
path: "./path_not_exist.go",
|
||||||
|
},
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "is dir",
|
||||||
|
args: args{
|
||||||
|
path: "../util",
|
||||||
|
},
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := IsExistsFile(tt.args.path); got != tt.want {
|
||||||
|
t.Errorf("IsExistsFile() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,13 +1,39 @@
|
|||||||
package util
|
package util
|
||||||
|
|
||||||
import "strings"
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
const FileSchema = "FILE"
|
const FileSchema = "FILE"
|
||||||
|
|
||||||
func ParseSchema(url string) string {
|
func ParseSchema(url string) string {
|
||||||
index := strings.Index(url, ":")
|
index := strings.Index(url, ":")
|
||||||
if index == -1 {
|
// if no schema or windows path like C:\a.txt, return FILE
|
||||||
|
if index == -1 || index == 1 {
|
||||||
return FileSchema
|
return FileSchema
|
||||||
}
|
}
|
||||||
return strings.ToUpper(url[:index])
|
schema := url[:index]
|
||||||
|
if schema == "data" {
|
||||||
|
schema, _ = ParseDataUri(url)
|
||||||
|
}
|
||||||
|
return strings.ToUpper(schema)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseDataUri parses a data URI and returns the MIME type and decode data.
|
||||||
|
func ParseDataUri(uri string) (string, []byte) {
|
||||||
|
re := regexp.MustCompile(`^data:(.*);base64,(.*)$`)
|
||||||
|
matches := re.FindStringSubmatch(uri)
|
||||||
|
if len(matches) != 3 {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
mime := matches[1]
|
||||||
|
base64Data := matches[2]
|
||||||
|
// 解码Base64数据
|
||||||
|
data, err := base64.StdEncoding.DecodeString(base64Data)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
return mime, data
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,10 @@
|
|||||||
package util
|
package util
|
||||||
|
|
||||||
import "testing"
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
func TestParseSchema(t *testing.T) {
|
func TestParseSchema(t *testing.T) {
|
||||||
type args struct {
|
type args struct {
|
||||||
@@ -35,7 +39,21 @@ func TestParseSchema(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "file-no-scheme",
|
name: "file-no-scheme",
|
||||||
args: args{
|
args: args{
|
||||||
url: "/home/bt.torrent",
|
url: "./url.go",
|
||||||
|
},
|
||||||
|
want: "FILE",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "data-uri",
|
||||||
|
args: args{
|
||||||
|
url: "data:application/x-bittorrent;base64,test",
|
||||||
|
},
|
||||||
|
want: "APPLICATION/X-BITTORRENT",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "windows-path",
|
||||||
|
args: args{
|
||||||
|
url: "D:\\bt.torrent",
|
||||||
},
|
},
|
||||||
want: "FILE",
|
want: "FILE",
|
||||||
},
|
},
|
||||||
@@ -48,3 +66,65 @@ func TestParseSchema(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParseDataUri(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
uri string
|
||||||
|
}
|
||||||
|
type result struct {
|
||||||
|
mime string
|
||||||
|
data []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
testData := []byte("test")
|
||||||
|
testData64 := base64.StdEncoding.EncodeToString(testData)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want result
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "success",
|
||||||
|
args: args{
|
||||||
|
uri: "data:application/x-bittorrent;base64," + testData64,
|
||||||
|
},
|
||||||
|
want: result{
|
||||||
|
mime: "application/x-bittorrent",
|
||||||
|
data: testData,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "fail-dirty-data",
|
||||||
|
args: args{
|
||||||
|
uri: "data::application/x-bittorrent;base64,!@$",
|
||||||
|
},
|
||||||
|
want: result{
|
||||||
|
mime: "",
|
||||||
|
data: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "fail-miss-data",
|
||||||
|
args: args{
|
||||||
|
uri: ":application/x-bittorrent;base64," + testData64,
|
||||||
|
},
|
||||||
|
want: result{
|
||||||
|
mime: "",
|
||||||
|
data: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
mime, data := ParseDataUri(tt.args.uri)
|
||||||
|
got := result{
|
||||||
|
mime: mime,
|
||||||
|
data: data,
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("ParseDataUri() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -31,12 +31,8 @@ class MainActivity : FlutterActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
"stop" -> {
|
"stop" -> {
|
||||||
try {
|
Libgopeed.stop()
|
||||||
Libgopeed.stop()
|
result.success(null)
|
||||||
result.success(null)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
result.error("ERROR", e.message, null)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
result.notImplemented()
|
result.notImplemented()
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
"downloadDirValid": "请选择下载目录",
|
"downloadDirValid": "请选择下载目录",
|
||||||
"connections": "连接数",
|
"connections": "连接数",
|
||||||
"maxRunning": "最大下载数",
|
"maxRunning": "最大下载数",
|
||||||
"items": "@count 条",
|
"items": "@count 项",
|
||||||
"subscribeTracker": "订阅 Tracker",
|
"subscribeTracker": "订阅 Tracker",
|
||||||
"subscribeFail": "订阅失败,请检查网络或稍后重试",
|
"subscribeFail": "订阅失败,请检查网络或稍后重试",
|
||||||
"update": "更新",
|
"update": "更新",
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
{
|
||||||
|
"label": "中文 (正體) ",
|
||||||
|
"error": "錯誤",
|
||||||
|
"tip": "提示",
|
||||||
|
"confirm": "確定",
|
||||||
|
"cancel": "取消",
|
||||||
|
"selectAll": "全選",
|
||||||
|
"task": "任務",
|
||||||
|
"downloading": "下載中",
|
||||||
|
"downloaded": "已完成",
|
||||||
|
"setting": "設定",
|
||||||
|
"donate": "斗內",
|
||||||
|
"exit": "離開",
|
||||||
|
"create": "新增任務",
|
||||||
|
"advancedOptions": "進階選項",
|
||||||
|
"downloadLink": "下載鏈接",
|
||||||
|
"downloadLinkValid": "請輸入下載鏈接",
|
||||||
|
"downloadLinkHit": "請輸入下載鏈接,支援 HTTP/HTTPS/MAGNET@append",
|
||||||
|
"downloadLinkHitDesktop": ",也可以通過拖拽Torrent文件至此以新增任務",
|
||||||
|
"download": "下載",
|
||||||
|
"noFileSelected": "沒有選取的文件",
|
||||||
|
"noStoragePermission": "沒有存儲權限",
|
||||||
|
"selectFile": "選擇文件",
|
||||||
|
"rename": "重命名",
|
||||||
|
"basic": "基礎",
|
||||||
|
"advanced": "進階",
|
||||||
|
"general": "通用設定",
|
||||||
|
"downloadDir": "下載目録",
|
||||||
|
"downloadDirValid": "請選擇下載目録",
|
||||||
|
"connections": "同時連接數量",
|
||||||
|
"maxRunning": "最大同時下載數",
|
||||||
|
"items": "@count 項",
|
||||||
|
"subscribeTracker": "已訂閲的 Tracker",
|
||||||
|
"subscribeFail": "訂閲失敗",
|
||||||
|
"update": "更新",
|
||||||
|
"updateDaily": "每日自動更新",
|
||||||
|
"lastUpdate": "上次更新時間: @time",
|
||||||
|
"addTracker": "增加新的 Tracker",
|
||||||
|
"addTrackerHit": "請輸入 Tracker 服務器地址,每行一條",
|
||||||
|
"ui": "介面",
|
||||||
|
"theme": "佈景主題",
|
||||||
|
"themeSystem": "跟隨系統",
|
||||||
|
"themeLight": "亮色主題",
|
||||||
|
"themeDark": "暗色主題",
|
||||||
|
"locale": "語言",
|
||||||
|
"about": "關於",
|
||||||
|
"homepage": "訪問主頁",
|
||||||
|
"version": "版本資訊",
|
||||||
|
"protocol": "通訊協定",
|
||||||
|
"port": "端口",
|
||||||
|
"apiToken": "接口 Token",
|
||||||
|
"notSet": "尚未設定",
|
||||||
|
"set": "已設定",
|
||||||
|
"effectAfterRestart": "該選項將在重新啓動軟體後生效",
|
||||||
|
"startAll": "全部開始",
|
||||||
|
"pauseAll": "全部暫停",
|
||||||
|
"deleteTask": "刪除任務",
|
||||||
|
"deleteTaskTip": "保留已經下載的文件",
|
||||||
|
"delete": "刪除",
|
||||||
|
"newVersionTitle": "發現新版本 @version",
|
||||||
|
"newVersionUpdate": "立刻更新",
|
||||||
|
"newVersionLater": "稍後更新"
|
||||||
|
}
|
||||||
@@ -21,6 +21,6 @@
|
|||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1.0</string>
|
<string>1.0</string>
|
||||||
<key>MinimumOSVersion</key>
|
<key>MinimumOSVersion</key>
|
||||||
<string>9.0</string>
|
<string>11.0</string>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
# Uncomment this line to define a global platform for your project
|
# Uncomment this line to define a global platform for your project
|
||||||
# platform :ios, '9.0'
|
# platform :ios, '11.0'
|
||||||
|
|
||||||
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||||
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
PODS:
|
||||||
|
- file_selector_ios (0.0.1):
|
||||||
|
- Flutter
|
||||||
|
- Flutter (1.0.0)
|
||||||
|
- package_info_plus (0.4.5):
|
||||||
|
- Flutter
|
||||||
|
- path_provider_ios (0.0.1):
|
||||||
|
- Flutter
|
||||||
|
- permission_handler_apple (9.0.4):
|
||||||
|
- Flutter
|
||||||
|
- shared_preferences_ios (0.0.1):
|
||||||
|
- Flutter
|
||||||
|
- url_launcher_ios (0.0.1):
|
||||||
|
- Flutter
|
||||||
|
|
||||||
|
DEPENDENCIES:
|
||||||
|
- file_selector_ios (from `.symlinks/plugins/file_selector_ios/ios`)
|
||||||
|
- Flutter (from `Flutter`)
|
||||||
|
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
||||||
|
- path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`)
|
||||||
|
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
|
||||||
|
- shared_preferences_ios (from `.symlinks/plugins/shared_preferences_ios/ios`)
|
||||||
|
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||||
|
|
||||||
|
EXTERNAL SOURCES:
|
||||||
|
file_selector_ios:
|
||||||
|
:path: ".symlinks/plugins/file_selector_ios/ios"
|
||||||
|
Flutter:
|
||||||
|
:path: Flutter
|
||||||
|
package_info_plus:
|
||||||
|
:path: ".symlinks/plugins/package_info_plus/ios"
|
||||||
|
path_provider_ios:
|
||||||
|
:path: ".symlinks/plugins/path_provider_ios/ios"
|
||||||
|
permission_handler_apple:
|
||||||
|
:path: ".symlinks/plugins/permission_handler_apple/ios"
|
||||||
|
shared_preferences_ios:
|
||||||
|
:path: ".symlinks/plugins/shared_preferences_ios/ios"
|
||||||
|
url_launcher_ios:
|
||||||
|
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
||||||
|
|
||||||
|
SPEC CHECKSUMS:
|
||||||
|
file_selector_ios: 6e9b6d7300556c730f224905726f0a2d5d874e51
|
||||||
|
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
|
||||||
|
package_info_plus: 6c92f08e1f853dc01228d6f553146438dafcd14e
|
||||||
|
path_provider_ios: 14f3d2fd28c4fdb42f44e0f751d12861c43cee02
|
||||||
|
permission_handler_apple: 44366e37eaf29454a1e7b1b7d736c2cceaeb17ce
|
||||||
|
shared_preferences_ios: 548a61f8053b9b8a49ac19c1ffbc8b92c50d68ad
|
||||||
|
url_launcher_ios: 839c58cdb4279282219f5e248c3321761ff3c4de
|
||||||
|
|
||||||
|
PODFILE CHECKSUM: ef19549a9bc3046e7bb7d2fab4d021637c0c58a3
|
||||||
|
|
||||||
|
COCOAPODS: 1.11.3
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
archiveVersion = 1;
|
archiveVersion = 1;
|
||||||
classes = {
|
classes = {
|
||||||
};
|
};
|
||||||
objectVersion = 50;
|
objectVersion = 54;
|
||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
@@ -13,28 +13,36 @@
|
|||||||
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
||||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
||||||
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
||||||
|
D081C25C294826C0006EB10B /* libc++.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = D081C25B294826C0006EB10B /* libc++.tbd */; };
|
||||||
|
D0E623A929482D160001185E /* Libgopeed.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0E623A729482D030001185E /* Libgopeed.xcframework */; };
|
||||||
|
D0E623AA29482D160001185E /* Libgopeed.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D0E623A729482D030001185E /* Libgopeed.xcframework */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||||
|
DCA5B0FA95A8007ADEDACA1B /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 70BC8D37FAC641C7F20125E8 /* Pods_Runner.framework */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXCopyFilesBuildPhase section */
|
/* Begin PBXCopyFilesBuildPhase section */
|
||||||
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
|
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
|
||||||
isa = PBXCopyFilesBuildPhase;
|
isa = PBXCopyFilesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 8;
|
||||||
dstPath = "";
|
dstPath = "";
|
||||||
dstSubfolderSpec = 10;
|
dstSubfolderSpec = 10;
|
||||||
files = (
|
files = (
|
||||||
|
D0E623AA29482D160001185E /* Libgopeed.xcframework in Embed Frameworks */,
|
||||||
);
|
);
|
||||||
name = "Embed Frameworks";
|
name = "Embed Frameworks";
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 1;
|
||||||
};
|
};
|
||||||
/* End PBXCopyFilesBuildPhase section */
|
/* End PBXCopyFilesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
|
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
|
||||||
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
|
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
|
||||||
|
25B61E78A388C87913468FD6 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
|
||||||
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
|
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
|
||||||
|
70BC8D37FAC641C7F20125E8 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
|
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||||
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
|
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
|
||||||
|
97232923D357E56CAA39281D /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
|
||||||
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
|
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
|
||||||
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
|
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
|
||||||
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
@@ -42,6 +50,9 @@
|
|||||||
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||||
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||||
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
|
BC90D28B9068C663815415CC /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
D081C25B294826C0006EB10B /* libc++.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = "libc++.tbd"; path = "usr/lib/libc++.tbd"; sourceTree = SDKROOT; };
|
||||||
|
D0E623A729482D030001185E /* Libgopeed.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = Libgopeed.xcframework; path = Frameworks/Libgopeed.xcframework; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
@@ -49,6 +60,9 @@
|
|||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
D0E623A929482D160001185E /* Libgopeed.xcframework in Frameworks */,
|
||||||
|
D081C25C294826C0006EB10B /* libc++.tbd in Frameworks */,
|
||||||
|
DCA5B0FA95A8007ADEDACA1B /* Pods_Runner.framework in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@@ -72,6 +86,8 @@
|
|||||||
9740EEB11CF90186004384FC /* Flutter */,
|
9740EEB11CF90186004384FC /* Flutter */,
|
||||||
97C146F01CF9000F007C117D /* Runner */,
|
97C146F01CF9000F007C117D /* Runner */,
|
||||||
97C146EF1CF9000F007C117D /* Products */,
|
97C146EF1CF9000F007C117D /* Products */,
|
||||||
|
ADF7F433B4BC23D6C18AB78B /* Pods */,
|
||||||
|
B4A1CBAC5AA50208793CFF34 /* Frameworks */,
|
||||||
);
|
);
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
@@ -98,6 +114,26 @@
|
|||||||
path = Runner;
|
path = Runner;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
ADF7F433B4BC23D6C18AB78B /* Pods */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
97232923D357E56CAA39281D /* Pods-Runner.debug.xcconfig */,
|
||||||
|
BC90D28B9068C663815415CC /* Pods-Runner.release.xcconfig */,
|
||||||
|
25B61E78A388C87913468FD6 /* Pods-Runner.profile.xcconfig */,
|
||||||
|
);
|
||||||
|
path = Pods;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
B4A1CBAC5AA50208793CFF34 /* Frameworks */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
D0E623A729482D030001185E /* Libgopeed.xcframework */,
|
||||||
|
D081C25B294826C0006EB10B /* libc++.tbd */,
|
||||||
|
70BC8D37FAC641C7F20125E8 /* Pods_Runner.framework */,
|
||||||
|
);
|
||||||
|
name = Frameworks;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
/* End PBXGroup section */
|
/* End PBXGroup section */
|
||||||
|
|
||||||
/* Begin PBXNativeTarget section */
|
/* Begin PBXNativeTarget section */
|
||||||
@@ -105,12 +141,14 @@
|
|||||||
isa = PBXNativeTarget;
|
isa = PBXNativeTarget;
|
||||||
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
|
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
|
||||||
buildPhases = (
|
buildPhases = (
|
||||||
|
F3093A8F123671F8F587626F /* [CP] Check Pods Manifest.lock */,
|
||||||
9740EEB61CF901F6004384FC /* Run Script */,
|
9740EEB61CF901F6004384FC /* Run Script */,
|
||||||
97C146EA1CF9000F007C117D /* Sources */,
|
97C146EA1CF9000F007C117D /* Sources */,
|
||||||
97C146EB1CF9000F007C117D /* Frameworks */,
|
97C146EB1CF9000F007C117D /* Frameworks */,
|
||||||
97C146EC1CF9000F007C117D /* Resources */,
|
97C146EC1CF9000F007C117D /* Resources */,
|
||||||
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
||||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
||||||
|
DDA801E9D83E2E88398514D3 /* [CP] Embed Pods Frameworks */,
|
||||||
);
|
);
|
||||||
buildRules = (
|
buildRules = (
|
||||||
);
|
);
|
||||||
@@ -127,7 +165,7 @@
|
|||||||
97C146E61CF9000F007C117D /* Project object */ = {
|
97C146E61CF9000F007C117D /* Project object */ = {
|
||||||
isa = PBXProject;
|
isa = PBXProject;
|
||||||
attributes = {
|
attributes = {
|
||||||
LastUpgradeCheck = 1300;
|
LastUpgradeCheck = 1430;
|
||||||
ORGANIZATIONNAME = "";
|
ORGANIZATIONNAME = "";
|
||||||
TargetAttributes = {
|
TargetAttributes = {
|
||||||
97C146ED1CF9000F007C117D = {
|
97C146ED1CF9000F007C117D = {
|
||||||
@@ -171,10 +209,12 @@
|
|||||||
/* Begin PBXShellScriptBuildPhase section */
|
/* Begin PBXShellScriptBuildPhase section */
|
||||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
|
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
|
||||||
isa = PBXShellScriptBuildPhase;
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
alwaysOutOfDate = 1;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
);
|
);
|
||||||
inputPaths = (
|
inputPaths = (
|
||||||
|
"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
|
||||||
);
|
);
|
||||||
name = "Thin Binary";
|
name = "Thin Binary";
|
||||||
outputPaths = (
|
outputPaths = (
|
||||||
@@ -185,6 +225,7 @@
|
|||||||
};
|
};
|
||||||
9740EEB61CF901F6004384FC /* Run Script */ = {
|
9740EEB61CF901F6004384FC /* Run Script */ = {
|
||||||
isa = PBXShellScriptBuildPhase;
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
alwaysOutOfDate = 1;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
);
|
);
|
||||||
@@ -197,6 +238,45 @@
|
|||||||
shellPath = /bin/sh;
|
shellPath = /bin/sh;
|
||||||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
|
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
|
||||||
};
|
};
|
||||||
|
DDA801E9D83E2E88398514D3 /* [CP] Embed Pods Frameworks */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputFileListPaths = (
|
||||||
|
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
||||||
|
);
|
||||||
|
name = "[CP] Embed Pods Frameworks";
|
||||||
|
outputFileListPaths = (
|
||||||
|
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
|
||||||
|
showEnvVarsInLog = 0;
|
||||||
|
};
|
||||||
|
F3093A8F123671F8F587626F /* [CP] Check Pods Manifest.lock */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputFileListPaths = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
||||||
|
"${PODS_ROOT}/Manifest.lock",
|
||||||
|
);
|
||||||
|
name = "[CP] Check Pods Manifest.lock";
|
||||||
|
outputFileListPaths = (
|
||||||
|
);
|
||||||
|
outputPaths = (
|
||||||
|
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||||
|
showEnvVarsInLog = 0;
|
||||||
|
};
|
||||||
/* End PBXShellScriptBuildPhase section */
|
/* End PBXShellScriptBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXSourcesBuildPhase section */
|
/* Begin PBXSourcesBuildPhase section */
|
||||||
@@ -272,7 +352,7 @@
|
|||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||||
MTL_ENABLE_DEBUG_INFO = NO;
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
SUPPORTED_PLATFORMS = iphoneos;
|
SUPPORTED_PLATFORMS = iphoneos;
|
||||||
@@ -288,6 +368,7 @@
|
|||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
|
DEVELOPMENT_TEAM = JH48DS925K;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
@@ -349,7 +430,7 @@
|
|||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||||
MTL_ENABLE_DEBUG_INFO = YES;
|
MTL_ENABLE_DEBUG_INFO = YES;
|
||||||
ONLY_ACTIVE_ARCH = YES;
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
@@ -398,7 +479,7 @@
|
|||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||||
MTL_ENABLE_DEBUG_INFO = NO;
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
SUPPORTED_PLATFORMS = iphoneos;
|
SUPPORTED_PLATFORMS = iphoneos;
|
||||||
@@ -416,6 +497,7 @@
|
|||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
|
DEVELOPMENT_TEAM = JH48DS925K;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
@@ -438,6 +520,7 @@
|
|||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
|
DEVELOPMENT_TEAM = JH48DS925K;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Scheme
|
<Scheme
|
||||||
LastUpgradeVersion = "1300"
|
LastUpgradeVersion = "1430"
|
||||||
version = "1.3">
|
version = "1.3">
|
||||||
<BuildAction
|
<BuildAction
|
||||||
parallelizeBuildables = "YES"
|
parallelizeBuildables = "YES"
|
||||||
|
|||||||
@@ -4,4 +4,7 @@
|
|||||||
<FileRef
|
<FileRef
|
||||||
location = "group:Runner.xcodeproj">
|
location = "group:Runner.xcodeproj">
|
||||||
</FileRef>
|
</FileRef>
|
||||||
|
<FileRef
|
||||||
|
location = "group:Pods/Pods.xcodeproj">
|
||||||
|
</FileRef>
|
||||||
</Workspace>
|
</Workspace>
|
||||||
@@ -1,13 +1,38 @@
|
|||||||
import UIKit
|
import UIKit
|
||||||
import Flutter
|
import Flutter
|
||||||
|
import Libgopeed
|
||||||
|
|
||||||
@UIApplicationMain
|
@UIApplicationMain
|
||||||
@objc class AppDelegate: FlutterAppDelegate {
|
@objc class AppDelegate: FlutterAppDelegate {
|
||||||
override func application(
|
override func application(
|
||||||
_ application: UIApplication,
|
_ application: UIApplication,
|
||||||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
||||||
) -> Bool {
|
) -> Bool {
|
||||||
GeneratedPluginRegistrant.register(with: self)
|
let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
|
||||||
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
let batteryChannel = FlutterMethodChannel(name: "gopeed.com/libgopeed",
|
||||||
}
|
binaryMessenger: controller.binaryMessenger)
|
||||||
|
batteryChannel.setMethodCallHandler({
|
||||||
|
(call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
|
||||||
|
switch call.method {
|
||||||
|
case "start":
|
||||||
|
let args = call.arguments as? Dictionary<String, Any>
|
||||||
|
let cfg = args?["cfg"] as? String
|
||||||
|
let portPrt = UnsafeMutablePointer<Int>.allocate(capacity: MemoryLayout<Int>.stride)
|
||||||
|
var error: NSError?
|
||||||
|
if LibgopeedStart(cfg, portPrt, &error){
|
||||||
|
result(portPrt.pointee)
|
||||||
|
}else{
|
||||||
|
result(FlutterError(code: "ERROR", message: error.debugDescription, details: nil))
|
||||||
|
}
|
||||||
|
case "stop":
|
||||||
|
LibgopeedStop()
|
||||||
|
result(nil)
|
||||||
|
default:
|
||||||
|
result(FlutterMethodNotImplemented)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
GeneratedPluginRegistrant.register(with: self)
|
||||||
|
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 564 B After Width: | Height: | Size: 714 B |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 4.8 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 4.8 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 7.2 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 6.1 KiB |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 6.6 KiB |
@@ -2,6 +2,8 @@
|
|||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
|
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||||
|
<true/>
|
||||||
<key>CFBundleDevelopmentRegion</key>
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||||
<key>CFBundleDisplayName</key>
|
<key>CFBundleDisplayName</key>
|
||||||
@@ -12,8 +14,6 @@
|
|||||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||||
<key>CFBundleInfoDictionaryVersion</key>
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
<string>6.0</string>
|
<string>6.0</string>
|
||||||
<key>CFBundleName</key>
|
|
||||||
<string>gopeed</string>
|
|
||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
@@ -24,6 +24,11 @@
|
|||||||
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||||
<key>LSRequiresIPhoneOS</key>
|
<key>LSRequiresIPhoneOS</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
<key>NSAppTransportSecurity</key>
|
||||||
|
<dict>
|
||||||
|
<key>NSAllowsArbitraryLoads</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
<key>UILaunchStoryboardName</key>
|
<key>UILaunchStoryboardName</key>
|
||||||
<string>LaunchScreen</string>
|
<string>LaunchScreen</string>
|
||||||
<key>UIMainStoryboardFile</key>
|
<key>UIMainStoryboardFile</key>
|
||||||
@@ -43,7 +48,7 @@
|
|||||||
</array>
|
</array>
|
||||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||||
<false/>
|
<false/>
|
||||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||||
<true/>
|
<true/>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:ui' as ui;
|
|
||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:app_links/app_links.dart';
|
import 'package:app_links/app_links.dart';
|
||||||
@@ -226,7 +225,7 @@ class AppController extends GetxController with WindowListener, TrayListener {
|
|||||||
return _defaultStartConfig!;
|
return _defaultStartConfig!;
|
||||||
}
|
}
|
||||||
_defaultStartConfig = StartConfig();
|
_defaultStartConfig = StartConfig();
|
||||||
if (!Util.isUnix()) {
|
if (!Util.supportUnixSocket()) {
|
||||||
// not support unix socket, use tcp
|
// not support unix socket, use tcp
|
||||||
_defaultStartConfig!.network = "tcp";
|
_defaultStartConfig!.network = "tcp";
|
||||||
_defaultStartConfig!.address = "127.0.0.1:0";
|
_defaultStartConfig!.address = "127.0.0.1:0";
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
@@ -10,6 +13,8 @@ class CreateController extends GetxController
|
|||||||
final isResolving = false.obs;
|
final isResolving = false.obs;
|
||||||
final showAdvanced = false.obs;
|
final showAdvanced = false.obs;
|
||||||
late TabController advancedTabController;
|
late TabController advancedTabController;
|
||||||
|
final oldUrl = "".obs;
|
||||||
|
final fileDataUri = "".obs;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
@@ -22,4 +27,13 @@ class CreateController extends GetxController
|
|||||||
advancedTabController.dispose();
|
advancedTabController.dispose();
|
||||||
super.onClose();
|
super.onClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setFileDataUri(Uint8List bytes) {
|
||||||
|
fileDataUri.value =
|
||||||
|
"data:application/x-bittorrent;base64,${base64.encode(bytes)}";
|
||||||
|
}
|
||||||
|
|
||||||
|
void clearFileDataUri() {
|
||||||
|
fileDataUri.value = "";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -30,6 +30,8 @@ class CreateView extends GetView<CreateController> {
|
|||||||
final _httpRefererController = TextEditingController();
|
final _httpRefererController = TextEditingController();
|
||||||
final _btTrackerController = TextEditingController();
|
final _btTrackerController = TextEditingController();
|
||||||
|
|
||||||
|
final _availableSchemes = ["http:", "https:", "magnet:"];
|
||||||
|
|
||||||
CreateView({Key? key}) : super(key: key);
|
CreateView({Key? key}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -37,8 +39,27 @@ class CreateView extends GetView<CreateController> {
|
|||||||
final String? filePath = Get.rootDelegate.arguments();
|
final String? filePath = Get.rootDelegate.arguments();
|
||||||
if (_urlController.text.isEmpty && filePath != null) {
|
if (_urlController.text.isEmpty && filePath != null) {
|
||||||
_urlController.text = filePath;
|
_urlController.text = filePath;
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
_urlController.selection = TextSelection.fromPosition(
|
||||||
await _doResolve();
|
TextPosition(offset: _urlController.text.length));
|
||||||
|
}
|
||||||
|
|
||||||
|
// if no file path, read from clipboard
|
||||||
|
if (filePath?.isEmpty ?? true) {
|
||||||
|
Clipboard.getData('text/plain').then((value) {
|
||||||
|
if (value?.text?.isNotEmpty ?? false) {
|
||||||
|
if (_availableSchemes
|
||||||
|
.where((e) =>
|
||||||
|
value!.text!.startsWith(e) ||
|
||||||
|
value.text!.startsWith(e.toUpperCase()))
|
||||||
|
.isNotEmpty) {
|
||||||
|
_urlController.text = value!.text!;
|
||||||
|
_urlController.selection = TextSelection.fromPosition(
|
||||||
|
TextPosition(offset: _urlController.text.length));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
recognizeMagnetUri(value!.text!);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,151 +72,192 @@ class CreateView extends GetView<CreateController> {
|
|||||||
title: Text('create'.tr),
|
title: Text('create'.tr),
|
||||||
),
|
),
|
||||||
body: DropTarget(
|
body: DropTarget(
|
||||||
onDragDone: (details) {
|
onDragDone: (details) async {
|
||||||
_urlController.text = details.files[0].path;
|
if (!Util.isWeb()) {
|
||||||
|
_urlController.text = details.files[0].path;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_urlController.text = details.files[0].name;
|
||||||
|
final bytes = await details.files[0].readAsBytes();
|
||||||
|
controller.setFileDataUri(bytes);
|
||||||
},
|
},
|
||||||
child: Padding(
|
child: GestureDetector(
|
||||||
padding:
|
behavior: HitTestBehavior.opaque,
|
||||||
const EdgeInsets.symmetric(vertical: 16.0, horizontal: 24.0),
|
onTap: () {
|
||||||
child: Form(
|
FocusScope.of(context).requestFocus(FocusNode());
|
||||||
key: _resolveFormKey,
|
},
|
||||||
autovalidateMode: AutovalidateMode.onUserInteraction,
|
child: Padding(
|
||||||
child: Column(children: [
|
padding:
|
||||||
Row(
|
const EdgeInsets.symmetric(vertical: 16.0, horizontal: 24.0),
|
||||||
children: [
|
child: Form(
|
||||||
|
key: _resolveFormKey,
|
||||||
|
autovalidateMode: AutovalidateMode.onUserInteraction,
|
||||||
|
child: Column(children: [
|
||||||
|
Row(children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: TextFormField(
|
child: TextFormField(
|
||||||
autofocus: true,
|
autofocus: true,
|
||||||
controller: _urlController,
|
controller: _urlController,
|
||||||
minLines: 1,
|
minLines: 1,
|
||||||
maxLines: 5,
|
maxLines: 5,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
hintText: _hitText(),
|
hintText: _hitText(),
|
||||||
hintStyle: const TextStyle(fontSize: 12),
|
hintStyle: const TextStyle(fontSize: 12),
|
||||||
labelText: 'downloadLink'.tr,
|
labelText: 'downloadLink'.tr,
|
||||||
icon: const Icon(Icons.link),
|
icon: const Icon(Icons.link),
|
||||||
suffixIcon: IconButton(
|
suffixIcon: IconButton(
|
||||||
onPressed: _urlController.clear,
|
onPressed: () {
|
||||||
icon: const Icon(Icons.clear),
|
_urlController.clear();
|
||||||
),
|
controller.clearFileDataUri();
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.clear),
|
||||||
),
|
),
|
||||||
validator: (v) {
|
),
|
||||||
return v!.trim().isNotEmpty
|
validator: (v) {
|
||||||
? null
|
return v!.trim().isNotEmpty
|
||||||
: 'downloadLinkValid'.tr;
|
? null
|
||||||
}),
|
: 'downloadLinkValid'.tr;
|
||||||
|
},
|
||||||
|
onChanged: (v) async {
|
||||||
|
controller.clearFileDataUri();
|
||||||
|
if (controller.oldUrl.value.isEmpty) {
|
||||||
|
recognizeMagnetUri(v);
|
||||||
|
}
|
||||||
|
controller.oldUrl.value = v;
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
Util.isWeb()
|
IconButton(
|
||||||
? null
|
icon: const Icon(Icons.folder_open),
|
||||||
: IconButton(
|
onPressed: () async {
|
||||||
icon: const Icon(Icons.folder_open),
|
var pr = await FilePicker.platform.pickFiles(
|
||||||
onPressed: () async {
|
type: FileType.custom,
|
||||||
var pr = await FilePicker.platform.pickFiles(
|
allowedExtensions: ["torrent"]);
|
||||||
type: FileType.custom,
|
if (pr != null) {
|
||||||
allowedExtensions: ["torrent"]);
|
if (!Util.isWeb()) {
|
||||||
if (pr != null) {
|
_urlController.text = pr.files[0].path ?? "";
|
||||||
_urlController.text = pr.files[0].path ?? "";
|
return;
|
||||||
}
|
}
|
||||||
}),
|
_urlController.text = pr.files[0].name;
|
||||||
].where((e) => e != null).map((e) => e!).toList(),
|
controller.setFileDataUri(pr.files[0].bytes!);
|
||||||
),
|
}
|
||||||
Obx(() => Visibility(
|
}),
|
||||||
visible: controller.showAdvanced.value,
|
]
|
||||||
child: Padding(
|
//.where((e) => e != null).map((e) => e!).toList(),
|
||||||
padding: const EdgeInsets.only(left: 40, top: 15),
|
),
|
||||||
child: Column(
|
Obx(() => Visibility(
|
||||||
children: [
|
visible: controller.showAdvanced.value,
|
||||||
TabBar(
|
child: Padding(
|
||||||
controller: controller.advancedTabController,
|
padding: const EdgeInsets.only(left: 40, top: 15),
|
||||||
tabs: const [
|
child: Column(
|
||||||
Tab(
|
children: [
|
||||||
text: 'HTTP',
|
TabBar(
|
||||||
),
|
controller: controller.advancedTabController,
|
||||||
Tab(
|
tabs: const [
|
||||||
text: 'BitTorrent',
|
Tab(
|
||||||
)
|
text: 'HTTP',
|
||||||
],
|
),
|
||||||
|
Tab(
|
||||||
|
text: 'BitTorrent',
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
AutoScaleTabBarView(
|
||||||
|
controller: controller.advancedTabController,
|
||||||
|
children: [
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
TextFormField(
|
||||||
|
controller: _httpUaController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'User-Agent',
|
||||||
|
)),
|
||||||
|
TextFormField(
|
||||||
|
controller: _httpCookieController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Cookie',
|
||||||
|
)),
|
||||||
|
TextFormField(
|
||||||
|
controller: _httpRefererController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Referer',
|
||||||
|
)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
TextFormField(
|
||||||
|
controller: _btTrackerController,
|
||||||
|
maxLines: 5,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'Trakers',
|
||||||
|
hintText: 'addTrackerHit'.tr,
|
||||||
|
)),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
))),
|
||||||
|
Center(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 15),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(right: 10),
|
||||||
|
child: TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
controller.showAdvanced.value =
|
||||||
|
!controller.showAdvanced.value;
|
||||||
|
},
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Obx(() => Checkbox(
|
||||||
|
value: controller.showAdvanced.value,
|
||||||
|
onChanged: (bool? value) {
|
||||||
|
controller.showAdvanced.value =
|
||||||
|
value ?? false;
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
Text('advancedOptions'.tr),
|
||||||
|
]),
|
||||||
),
|
),
|
||||||
AutoScaleTabBarView(
|
|
||||||
controller: controller.advancedTabController,
|
|
||||||
children: [
|
|
||||||
Column(
|
|
||||||
children: [
|
|
||||||
TextFormField(
|
|
||||||
controller: _httpUaController,
|
|
||||||
decoration: const InputDecoration(
|
|
||||||
labelText: 'User-Agent',
|
|
||||||
)),
|
|
||||||
TextFormField(
|
|
||||||
controller: _httpCookieController,
|
|
||||||
decoration: const InputDecoration(
|
|
||||||
labelText: 'Cookie',
|
|
||||||
)),
|
|
||||||
TextFormField(
|
|
||||||
controller: _httpRefererController,
|
|
||||||
decoration: const InputDecoration(
|
|
||||||
labelText: 'Referer',
|
|
||||||
)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Column(
|
|
||||||
children: [
|
|
||||||
TextFormField(
|
|
||||||
controller: _btTrackerController,
|
|
||||||
maxLines: 5,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'Trakers',
|
|
||||||
hintText: 'addTrackerHit'.tr,
|
|
||||||
)),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
],
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
))),
|
|
||||||
Center(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.only(top: 15),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(right: 10),
|
|
||||||
child: TextButton(
|
|
||||||
onPressed: () {
|
|
||||||
controller.showAdvanced.value =
|
|
||||||
!controller.showAdvanced.value;
|
|
||||||
},
|
|
||||||
child:
|
|
||||||
Row(mainAxisSize: MainAxisSize.min, children: [
|
|
||||||
Obx(() => Checkbox(
|
|
||||||
value: controller.showAdvanced.value,
|
|
||||||
onChanged: (bool? value) {
|
|
||||||
controller.showAdvanced.value =
|
|
||||||
value ?? false;
|
|
||||||
},
|
|
||||||
)),
|
|
||||||
Text('advancedOptions'.tr),
|
|
||||||
]),
|
|
||||||
),
|
),
|
||||||
),
|
SizedBox(
|
||||||
SizedBox(
|
width: 150,
|
||||||
width: 150,
|
child: RoundedLoadingButton(
|
||||||
child: RoundedLoadingButton(
|
color: Get.theme.colorScheme.secondary,
|
||||||
color: Get.theme.colorScheme.secondary,
|
onPressed: _doResolve,
|
||||||
onPressed: _doResolve,
|
controller: _confirmController,
|
||||||
controller: _confirmController,
|
child: Text('confirm'.tr),
|
||||||
child: Text('confirm'.tr),
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
),
|
)),
|
||||||
)),
|
]))),
|
||||||
]))),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// recognize magnet uri, if length == 40, auto add magnet prefix
|
||||||
|
recognizeMagnetUri(String text) {
|
||||||
|
if (text.length != 40) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final exp = RegExp(r"[0-9a-fA-F]+");
|
||||||
|
if (exp.hasMatch(text)) {
|
||||||
|
final uri = "magnet:?xt=urn:btih:$text";
|
||||||
|
_urlController.text = uri;
|
||||||
|
_urlController.selection = TextSelection.fromPosition(
|
||||||
|
TextPosition(offset: _urlController.text.length));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _doResolve() async {
|
Future<void> _doResolve() async {
|
||||||
if (controller.isResolving.value) {
|
if (controller.isResolving.value) {
|
||||||
return;
|
return;
|
||||||
@@ -205,8 +267,11 @@ class CreateView extends GetView<CreateController> {
|
|||||||
_confirmController.start();
|
_confirmController.start();
|
||||||
if (_resolveFormKey.currentState!.validate()) {
|
if (_resolveFormKey.currentState!.validate()) {
|
||||||
Object? extra;
|
Object? extra;
|
||||||
|
final submitUrl = Util.isWeb() && controller.fileDataUri.isNotEmpty
|
||||||
|
? controller.fileDataUri.value
|
||||||
|
: _urlController.text;
|
||||||
if (controller.showAdvanced.value) {
|
if (controller.showAdvanced.value) {
|
||||||
final u = Uri.parse(_urlController.text);
|
final u = Uri.parse(submitUrl);
|
||||||
if (u.scheme.startsWith("http")) {
|
if (u.scheme.startsWith("http")) {
|
||||||
extra = ReqExtraHttp()
|
extra = ReqExtraHttp()
|
||||||
..header = {
|
..header = {
|
||||||
@@ -220,7 +285,7 @@ class CreateView extends GetView<CreateController> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
final rr = await resolve(Request(
|
final rr = await resolve(Request(
|
||||||
url: _urlController.text,
|
url: submitUrl,
|
||||||
extra: extra,
|
extra: extra,
|
||||||
));
|
));
|
||||||
await _showResolveDialog(rr);
|
await _showResolveDialog(rr);
|
||||||
@@ -235,7 +300,8 @@ class CreateView extends GetView<CreateController> {
|
|||||||
|
|
||||||
String _hitText() {
|
String _hitText() {
|
||||||
return 'downloadLinkHit'.trParams({
|
return 'downloadLinkHit'.trParams({
|
||||||
'append': Util.isDesktop() ? 'downloadLinkHitDesktop'.tr : '',
|
'append':
|
||||||
|
Util.isDesktop() || Util.isWeb() ? 'downloadLinkHitDesktop'.tr : '',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -260,7 +326,7 @@ class CreateView extends GetView<CreateController> {
|
|||||||
var width = MediaQuery.of(context).size.width;
|
var width = MediaQuery.of(context).size.width;
|
||||||
|
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
height: height * 0.5,
|
height: height * 0.75,
|
||||||
width: width,
|
width: width,
|
||||||
child: Form(
|
child: Form(
|
||||||
key: createFormKey,
|
key: createFormKey,
|
||||||
|
|||||||
@@ -334,21 +334,21 @@ class SettingView extends GetView<SettingController> {
|
|||||||
width: 150,
|
width: 150,
|
||||||
child: DropdownButtonFormField<String>(
|
child: DropdownButtonFormField<String>(
|
||||||
value: startCfg.value.network,
|
value: startCfg.value.network,
|
||||||
onChanged: (value) async {
|
onChanged: Util.isDesktop()
|
||||||
startCfg.update((val) {
|
? (value) async {
|
||||||
val!.network = value!;
|
startCfg.update((val) {
|
||||||
});
|
val!.network = value!;
|
||||||
|
});
|
||||||
|
|
||||||
await debounceSave(needRestart: true);
|
await debounceSave(needRestart: true);
|
||||||
},
|
}
|
||||||
|
: null,
|
||||||
items: [
|
items: [
|
||||||
!Util.isMobile()
|
const DropdownMenuItem<String>(
|
||||||
? const DropdownMenuItem<String>(
|
value: 'tcp',
|
||||||
value: 'tcp',
|
child: Text('TCP'),
|
||||||
child: Text('TCP'),
|
),
|
||||||
)
|
Util.supportUnixSocket()
|
||||||
: null,
|
|
||||||
Util.isUnix()
|
|
||||||
? const DropdownMenuItem<String>(
|
? const DropdownMenuItem<String>(
|
||||||
value: 'unix',
|
value: 'unix',
|
||||||
child: Text('Unix'),
|
child: Text('Unix'),
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
|
import '../controllers/task_files_controller.dart';
|
||||||
|
|
||||||
|
class TaskFilesBinding extends Bindings {
|
||||||
|
@override
|
||||||
|
void dependencies() {
|
||||||
|
Get.lazyPut<TaskFilesController>(
|
||||||
|
() => TaskFilesController(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
import 'package:get/get.dart';
|
||||||
|
import '../../../../api/api.dart';
|
||||||
|
import '../../../../api/model/resource.dart';
|
||||||
|
import '../../../../api/model/task.dart';
|
||||||
|
|
||||||
|
class FileItem {
|
||||||
|
final bool isDirectory;
|
||||||
|
final String path;
|
||||||
|
final String name;
|
||||||
|
final int size;
|
||||||
|
|
||||||
|
FileItem(this.isDirectory, this.path, this.name, this.size);
|
||||||
|
|
||||||
|
String get fullPath => "${path == "/" ? path : "$path/"}$name";
|
||||||
|
}
|
||||||
|
|
||||||
|
class TaskFilesController extends GetxController {
|
||||||
|
final Map<String, List<FileItem>> _dirMap = {};
|
||||||
|
|
||||||
|
final task = Rx<Task?>(null);
|
||||||
|
final fileList = <FileItem>[].obs;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onInit() async {
|
||||||
|
super.onInit();
|
||||||
|
|
||||||
|
final taskId = Get.rootDelegate.parameters['id'];
|
||||||
|
final tasks = await getTasks([]);
|
||||||
|
task.value = tasks.firstWhere((element) => element.id == taskId);
|
||||||
|
parseDirMap(task.value!.meta.res.files);
|
||||||
|
toDir("/");
|
||||||
|
}
|
||||||
|
|
||||||
|
void parseDirMap(List<FileInfo> fileList) {
|
||||||
|
for (final file in fileList) {
|
||||||
|
String dir = file.path;
|
||||||
|
if (!dir.startsWith("/")) {
|
||||||
|
dir = "/$dir";
|
||||||
|
}
|
||||||
|
if (!_dirMap.containsKey(dir)) {
|
||||||
|
_dirMap[dir] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
_dirMap[dir]!.add(FileItem(false, dir, file.name, file.size));
|
||||||
|
|
||||||
|
void findParent(String dir) {
|
||||||
|
final parentDirIndex = dir.lastIndexOf("/");
|
||||||
|
String parentDir =
|
||||||
|
parentDirIndex == 0 ? "/" : dir.substring(0, parentDirIndex);
|
||||||
|
if (!_dirMap.containsKey(parentDir)) {
|
||||||
|
_dirMap[parentDir] = [];
|
||||||
|
}
|
||||||
|
String dirName = dir.substring(dir.lastIndexOf("/") + 1);
|
||||||
|
if (!_dirMap[parentDir]!.any((element) => element.name == dirName)) {
|
||||||
|
_dirMap[parentDir]!.add(FileItem(true, parentDir, dirName, 0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while (dir != "/" && dir != "") {
|
||||||
|
findParent(dir);
|
||||||
|
dir = dir.substring(0, dir.lastIndexOf("/"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// sort children, directories first then files, alphabetically
|
||||||
|
_dirMap.forEach((key, value) {
|
||||||
|
value.sort((a, b) {
|
||||||
|
if (a.isDirectory && !b.isDirectory) {
|
||||||
|
return -1;
|
||||||
|
} else if (!a.isDirectory && b.isDirectory) {
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
return a.name.compareTo(b.name);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void toDir(String dir) {
|
||||||
|
fileList.value = _dirMap[dir]?.toList() ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
int dirItemCount(String dir) {
|
||||||
|
return _dirMap[dir]?.length ?? 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:open_file/open_file.dart';
|
||||||
|
import 'package:share_plus/share_plus.dart';
|
||||||
|
|
||||||
|
import '../../../../util/file_icon.dart';
|
||||||
|
import '../../../../util/icons.dart';
|
||||||
|
import '../../../../util/util.dart';
|
||||||
|
import '../../../views/breadcrumb_view.dart';
|
||||||
|
import '../controllers/task_files_controller.dart';
|
||||||
|
|
||||||
|
class TaskFilesView extends GetView<TaskFilesController> {
|
||||||
|
const TaskFilesView({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
leading: IconButton(
|
||||||
|
icon: const Icon(Icons.arrow_back),
|
||||||
|
onPressed: () => Get.rootDelegate.popRoute()),
|
||||||
|
// actions: [],
|
||||||
|
title: Obx(() => Text(controller.task.value?.meta.res.name ?? "")),
|
||||||
|
),
|
||||||
|
body: Obx(() {
|
||||||
|
final fileList = controller.fileList;
|
||||||
|
final breadcrumbItems = ["/"];
|
||||||
|
if (fileList.isNotEmpty) {
|
||||||
|
final file = fileList.first;
|
||||||
|
final path = file.path.substring(1);
|
||||||
|
if (path.isNotEmpty) {
|
||||||
|
final pathArr = path.split("/");
|
||||||
|
for (int i = 0; i < pathArr.length; i++) {
|
||||||
|
breadcrumbItems.add(pathArr[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
Breadcrumb(
|
||||||
|
items: breadcrumbItems,
|
||||||
|
onItemTap: (index) {
|
||||||
|
final targetDirArr = <String>[];
|
||||||
|
for (int i = 0; i <= index; i++) {
|
||||||
|
targetDirArr.add(breadcrumbItems[i]);
|
||||||
|
}
|
||||||
|
controller
|
||||||
|
.toDir(targetDirArr.join("/").replaceFirst('//', '/'));
|
||||||
|
}).paddingOnly(left: 16, top: 16, bottom: 8),
|
||||||
|
Expanded(
|
||||||
|
child: ListView.builder(
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final meta = controller.task.value!.meta;
|
||||||
|
final file = fileList[index];
|
||||||
|
return ListTile(
|
||||||
|
leading: file.isDirectory
|
||||||
|
? const Icon(Icons.folder)
|
||||||
|
: Icon(FaIcons.allIcons[findIcon(file.name)]),
|
||||||
|
title: Text(fileList[index].name),
|
||||||
|
subtitle: file.isDirectory
|
||||||
|
? Text('items'.trParams({
|
||||||
|
'count': controller
|
||||||
|
.dirItemCount(file.fullPath)
|
||||||
|
.toString()
|
||||||
|
}))
|
||||||
|
: Text(Util.fmtByte(file.size)),
|
||||||
|
trailing: file.isDirectory
|
||||||
|
? null
|
||||||
|
: SizedBox(
|
||||||
|
width: 100,
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.play_circle),
|
||||||
|
onPressed: () {
|
||||||
|
OpenFile.open(
|
||||||
|
Util.safePathJoin([
|
||||||
|
meta.opts.path,
|
||||||
|
meta.res.rootDir,
|
||||||
|
file.fullPath
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.share),
|
||||||
|
onPressed: () {
|
||||||
|
final xfile = XFile(Util.safePathJoin([
|
||||||
|
meta.opts.path,
|
||||||
|
meta.res.rootDir,
|
||||||
|
file.fullPath
|
||||||
|
]));
|
||||||
|
Share.shareXFiles([xfile]);
|
||||||
|
})
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
if (file.isDirectory) {
|
||||||
|
controller.toDir(file.fullPath);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
itemCount: controller.fileList.length,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
import 'package:gopeed/app/modules/task/views/task_files_view.dart';
|
||||||
import 'package:gopeed/app/modules/task/views/task_view.dart';
|
import 'package:gopeed/app/modules/task/views/task_view.dart';
|
||||||
|
|
||||||
import '../modules/create/bindings/create_binding.dart';
|
import '../modules/create/bindings/create_binding.dart';
|
||||||
@@ -10,6 +11,7 @@ import '../modules/root/views/root_view.dart';
|
|||||||
import '../modules/setting/bindings/setting_binding.dart';
|
import '../modules/setting/bindings/setting_binding.dart';
|
||||||
import '../modules/setting/views/setting_view.dart';
|
import '../modules/setting/views/setting_view.dart';
|
||||||
import '../modules/task/bindings/task_binding.dart';
|
import '../modules/task/bindings/task_binding.dart';
|
||||||
|
import '../modules/task/bindings/task_files_binding.dart';
|
||||||
|
|
||||||
part 'app_routes.dart';
|
part 'app_routes.dart';
|
||||||
|
|
||||||
@@ -37,7 +39,14 @@ class AppPages {
|
|||||||
name: _Paths.TASK,
|
name: _Paths.TASK,
|
||||||
page: () => const TaskView(),
|
page: () => const TaskView(),
|
||||||
transition: Transition.noTransition,
|
transition: Transition.noTransition,
|
||||||
binding: TaskBinding()),
|
binding: TaskBinding(),
|
||||||
|
children: [
|
||||||
|
GetPage(
|
||||||
|
name: _Paths.TASK_FILES,
|
||||||
|
page: () => const TaskFilesView(),
|
||||||
|
transition: Transition.noTransition,
|
||||||
|
binding: TaskFilesBinding()),
|
||||||
|
]),
|
||||||
GetPage(
|
GetPage(
|
||||||
name: _Paths.SETTING,
|
name: _Paths.SETTING,
|
||||||
page: () => const SettingView(),
|
page: () => const SettingView(),
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ abstract class Routes {
|
|||||||
static const HOME = _Paths.HOME;
|
static const HOME = _Paths.HOME;
|
||||||
static const CREATE = _Paths.CREATE;
|
static const CREATE = _Paths.CREATE;
|
||||||
static const TASK = _Paths.HOME + _Paths.TASK;
|
static const TASK = _Paths.HOME + _Paths.TASK;
|
||||||
|
static const TASK_FILES = TASK + _Paths.TASK_FILES;
|
||||||
static const SETTING = _Paths.HOME + _Paths.SETTING;
|
static const SETTING = _Paths.HOME + _Paths.SETTING;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -16,5 +17,6 @@ abstract class _Paths {
|
|||||||
static const HOME = '/home';
|
static const HOME = '/home';
|
||||||
static const CREATE = '/create';
|
static const CREATE = '/create';
|
||||||
static const TASK = '/task';
|
static const TASK = '/task';
|
||||||
|
static const TASK_FILES = '/files';
|
||||||
static const SETTING = '/setting';
|
static const SETTING = '/setting';
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
|
class Breadcrumb extends StatelessWidget {
|
||||||
|
final List<String> items;
|
||||||
|
final Function(int)? onItemTap;
|
||||||
|
final TextStyle textStyle;
|
||||||
|
final TextStyle activeTextStyle;
|
||||||
|
|
||||||
|
const Breadcrumb({
|
||||||
|
super.key,
|
||||||
|
required this.items,
|
||||||
|
this.onItemTap,
|
||||||
|
this.textStyle = const TextStyle(fontSize: 16),
|
||||||
|
this.activeTextStyle =
|
||||||
|
const TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
List<Widget> children = [];
|
||||||
|
for (int i = 0; i < items.length; i++) {
|
||||||
|
children.add(
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
if (onItemTap != null) {
|
||||||
|
onItemTap!(i);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Text(
|
||||||
|
items[i],
|
||||||
|
style: i == items.length - 1 ? activeTextStyle : textStyle,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (i != items.length - 1) {
|
||||||
|
children.add(const Text(" > "));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
...(children.length == 1
|
||||||
|
? children.sublist(0, 1)
|
||||||
|
: children.sublist(0, 2)),
|
||||||
|
children.length > 2
|
||||||
|
? Expanded(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
reverse: true,
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
child: Row(
|
||||||
|
children: children.sublist(2),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
].where((e) => e != null).map((e) => e!).toList(),
|
||||||
|
).paddingOnly(right: 12);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:gopeed/api/model/meta.dart';
|
import 'package:gopeed/api/model/meta.dart';
|
||||||
import 'package:gopeed/api/model/resource.dart';
|
|
||||||
import 'package:path/path.dart' as path;
|
import 'package:path/path.dart' as path;
|
||||||
|
|
||||||
import '../../api/api.dart';
|
import '../../api/api.dart';
|
||||||
@@ -109,11 +108,16 @@ class BuildTaskListView extends GetView {
|
|||||||
List<Widget> buildActions() {
|
List<Widget> buildActions() {
|
||||||
final list = <Widget>[];
|
final list = <Widget>[];
|
||||||
if (isDone()) {
|
if (isDone()) {
|
||||||
if (Util.isDesktop()) {
|
if (Util.isDesktop() || Util.isMobile()) {
|
||||||
list.add(IconButton(
|
list.add(IconButton(
|
||||||
icon: const Icon(Icons.folder_open),
|
icon: const Icon(Icons.folder_open),
|
||||||
onPressed: () async {
|
onPressed: () {
|
||||||
await FileExplorer.openAndSelectFile(buildExplorerUrl(task));
|
if (Util.isDesktop()) {
|
||||||
|
FileExplorer.openAndSelectFile(buildExplorerUrl(task));
|
||||||
|
} else {
|
||||||
|
Get.rootDelegate
|
||||||
|
.toNamed(Routes.TASK_FILES, parameters: {'id': task.id});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ class _DirectorySelectorState extends State<DirectorySelector> {
|
|||||||
return v!.trim().isNotEmpty ? null : 'downloadDirValid'.tr;
|
return v!.trim().isNotEmpty ? null : 'downloadDirValid'.tr;
|
||||||
},
|
},
|
||||||
)),
|
)),
|
||||||
Util.isDesktop() || Util.isAndroid()
|
!Util.isWeb() && !Util.isIOS()
|
||||||
? IconButton(
|
? IconButton(
|
||||||
icon: const Icon(Icons.folder_open),
|
icon: const Icon(Icons.folder_open),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ class AppTranslation {
|
|||||||
'ja_JP': Locales.ja_JP,
|
'ja_JP': Locales.ja_JP,
|
||||||
'ru_RU': Locales.ru_RU,
|
'ru_RU': Locales.ru_RU,
|
||||||
'zh_CN': Locales.zh_CN,
|
'zh_CN': Locales.zh_CN,
|
||||||
|
'zh_TW': Locales.zh_TW,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -357,7 +358,7 @@ class Locales {
|
|||||||
'downloadDirValid': '请选择下载目录',
|
'downloadDirValid': '请选择下载目录',
|
||||||
'connections': '连接数',
|
'connections': '连接数',
|
||||||
'maxRunning': '最大下载数',
|
'maxRunning': '最大下载数',
|
||||||
'items': '@count 条',
|
'items': '@count 项',
|
||||||
'subscribeTracker': '订阅 Tracker',
|
'subscribeTracker': '订阅 Tracker',
|
||||||
'subscribeFail': '订阅失败,请检查网络或稍后重试',
|
'subscribeFail': '订阅失败,请检查网络或稍后重试',
|
||||||
'update': '更新',
|
'update': '更新',
|
||||||
@@ -389,4 +390,67 @@ class Locales {
|
|||||||
'newVersionUpdate': '立即下载',
|
'newVersionUpdate': '立即下载',
|
||||||
'newVersionLater': '稍后再说',
|
'newVersionLater': '稍后再说',
|
||||||
};
|
};
|
||||||
|
static const zh_TW = {
|
||||||
|
'label': '中文 (正體) ',
|
||||||
|
'error': '錯誤',
|
||||||
|
'tip': '提示',
|
||||||
|
'confirm': '確定',
|
||||||
|
'cancel': '取消',
|
||||||
|
'selectAll': '全選',
|
||||||
|
'task': '任務',
|
||||||
|
'downloading': '下載中',
|
||||||
|
'downloaded': '已完成',
|
||||||
|
'setting': '設定',
|
||||||
|
'donate': '斗內',
|
||||||
|
'exit': '離開',
|
||||||
|
'create': '新增任務',
|
||||||
|
'advancedOptions': '進階選項',
|
||||||
|
'downloadLink': '下載鏈接',
|
||||||
|
'downloadLinkValid': '請輸入下載鏈接',
|
||||||
|
'downloadLinkHit': '請輸入下載鏈接,支援 HTTP/HTTPS/MAGNET@append',
|
||||||
|
'downloadLinkHitDesktop': ',也可以通過拖拽Torrent文件至此以新增任務',
|
||||||
|
'download': '下載',
|
||||||
|
'noFileSelected': '沒有選取的文件',
|
||||||
|
'noStoragePermission': '沒有存儲權限',
|
||||||
|
'selectFile': '選擇文件',
|
||||||
|
'rename': '重命名',
|
||||||
|
'basic': '基礎',
|
||||||
|
'advanced': '進階',
|
||||||
|
'general': '通用設定',
|
||||||
|
'downloadDir': '下載目録',
|
||||||
|
'downloadDirValid': '請選擇下載目録',
|
||||||
|
'connections': '同時連接數量',
|
||||||
|
'maxRunning': '最大同時下載數',
|
||||||
|
'items': '@count 項',
|
||||||
|
'subscribeTracker': '已訂閲的 Tracker',
|
||||||
|
'subscribeFail': '訂閲失敗',
|
||||||
|
'update': '更新',
|
||||||
|
'updateDaily': '每日自動更新',
|
||||||
|
'lastUpdate': '上次更新時間: @time',
|
||||||
|
'addTracker': '增加新的 Tracker',
|
||||||
|
'addTrackerHit': '請輸入 Tracker 服務器地址,每行一條',
|
||||||
|
'ui': '介面',
|
||||||
|
'theme': '佈景主題',
|
||||||
|
'themeSystem': '跟隨系統',
|
||||||
|
'themeLight': '亮色主題',
|
||||||
|
'themeDark': '暗色主題',
|
||||||
|
'locale': '語言',
|
||||||
|
'about': '關於',
|
||||||
|
'homepage': '訪問主頁',
|
||||||
|
'version': '版本資訊',
|
||||||
|
'protocol': '通訊協定',
|
||||||
|
'port': '端口',
|
||||||
|
'apiToken': '接口 Token',
|
||||||
|
'notSet': '尚未設定',
|
||||||
|
'set': '已設定',
|
||||||
|
'effectAfterRestart': '該選項將在重新啓動軟體後生效',
|
||||||
|
'startAll': '全部開始',
|
||||||
|
'pauseAll': '全部暫停',
|
||||||
|
'deleteTask': '刪除任務',
|
||||||
|
'deleteTaskTip': '保留已經下載的文件',
|
||||||
|
'delete': '刪除',
|
||||||
|
'newVersionTitle': '發現新版本 @version',
|
||||||
|
'newVersionUpdate': '立刻更新',
|
||||||
|
'newVersionLater': '稍後更新',
|
||||||
|
};
|
||||||
}
|
}
|
||||||
@@ -12,6 +12,13 @@ class Util {
|
|||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static String safePathJoin(List<String> paths) {
|
||||||
|
return paths
|
||||||
|
.map((e) => safeDir(e))
|
||||||
|
.join("/")
|
||||||
|
.replaceAll(RegExp(r'//'), "/");
|
||||||
|
}
|
||||||
|
|
||||||
static String fmtByte(int byte) {
|
static String fmtByte(int byte) {
|
||||||
if (byte < 0) {
|
if (byte < 0) {
|
||||||
return "0 B";
|
return "0 B";
|
||||||
@@ -53,15 +60,15 @@ class Util {
|
|||||||
return !kIsWeb && Platform.isMacOS;
|
return !kIsWeb && Platform.isMacOS;
|
||||||
}
|
}
|
||||||
|
|
||||||
static isUnix() {
|
static isWeb() {
|
||||||
|
return kIsWeb;
|
||||||
|
}
|
||||||
|
|
||||||
|
static supportUnixSocket() {
|
||||||
if (kIsWeb) {
|
if (kIsWeb) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return !Platform.isWindows;
|
return Platform.isLinux || Platform.isMacOS || Platform.isAndroid;
|
||||||
}
|
|
||||||
|
|
||||||
static isWeb() {
|
|
||||||
return kIsWeb;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static List<String> textToLines(String text) {
|
static List<String> textToLines(String text) {
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 8.2 KiB After Width: | Height: | Size: 8.2 KiB |
|
Before Width: | Height: | Size: 672 B After Width: | Height: | Size: 709 B |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
@@ -245,10 +245,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: desktop_drop
|
name: desktop_drop
|
||||||
sha256: "0cd056191b701a2b5ba040f2306349e461fafdaa5df4569b2228cdf87b58eced"
|
sha256: ebba9c9cb0b54385998a977d741cc06fd8324878c08d5a36e9da61cd56b04cc6
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.3.3"
|
version: "0.4.3"
|
||||||
dio:
|
dio:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -293,10 +293,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: file_picker
|
name: file_picker
|
||||||
sha256: b1729fc96627dd44012d0a901558177418818d6bd428df59dcfeb594e5f66432
|
sha256: "9d6e95ec73abbd31ec54d0e0df8a961017e165aba1395e462e5b31ea0c165daf"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.3.2"
|
version: "5.3.1"
|
||||||
fixnum:
|
fixnum:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -565,6 +565,14 @@ packages:
|
|||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.4"
|
version: "1.0.4"
|
||||||
|
open_file:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: open_file
|
||||||
|
sha256: a5a32d44acb7c899987d0999e1e3cbb0a0f1adebbf41ac813ec6d2d8faa0af20
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "3.3.2"
|
||||||
package_config:
|
package_config:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -757,6 +765,22 @@ packages:
|
|||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.4.0"
|
version: "0.4.0"
|
||||||
|
share_plus:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: share_plus
|
||||||
|
sha256: "6cec740fa0943a826951223e76218df002804adb588235a8910dc3d6b0654e11"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "7.1.0"
|
||||||
|
share_plus_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: share_plus_platform_interface
|
||||||
|
sha256: "357412af4178d8e11d14f41723f80f12caea54cf0d5cd29af9dcdab85d58aea7"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "3.3.0"
|
||||||
shared_preferences:
|
shared_preferences:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -1018,6 +1042,14 @@ packages:
|
|||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.7"
|
version: "3.0.7"
|
||||||
|
uuid:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: uuid
|
||||||
|
sha256: "648e103079f7c64a36dc7d39369cabb358d377078a051d6ae2ad3aa539519313"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.7"
|
||||||
vector_math:
|
vector_math:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1046,10 +1078,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: win32
|
name: win32
|
||||||
sha256: dfdf0136e0aa7a1b474ea133e67cb0154a0acd2599c4f3ada3b49d38d38793ee
|
sha256: "5a751eddf9db89b3e5f9d50c20ab8612296e4e8db69009788d6c8b060a84191c"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.0.5"
|
version: "4.1.4"
|
||||||
window_manager:
|
window_manager:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
|
|||||||
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
||||||
# Read more about iOS versioning at
|
# Read more about iOS versioning at
|
||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||||
version: 1.3.6+1
|
version: 1.3.13+1
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=3.0.0 <4.0.0'
|
sdk: '>=3.0.0 <4.0.0'
|
||||||
@@ -42,13 +42,13 @@ dependencies:
|
|||||||
json_annotation: ^4.6.0
|
json_annotation: ^4.6.0
|
||||||
dio: ^5.2.1
|
dio: ^5.2.1
|
||||||
path_provider: ^2.0.11
|
path_provider: ^2.0.11
|
||||||
file_picker: ^5.2.5
|
file_picker: 5.3.1
|
||||||
macos_secure_bookmarks: ^0.2.1
|
macos_secure_bookmarks: ^0.2.1
|
||||||
rounded_loading_button: ^2.1.0
|
rounded_loading_button: ^2.1.0
|
||||||
url_launcher: ^6.1.6
|
url_launcher: ^6.1.6
|
||||||
shared_preferences: ^2.2.0
|
shared_preferences: ^2.2.0
|
||||||
logger: ^1.3.0
|
logger: ^1.3.0
|
||||||
desktop_drop: ^0.3.3
|
desktop_drop: ^0.4.3
|
||||||
package_info_plus: ^4.0.0
|
package_info_plus: ^4.0.0
|
||||||
fluent_ui: ^4.4.0
|
fluent_ui: ^4.4.0
|
||||||
path: any
|
path: any
|
||||||
@@ -58,6 +58,8 @@ dependencies:
|
|||||||
window_manager: ^0.3.4
|
window_manager: ^0.3.4
|
||||||
tray_manager: ^0.2.0
|
tray_manager: ^0.2.0
|
||||||
autoscale_tabbarview: ^1.0.2
|
autoscale_tabbarview: ^1.0.2
|
||||||
|
share_plus: ^7.1.0
|
||||||
|
open_file: ^3.3.2
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
@@ -114,8 +116,10 @@ flutter:
|
|||||||
|
|
||||||
# flutter pub run flutter_launcher_icons:main
|
# flutter pub run flutter_launcher_icons:main
|
||||||
flutter_icons:
|
flutter_icons:
|
||||||
image_path_android: "assets/icon/icon_1024.png"
|
image_path: "assets/icon/icon_1024.png"
|
||||||
android: true # can specify file name here e.g. "ic_launcher"
|
android: true # can specify file name here e.g. "ic_launcher"
|
||||||
|
ios: true
|
||||||
|
remove_alpha_ios: true
|
||||||
web:
|
web:
|
||||||
generate: true
|
generate: true
|
||||||
image_path: "assets/icon/icon_1024.png"
|
image_path: "assets/icon/icon_1024.png"
|
||||||
|
|||||||