Compare commits

...
Author SHA1 Message Date
Levi bb298bc5ce fix 2024-10-19 12:19:27 +08:00
Levi 8a375e1f5b Merge remote-tracking branch 'remotes/origin/main' into fix/windows_localtion_permission 2024-10-19 12:17:22 +08:00
Levi a22b431150 feat: upgrade flutter to v3.24.3 (#791) 2024-10-19 12:12:41 +08:00
liwei f5ed36de13 fix: avoid request locatoin permisson on windows 2024-10-17 11:43:40 +08:00
Levi eb7c9ad7f2 release: v1.6.1 2024-10-16 18:31:56 +08:00
Levi ea11fc38f1 ci: fix windows build version (#785) 2024-10-16 18:30:41 +08:00
towermom9 1be16e9f2c feat: Updated the indonesian translation (#777) 2024-10-10 13:18:11 +08:00
Ayase 1d86d14a20 fix: open files on linux (#765) 2024-10-03 21:40:49 +08:00
𝗦𝗵𝗟𝗲𝗿𝗣 5ad15cc4ac feat: Update tr_tr.dart (#754) 2024-09-23 09:19:45 +08:00
Levi 49604966fd release: v1.6.0 2024-09-21 11:00:47 +08:00
Levi bdb8ed177f feat: supports batch operation of tasks by filter (#753) 2024-09-21 10:19:52 +08:00
Levi 2dc7048cba feat: support download directory white list (#752) 2024-09-20 21:32:21 +08:00
Levi d3750464aa fix: torrent stats panic (#737) 2024-09-20 12:28:24 +08:00
Levi 5c9bcd3731 fix: DotAll flag not working with group (#750) 2024-09-19 21:28:46 +08:00
Aman Raj Singh Mourya bdda550b2c feat: avoid using the depreciated background (#746) 2024-09-18 14:39:01 +08:00
liwei 3308d966f0 docs: fix banner transparent 2024-09-13 18:40:58 +08:00
Levi 18041ed9ec docs: improve banner image (#740) 2024-09-11 19:02:39 +08:00
Levi 56b85e545e release: v1.5.9 2024-09-07 19:20:12 +08:00
Levi c5af916952 feat: support default direct download config (#735) 2024-09-07 14:08:17 +08:00
Levi a1981328aa fix: avoid HTTP zero connections (#729) 2024-09-07 13:49:32 +08:00
Levi 51244133e0 feat: support extension onDone event (#728) 2024-09-07 10:10:39 +08:00
Levi 0c17093b1a feat: task separate proxy (#727) 2024-09-06 18:35:23 +08:00
Levi 6538b29b62 feat: macos round icon (#719) 2024-09-03 18:47:49 +08:00
Levi 81e90e6b89 fix: try request grant external storage on Android (#660) 2024-09-03 18:47:06 +08:00
JuanQ 4f39296664 feat: Add spanish language (#667) 2024-08-27 08:59:18 +08:00
Levi ece5b91dc2 fix: torrent redundant directory (#649) 2024-08-14 21:54:59 +08:00
Levi d9f04ae928 fix: tasks block when paused while not ready (#648) 2024-08-14 21:22:23 +08:00
Levi e3ccecbd9c fix: task directory open (#647) 2024-08-14 21:21:41 +08:00
Levi fb1c2cb060 fix: downgrade minSdkVersion (#642) 2024-08-14 21:21:09 +08:00
72 changed files with 1947 additions and 579 deletions

No files matched your search

+24 -24
View File
@@ -4,17 +4,17 @@ on:
workflow_dispatch:
inputs:
platform:
description: 'Build platform'
description: "Build platform"
required: true
default: 'all'
default: "all"
test:
description: 'Test mode'
description: "Test mode"
required: true
default: "false"
env:
GO_VERSION: "1.22"
FLUTTER_VERSION: "3.16.8"
FLUTTER_VERSION: "3.24.3"
permissions:
contents: write
@@ -35,7 +35,7 @@ jobs:
build-windows:
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'windows' }}
runs-on: windows-latest
needs: [ get-release ]
needs: [get-release]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v4
@@ -51,7 +51,7 @@ jobs:
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\"
go build -tags nosqlite -ldflags="-w -s -X github.com/GopeedLab/gopeed/pkg/base.Version=$VERSION" -buildmode=c-shared -o ui/flutter/windows/libgopeed.dll github.com/GopeedLab/gopeed/bind/desktop
go build -tags nosqlite -ldflags="-w -s -X github.com/GopeedLab/gopeed/pkg/base.Version=$env:VERSION" -buildmode=c-shared -o ui/flutter/windows/libgopeed.dll github.com/GopeedLab/gopeed/bind/desktop
cd ui/flutter
$TAG = "v$env:VERSION"
flutter build windows
@@ -137,7 +137,7 @@ jobs:
build-macos-arm64-lib:
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'macos' }}
runs-on: macos-latest
needs: [ get-release ]
needs: [get-release]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v4
@@ -155,7 +155,7 @@ jobs:
build-macos:
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'macos' }}
runs-on: macos-13
needs: [ get-release, build-macos-arm64-lib ]
needs: [get-release, build-macos-arm64-lib]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v4
@@ -214,7 +214,7 @@ jobs:
build-linux:
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'linux' }}
runs-on: ubuntu-latest
needs: [ get-release ]
needs: [get-release]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v4
@@ -252,7 +252,7 @@ jobs:
build-snap:
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'snap' }}
runs-on: ubuntu-latest
needs: [ get-release ]
needs: [get-release]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v4
@@ -268,31 +268,31 @@ jobs:
run: |
go build -tags nosqlite -ldflags="-w -s -X github.com/GopeedLab/gopeed/pkg/base.Version=$VERSION" -buildmode=c-shared -o ui/flutter/linux/bundle/lib/libgopeed.so github.com/GopeedLab/gopeed/bind/desktop
cd ui/flutter
sudo snap install snapcraft --classic
mkdir -p snap/gui
cp assets/icon/icon.svg snap/gui/gopeed.svg
cat>snap/snapcraft.yaml<<EOF
name: gopeed
version: $VERSION
summary: A modern download manager
description: High speed downloader that supports all platforms.
confinement: strict
base: core22
grade: stable
architectures:
- build-on: amd64
build-for: amd64
slots:
dbus-gopeed:
interface: dbus
bus: session
name: com.gopeed.gopeed
apps:
gopeed:
command: bin/gopeed
@@ -345,10 +345,10 @@ jobs:
mkdir -p \$CRAFT_PART_INSTALL/bin/
cp -r build/linux/*/release/bundle/* \$CRAFT_PART_INSTALL/bin/
EOF
cp linux/assets/com.gopeed.Gopeed.desktop snap/gui/gopeed.desktop
sed -i 's/Icon=com.gopeed.Gopeed/Icon=\${SNAP}\/meta\/gui\/gopeed.svg/g' snap/gui/gopeed.desktop
snapcraft --use-lxd
# Snapcraft login
@@ -357,7 +357,7 @@ jobs:
build-android:
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'android' }}
runs-on: ubuntu-latest
needs: [ get-release ]
needs: [get-release]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v4
@@ -366,7 +366,7 @@ jobs:
- uses: actions/setup-java@v3
with:
distribution: "zulu"
java-version: "11"
java-version: "17"
- uses: subosito/flutter-action@v2
with:
flutter-version: ${{ env.FLUTTER_VERSION }}
@@ -395,7 +395,7 @@ jobs:
build-ios:
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'ios' }}
runs-on: macos-latest
needs: [ get-release ]
needs: [get-release]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v4
@@ -428,7 +428,7 @@ jobs:
build-web:
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'web' }}
runs-on: ubuntu-latest
needs: [ get-release ]
needs: [get-release]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v4
@@ -446,7 +446,7 @@ jobs:
cd ../../
cp -r ui/flutter/build/web cmd/web/dist
mkdir -p dist/zip
goos_arr=(windows darwin linux)
goarch_arr=(386 amd64 arm64)
export CGO_ENABLED=0
@@ -477,7 +477,7 @@ jobs:
build-docker:
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'docker' }}
runs-on: ubuntu-latest
needs: [ get-release ]
needs: [get-release]
steps:
- name: Remove unnecessary files
run: |
+1 -1
View File
@@ -28,7 +28,7 @@ on:
env:
GO_VERSION: "1.22"
FLUTTER_VERSION: "3.16.8"
FLUTTER_VERSION: "3.24.3"
jobs:
# lint:
+4 -4
View File
@@ -1,3 +1,5 @@
# [![](_docs/img/banner.png)](https://gopeed.com)
[![Test Status](https://github.com/GopeedLab/gopeed/workflows/test/badge.svg)](https://github.com/GopeedLab/gopeed/actions?query=workflow%3Atest)
[![Codecov](https://codecov.io/gh/GopeedLab/gopeed/branch/main/graph/badge.svg)](https://codecov.io/gh/GopeedLab/gopeed)
[![Release](https://img.shields.io/github/release/GopeedLab/gopeed.svg)](https://github.com/GopeedLab/gopeed/releases)
@@ -7,8 +9,6 @@
<a href="https://trendshift.io/repositories/7953" target="_blank"><img src="https://trendshift.io/api/badge/repositories/7953" alt="GopeedLab%2Fgopeed | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
![](_docs/img/banner.png)
[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/R6R6IJGN6)
[English](/README.md) | [中文](/README_zh-CN.md) | [日本語](/README_ja-JP.md) | [正體中文](/README_zh-TW.md) | [Tiếng Việt](/README_vi-VN.md)
@@ -114,8 +114,8 @@ This project is divided into two parts, the front end uses `flutter`, the back e
### Environment
1. Golang 1.21+
2. Flutter 3.16+
1. Golang 1.22+
2. Flutter 3.24+
### Clone
+3 -3
View File
@@ -1,3 +1,5 @@
# [![](_docs/img/banner.png)](https://gopeed.com)
[![Test Status](https://github.com/GopeedLab/gopeed/workflows/test/badge.svg)](https://github.com/GopeedLab/gopeed/actions?query=workflow%3Atest)
[![Codecov](https://codecov.io/gh/GopeedLab/gopeed/branch/main/graph/badge.svg)](https://codecov.io/gh/GopeedLab/gopeed)
[![Release](https://img.shields.io/github/release/GopeedLab/gopeed.svg)](https://github.com/GopeedLab/gopeed/releases)
@@ -7,8 +9,6 @@
<a href="https://trendshift.io/repositories/7953" target="_blank"><img src="https://trendshift.io/api/badge/repositories/7953" alt="GopeedLab%2Fgopeed | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
![](_docs/img/banner.png)
[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/R6R6IJGN6)
[English](/README.md) | [中文](/README_zh-CN.md) | [日本語](/README_ja-JP.md) | [正體中文](/README_zh-TW.md) | [Tiếng Việt](/README_vi-VN.md)
@@ -103,7 +103,7 @@ Gopeed (正式名 Go Speed) は `Golang` + `Flutter` によって開発された
### 環境
1. Go 言語 1.22+
2. Flutter 3.16+
2. Flutter 3.24+
### クローン
+4 -4
View File
@@ -1,3 +1,5 @@
# [![](_docs/img/banner.png)](https://gopeed.com)
[![Trạng thái kiểm tra](https://github.com/GopeedLab/gopeed/workflows/test/badge.svg)](https://github.com/GopeedLab/gopeed/actions?query=workflow%3Atest)
[![Codecov](https://codecov.io/gh/GopeedLab/gopeed/branch/main/graph/badge.svg)](https://codecov.io/gh/GopeedLab/gopeed)
[![Phiên bản](https://img.shields.io/github/release/GopeedLab/gopeed.svg)](https://github.com/GopeedLab/gopeed/releases)
@@ -7,8 +9,6 @@
<a href="https://trendshift.io/repositories/7953" target="_blank"><img src="https://trendshift.io/api/badge/repositories/7953" alt="GopeedLab%2Fgopeed | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
![](_docs/img/banner.png)
[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/R6R6IJGN6)
[English](/README.md) | [中文](/README_zh-CN.md) | [日本語](/README_ja-JP.md) | [正體中文](/README_zh-TW.md) | [Tiếng Việt](/README_vi-VN.md)
@@ -111,7 +111,7 @@ Dự án này được chia thành hai phần, phần giao diện sử dụng `f
### Environment
1. Golang 1.22+
2. Flutter 3.16+
2. Flutter 3.24+
### Clone
@@ -210,4 +210,4 @@ go build -tags nosqlite,web -ldflags="-s -w" -o bin/ github.com/GopeedLab/gopeed
## Giấy phép
[GPLv3](LICENSE)
[GPLv3](LICENSE)
+3 -3
View File
@@ -1,3 +1,5 @@
# [![](_docs/img/banner.png)](https://gopeed.com)
[![Test Status](https://github.com/GopeedLab/gopeed/workflows/test/badge.svg)](https://github.com/GopeedLab/gopeed/actions?query=workflow%3Atest)
[![Codecov](https://codecov.io/gh/GopeedLab/gopeed/branch/main/graph/badge.svg)](https://codecov.io/gh/GopeedLab/gopeed)
[![Release](https://img.shields.io/github/release/GopeedLab/gopeed.svg)](https://github.com/GopeedLab/gopeed/releases)
@@ -7,8 +9,6 @@
<a href="https://trendshift.io/repositories/7953" target="_blank"><img src="https://trendshift.io/api/badge/repositories/7953" alt="GopeedLab%2Fgopeed | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
![](_docs/img/banner.png)
[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/R6R6IJGN6)
[English](/README.md) | [中文](/README_zh-CN.md) | [日本語](/README_ja-JP.md) | [正體中文](/README_zh-TW.md) | [Tiếng Việt](/README_vi-VN.md)
@@ -115,7 +115,7 @@ Gopeed 还提供了浏览器扩展用于接管浏览器下载,支持 Chrome、
### 环境要求
1. Golang 1.22+
2. Flutter 3.16+
2. Flutter 3.24+
### 克隆项目
+4 -4
View File
@@ -1,3 +1,5 @@
# [![](_docs/img/banner.png)](https://gopeed.com)
[![Test Status](https://github.com/GopeedLab/gopeed/workflows/test/badge.svg)](https://github.com/GopeedLab/gopeed/actions?query=workflow%3Atest)
[![Codecov](https://codecov.io/gh/GopeedLab/gopeed/branch/main/graph/badge.svg)](https://codecov.io/gh/GopeedLab/gopeed)
[![Release](https://img.shields.io/github/release/GopeedLab/gopeed.svg)](https://github.com/GopeedLab/gopeed/releases)
@@ -7,8 +9,6 @@
<a href="https://trendshift.io/repositories/7953" target="_blank"><img src="https://trendshift.io/api/badge/repositories/7953" alt="GopeedLab%2Fgopeed | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
![](_docs/img/banner.png)
[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/R6R6IJGN6)
[English](/README.md) | [中文](/README_zh-CN.md) | [日本語](/README_ja-JP.md) | [正體中文](/README_zh-TW.md) | [Tiếng Việt](/README_vi-VN.md)
@@ -86,7 +86,7 @@ Gopeed(全稱 Go Speed),是一款使用`Golang`+`Flutter`編寫的高速
更多關於安裝的內容請參考[安裝文檔](https://docs.gopeed.com/zh/install.html)
### 使用CLI安裝
### 使用 CLI 安裝
使用`go install`安裝:
@@ -111,7 +111,7 @@ go install github.com/GopeedLab/gopeed/cmd/gopeed@latest
### 開發環境
1. Golang 1.22+
2. Flutter 3.16+
2. Flutter 3.24+
### 克隆項目
Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 51 KiB

+3 -3
View File
@@ -6,7 +6,7 @@ require (
github.com/anacrolix/missinggo/v2 v2.7.3
github.com/anacrolix/torrent v1.56.1
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5
github.com/dop251/goja v0.0.0-20240806095544-3491d4a58fbe
github.com/dop251/goja v0.0.0-20240919115326-6c7d1df7ff05
github.com/dop251/goja_nodejs v0.0.0-20240728170619-29b559befffc
github.com/go-git/go-git/v5 v5.8.1
github.com/gorilla/handlers v1.5.1
@@ -66,7 +66,7 @@ require (
github.com/go-sourcemap/sourcemap v2.1.4+incompatible // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/google/btree v1.1.2 // indirect
github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 // indirect
github.com/google/pprof v0.0.0-20240910150728-a0b0bb1d4134 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/huandu/xstrings v1.5.0 // indirect
@@ -114,7 +114,7 @@ require (
golang.org/x/mod v0.19.0 // indirect
golang.org/x/net v0.27.0 // indirect
golang.org/x/sys v0.22.0 // indirect
golang.org/x/text v0.17.0 // indirect
golang.org/x/text v0.18.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.23.0 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
+6
View File
@@ -127,6 +127,8 @@ github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cn
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
github.com/dop251/goja v0.0.0-20240806095544-3491d4a58fbe h1:jwFJkgsdelB87ohlXaAGSd05Cb5ALDFa9iW9IGRHcRM=
github.com/dop251/goja v0.0.0-20240806095544-3491d4a58fbe/go.mod h1:DF+w/nLMIkvRpyhd/0K+Okbh3fVZBtXLwRtS/ccAa5w=
github.com/dop251/goja v0.0.0-20240919115326-6c7d1df7ff05 h1:oK4+QcKsczZjHYTHD0JAdkvq5w74JEkG95J0XNBx/BI=
github.com/dop251/goja v0.0.0-20240919115326-6c7d1df7ff05/go.mod h1:MxLav0peU43GgvwVgNbLAj1s/bSGboKkhuULvq/7hx4=
github.com/dop251/goja_nodejs v0.0.0-20240728170619-29b559befffc h1:MKYt39yZJi0Z9xEeRmDX2L4ocE0ETKcHKw6MVL3R+co=
github.com/dop251/goja_nodejs v0.0.0-20240728170619-29b559befffc/go.mod h1:VULptt4Q/fNzQUJlqY/GP3qHyU7ZH46mFkBZe0ZTokU=
github.com/dustin/go-humanize v0.0.0-20180421182945-02af3965c54e/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
@@ -214,6 +216,8 @@ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 h1:FKHo8hFI3A+7w0aUQuYXQ+6EN5stWmeY/AZqtM8xk9k=
github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo=
github.com/google/pprof v0.0.0-20240910150728-a0b0bb1d4134 h1:c5FlPPgxOn7kJz3VoPLkQYQXGBS3EklQ4Zfi57uOuqQ=
github.com/google/pprof v0.0.0-20240910150728-a0b0bb1d4134/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@@ -569,6 +573,8 @@ golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+5 -2
View File
@@ -2,13 +2,15 @@ package controller
import (
"github.com/GopeedLab/gopeed/pkg/base"
"net/http"
"net/url"
"os"
"path/filepath"
)
type Controller struct {
GetConfig func(v any)
ProxyConfig *base.DownloaderProxyConfig
GetConfig func(v any)
GetProxy func(requestProxy *base.RequestProxy) func(*http.Request) (*url.URL, error)
FileController
//ContextDialer() (proxy.Dialer, error)
}
@@ -23,6 +25,7 @@ type DefaultFileController struct {
func NewController() *Controller {
return &Controller{
GetConfig: func(v any) {},
GetProxy: func(requestProxy *base.RequestProxy) func(*http.Request) (*url.URL, error) { return nil },
FileController: &DefaultFileController{},
}
}
+4
View File
@@ -110,6 +110,10 @@ type FetcherManager interface {
Filters() []*SchemeFilter
// Build returns a new fetcher.
Build() Fetcher
// ParseName name displayed when the task is not yet resolved, parsed from the request URL
ParseName(u string) string
// AutoRename returns whether the fetcher need renaming the download file when has the same name file.
AutoRename() bool
// DefaultConfig returns the default configuration of the protocol.
DefaultConfig() any
+47 -11
View File
@@ -11,6 +11,7 @@ import (
"github.com/GopeedLab/gopeed/pkg/util"
"github.com/anacrolix/torrent"
"github.com/anacrolix/torrent/metainfo"
"net/url"
"path/filepath"
"strings"
"sync"
@@ -72,7 +73,7 @@ func (f *Fetcher) initClient() (err error) {
cfg.Bep20 = fmt.Sprintf("-GP%s-", parseBep20())
cfg.ExtendedHandshakeClientVersion = fmt.Sprintf("Gopeed %s", base.Version)
cfg.ListenPort = f.config.ListenPort
cfg.HTTPProxy = f.ctl.ProxyConfig.ToHandler()
cfg.HTTPProxy = f.ctl.GetProxy(f.meta.Req.Proxy)
cfg.DefaultStorage = newFileOpts(newFileClientOpts{
ClientBaseDir: cfg.DataDir,
HandleFileTorrent: func(infoHash metainfo.Hash, ft *fileTorrentImpl) {
@@ -97,18 +98,21 @@ func (f *Fetcher) initClient() (err error) {
}
func (f *Fetcher) Resolve(req *base.Request) error {
if err := base.ParseReqExtra[bt.ReqExtra](req); err != nil {
return err
}
f.meta.Req = req
if err := f.addTorrent(req, false); err != nil {
return err
}
f.updateRes()
f.meta.Req = req
return nil
}
func (f *Fetcher) Create(opts *base.Options) (err error) {
f.meta.Opts = opts
if f.meta.Res != nil {
torrentDirMap[f.meta.Res.Hash] = f.meta.FolderPath()
torrentDirMap[f.meta.Res.Hash] = opts.Path
}
return nil
}
@@ -120,7 +124,7 @@ func (f *Fetcher) Start() (err error) {
}
}
if ft, ok := ftMap[f.meta.Res.Hash]; ok {
ft.setTorrentDir(f.meta.FolderPath())
ft.setTorrentDir(f.meta.Opts.Path)
}
files := f.torrent.Files()
// If the user does not specify the file to download, all files will be downloaded by default
@@ -245,16 +249,18 @@ func (f *Fetcher) isDone() bool {
func (f *Fetcher) updateRes() {
res := &base.Resource{
Name: f.torrent.Name(),
Range: true,
Files: make([]*base.FileInfo, len(f.torrent.Files())),
Hash: f.torrent.InfoHash().String(),
}
f.torrent.PeerConns()
// Directory torrent
if f.torrent.Info().Length == 0 {
res.Name = f.torrent.Name()
}
for i, file := range f.torrent.Files() {
res.Files[i] = &base.FileInfo{
Name: filepath.Base(file.DisplayPath()),
Path: util.Dir(file.Path()),
Path: util.Dir(file.DisplayPath()),
Size: file.Length(),
}
}
@@ -289,7 +295,7 @@ func (f *Fetcher) doUpload(fromUpload bool) {
continue
}
stats := f.torrent.Stats()
stats := f.torrentStats()
f.data.SeedBytes = lastData.SeedBytes + stats.BytesWrittenData.Int64()
// Check is download complete, if not don't check and stop seeding
@@ -326,6 +332,17 @@ func (f *Fetcher) doUpload(fromUpload bool) {
}
}
// Get torrent stats maybe panic, see https://github.com/anacrolix/torrent/issues/972
func (f *Fetcher) torrentStats() torrent.TorrentStats {
defer func() {
if r := recover(); r != nil {
// ignore panic
}
}()
return f.torrent.Stats()
}
func (f *Fetcher) UploadedBytes() int64 {
return f.data.SeedBytes
}
@@ -336,9 +353,6 @@ func (f *Fetcher) WaitUpload() (err error) {
}
func (f *Fetcher) addTorrent(req *base.Request, fromUpload bool) (err error) {
if err = base.ParseReqExtra[bt.ReqExtra](req); err != nil {
return
}
if err = f.initClient(); err != nil {
return
}
@@ -458,6 +472,28 @@ func (fm *FetcherManager) Build() fetcher.Fetcher {
return &Fetcher{}
}
func (fm *FetcherManager) ParseName(u string) string {
var name string
url, err := url.Parse(u)
if err != nil {
return ""
}
params := url.Query()
if params.Get("dn") != "" {
return params.Get("dn")
}
if params.Get("xt") != "" {
xt := strings.Split(params.Get("xt"), ":")
return xt[len(xt)-1]
}
return name
}
func (fm *FetcherManager) AutoRename() bool {
return false
}
func (fm *FetcherManager) DefaultConfig() any {
return &config{
ListenPort: 0,
+118 -25
View File
@@ -8,6 +8,8 @@ import (
"github.com/GopeedLab/gopeed/internal/test"
"github.com/GopeedLab/gopeed/pkg/base"
"github.com/GopeedLab/gopeed/pkg/protocol/bt"
gohttp "net/http"
"net/url"
"os"
"reflect"
"testing"
@@ -33,7 +35,6 @@ func TestFetcher_Resolve_DataUri_Torrent(t *testing.T) {
}
want := &base.Resource{
Name: "ubuntu-22.04-live-server-amd64.iso",
Size: 1466714112,
Range: true,
Files: []*base.FileInfo{
@@ -69,33 +70,123 @@ func TestFetcher_ResolveWithProxy(t *testing.T) {
}
func doResolve(t *testing.T, fetcher fetcher.Fetcher) {
err := fetcher.Resolve(&base.Request{
URL: "./testdata/ubuntu-22.04-live-server-amd64.iso.torrent",
Extra: bt.ReqExtra{
Trackers: []string{
"udp://tracker.birkenwald.de:6969/announce",
"udp://tracker.bitsearch.to:1337/announce",
t.Run("Resolve Single File", func(t *testing.T) {
err := fetcher.Resolve(&base.Request{
URL: "./testdata/ubuntu-22.04-live-server-amd64.iso.torrent",
Extra: bt.ReqExtra{
Trackers: []string{
"udp://tracker.birkenwald.de:6969/announce",
"udp://tracker.bitsearch.to:1337/announce",
},
},
},
})
if err != nil {
panic(err)
}
})
if err != nil {
panic(err)
}
want := &base.Resource{
Name: "ubuntu-22.04-live-server-amd64.iso",
Size: 1466714112,
Range: true,
Files: []*base.FileInfo{
{
Name: "ubuntu-22.04-live-server-amd64.iso",
Size: 1466714112,
want := &base.Resource{
Size: 1466714112,
Range: true,
Files: []*base.FileInfo{
{
Name: "ubuntu-22.04-live-server-amd64.iso",
Size: 1466714112,
},
},
},
Hash: "8a55cfbd5ca5d11507364765936c4f9e55b253ed",
Hash: "8a55cfbd5ca5d11507364765936c4f9e55b253ed",
}
if !reflect.DeepEqual(want, fetcher.Meta().Res) {
t.Errorf("Resolve() got = %v, want %v", fetcher.Meta().Res, want)
}
})
t.Run("Resolve Multi Files", func(t *testing.T) {
err := fetcher.Resolve(&base.Request{
URL: "./testdata/test.torrent",
Extra: bt.ReqExtra{
Trackers: []string{
"udp://tracker.birkenwald.de:6969/announce",
"udp://tracker.bitsearch.to:1337/announce",
},
},
})
if err != nil {
panic(err)
}
want := &base.Resource{
Name: "test",
Size: 107484864,
Range: true,
Files: []*base.FileInfo{
{
Name: "c.txt",
Path: "path",
Size: 98501754,
},
{
Name: "b.txt",
Size: 8904996,
},
{
Name: "a.txt",
Size: 78114,
},
},
Hash: "ccbc92b0cd8deec16a2ef4be242a8c9243b1cedb",
}
if !reflect.DeepEqual(want, fetcher.Meta().Res) {
t.Errorf("Resolve() got = %v, want %v", fetcher.Meta().Res, want)
}
})
}
func TestFetcherManager_ParseName(t *testing.T) {
type args struct {
u string
}
if !reflect.DeepEqual(want, fetcher.Meta().Res) {
t.Errorf("Resolve() got = %v, want %v", fetcher.Meta().Res, want)
tests := []struct {
name string
args args
want string
}{
{
name: "broken url",
args: args{
u: "magnet://!@#%github.com",
},
want: "",
},
{
name: "dn",
args: args{
u: "magnet:?xt=urn:btih:8a55cfbd5ca5d11507364765936c4f9e55b253ed&dn=ubuntu-22.04-live-server-amd64.iso",
},
want: "ubuntu-22.04-live-server-amd64.iso",
},
{
name: "no dn",
args: args{
u: "magnet:?xt=urn:btih:8a55cfbd5ca5d11507364765936c4f9e55b253ed",
},
want: "8a55cfbd5ca5d11507364765936c4f9e55b253ed",
},
{
name: "non standard magnet",
args: args{
u: "magnet:?xxt=abcd",
},
want: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
fm := &FetcherManager{}
if got := fm.ParseName(tt.args.u); got != tt.want {
t.Errorf("ParseName() = %v, want %v", got, tt.want)
}
})
}
}
@@ -121,7 +212,9 @@ func buildConfigFetcher(proxyConfig *base.DownloaderProxyConfig) fetcher.Fetcher
newController.GetConfig = func(v any) {
json.Unmarshal([]byte(test.ToJson(mockCfg)), v)
}
newController.ProxyConfig = proxyConfig
newController.GetProxy = func(requestProxy *base.RequestProxy) func(*gohttp.Request) (*url.URL, error) {
return proxyConfig.ToHandler()
}
fetcher.Setup(newController)
return fetcher
}
Binary file not shown.
+29 -5
View File
@@ -79,6 +79,7 @@ func (f *Fetcher) Resolve(req *base.Request) error {
if err := base.ParseReqExtra[fhttp.ReqExtra](req); err != nil {
return err
}
f.meta.Req = req
httpReq, err := f.buildRequest(nil, req)
if err != nil {
return err
@@ -99,7 +100,7 @@ func (f *Fetcher) Resolve(req *base.Request) error {
}
if base.HttpCodePartialContent == httpResp.StatusCode || (base.HttpCodeOK == httpResp.StatusCode && httpResp.Header.Get(base.HttpHeaderAcceptRanges) == base.HttpHeaderBytes && strings.HasPrefix(httpResp.Header.Get(base.HttpHeaderContentRange), base.HttpHeaderBytes)) {
// 1.返回206响应码表示支持断点下载 2.不返回206但是Accept-Ranges首部并且等于bytes也表示支持断点下载
// response 206 status code, support breakpoint continuation
res.Range = true
// 解析资源大小: bytes 0-1000/1001 => 1001
contentTotal := path.Base(httpResp.Header.Get(base.HttpHeaderContentRange))
@@ -111,7 +112,8 @@ func (f *Fetcher) Resolve(req *base.Request) error {
res.Size = parse
}
} else if base.HttpCodeOK == httpResp.StatusCode {
// 返回200响应码,不支持断点下载,通过Content-Length头获取文件大小,获取不到的话可能是chunked编码
// response 200 status code, not support breakpoint continuation, get file size by Content-Length header
// if not found, maybe chunked encoding
contentLength := httpResp.Header.Get(base.HttpHeaderContentLength)
if contentLength != "" {
parse, err := strconv.ParseInt(contentLength, 10, 64)
@@ -152,7 +154,6 @@ func (f *Fetcher) Resolve(req *base.Request) error {
file.Name = httpReq.URL.Hostname()
}
res.Files = append(res.Files, file)
f.meta.Req = req
f.meta.Res = res
return nil
}
@@ -170,8 +171,12 @@ func (f *Fetcher) Create(opts *base.Options) error {
opts.Extra = &fhttp.OptsExtra{}
}
extra := opts.Extra.(*fhttp.OptsExtra)
if extra.Connections == 0 {
if extra.Connections <= 0 {
extra.Connections = f.config.Connections
// Avoid zero connections configuration
if extra.Connections <= 0 {
extra.Connections = 1
}
}
return nil
}
@@ -443,7 +448,7 @@ func (f *Fetcher) splitChunk() (chunks []*chunk) {
func (f *Fetcher) buildClient() *http.Client {
transport := &http.Transport{
Proxy: f.ctl.ProxyConfig.ToHandler(),
Proxy: f.ctl.GetProxy(f.meta.Req.Proxy),
}
// Cookie handle
jar, _ := cookiejar.New(nil)
@@ -481,6 +486,25 @@ func (fm *FetcherManager) Build() fetcher.Fetcher {
return &Fetcher{}
}
func (fm *FetcherManager) ParseName(u string) string {
var name string
url, err := url.Parse(u)
if err != nil {
return ""
}
// Get filePath by URL
name = path.Base(url.Path)
// If file name is empty, use host name
if name == "" || name == "/" || name == "." {
name = url.Hostname()
}
return name
}
func (fm *FetcherManager) AutoRename() bool {
return true
}
func (fm *FetcherManager) DefaultConfig() any {
return &config{
UserAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36",
+59 -7
View File
@@ -9,6 +9,8 @@ import (
"github.com/GopeedLab/gopeed/pkg/base"
"github.com/GopeedLab/gopeed/pkg/protocol/http"
"net"
gohttp "net/http"
"net/url"
"testing"
"time"
)
@@ -192,6 +194,54 @@ func TestFetcher_ConfigUseServerCtime(t *testing.T) {
}
}
func TestFetcherManager_ParseName(t *testing.T) {
type args struct {
u string
}
tests := []struct {
name string
args args
want string
}{
{
name: "broken url",
args: args{
u: "https://!@#%github.com",
},
want: "",
},
{
name: "file path",
args: args{
u: "https://github.com/index.html",
},
want: "index.html",
},
{
name: "file path with query and hash",
args: args{
u: "https://github.com/a/b/index.html/#list?name=1",
},
want: "index.html",
},
{
name: "no file path",
args: args{
u: "https://github.com",
},
want: "github.com",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
fm := &FetcherManager{}
if got := fm.ParseName(tt.args.u); got != tt.want {
t.Errorf("ParseName() = %v, want %v", got, tt.want)
}
})
}
}
func downloadReady(listener net.Listener, connections int, t *testing.T) fetcher.Fetcher {
return doDownloadReady(buildFetcher(), listener, connections, t)
}
@@ -337,10 +387,12 @@ func downloadResume(listener net.Listener, connections int, t *testing.T) {
func downloadWithProxy(httpListener net.Listener, proxyListener net.Listener, t *testing.T) {
fetcher := downloadReady(httpListener, 4, t)
ctl := controller.NewController()
ctl.ProxyConfig = &base.DownloaderProxyConfig{
Enable: true,
Scheme: "socks5",
Host: proxyListener.Addr().String(),
ctl.GetProxy = func(requestProxy *base.RequestProxy) func(*gohttp.Request) (*url.URL, error) {
return (&base.DownloaderProxyConfig{
Enable: true,
Scheme: "socks5",
Host: proxyListener.Addr().String(),
}).ToHandler()
}
fetcher.Setup(ctl)
err := fetcher.Start()
@@ -359,11 +411,11 @@ func downloadWithProxy(httpListener net.Listener, proxyListener net.Listener, t
}
func buildFetcher() *Fetcher {
fb := new(FetcherManager)
fetcher := fb.Build()
fm := new(FetcherManager)
fetcher := fm.Build()
newController := controller.NewController()
newController.GetConfig = func(v any) {
json.Unmarshal([]byte(test.ToJson(fb.DefaultConfig())), v)
json.Unmarshal([]byte(test.ToJson(fm.DefaultConfig())), v)
}
fetcher.Setup(newController)
return fetcher.(*Fetcher)
-8
View File
@@ -1,8 +0,0 @@
package base
import "errors"
var (
NotFound = errors.New("not found")
BadParams = errors.New("bad params")
)
+34 -9
View File
@@ -16,6 +16,8 @@ type Request struct {
Extra any `json:"extra"`
// Labels is used to mark the download task
Labels map[string]string `json:"labels"`
// Proxy is special proxy config for request
Proxy *RequestProxy `json:"proxy"`
}
func (r *Request) Validate() error {
@@ -25,6 +27,37 @@ func (r *Request) Validate() error {
return nil
}
type RequestProxyMode string
const (
// RequestProxyModeFollow follow setting proxy
RequestProxyModeFollow RequestProxyMode = "follow"
// RequestProxyModeNone not use proxy
RequestProxyModeNone RequestProxyMode = "none"
// RequestProxyModeCustom custom proxy
RequestProxyModeCustom RequestProxyMode = "custom"
)
type RequestProxy struct {
Mode RequestProxyMode `json:"mode"`
Scheme string `json:"scheme"`
Host string `json:"host"`
Usr string `json:"usr"`
Pwd string `json:"pwd"`
}
func (p *RequestProxy) ToHandler() func(r *http.Request) (*url.URL, error) {
if p == nil || p.Mode != RequestProxyModeCustom {
return nil
}
if p.Scheme == "" || p.Host == "" {
return nil
}
return http.ProxyURL(util.BuildProxyUrl(p.Scheme, p.Host, p.Usr, p.Pwd))
}
// Resource download resource
type Resource struct {
// if name is not empty, the resource is a folder and the name is the folder name
@@ -94,15 +127,7 @@ func (o *Options) InitSelectFiles(fileSize int) {
}
func (o *Options) Clone() *Options {
if o == nil {
return nil
}
return &Options{
Name: o.Name,
Path: o.Path,
SelectFiles: o.SelectFiles,
Extra: o.Extra,
}
return util.DeepClone(o)
}
func ParseReqExtra[E any](req *Request) error {
+206 -104
View File
@@ -12,6 +12,9 @@ import (
"github.com/rs/zerolog"
"github.com/rs/zerolog/pkgerrors"
"github.com/virtuald/go-paniclog"
"math"
gohttp "net/http"
"net/url"
"os"
"path/filepath"
"sort"
@@ -253,7 +256,20 @@ func (d *Downloader) setupFetcher(fm fetcher.FetcherManager, fetcher fetcher.Fet
ctl.GetConfig = func(v any) {
d.getProtocolConfig(fm.Name(), v)
}
ctl.ProxyConfig = d.cfg.Proxy
// Get proxy config, task request proxy config has higher priority, then use global proxy config
ctl.GetProxy = func(requestProxy *base.RequestProxy) func(*gohttp.Request) (*url.URL, error) {
if requestProxy == nil {
return d.cfg.Proxy.ToHandler()
}
switch requestProxy.Mode {
case base.RequestProxyModeNone:
return nil
case base.RequestProxyModeCustom:
return requestProxy.ToHandler()
default:
return d.cfg.Proxy.ToHandler()
}
}
fetcher.Setup(ctl)
}
@@ -356,56 +372,28 @@ func (d *Downloader) Create(rrId string, opts *base.Options) (taskId string, err
return d.doCreate(fetcher, opts)
}
func (d *Downloader) Pause(id string) (err error) {
task := d.GetTask(id)
if task == nil {
func (d *Downloader) Pause(filter *TaskFilter) (err error) {
if filter == nil || filter.IsEmpty() {
return d.pauseAll()
}
filter.NotStatuses = []base.Status{base.DownloadStatusPause, base.DownloadStatusError, base.DownloadStatusDone}
pauseTasks := d.GetTasksByFilter(filter)
if len(pauseTasks) == 0 {
return ErrTaskNotFound
}
func() {
task.lock.Lock()
defer task.lock.Unlock()
if task.Status == base.DownloadStatusPause {
return
}
for _, task := range pauseTasks {
if err = d.doPause(task); err != nil {
return
}
d.notifyRunning()
}()
}
d.notifyRunning()
return
}
func (d *Downloader) Continue(id string) (err error) {
task := d.GetTask(id)
if task == nil {
return ErrTaskNotFound
}
func() {
d.lock.Lock()
defer d.lock.Unlock()
remainRunningCount := d.remainRunningCount()
if remainRunningCount == 0 {
for _, t := range d.tasks {
if t.Status == base.DownloadStatusRunning {
if err = d.doPause(t); err != nil {
return
}
t.Status = base.DownloadStatusWait
d.waitTasks = append(d.waitTasks, t)
break
}
}
}
}()
return d.doStart(task)
}
func (d *Downloader) PauseAll() (err error) {
func (d *Downloader) pauseAll() (err error) {
func() {
d.lock.Lock()
defer d.lock.Unlock()
@@ -423,7 +411,65 @@ func (d *Downloader) PauseAll() (err error) {
return
}
func (d *Downloader) ContinueAll() (err error) {
// Continue specific tasks, if continue tasks will exceed maxRunning, it needs pause some running tasks before that
func (d *Downloader) Continue(filter *TaskFilter) (err error) {
if filter == nil || filter.IsEmpty() {
return d.continueAll()
}
filter.NotStatuses = []base.Status{base.DownloadStatusRunning, base.DownloadStatusDone}
continueTasks := d.GetTasksByFilter(filter)
if len(continueTasks) == 0 {
return ErrTaskNotFound
}
realContinueTasks := make([]*Task, 0)
func() {
d.lock.Lock()
defer d.lock.Unlock()
continueCount := len(continueTasks)
remainRunningCount := d.remainRunningCount()
needRunningCount := int(math.Min(float64(d.cfg.MaxRunning), float64(continueCount)))
needPauseCount := needRunningCount - remainRunningCount
if needPauseCount > 0 {
pausedCount := 0
for _, task := range d.tasks {
if task.Status == base.DownloadStatusRunning {
if err = d.doPause(task); err != nil {
return
}
task.Status = base.DownloadStatusWait
d.waitTasks = append(d.waitTasks, task)
pausedCount++
}
if pausedCount == needPauseCount {
break
}
}
}
for _, task := range continueTasks {
if len(realContinueTasks) < needRunningCount {
realContinueTasks = append(realContinueTasks, task)
} else {
task.Status = base.DownloadStatusWait
d.waitTasks = append(d.waitTasks, task)
}
}
}()
for _, task := range realContinueTasks {
if err = d.doStart(task); err != nil {
return
}
}
return
}
// continueAll continue all tasks but does not affect tasks already running
func (d *Downloader) continueAll() (err error) {
continuedTasks := make([]*Task, 0)
func() {
@@ -444,8 +490,7 @@ func (d *Downloader) ContinueAll() (err error) {
}()
for _, task := range continuedTasks {
tt := task
if err = d.doStart(tt); err != nil {
if err = d.doStart(task); err != nil {
return
}
}
@@ -453,40 +498,26 @@ func (d *Downloader) ContinueAll() (err error) {
return
}
func (d *Downloader) Delete(id string, force bool) (err error) {
task := d.GetTask(id)
if task == nil {
return ErrTaskNotFound
func (d *Downloader) ContinueBatch(filter *TaskFilter) (err error) {
if filter == nil || filter.IsEmpty() {
return d.continueAll()
}
func() {
d.lock.Lock()
defer d.lock.Unlock()
for i, t := range d.tasks {
if t.ID == id {
d.tasks = append(d.tasks[:i], d.tasks[i+1:]...)
break
}
continueTasks := d.GetTasksByFilter(filter)
for _, task := range continueTasks {
if err = d.doStart(task); err != nil {
return
}
for i, t := range d.waitTasks {
if t.ID == id {
d.waitTasks = append(d.waitTasks[:i], d.waitTasks[i+1:]...)
break
}
}
}()
err = d.doDelete(task, force)
if err != nil {
return
}
d.notifyRunning()
return
}
func (d *Downloader) DeleteByStatues(statues []base.Status, force bool) (err error) {
deleteTasks := d.GetTasksByStatues(statues)
func (d *Downloader) Delete(filter *TaskFilter, force bool) (err error) {
if filter == nil || filter.IsEmpty() {
return d.deleteAll()
}
deleteTasks := d.GetTasksByFilter(filter)
if len(deleteTasks) == 0 {
return
}
@@ -528,6 +559,23 @@ func (d *Downloader) DeleteByStatues(statues []base.Status, force bool) (err err
return
}
func (d *Downloader) deleteAll() (err error) {
func() {
d.lock.Lock()
defer d.lock.Unlock()
d.tasks = make([]*Task, 0)
d.waitTasks = make([]*Task, 0)
}()
for _, task := range d.tasks {
if err = d.doDelete(task, true); err != nil {
return
}
}
return
}
func (d *Downloader) Stats(id string) (sr any, err error) {
task := d.GetTask(id)
if task == nil {
@@ -550,9 +598,6 @@ func (d *Downloader) Stats(id string) (sr any, err error) {
func (d *Downloader) doDelete(task *Task, force bool) (err error) {
err = func() error {
d.lock.Lock()
defer d.lock.Unlock()
if err := d.storage.Delete(bucketTask, task.ID); err != nil {
return err
}
@@ -580,10 +625,10 @@ func (d *Downloader) doDelete(task *Task, force bool) (err error) {
task = nil
return nil
}()
if err != nil {
d.Logger.Error().Stack().Err(err).Msgf("delete task failed, task id: %s", task.ID)
}
return
}
@@ -591,12 +636,12 @@ func (d *Downloader) Close() error {
d.closed.Store(true)
closeArr := []func() error{
d.PauseAll,
d.storage.Close,
d.pauseAll,
}
for _, fm := range d.cfg.FetchManagers {
closeArr = append(closeArr, fm.Close)
}
closeArr = append(closeArr, d.storage.Close)
// Make sure all resources are released, if had error, return the last error
var lastErr error
for i, close := range closeArr {
@@ -653,17 +698,51 @@ func (d *Downloader) GetTasks() []*Task {
return d.tasks
}
func (d *Downloader) GetTasksByStatues(statues []base.Status) []*Task {
if len(statues) == 0 {
// GetTasksByFilter get tasks by filter, if filter is nil, return all tasks
// return tasks and if match all tasks
func (d *Downloader) GetTasksByFilter(filter *TaskFilter) []*Task {
if filter == nil || filter.IsEmpty() {
return d.tasks
}
idMatch := func(task *Task) bool {
if len(filter.IDs) == 0 {
return true
}
for _, id := range filter.IDs {
if task.ID == id {
return true
}
}
return false
}
statusMatch := func(task *Task) bool {
if len(filter.Statuses) == 0 {
return true
}
for _, status := range filter.Statuses {
if task.Status == status {
return true
}
}
return false
}
notStatusMatch := func(task *Task) bool {
if len(filter.NotStatuses) == 0 {
return true
}
for _, status := range filter.NotStatuses {
if task.Status == status {
return false
}
}
return true
}
tasks := make([]*Task, 0)
for _, task := range d.tasks {
for _, status := range statues {
if task.Status == status {
tasks = append(tasks, task)
break
}
if idMatch(task) && statusMatch(task) && notStatusMatch(task) {
tasks = append(tasks, task)
}
}
return tasks
@@ -732,6 +811,12 @@ func (d *Downloader) watch(task *Task) {
d.doOnError(task, err)
return
}
// When delete a not resolved task, need check if the task resource is nil
if task.Meta.Res == nil {
return
}
task.Progress.Used = task.timer.Used()
if task.Meta.Res.Size == 0 {
task.Meta.Res.Size = task.fetcher.Progress().TotalDownloaded()
@@ -748,6 +833,7 @@ func (d *Downloader) watch(task *Task) {
d.emit(EventKeyDone, task)
d.emit(EventKeyFinally, task, err)
d.notifyRunning()
d.triggerOnDone(task)
if e, ok := task.Meta.Opts.Extra.(*http.OptsExtra); ok {
downloadFilePath := task.Meta.SingleFilepath()
@@ -843,6 +929,20 @@ func (d *Downloader) doCreate(f fetcher.Fetcher, opts *base.Options) (taskId str
opts.Path = storeConfig.DownloadDir
}
// if enable white download directory, check if the download directory is in the white list
if len(d.cfg.DownloadDirWhiteList) > 0 {
inWhiteList := false
for _, dir := range d.cfg.DownloadDirWhiteList {
if match, err := filepath.Match(dir, opts.Path); match && err == nil {
inWhiteList = true
break
}
}
if !inWhiteList {
return "", errors.New("download directory is not in white list")
}
}
fm, err := d.parseFm(f.Meta().Req.URL)
if err != nil {
return
@@ -932,26 +1032,28 @@ func (d *Downloader) doStart(task *Task) (err error) {
}
if isCreate {
d.checkDuplicateLock.Lock()
defer d.checkDuplicateLock.Unlock()
task.Meta.Opts.Name = util.ReplaceInvalidFilename(task.Meta.Opts.Name)
// check if the download file is duplicated and rename it automatically.
if task.Meta.Res.Name != "" {
task.Meta.Res.Name = util.ReplaceInvalidFilename(task.Meta.Res.Name)
fullDirPath := task.Meta.FolderPath()
newName, err := util.CheckDuplicateAndRename(fullDirPath)
if err != nil {
return err
if task.fetcherManager.AutoRename() {
d.checkDuplicateLock.Lock()
defer d.checkDuplicateLock.Unlock()
task.Meta.Opts.Name = util.ReplaceInvalidFilename(task.Meta.Opts.Name)
// check if the download file is duplicated and rename it automatically.
if task.Meta.Res.Name != "" {
task.Meta.Res.Name = util.ReplaceInvalidFilename(task.Meta.Res.Name)
fullDirPath := task.Meta.FolderPath()
newName, err := util.CheckDuplicateAndRename(fullDirPath)
if err != nil {
return err
}
task.Meta.Opts.Name = newName
} else {
task.Meta.Res.Files[0].Name = util.ReplaceInvalidFilename(task.Meta.Res.Files[0].Name)
fullFilePath := task.Meta.SingleFilepath()
newName, err := util.CheckDuplicateAndRename(fullFilePath)
if err != nil {
return err
}
task.Meta.Opts.Name = newName
}
task.Meta.Opts.Name = newName
} else {
task.Meta.Res.Files[0].Name = util.ReplaceInvalidFilename(task.Meta.Res.Files[0].Name)
fullFilePath := task.Meta.SingleFilepath()
newName, err := util.CheckDuplicateAndRename(fullFilePath)
if err != nil {
return err
}
task.Meta.Opts.Name = newName
}
task.Meta.Res.CalcSize(task.Meta.Opts.SelectFiles)
+231 -16
View File
@@ -82,7 +82,45 @@ func TestDownloader_Create(t *testing.T) {
want := test.FileMd5(test.BuildFile)
got := test.FileMd5(test.DownloadFile)
if want != got {
t.Errorf("Download() got = %v, want %v", got, want)
t.Errorf("Downloader_Create() got = %v, want %v", got, want)
}
}
func TestDownloader_CreateNotInWhite(t *testing.T) {
listener := test.StartTestFileServer()
defer listener.Close()
downloader := NewDownloader(&DownloaderConfig{
DownloadDirWhiteList: []string{"./downloads"},
})
if err := downloader.Setup(); err != nil {
t.Fatal(err)
}
defer downloader.Clear()
req := &base.Request{
URL: "http://" + listener.Addr().String() + "/" + test.BuildName,
}
rr, err := downloader.Resolve(req)
if err != nil {
t.Fatal(err)
}
var wg sync.WaitGroup
wg.Add(1)
downloader.Listener(func(event *Event) {
if event.Key == EventKeyDone {
wg.Done()
}
})
_, err = downloader.Create(rr.ID, &base.Options{
Path: test.Dir,
Name: test.DownloadName,
Extra: http.OptsExtra{
Connections: 4,
},
})
if !strings.Contains(err.Error(), "white") {
t.Errorf("TestDownloader_CreateNotInWhite() got = %v, want %v", err.Error(), "not in white list")
}
}
@@ -95,7 +133,7 @@ func TestDownloader_CreateDirectBatch(t *testing.T) {
t.Fatal(err)
}
defer func() {
downloader.DeleteByStatues(nil, true)
downloader.Delete(nil, true)
downloader.Clear()
}()
@@ -159,21 +197,21 @@ func TestDownloader_CreateDirectBatch(t *testing.T) {
func TestDownloader_CreateWithProxy(t *testing.T) {
// No proxy
doTestDownloaderCreateWithProxy(t, false, func(proxyCfg *base.DownloaderProxyConfig) *base.DownloaderProxyConfig {
doTestDownloaderCreateWithProxy(t, false, nil, func(proxyCfg *base.DownloaderProxyConfig) *base.DownloaderProxyConfig {
return nil
}, nil)
// Disable proxy
doTestDownloaderCreateWithProxy(t, false, func(proxyCfg *base.DownloaderProxyConfig) *base.DownloaderProxyConfig {
doTestDownloaderCreateWithProxy(t, false, nil, func(proxyCfg *base.DownloaderProxyConfig) *base.DownloaderProxyConfig {
proxyCfg.Enable = false
return proxyCfg
}, nil)
// Enable system proxy but not set proxy environment variable
doTestDownloaderCreateWithProxy(t, false, func(proxyCfg *base.DownloaderProxyConfig) *base.DownloaderProxyConfig {
doTestDownloaderCreateWithProxy(t, false, nil, func(proxyCfg *base.DownloaderProxyConfig) *base.DownloaderProxyConfig {
proxyCfg.System = true
return proxyCfg
}, nil)
// Enable proxy but error proxy environment variable
doTestDownloaderCreateWithProxy(t, false, func(proxyCfg *base.DownloaderProxyConfig) *base.DownloaderProxyConfig {
doTestDownloaderCreateWithProxy(t, false, nil, func(proxyCfg *base.DownloaderProxyConfig) *base.DownloaderProxyConfig {
os.Setenv("HTTP_PROXY", "http://127.0.0.1:1234")
os.Setenv("HTTPS_PROXY", "http://127.0.0.1:1234")
proxyCfg.System = true
@@ -184,33 +222,50 @@ func TestDownloader_CreateWithProxy(t *testing.T) {
}
})
// Enable system proxy and set proxy environment variable
doTestDownloaderCreateWithProxy(t, false, func(proxyCfg *base.DownloaderProxyConfig) *base.DownloaderProxyConfig {
doTestDownloaderCreateWithProxy(t, false, nil, func(proxyCfg *base.DownloaderProxyConfig) *base.DownloaderProxyConfig {
os.Setenv("HTTP_PROXY", proxyCfg.ToUrl().String())
os.Setenv("HTTPS_PROXY", proxyCfg.ToUrl().String())
proxyCfg.System = true
return proxyCfg
}, nil)
// Invalid proxy scheme
doTestDownloaderCreateWithProxy(t, false, func(proxyCfg *base.DownloaderProxyConfig) *base.DownloaderProxyConfig {
doTestDownloaderCreateWithProxy(t, false, nil, func(proxyCfg *base.DownloaderProxyConfig) *base.DownloaderProxyConfig {
proxyCfg.Scheme = ""
return proxyCfg
}, nil)
// Invalid proxy host
doTestDownloaderCreateWithProxy(t, false, func(proxyCfg *base.DownloaderProxyConfig) *base.DownloaderProxyConfig {
doTestDownloaderCreateWithProxy(t, false, nil, func(proxyCfg *base.DownloaderProxyConfig) *base.DownloaderProxyConfig {
proxyCfg.Host = ""
return proxyCfg
}, nil)
// Use proxy without auth
doTestDownloaderCreateWithProxy(t, false, func(proxyCfg *base.DownloaderProxyConfig) *base.DownloaderProxyConfig {
doTestDownloaderCreateWithProxy(t, false, nil, func(proxyCfg *base.DownloaderProxyConfig) *base.DownloaderProxyConfig {
return proxyCfg
}, nil)
// Use proxy with auth
doTestDownloaderCreateWithProxy(t, true, func(proxyCfg *base.DownloaderProxyConfig) *base.DownloaderProxyConfig {
doTestDownloaderCreateWithProxy(t, true, nil, func(proxyCfg *base.DownloaderProxyConfig) *base.DownloaderProxyConfig {
return proxyCfg
}, nil)
// Request proxy mode follow
doTestDownloaderCreateWithProxy(t, false, func(reqProxy *base.RequestProxy) *base.RequestProxy {
reqProxy.Mode = base.RequestProxyModeFollow
return reqProxy
}, nil, nil)
// Request proxy mode none
doTestDownloaderCreateWithProxy(t, false, func(reqProxy *base.RequestProxy) *base.RequestProxy {
reqProxy.Mode = base.RequestProxyModeNone
return reqProxy
}, nil, nil)
// Request proxy mode custom
doTestDownloaderCreateWithProxy(t, false, func(reqProxy *base.RequestProxy) *base.RequestProxy {
return reqProxy
}, nil, nil)
}
func doTestDownloaderCreateWithProxy(t *testing.T, auth bool, buildProxyConfig func(proxyCfg *base.DownloaderProxyConfig) *base.DownloaderProxyConfig, errHandler func(err error)) {
func doTestDownloaderCreateWithProxy(t *testing.T, auth bool, buildReqProxy func(reqProxy *base.RequestProxy) *base.RequestProxy, buildProxyConfig func(proxyCfg *base.DownloaderProxyConfig) *base.DownloaderProxyConfig, errHandler func(err error)) {
usr, pwd := "", ""
if auth {
usr, pwd = "admin", "123"
@@ -223,17 +278,29 @@ func doTestDownloaderCreateWithProxy(t *testing.T, auth bool, buildProxyConfig f
t.Fatal(err)
}
defer downloader.Clear()
downloader.cfg.DownloaderStoreConfig.Proxy = buildProxyConfig(&base.DownloaderProxyConfig{
globalProxyCfg := &base.DownloaderProxyConfig{
Enable: true,
Scheme: "socks5",
Host: proxyListener.Addr().String(),
Usr: usr,
Pwd: pwd,
})
}
if buildProxyConfig != nil {
globalProxyCfg = buildProxyConfig(globalProxyCfg)
}
downloader.cfg.DownloaderStoreConfig.Proxy = globalProxyCfg
req := &base.Request{
URL: test.ExternalDownloadUrl,
}
if buildReqProxy != nil {
req.Proxy = buildReqProxy(&base.RequestProxy{
Scheme: "socks5",
Host: proxyListener.Addr().String(),
Usr: usr,
Pwd: pwd,
})
}
rr, err := downloader.Resolve(req)
if err != nil {
if errHandler == nil {
@@ -333,7 +400,7 @@ func TestDownloader_StoreAndRestore(t *testing.T) {
t.Fatal(err)
}
time.Sleep(time.Millisecond * 1001)
err = downloader.Pause(id)
err = downloader.Pause(&TaskFilter{IDs: []string{id}})
if err != nil {
t.Fatal(err)
}
@@ -357,7 +424,7 @@ func TestDownloader_StoreAndRestore(t *testing.T) {
wg.Done()
}
})
err = downloader.Continue(id)
err = downloader.Continue(&TaskFilter{IDs: []string{id}})
wg.Wait()
if err != nil {
t.Fatal(err)
@@ -418,3 +485,151 @@ func TestDownloader_Protocol_Config(t *testing.T) {
t.Errorf("GetConfig() got = %v, want %v", test.ToJson(storeCfg), test.ToJson(newStoreCfg))
}
}
func TestDownloader_GetTasksByFilter(t *testing.T) {
listener := test.StartTestFileServer()
defer listener.Close()
downloader := NewDownloader(nil)
if err := downloader.Setup(); err != nil {
t.Fatal(err)
}
defer func() {
downloader.Delete(nil, true)
downloader.Clear()
}()
reqs := make([]*base.Request, 0)
fileNames := make([]string, 0)
for i := 0; i < 10; i++ {
req := &base.Request{
URL: "http://" + listener.Addr().String() + "/" + test.BuildName,
}
reqs = append(reqs, req)
if i == 0 {
fileNames = append(fileNames, test.DownloadName)
} else {
arr := strings.Split(test.DownloadName, ".")
fileNames = append(fileNames, arr[0]+" ("+strconv.Itoa(i)+")."+arr[1])
}
}
var wg sync.WaitGroup
wg.Add(len(reqs))
downloader.Listener(func(event *Event) {
if event.Key == EventKeyDone {
wg.Done()
}
})
taskIds, err := downloader.CreateDirectBatch(reqs, &base.Options{
Path: test.Dir,
Name: test.DownloadName,
Extra: http.OptsExtra{
Connections: 4,
},
})
if err != nil {
t.Fatal(err)
}
wg.Wait()
t.Run("GetTasksByFilter nil", func(t *testing.T) {
tasks := downloader.GetTasksByFilter(nil)
if len(tasks) != len(reqs) {
t.Errorf("GetTasksByFilter nil task got = %v, want %v", len(tasks), len(reqs))
}
})
t.Run("GetTasksByFilter empty", func(t *testing.T) {
tasks := downloader.GetTasksByFilter(&TaskFilter{})
if len(tasks) != len(reqs) {
t.Errorf("GetTasksByFilter empty task got = %v, want %v", len(tasks), len(reqs))
}
})
t.Run("GetTasksByFilter ids", func(t *testing.T) {
tasks := downloader.GetTasksByFilter(&TaskFilter{
IDs: taskIds,
})
if len(tasks) != len(reqs) {
t.Errorf("GetTasksByFilter ids task got = %v, want %v", len(tasks), len(reqs))
}
})
t.Run("GetTasksByFilter match ids", func(t *testing.T) {
tasks := downloader.GetTasksByFilter(&TaskFilter{
IDs: []string{taskIds[0]},
})
if len(tasks) != 1 {
t.Errorf("GetTasksByFilter ids task got = %v, want %v", len(tasks), 1)
}
})
t.Run("GetTasksByFilter not match ids", func(t *testing.T) {
tasks := downloader.GetTasksByFilter(&TaskFilter{
IDs: []string{"xxx"},
})
if len(tasks) != 0 {
t.Errorf("GetTasksByFilter ids task got = %v, want %v", len(tasks), 0)
}
})
t.Run("GetTasksByFilter status", func(t *testing.T) {
tasks := downloader.GetTasksByFilter(&TaskFilter{
Statuses: []base.Status{base.DownloadStatusDone},
})
if len(tasks) != len(reqs) {
t.Errorf("GetTasksByFilter status task got = %v, want %v", len(tasks), len(reqs))
}
})
t.Run("GetTasksByFilter not match status", func(t *testing.T) {
tasks := downloader.GetTasksByFilter(&TaskFilter{
Statuses: []base.Status{base.DownloadStatusError},
})
if len(tasks) != 0 {
t.Errorf("GetTasksByFilter status task got = %v, want %v", len(tasks), 0)
}
})
t.Run("GetTasksByFilter match notStatus", func(t *testing.T) {
tasks := downloader.GetTasksByFilter(&TaskFilter{
NotStatuses: []base.Status{base.DownloadStatusRunning, base.DownloadStatusPause},
})
if len(tasks) != len(reqs) {
t.Errorf("GetTasksByFilter match notStatus task got = %v, want %v", len(tasks), len(reqs))
}
})
t.Run("GetTasksByFilter not match notStatus", func(t *testing.T) {
tasks := downloader.GetTasksByFilter(&TaskFilter{
NotStatuses: []base.Status{base.DownloadStatusDone},
})
if len(tasks) != 0 {
t.Errorf("GetTasksByFilter not match notStatus task got = %v, want %v", len(tasks), 0)
}
})
t.Run("GetTasksByFilter match ids and status", func(t *testing.T) {
tasks := downloader.GetTasksByFilter(&TaskFilter{
IDs: []string{taskIds[0]},
Statuses: []base.Status{base.DownloadStatusDone},
})
if len(tasks) != 1 {
t.Errorf("GetTasksByFilter match ids and status task got = %v, want %v", len(tasks), 1)
}
})
t.Run("GetTasksByFilter not match ids and status", func(t *testing.T) {
tasks := downloader.GetTasksByFilter(&TaskFilter{
IDs: []string{taskIds[0]},
Statuses: []base.Status{base.DownloadStatusError},
})
if len(tasks) != 0 {
t.Errorf("GetTasksByFilter not match ids and status task got = %v, want %v", len(tasks), 0)
}
})
}
+1 -1
View File
@@ -106,7 +106,7 @@ func (e *Engine) await(value any) {
}
func (e *Engine) Close() {
e.loop.Stop()
e.loop.StopNoWait()
}
type Config struct {
+30 -7
View File
@@ -38,7 +38,7 @@ const (
EventOnResolve ActivationEvent = "onResolve"
EventOnStart ActivationEvent = "onStart"
EventOnError ActivationEvent = "onError"
//EventOnDone ActivationEvent = "onDone"
EventOnDone ActivationEvent = "onDone"
)
func (d *Downloader) InstallExtensionByGit(url string) (*Extension, error) {
@@ -304,6 +304,17 @@ func (d *Downloader) triggerOnError(task *Task, err error) {
)
}
func (d *Downloader) triggerOnDone(task *Task) {
doTrigger(d,
EventOnDone,
task.Meta.Req,
&OnErrorContext{
Task: NewExtensionTask(d, task),
},
nil,
)
}
func doTrigger[T any](d *Downloader, event ActivationEvent, req *base.Request, ctx T, handler func(ext *Extension, gopeed *Instance, ctx T)) error {
// init extension global object
gopeed := &Instance{
@@ -611,9 +622,9 @@ func (h InstanceEvents) OnError(fn goja.Callable) {
h.register(EventOnError, fn)
}
//func (h InstanceEvents) OnDone(fn goja.Callable) {
// h.register(HookEventOnDone, fn)
//}
func (h InstanceEvents) OnDone(fn goja.Callable) {
h.register(EventOnDone, fn)
}
type ExtensionInfo struct {
Identity string `json:"identity"`
@@ -687,6 +698,10 @@ type OnErrorContext struct {
Error error `json:"error"`
}
type OnDoneContext struct {
Task *Task `json:"task"`
}
type ExtensionTask struct {
*Task
@@ -694,18 +709,26 @@ type ExtensionTask struct {
}
func NewExtensionTask(download *Downloader, task *Task) *ExtensionTask {
newTask := task.clone()
// Assign the pointer of the properties that the extension supports modification
newTask.Meta = task.Meta
newTask.Status = task.Status
return &ExtensionTask{
Task: task.clone(),
Task: newTask,
download: download,
}
}
func (t *ExtensionTask) Continue() error {
return t.download.Continue(t.ID)
return t.download.Continue(&TaskFilter{
IDs: []string{t.ID},
})
}
func (t *ExtensionTask) Pause() error {
return t.download.Pause(t.ID)
return t.download.Pause(&TaskFilter{
IDs: []string{t.ID},
})
}
func parseSettings(settings []*Setting) map[string]any {
+39 -2
View File
@@ -247,8 +247,45 @@ func TestDownloader_Extension_OnError(t *testing.T) {
}
// extension on error modify url and continue download
task := downloader.GetTask(id)
if task.Meta.Req.URL != "https://github.com" {
t.Fatalf("except url: https://github.com, actual: %s", task.Meta.Req.URL)
if task.Status != base.DownloadStatusDone {
t.Fatalf("except status is done, actual: %s", task.Status)
}
})
}
func TestDownloader_Extension_OnDone(t *testing.T) {
setupDownloader(func(downloader *Downloader) {
if _, err := downloader.InstallExtensionByFolder("./testdata/extensions/on_done", false); err != nil {
t.Fatal(err)
}
errCh := make(chan error, 1)
downloader.Listener(func(event *Event) {
if event.Key == EventKeyFinally {
errCh <- event.Err
}
})
id, err := downloader.CreateDirect(&base.Request{
URL: "https://github.com",
}, nil)
if err != nil {
t.Fatal(err)
}
select {
case err = <-errCh:
break
case <-time.After(time.Second * 10):
err = errors.New("timeout")
}
// wait for script execution
time.Sleep(time.Millisecond * 3000)
if err != nil {
panic("extension on done download error: " + err.Error())
}
// extension on error modify url and continue download
task := downloader.GetTask(id)
if task.Meta.Req.Labels["modified"] != "true" {
t.Fatalf("except label: modified=true, actual: %s", task.Meta.Req.Labels["modified"])
}
if task.Status != base.DownloadStatusDone {
t.Fatalf("except status is done, actual: %s", task.Status)
+57 -13
View File
@@ -1,6 +1,7 @@
package download
import (
"encoding/json"
"github.com/GopeedLab/gopeed/internal/controller"
"github.com/GopeedLab/gopeed/internal/fetcher"
"github.com/GopeedLab/gopeed/internal/protocol/bt"
@@ -49,22 +50,54 @@ func NewTask() *Task {
}
}
// Name returns the display name of the task.
func (t *Task) Name() string {
// Custom name first
if t.Meta.Opts.Name != "" {
return t.Meta.Opts.Name
}
// Task is not resolved, parse the name from the URL
if t.Meta.Res == nil {
fallbackName := "unknown"
if t.fetcherManager == nil {
return fallbackName
}
parseName := t.fetcherManager.ParseName(t.Meta.Req.URL)
if parseName == "" {
return fallbackName
}
return parseName
}
// Task is a folder
if t.Meta.Res.Name != "" {
return t.Meta.Res.Name
}
// Get the name of the first file
return t.Meta.Res.Files[0].Name
}
func (t *Task) MarshalJSON() ([]byte, error) {
type rawTaskType Task
jsonTask := struct {
rawTaskType
Name string `json:"name"`
}{
rawTaskType(*t),
t.Name(),
}
return json.Marshal(jsonTask)
}
func (t *Task) updateStatus(status base.Status) {
t.UpdatedAt = time.Now()
t.Status = status
}
func (t *Task) clone() *Task {
return &Task{
ID: t.ID,
Protocol: t.Protocol,
Meta: t.Meta,
Status: t.Status,
Uploading: t.Uploading,
Progress: t.Progress,
CreatedAt: t.CreatedAt,
UpdatedAt: t.UpdatedAt,
}
return util.DeepClone(t)
}
func (t *Task) calcSpeed(speedArr []int64, downloaded int64, usedTime float64) int64 {
@@ -79,13 +112,24 @@ func (t *Task) calcSpeed(speedArr []int64, downloaded int64, usedTime float64) i
return int64(float64(total) / float64(len(speedArr)) / usedTime)
}
type TaskFilter struct {
IDs []string
Statuses []base.Status
NotStatuses []base.Status
}
func (f *TaskFilter) IsEmpty() bool {
return len(f.IDs) == 0 && len(f.Statuses) == 0 && len(f.NotStatuses) == 0
}
type DownloaderConfig struct {
Controller *controller.Controller
FetchManagers []fetcher.FetcherManager
RefreshInterval int `json:"refreshInterval"` // RefreshInterval time duration to refresh task progress(ms)
Storage Storage
StorageDir string
RefreshInterval int `json:"refreshInterval"` // RefreshInterval time duration to refresh task progress(ms)
Storage Storage
StorageDir string
DownloadDirWhiteList []string `json:"downloadDirWhiteList"`
ProductionMode bool
+5
View File
@@ -0,0 +1,5 @@
gopeed.events.onDone(async function (ctx) {
gopeed.logger.info("url", ctx.task.meta.req.url);
ctx.task.meta.req.labels['modified'] = 'true';
});
+16
View File
@@ -0,0 +1,16 @@
{
"name": "on-done",
"title": "gopeed extension on done event test",
"version": "0.0.1",
"scripts": [
{
"event": "onDone",
"match": {
"urls": [
"*://github.com/*"
]
},
"entry": "index.js"
}
]
}
+75 -29
View File
@@ -74,13 +74,27 @@ func CreateTaskBatch(w http.ResponseWriter, r *http.Request) {
}
func PauseTask(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
taskId := vars["id"]
if taskId == "" {
WriteJson(w, model.NewErrorResult("param invalid: id", model.CodeInvalidParam))
filter, errResult := parseIdFilter(r)
if errResult != nil {
WriteJson(w, errResult)
return
}
if err := Downloader.Pause(taskId); err != nil {
if err := Downloader.Pause(filter); err != nil {
WriteJson(w, model.NewErrorResult(err.Error()))
return
}
WriteJson(w, model.NewNilResult())
}
func PauseTasks(w http.ResponseWriter, r *http.Request) {
filter, errResult := parseFilter(r)
if errResult != nil {
WriteJson(w, errResult)
return
}
if err := Downloader.Pause(filter); err != nil {
WriteJson(w, model.NewErrorResult(err.Error()))
return
}
@@ -88,29 +102,27 @@ func PauseTask(w http.ResponseWriter, r *http.Request) {
}
func ContinueTask(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
taskId := vars["id"]
if taskId == "" {
WriteJson(w, model.NewErrorResult("param invalid: id", model.CodeInvalidParam))
filter, errResult := parseIdFilter(r)
if errResult != nil {
WriteJson(w, errResult)
return
}
if err := Downloader.Continue(taskId); err != nil {
if err := Downloader.Continue(filter); err != nil {
WriteJson(w, model.NewErrorResult(err.Error()))
return
}
WriteJson(w, model.NewNilResult())
}
func PauseAllTask(w http.ResponseWriter, r *http.Request) {
if err := Downloader.PauseAll(); err != nil {
WriteJson(w, model.NewErrorResult(err.Error()))
func ContinueTasks(w http.ResponseWriter, r *http.Request) {
filter, errResult := parseFilter(r)
if errResult != nil {
WriteJson(w, errResult)
return
}
WriteJson(w, model.NewNilResult())
}
func ContinueAllTask(w http.ResponseWriter, r *http.Request) {
if err := Downloader.ContinueAll(); err != nil {
if err := Downloader.Continue(filter); err != nil {
WriteJson(w, model.NewErrorResult(err.Error()))
return
}
@@ -118,14 +130,14 @@ func ContinueAllTask(w http.ResponseWriter, r *http.Request) {
}
func DeleteTask(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
taskId := vars["id"]
force := r.FormValue("force")
if taskId == "" {
WriteJson(w, model.NewErrorResult("param invalid: id", model.CodeInvalidParam))
filter, errResult := parseIdFilter(r)
if errResult != nil {
WriteJson(w, errResult)
return
}
if err := Downloader.Delete(taskId, force == "true"); err != nil {
force := r.FormValue("force")
if err := Downloader.Delete(filter, force == "true"); err != nil {
WriteJson(w, model.NewErrorResult(err.Error()))
return
}
@@ -133,10 +145,14 @@ func DeleteTask(w http.ResponseWriter, r *http.Request) {
}
func DeleteTasks(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
status := r.Form["status"]
filter, errResult := parseFilter(r)
if errResult != nil {
WriteJson(w, errResult)
return
}
force := r.FormValue("force")
if err := Downloader.DeleteByStatues(convertStatues(status), force == "true"); err != nil {
if err := Downloader.Delete(filter, force == "true"); err != nil {
WriteJson(w, model.NewErrorResult(err.Error()))
return
}
@@ -159,9 +175,13 @@ func GetTask(w http.ResponseWriter, r *http.Request) {
}
func GetTasks(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
status := r.Form["status"]
tasks := Downloader.GetTasksByStatues(convertStatues(status))
filter, errResult := parseFilter(r)
if errResult != nil {
WriteJson(w, errResult)
return
}
tasks := Downloader.GetTasksByFilter(filter)
WriteJson(w, model.NewOkResult(tasks))
}
@@ -324,6 +344,32 @@ func GetStats(w http.ResponseWriter, r *http.Request) {
WriteJson(w, model.NewOkResult(statsResult))
}
func parseIdFilter(r *http.Request) (*download.TaskFilter, any) {
vars := mux.Vars(r)
taskId := vars["id"]
if taskId == "" {
return nil, model.NewErrorResult("param invalid: id", model.CodeInvalidParam)
}
filter := &download.TaskFilter{
IDs: []string{taskId},
}
return filter, nil
}
func parseFilter(r *http.Request) (*download.TaskFilter, any) {
if err := r.ParseForm(); err != nil {
return nil, model.NewErrorResult(err.Error())
}
filter := &download.TaskFilter{
IDs: r.Form["id"],
Statuses: convertStatues(r.Form["status"]),
NotStatuses: convertStatues(r.Form["notStatus"]),
}
return filter, nil
}
func convertStatues(statues []string) []base.Status {
result := make([]base.Status, 0)
for _, status := range statues {
+8 -7
View File
@@ -14,13 +14,14 @@ const (
)
type StartConfig struct {
Network string `json:"network"`
Address string `json:"address"`
RefreshInterval int `json:"refreshInterval"`
Storage Storage `json:"storage"`
StorageDir string `json:"storageDir"`
ApiToken string `json:"apiToken"`
DownloadConfig *base.DownloaderStoreConfig `json:"downloadConfig"`
Network string `json:"network"`
Address string `json:"address"`
RefreshInterval int `json:"refreshInterval"`
Storage Storage `json:"storage"`
StorageDir string `json:"storageDir"`
WhiteDownloadDirs []string `json:"whiteDownloadDirs"`
ApiToken string `json:"apiToken"`
DownloadConfig *base.DownloaderStoreConfig `json:"downloadConfig"`
ProductionMode bool
+2 -2
View File
@@ -104,9 +104,9 @@ func BuildServer(startCfg *model.StartConfig) (*http.Server, net.Listener, error
r.Methods(http.MethodPost).Path("/api/v1/tasks").HandlerFunc(CreateTask)
r.Methods(http.MethodPost).Path("/api/v1/tasks/batch").HandlerFunc(CreateTaskBatch)
r.Methods(http.MethodPut).Path("/api/v1/tasks/{id}/pause").HandlerFunc(PauseTask)
r.Methods(http.MethodPut).Path("/api/v1/tasks/pause").HandlerFunc(PauseTasks)
r.Methods(http.MethodPut).Path("/api/v1/tasks/{id}/continue").HandlerFunc(ContinueTask)
r.Methods(http.MethodPut).Path("/api/v1/tasks/pause").HandlerFunc(PauseAllTask)
r.Methods(http.MethodPut).Path("/api/v1/tasks/continue").HandlerFunc(ContinueAllTask)
r.Methods(http.MethodPut).Path("/api/v1/tasks/continue").HandlerFunc(ContinueTasks)
r.Methods(http.MethodDelete).Path("/api/v1/tasks/{id}").HandlerFunc(DeleteTask)
r.Methods(http.MethodDelete).Path("/api/v1/tasks").HandlerFunc(DeleteTasks)
r.Methods(http.MethodGet).Path("/api/v1/tasks/{id}").HandlerFunc(GetTask)
+2 -2
View File
@@ -582,8 +582,8 @@ func doTest(handler func()) {
Downloader.Clear()
}()
defer func() {
Downloader.PauseAll()
Downloader.DeleteByStatues(nil, true)
Downloader.Pause(nil)
Downloader.Delete(nil, true)
os.RemoveAll(cfg.StorageDir)
}()
taskReq.URL = "http://" + fileListener.Addr().String() + "/" + test.BuildName
+14
View File
@@ -12,3 +12,17 @@ func MapToStruct(s any, v any) error {
}
return json.Unmarshal(b, v)
}
func DeepClone[T any](v *T) *T {
if v == nil {
return nil
}
var t T
b, err := json.Marshal(v)
if err != nil {
return &t
}
json.Unmarshal(b, &t)
return &t
}
+60
View File
@@ -0,0 +1,60 @@
package util
import (
"reflect"
"testing"
)
func TestDeepClone(t *testing.T) {
type user struct {
Name string `json:"name"`
Age int `json:"age"`
v int
}
type args[T any] struct {
v *T
}
type testCase[T any] struct {
name string
args args[T]
want *T
}
tests := []testCase[user]{
{
name: "case 1",
args: args[user]{
v: &user{
Name: "test",
Age: 10,
},
},
want: &user{
Name: "test",
Age: 10,
},
},
{
name: "case 2",
args: args[user]{
v: &user{
Name: "test",
Age: 10,
v: 1,
},
},
want: &user{
Name: "test",
Age: 10,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := DeepClone(tt.args.v); !reflect.DeepEqual(got, tt.want) {
t.Errorf("DeepClone() = %v, want %v", got, tt.want)
}
})
}
}
+1 -1
View File
@@ -47,7 +47,7 @@ android {
applicationId "com.gopeed"
// You can update the following values to match your application needs.
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration.
minSdkVersion 32
minSdkVersion 21
targetSdkVersion 29
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
@@ -2,9 +2,15 @@
package="com.gopeed">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<application
android:requestLegacyExternalStorage="true"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"
android:label="Gopeed"
Binary file not shown.

Before

Width:  |  Height:  |  Size: 181 KiB

After

Width:  |  Height:  |  Size: 63 KiB

@@ -79,6 +79,7 @@ class ExtraConfig {
String themeMode;
String locale;
bool lastDeleteTaskKeep;
bool defaultDirectDownload;
ExtraConfigBt bt = ExtraConfigBt();
@@ -86,6 +87,7 @@ class ExtraConfig {
this.themeMode = '',
this.locale = '',
this.lastDeleteTaskKeep = false,
this.defaultDirectDownload = false,
});
factory ExtraConfig.fromJson(Map<String, dynamic>? json) =>
@@ -9,7 +9,7 @@ part of 'downloader_config.dart';
DownloaderConfig _$DownloaderConfigFromJson(Map<String, dynamic> json) =>
DownloaderConfig(
downloadDir: json['downloadDir'] as String? ?? '',
maxRunning: json['maxRunning'] as int? ?? 0,
maxRunning: (json['maxRunning'] as num?)?.toInt() ?? 0,
)
..protocolConfig = ProtocolConfig.fromJson(
json['protocolConfig'] as Map<String, dynamic>?)
@@ -38,7 +38,7 @@ Map<String, dynamic> _$ProtocolConfigToJson(ProtocolConfig instance) =>
HttpConfig _$HttpConfigFromJson(Map<String, dynamic> json) => HttpConfig(
userAgent: json['userAgent'] as String? ?? '',
connections: json['connections'] as int? ?? 0,
connections: (json['connections'] as num?)?.toInt() ?? 0,
useServerCtime: json['useServerCtime'] as bool? ?? false,
);
@@ -50,14 +50,14 @@ Map<String, dynamic> _$HttpConfigToJson(HttpConfig instance) =>
};
BtConfig _$BtConfigFromJson(Map<String, dynamic> json) => BtConfig(
listenPort: json['listenPort'] as int? ?? 0,
listenPort: (json['listenPort'] as num?)?.toInt() ?? 0,
trackers: (json['trackers'] as List<dynamic>?)
?.map((e) => e as String)
.toList() ??
const [],
seedKeep: json['seedKeep'] as bool? ?? false,
seedRatio: (json['seedRatio'] as num?)?.toDouble() ?? 0,
seedTime: json['seedTime'] as int? ?? 0,
seedTime: (json['seedTime'] as num?)?.toInt() ?? 0,
);
Map<String, dynamic> _$BtConfigToJson(BtConfig instance) => <String, dynamic>{
@@ -72,6 +72,7 @@ ExtraConfig _$ExtraConfigFromJson(Map<String, dynamic> json) => ExtraConfig(
themeMode: json['themeMode'] as String? ?? '',
locale: json['locale'] as String? ?? '',
lastDeleteTaskKeep: json['lastDeleteTaskKeep'] as bool? ?? false,
defaultDirectDownload: json['defaultDirectDownload'] as bool? ?? false,
)..bt = ExtraConfigBt.fromJson(json['bt'] as Map<String, dynamic>);
Map<String, dynamic> _$ExtraConfigToJson(ExtraConfig instance) =>
@@ -79,6 +80,7 @@ Map<String, dynamic> _$ExtraConfigToJson(ExtraConfig instance) =>
'themeMode': instance.themeMode,
'locale': instance.locale,
'lastDeleteTaskKeep': instance.lastDeleteTaskKeep,
'defaultDirectDownload': instance.defaultDirectDownload,
'bt': instance.bt.toJson(),
};
+23
View File
@@ -7,11 +7,13 @@ class Request {
String url;
Object? extra;
Map<String, String>? labels = {};
RequestProxy? proxy;
Request({
required this.url,
this.extra,
this.labels,
this.proxy,
});
factory Request.fromJson(Map<String, dynamic> json) =>
@@ -45,3 +47,24 @@ class ReqExtraBt {
Map<String, dynamic> toJson() => _$ReqExtraBtToJson(this);
}
enum RequestProxyMode {
follow,
none,
custom,
}
@JsonSerializable()
class RequestProxy {
RequestProxyMode mode = RequestProxyMode.follow;
String scheme = 'http';
String host = '';
String usr = '';
String pwd = '';
RequestProxy();
factory RequestProxy.fromJson(Map<String, dynamic> json) =>
_$RequestProxyFromJson(json);
Map<String, dynamic> toJson() => _$RequestProxyToJson(this);
}
+26
View File
@@ -12,6 +12,9 @@ Request _$RequestFromJson(Map<String, dynamic> json) => Request(
labels: (json['labels'] as Map<String, dynamic>?)?.map(
(k, e) => MapEntry(k, e as String),
),
proxy: json['proxy'] == null
? null
: RequestProxy.fromJson(json['proxy'] as Map<String, dynamic>),
);
Map<String, dynamic> _$RequestToJson(Request instance) {
@@ -27,6 +30,7 @@ Map<String, dynamic> _$RequestToJson(Request instance) {
writeNotNull('extra', instance.extra);
writeNotNull('labels', instance.labels);
writeNotNull('proxy', instance.proxy?.toJson());
return val;
}
@@ -50,3 +54,25 @@ Map<String, dynamic> _$ReqExtraBtToJson(ReqExtraBt instance) =>
<String, dynamic>{
'trackers': instance.trackers,
};
RequestProxy _$RequestProxyFromJson(Map<String, dynamic> json) => RequestProxy()
..mode = $enumDecode(_$RequestProxyModeEnumMap, json['mode'])
..scheme = json['scheme'] as String
..host = json['host'] as String
..usr = json['usr'] as String
..pwd = json['pwd'] as String;
Map<String, dynamic> _$RequestProxyToJson(RequestProxy instance) =>
<String, dynamic>{
'mode': _$RequestProxyModeEnumMap[instance.mode]!,
'scheme': instance.scheme,
'host': instance.host,
'usr': instance.usr,
'pwd': instance.pwd,
};
const _$RequestProxyModeEnumMap = {
RequestProxyMode.follow: 'follow',
RequestProxyMode.none: 'none',
RequestProxyMode.custom: 'custom',
};
+2 -2
View File
@@ -8,7 +8,7 @@ part of 'resource.dart';
Resource _$ResourceFromJson(Map<String, dynamic> json) => Resource(
name: json['name'] as String? ?? "",
size: json['size'] as int? ?? 0,
size: (json['size'] as num?)?.toInt() ?? 0,
range: json['range'] as bool? ?? false,
files: (json['files'] as List<dynamic>)
.map((e) => FileInfo.fromJson(e as Map<String, dynamic>))
@@ -27,7 +27,7 @@ Map<String, dynamic> _$ResourceToJson(Resource instance) => <String, dynamic>{
FileInfo _$FileInfoFromJson(Map<String, dynamic> json) => FileInfo(
path: json['path'] as String? ?? "",
name: json['name'] as String,
size: json['size'] as int? ?? 0,
size: (json['size'] as num?)?.toInt() ?? 0,
req: json['req'] == null
? null
: Request.fromJson(json['req'] as Map<String, dynamic>),
+2
View File
@@ -11,6 +11,7 @@ enum Protocol { http, bt }
@JsonSerializable(explicitToJson: true)
class Task {
String id;
String name;
Protocol? protocol;
Meta meta;
Status status;
@@ -21,6 +22,7 @@ class Task {
Task({
required this.id,
required this.name,
required this.meta,
required this.status,
required this.uploading,
+7 -5
View File
@@ -8,6 +8,7 @@ part of 'task.dart';
Task _$TaskFromJson(Map<String, dynamic> json) => Task(
id: json['id'] as String,
name: json['name'] as String,
meta: Meta.fromJson(json['meta'] as Map<String, dynamic>),
status: $enumDecode(_$StatusEnumMap, json['status']),
uploading: json['uploading'] as bool,
@@ -19,6 +20,7 @@ Task _$TaskFromJson(Map<String, dynamic> json) => Task(
Map<String, dynamic> _$TaskToJson(Task instance) {
final val = <String, dynamic>{
'id': instance.id,
'name': instance.name,
};
void writeNotNull(String key, dynamic value) {
@@ -52,11 +54,11 @@ const _$ProtocolEnumMap = {
};
Progress _$ProgressFromJson(Map<String, dynamic> json) => Progress(
used: json['used'] as int,
speed: json['speed'] as int,
downloaded: json['downloaded'] as int,
uploadSpeed: json['uploadSpeed'] as int,
uploaded: json['uploaded'] as int,
used: (json['used'] as num).toInt(),
speed: (json['speed'] as num).toInt(),
downloaded: (json['downloaded'] as num).toInt(),
uploadSpeed: (json['uploadSpeed'] as num).toInt(),
uploaded: (json['uploaded'] as num).toInt(),
);
Map<String, dynamic> _$ProgressToJson(Progress instance) => <String, dynamic>{
@@ -173,7 +173,7 @@ class AppController extends GetxController with WindowListener, TrayListener {
});
// Check initial link if app was in cold state (terminated)
final uri = await _appLinks.getInitialAppLink();
final uri = await _appLinks.getInitialLink();
if (uri != null) {
await _toCreate(uri);
}
@@ -3,6 +3,9 @@ import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:gopeed/api/model/request.dart';
import '../../app/controllers/app_controller.dart';
class CreateController extends GetxController
with GetSingleTickerProviderStateMixin {
@@ -13,6 +16,7 @@ class CreateController extends GetxController
final isConfirming = false.obs;
final showAdvanced = false.obs;
final directDownload = false.obs;
final proxyConfig = Rx<RequestProxy?>(null);
late TabController advancedTabController;
final oldUrl = "".obs;
final fileDataUri = "".obs;
@@ -21,6 +25,11 @@ class CreateController extends GetxController
void onInit() {
super.onInit();
advancedTabController = TabController(length: 2, vsync: this);
directDownload.value = Get.find<AppController>()
.downloaderConfig
.value
.extra
.defaultDirectDownload;
}
@override
@@ -32,6 +32,10 @@ class CreateView extends GetView<CreateController> {
final _connectionsController = TextEditingController();
final _pathController = TextEditingController();
final _confirmController = RoundedLoadingButtonController();
final _proxyIpController = TextEditingController();
final _proxyPortController = TextEditingController();
final _proxyUsrController = TextEditingController();
final _proxyPwdController = TextEditingController();
final _httpUaController = TextEditingController();
final _httpCookieController = TextEditingController();
final _httpRefererController = TextEditingController();
@@ -206,7 +210,7 @@ class CreateView extends GetView<CreateController> {
decoration: BoxDecoration(
color: Theme.of(context)
.colorScheme
.background,
.surface,
borderRadius:
BorderRadius.circular(
10.0),
@@ -261,7 +265,164 @@ class CreateView extends GetView<CreateController> {
() => Visibility(
visible: controller.showAdvanced.value,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Transform.translate(
offset: const Offset(-40, 0),
child: Row(
children: [
const Icon(
Icons.wifi_2_bar,
color: Colors.grey,
),
const SizedBox(
width: 15,
),
SizedBox(
width: 150,
child: DropdownButton<
RequestProxyMode>(
hint: Text('proxy'.tr),
isExpanded: true,
value: controller
.proxyConfig.value?.mode,
onChanged: (value) async {
if (value != null) {
controller.proxyConfig
.value = RequestProxy()
..mode = value;
}
},
items: [
DropdownMenuItem<
RequestProxyMode>(
value:
RequestProxyMode.follow,
child: Text(
'followSettings'.tr),
),
DropdownMenuItem<
RequestProxyMode>(
value:
RequestProxyMode.none,
child: Text('noProxy'.tr),
),
DropdownMenuItem<
RequestProxyMode>(
value:
RequestProxyMode.custom,
child:
Text('customProxy'.tr),
),
],
))
],
),
),
...(controller.proxyConfig.value?.mode ==
RequestProxyMode.custom
? [
SizedBox(
width: 150,
child: DropdownButtonFormField<
String>(
value: controller
.proxyConfig.value?.scheme,
onChanged: (value) async {
if (value != null) {}
},
items: const [
DropdownMenuItem<String>(
value: 'http',
child: Text('HTTP'),
),
DropdownMenuItem<String>(
value: 'https',
child: Text('HTTPS'),
),
DropdownMenuItem<String>(
value: 'socks5',
child: Text('SOCKS5'),
),
],
),
),
Row(children: [
Flexible(
child: TextFormField(
controller:
_proxyIpController,
decoration: InputDecoration(
labelText: 'server'.tr,
contentPadding:
const EdgeInsets.all(
0.0),
),
),
),
const Padding(
padding: EdgeInsets.only(
left: 10)),
Flexible(
child: TextFormField(
controller:
_proxyPortController,
decoration: InputDecoration(
labelText: 'port'.tr,
contentPadding:
const EdgeInsets.all(
0.0),
),
keyboardType:
TextInputType.number,
inputFormatters: [
FilteringTextInputFormatter
.digitsOnly,
NumericalRangeFormatter(
min: 0, max: 65535),
],
),
),
]),
Row(children: [
Flexible(
child: TextFormField(
controller:
_proxyUsrController,
decoration: InputDecoration(
labelText: 'username'.tr,
contentPadding:
const EdgeInsets.all(
0.0),
),
),
),
const Padding(
padding: EdgeInsets.only(
left: 10)),
Flexible(
child: TextFormField(
controller:
_proxyPwdController,
decoration: InputDecoration(
labelText: 'password'.tr,
contentPadding:
const EdgeInsets.all(
0.0),
),
),
),
])
]
: const []),
],
),
const Divider(),
TabBar(
controller: controller.advancedTabController,
tabs: const [
@@ -416,18 +577,21 @@ class CreateView extends GetView<CreateController> {
if (isDirect) {
await Future.wait(urls.map((url) {
return createTask(CreateTask(
req: Request(url: url, extra: parseReqExtra(url)),
req: Request(
url: url, extra: parseReqExtra(url), proxy: parseProxy()),
opt: Options(
name: isMultiLine ? "" : _renameController.text,
path: _pathController.text,
selectFiles: [],
extra: parseReqOptsExtra())));
name: isMultiLine ? "" : _renameController.text,
path: _pathController.text,
selectFiles: [],
extra: parseReqOptsExtra(),
)));
}));
Get.rootDelegate.offNamed(Routes.TASK);
} else {
final rr = await resolve(Request(
url: submitUrl,
extra: parseReqExtra(_urlController.text),
proxy: parseProxy(),
));
await _showResolveDialog(rr);
}
@@ -440,6 +604,18 @@ class CreateView extends GetView<CreateController> {
}
}
RequestProxy? parseProxy() {
if (controller.proxyConfig.value?.mode == RequestProxyMode.custom) {
return RequestProxy()
..mode = RequestProxyMode.custom
..scheme = _proxyIpController.text
..host = "${_proxyIpController.text}:${_proxyPortController.text}"
..usr = _proxyUsrController.text
..pwd = _proxyPwdController.text;
}
return controller.proxyConfig.value;
}
Object? parseReqExtra(String url) {
Object? reqExtra;
if (controller.showAdvanced.value) {
@@ -543,7 +719,7 @@ class CreateView extends GetView<CreateController> {
controller.selectedIndexes.map((index) {
final file = rr.res.files[index];
return createTask(CreateTask(
req: file.req!,
req: file.req!..proxy = parseProxy(),
opt: Options(
name: file.name,
path: path.join(_pathController.text,
@@ -3,7 +3,6 @@ import 'dart:io';
import 'package:badges/badges.dart' as badges;
import 'package:flutter/material.dart';
import 'package:path/path.dart' as path;
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:gopeed/app/views/copy_button.dart';
@@ -110,6 +109,28 @@ class SettingView extends GetView<SettingController> {
],
);
});
final buildDefaultDirectDownload =
_buildConfigItem('defaultDirectDownload'.tr, () {
return appController.downloaderConfig.value.extra.defaultDirectDownload
? 'on'.tr
: 'off'.tr;
}, (Key key) {
return Container(
alignment: Alignment.centerLeft,
child: Switch(
value:
appController.downloaderConfig.value.extra.defaultDirectDownload,
onChanged: (bool value) async {
appController.downloaderConfig.update((val) {
val!.extra.defaultDirectDownload = value;
});
await debounceSave();
},
),
);
});
buildBrowserExtension() {
return ListTile(
title: Text('browserExtension'.tr),
@@ -847,7 +868,7 @@ class SettingView extends GetView<SettingController> {
// advanced config log items start
buildLogsDir() {
return ListTile(
title: Text("日志目录"),
title: Text("logDirectory".tr),
subtitle: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@@ -904,6 +925,7 @@ class SettingView extends GetView<SettingController> {
children: _addDivider([
buildDownloadDir(),
buildMaxRunning(),
buildDefaultDirectDownload(),
buildBrowserExtension(),
buildAutoStartup(),
]),
@@ -981,7 +1003,7 @@ class SettingView extends GetView<SettingController> {
: null,
]),
)),
const Text('开发者'),
Text('developer'.tr),
Card(
child: Column(
children: _addDivider([
@@ -82,8 +82,7 @@ class TaskView extends GetView<TaskController> {
),
ListTile(
title: Text('taskName'.tr),
subtitle:
buildTooltipSubtitle(selectTask.value?.showName)),
subtitle: buildTooltipSubtitle(selectTask.value?.name)),
ListTile(
title: Text('taskUrl'.tr),
subtitle:
@@ -121,42 +120,12 @@ class TaskView extends GetView<TaskController> {
}
extension TaskEnhance on Task {
String get showName {
if (meta.opts.name.isNotEmpty) {
return meta.opts.name;
}
if (meta.res == null) {
final u = Uri.parse(meta.req.url);
if (u.scheme.startsWith("http")) {
return u.path.isNotEmpty
? u.path.substring(u.path.lastIndexOf("/") + 1)
: u.host;
} else {
final params = u.queryParameters;
if (params.containsKey("dn")) {
return params["dn"]!;
} else {
return params["xt"]!.split(":").last;
}
}
}
if (meta.res!.name.isNotEmpty) {
return meta.res!.name;
}
return meta.res!.files[0].name;
}
bool get isFolder {
return meta.res?.name.isNotEmpty ?? false;
}
String get explorerUrl {
if (isFolder) {
return path.join(Util.safeDir(meta.opts.path),
Util.safeDir(meta.res!.files[0].path), showName);
} else {
return path.join(Util.safeDir(meta.opts.path), Util.safeDir(showName));
}
return path.join(Util.safeDir(meta.opts.path), Util.safeDir(name));
}
Future<void> explorer() async {
@@ -183,10 +183,10 @@ class BuildTaskListView extends GetView {
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
title: Text(task.showName),
title: Text(task.name),
leading: isFolderTask()
? const Icon(FaIcons.folder)
: Icon(FaIcons.allIcons[findIcon(task.showName)])),
: Icon(FaIcons.allIcons[findIcon(task.name)])),
Row(
children: [
Expanded(
@@ -1,16 +1,20 @@
import 'dart:io';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:lecle_downloads_path_provider/lecle_downloads_path_provider.dart';
import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:toggle_switch/toggle_switch.dart';
import '../../util/mac_secure_util.dart';
import '../../util/message.dart';
import '../../util/util.dart';
final deviceInfo = DeviceInfoPlugin();
class DirectorySelector extends StatefulWidget {
final TextEditingController controller;
final bool showLabel;
@@ -68,12 +72,26 @@ class _DirectorySelectorState extends State<DirectorySelector> {
if (index == 0) {
return false;
}
// Check write permission
final downloadDir =
(await DownloadsPath.downloadsDirectory())?.path;
if (downloadDir == null) {
return true;
}
// Check and request external storage permission when sdk version < 30 (android 11)
if ((await deviceInfo.androidInfo).version.sdkInt < 30) {
var status = await Permission.storage.status;
if (!status.isGranted) {
status = await Permission.storage.request();
if (!status.isGranted) {
showErrorMessage('noStoragePermission'.tr);
return true;
}
}
}
// Check write permission
final fileRandomeName =
"test_${DateTime.now().millisecondsSinceEpoch}.tmp";
final testFile = File('$downloadDir/Gopeed/$fileRandomeName');
+5 -1
View File
@@ -17,6 +17,7 @@ const enUS = {
'create': 'Create Task',
'directDownload': 'Direct Download',
'advancedOptions': 'Advanced Options',
'followSettings': 'Follow Settings',
'downloadLink': 'Download Link',
'downloadLinkValid': 'Please enter the download link',
'downloadLinkHit':
@@ -35,6 +36,7 @@ const enUS = {
'connections': 'Connections',
'useServerCtime': 'Use server time for file creation',
'maxRunning': 'Max Running Tasks',
'defaultDirectDownload': 'Check direct download by default',
'items': '@count items',
'subscribeTracker': 'Subscribe Tracker',
'subscribeFail':
@@ -60,6 +62,8 @@ const enUS = {
'set': 'SET',
'portInUse': 'Port [@port] is in use, please change the port',
'effectAfterRestart': 'Effect after restart',
'developer': 'Developer',
'logDirectory': 'Log Directory',
'show': 'Show',
'startAll': 'Start All',
'pauseAll': 'Pause All',
@@ -96,7 +100,7 @@ const enUS = {
'browserExtension': 'Browser Extension',
'launchAtStartup': 'Launch at Startup',
'seedConfig': 'Seed Config',
'seedKeep': 'Keep seeding',
'seedKeep': 'Keep seeding until manually stopped',
'seedRatio': 'Seed ratio',
'seedTime': 'Seed time (minutes)',
'taskDetail': 'Task Detail',
+107
View File
@@ -0,0 +1,107 @@
const esES = {
'es_ES': {
'label': 'Español',
'error': 'Error',
'tip': 'Consejo',
'confirm': 'Confirmar',
'cancel': 'Cancelar',
'on': 'Activado',
'off': 'Desactivado',
'selectAll': 'Seleccionar Todo',
'task': 'Tareas',
'downloading': 'descargando',
'downloaded': 'descargado',
'setting': 'Ajustes',
'donate': 'Donar',
'exit': 'Salir',
'create': 'Crear Tarea',
'directDownload': 'Descarga Directa',
'advancedOptions': 'Opciones Avanzadas',
'downloadLink': 'Enlace de Descarga',
'downloadLinkValid': 'Por favor, introduce el enlace de descarga',
'downloadLinkHit':
'Por favor, introduce el enlace de descarga, se admite HTTP/HTTPS/MAGNET@append',
'downloadLinkHitDesktop': ', o arrastra el archivo torrent aquí directamente',
'download': 'Descargar',
'noFileSelected': 'Por favor, selecciona al menos un archivo para continuar.',
'noStoragePermission': 'Se requiere permiso de almacenamiento',
'selectFile': 'Seleccionar Archivo',
'rename': 'Renombrar',
'basic': 'Básico',
'advanced': 'Avanzado',
'general': 'General',
'downloadDir': 'Directorio de Descarga',
'downloadDirValid': 'Por favor, selecciona el directorio de descarga',
'connections': 'Conexiones',
'useServerCtime': 'Usar tiempo del servidor para la creación de archivos',
'maxRunning': 'Máximo de Tareas en Ejecución',
'items': '@count elementos',
'subscribeTracker': 'Suscribirse al Tracker',
'subscribeFail':
'Suscripción fallida, por favor verifica la red o intenta más tarde',
'update': 'Actualizar',
'updateDaily': 'Actualizar diariamente',
'lastUpdate': 'Última actualización: @time',
'addTracker': 'Añadir Tracker',
'addTrackerHit': 'Por favor, introduce la URL del servidor tracker, una por línea',
'ui': 'Interfaz',
'theme': 'Tema',
'themeSystem': 'Sistema',
'themeLight': 'Claro',
'themeDark': 'Oscuro',
'locale': 'Idioma',
'about': 'Acerca de',
'homepage': 'Página de inicio',
'version': 'Versión',
'protocol': 'Protocolo',
'port': 'Puerto',
'apiToken': 'Token API',
'notSet': 'No establecido',
'set': 'Establecido',
'portInUse': 'El puerto [@port] está en uso, por favor cambia el puerto',
'effectAfterRestart': 'Efectivo después de reiniciar',
'show': 'Mostrar',
'startAll': 'Iniciar Todo',
'pauseAll': 'Pausar Todo',
'deleteTask': 'Eliminar Tarea',
'deleteTaskTip': 'Mantener archivos descargados',
'delete': 'Eliminar',
'newVersionTitle': 'Nueva versión @version disponible',
'newVersionUpdate': 'Actualizar Ahora',
'newVersionLater': 'Más tarde',
'extensions': 'Extensiones',
'extensionInstallUrl': 'URL de instalación',
'extensionInstallSuccess': 'Instalado con éxito',
'extensionUpdateSuccess': 'Actualizado con éxito',
'extensionDelete': 'Eliminar Extensión',
'extensionAlreadyLatest': 'Ya es la versión más reciente',
'extensionFind': 'Buscar Extensiones',
'extensionDevelop': 'Desarrollar Extensiones',
'history': 'Historial',
'clearHistory': 'Borrar Historial',
'noHistoryFound': 'No se encontró historial',
'serviceTitle': 'Servicio de Descarga',
'serviceText': 'En ejecución',
'network': 'Red',
'proxy': 'Proxy',
'noProxy': 'Sin Proxy',
'systemProxy': 'Proxy del Sistema',
'customProxy': 'Proxy Personalizado',
'server': 'Servidor',
'username': 'Nombre de usuario',
'password': 'Contraseña',
'thanks': 'Agradecimientos',
'thanksDesc':
'¡Gracias a todos los contribuyentes que han ayudado a construir y desarrollar la comunidad Gopeed!',
'browserExtension': 'Extensión del Navegador',
'launchAtStartup': 'Iniciar al Arranque',
'seedConfig': 'Configuración de Semilla',
'seedKeep': 'Mantener sembrado',
'seedRatio': 'Ratio de semilla',
'seedTime': 'Tiempo de semilla (minutos)',
'taskDetail': 'Detalles de la Tarea',
'taskName': 'Nombre de la Tarea',
'taskUrl': 'URL de la Tarea',
'downloadPath': 'Ruta de Descarga',
},
};
+12
View File
@@ -17,6 +17,7 @@ const idID = {
'create': 'Membuat Tugas',
'directDownload': 'Unduhan Langsung',
'advancedOptions': 'Pengaturan Lanjutan',
'followSettings': 'Mengikuti Pengaturan',
'downloadLink': 'Tautan Unduhan',
'downloadLinkValid': 'Silakan masukkan tautan unduhan',
'downloadLinkHit':
@@ -35,6 +36,7 @@ const idID = {
'connections': 'Koneksi',
'useServerCtime': 'Gunakan waktu server untuk pembuatan file',
'maxRunning': 'Tugas Maksimum yang Berjalan',
'defaultDirectDownload': 'Pilih unduhan langsung sebagai opsi standar',
'items': '@count items',
'subscribeTracker': 'Berlangganan Tracker',
'subscribeFail':
@@ -60,6 +62,8 @@ const idID = {
'set': 'ATUR',
'portInUse': 'Port [@port] sedang digunakan, silakan ubah port',
'effectAfterRestart': 'Berlaku setelah restart',
'developer': 'Pengembang',
'logDirectory': 'Direktori Log',
'show': 'Tampilkan',
'startAll': 'Mulai Semua',
'pauseAll': 'Jeda Semua',
@@ -95,5 +99,13 @@ const idID = {
'Terima kasih kepada semua kontributor yang telah membantu membangun dan mengembangkan komunitas Gopeed!',
'browserExtension': 'Ekstensi Perambanan',
'launchAtStartup': 'Luncurkan saat Startup',
'seedConfig': 'Konfigurasi Seed',
'seedKeep': 'Terus seeding hingga dihentikan secara manual',
'seedRatio': 'Rasio Seed',
'seedTime': 'Waktu Seed (menit)',
'taskDetail': 'Detail Tugas',
'taskName': 'Nama Tugas',
'taskUrl': 'URL Tugas',
'downloadPath': 'Direktori Unduhan',
},
};
+15 -6
View File
@@ -17,10 +17,10 @@ const trTR = {
'create': 'Görev oluştur',
'directDownload': 'Doğrudan indir',
'advancedOptions': 'Gelişmiş ayarlar',
'followSettings': 'Ayarları takip et',
'downloadLink': 'İndirme bağlantısı',
'downloadLinkValid': 'Lütfen indirme bağlantısını girin',
'downloadLinkHit':
'Lütfen indirme bağlantısını girin, HTTP/HTTPS/MAGNET desteklenir@append',
'downloadLinkHit': 'Lütfen indirme bağlantısını girin, HTTP/HTTPS/MAGNET desteklenir@append',
'downloadLinkHitDesktop': ', veya torrent dosyasını doğrudan buraya sürükleyin',
'download': 'İndir',
'noFileSelected': 'Devam etmek için lütfen en az bir dosya seçin.',
@@ -35,10 +35,10 @@ const trTR = {
'connections': 'Bağlantılar',
'useServerCtime': 'Dosya oluşturma için sunucu saatini kullan',
'maxRunning': 'Maksimum çalışan görev sayısı',
'defaultDirectDownload': 'Doğrudan indir seçili olsun',
'items': '@count tane',
'subscribeTracker': 'İzleyiciye abone ol',
'subscribeFail':
'Abone olunamadı, lütfen ağı kontrol edin veya daha sonra tekrar deneyin',
'subscribeFail': 'Abone olunamadı, lütfen ağı kontrol edin veya daha sonra tekrar deneyin',
'update': 'Güncelle',
'updateDaily': 'Günlük olarak güncelle',
'lastUpdate': 'Son güncellenme tarihi: @time',
@@ -60,6 +60,8 @@ const trTR = {
'set': 'AYARLI',
'portInUse': 'Bağlantı noktası [@port] kullanımda, lütfen bağlantı noktasını değiştirin',
'effectAfterRestart': 'Yeniden başlatma sonrası etki edecektir',
'developer': 'Geliştirici',
'logDirectory': 'Kayıt dizini',
'show': 'Göster',
'startAll': 'Hepsini başlat',
'pauseAll': 'Hepsini durdur',
@@ -91,9 +93,16 @@ const trTR = {
'username': 'Kullanıcı adı',
'password': 'Şifre',
'thanks': 'Teşekkürler',
'thanksDesc':
'Gopeed topluluğunun oluşmasına ve gelişmesine yardımcı olan tüm katılımcılara teşekkürler!',
'thanksDesc': 'Gopeed topluluğunun oluşmasına ve gelişmesine yardımcı olan tüm katılımcılara teşekkürler!',
'browserExtension': 'Tarayıcı uzantısı',
'launchAtStartup': 'Başlangıçta başlat',
'seedConfig': 'Seed ayarları',
'seedKeep': 'Manuel durdurulana kadar seed etmeye devam et',
'seedRatio': 'Seed oranı',
'seedTime': 'Seed süresi (dakika)',
'taskDetail': 'Görev detayı',
'taskName': 'Görev adı',
'taskUrl': 'Görev URL',
'downloadPath': 'İndirme yolu',
},
};
+5 -1
View File
@@ -17,6 +17,7 @@ const zhCN = {
'create': '创建任务',
'directDownload': '直接下载',
'advancedOptions': '高级选项',
'followSettings': '跟随设置',
'downloadLink': '下载链接',
'downloadLinkValid': '请输入下载链接',
'downloadLinkHit': '请输入下载链接,支持 HTTP/HTTPS/MAGNET@append',
@@ -34,6 +35,7 @@ const zhCN = {
'connections': '连接数',
'useServerCtime': '创建文件使用服务器时间',
'maxRunning': '最大下载数',
'defaultDirectDownload': '默认勾选直接下载',
'items': '@count 项',
'subscribeTracker': '订阅 Tracker',
'subscribeFail': '订阅失败,请检查网络或稍后重试',
@@ -58,6 +60,8 @@ const zhCN = {
'set': '已设置',
'portInUse': '端口[@port]已被占用,请更换端口',
'effectAfterRestart': '此配置项将在重启应用后生效',
'developer': '开发者',
'logDirectory': '日志目录',
'show': '显示',
'startAll': '全部开始',
'pauseAll': '全部暂停',
@@ -93,7 +97,7 @@ const zhCN = {
'browserExtension': '浏览器扩展',
'launchAtStartup': '开机自动运行',
'seedConfig': '做种设置',
'seedKeep': '持续做种',
'seedKeep': '持续做种直到手动停止',
'seedRatio': '做种分享率',
'seedTime': '做种时间(分钟)',
'taskDetail': '任务详情',
+5 -7
View File
@@ -17,6 +17,7 @@ const zhTW = {
'create': '建立任務',
'directDownload': '直接下載',
'advancedOptions': '進階選項',
'followSettings': '跟隨設定',
'downloadLink': '下載連結',
'downloadLinkValid': '請輸入下載連結',
'downloadLinkHit': '請輸入下載連結,支援 HTTP/HTTPS/MAGNET@append',
@@ -34,6 +35,7 @@ const zhTW = {
'connections': '連接數',
'useServerCtime': '使用伺服器時間作為檔案建立時間',
'maxRunning': '最大執行任務數',
'defaultDirectDownload': '預設勾選直接下載',
'items': '@count 個項目',
'subscribeTracker': '訂閱 Tracker',
'subscribeFail': '訂閱失敗,請檢查網路或稍後再試',
@@ -58,6 +60,8 @@ const zhTW = {
'set': '已設定',
'portInUse': '連接埠 [@port] 已被使用,請更改連接埠',
'effectAfterRestart': '重新啟動後生效',
'developer': '開發者',
'logDirectory': '日誌目錄',
'show': '顯示',
'startAll': '全部開始',
'pauseAll': '全部暫停',
@@ -93,15 +97,9 @@ const zhTW = {
'browserExtension': '瀏覽器擴充功能',
'launchAtStartup': '開機自動執行',
'seedConfig': '做種設定',
'seedKeep': '持續做種',
'seedKeep': '持續做種直到手動停止',
'seedRatio': '做種分享率',
'seedTime': '做種時間(分鐘)',
/*
'taskDetail': '任务详情',
'taskName': '任务名称',
'taskUrl': '任务链接',
'downloadPath': '下载路径',
*/
'taskDetail': '任務詳情',
'taskName': '任務名稱',
'taskUrl': '任務連結',
+2
View File
@@ -13,6 +13,7 @@ import 'langs/tr_tr.dart';
import 'langs/vi_vn.dart';
import 'langs/zh_cn.dart';
import 'langs/zh_tw.dart';
import 'langs/es_es.dart';
final messages = _Messages();
@@ -33,5 +34,6 @@ class _Messages extends Translations {
...itIT,
...idID,
...frFR,
...esES,
};
}
+22 -18
View File
@@ -21,29 +21,33 @@ class FileExplorer {
} else if (Platform.isMacOS) {
Process.run('open', ['-R', filePath]);
} else if (Platform.isLinux) {
if (await _isUbuntuOrDebian()) {
Process.run('xdg-open', [filePath]);
} else if (await _isCentOS()) {
Process.run('nautilus', ['--select', filePath]);
_linuxOpen(filePath);
}
}
static Future<void> _linuxOpen(String filePath) async {
if (await Process.run('which', ['xdg-open'])
.then((value) => value.exitCode == 0)) {
final result = await Process.run('xdg-open', [filePath]);
if (result.exitCode != 0) {
_openWithFileManager(filePath);
}
} else {
_openWithFileManager(filePath);
}
}
static Future<bool> _isUbuntuOrDebian() async {
final result = await Process.run('lsb_release', ['-i']);
if (result.exitCode != 0) {
return false;
static Future<void> _openWithFileManager(String filePath) async {
final desktop = Platform.environment['XDG_CURRENT_DESKTOP'];
if (desktop == null) {
throw Exception('XDG_CURRENT_DESKTOP is not set');
}
final output = result.stdout.toString().toLowerCase();
return output.contains('ubuntu') || output.contains('debian');
}
static Future<bool> _isCentOS() async {
final result = await Process.run('cat', ['/etc/os-release']);
if (result.exitCode != 0) {
return false;
if (desktop == 'GNOME') {
await Process.run('nautilus', ['--select', filePath]);
} else if (desktop == 'KDE') {
await Process.run('dolphin', ['--select', filePath]);
} else {
throw Exception('Unsupported desktop environment');
}
final output = result.stdout.toString().toLowerCase();
return output.contains('centos');
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
platform :osx, '10.11'
platform :osx, '10.14'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
+52 -20
View File
@@ -1,58 +1,90 @@
PODS:
- app_links (1.0.0):
- FlutterMacOS
- desktop_drop (0.0.1):
- FlutterMacOS
- file_selector_macos (0.0.1):
- device_info_plus (0.0.1):
- FlutterMacOS
- FlutterMacOS (1.0.0)
- macos_secure_bookmarks (0.0.3):
- FlutterMacOS
- package_info_plus (0.0.1):
- FlutterMacOS
- path_provider_macos (0.0.1):
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
- shared_preferences_macos (0.0.1):
- screen_retriever (0.0.1):
- FlutterMacOS
- share_plus (0.0.1):
- FlutterMacOS
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
- tray_manager (0.0.1):
- FlutterMacOS
- url_launcher_macos (0.0.1):
- FlutterMacOS
- window_manager (0.2.0):
- FlutterMacOS
DEPENDENCIES:
- app_links (from `Flutter/ephemeral/.symlinks/plugins/app_links/macos`)
- desktop_drop (from `Flutter/ephemeral/.symlinks/plugins/desktop_drop/macos`)
- file_selector_macos (from `Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos`)
- device_info_plus (from `Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos`)
- FlutterMacOS (from `Flutter/ephemeral`)
- macos_secure_bookmarks (from `Flutter/ephemeral/.symlinks/plugins/macos_secure_bookmarks/macos`)
- package_info_plus (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos`)
- path_provider_macos (from `Flutter/ephemeral/.symlinks/plugins/path_provider_macos/macos`)
- shared_preferences_macos (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_macos/macos`)
- path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
- screen_retriever (from `Flutter/ephemeral/.symlinks/plugins/screen_retriever/macos`)
- share_plus (from `Flutter/ephemeral/.symlinks/plugins/share_plus/macos`)
- shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`)
- tray_manager (from `Flutter/ephemeral/.symlinks/plugins/tray_manager/macos`)
- url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
- window_manager (from `Flutter/ephemeral/.symlinks/plugins/window_manager/macos`)
EXTERNAL SOURCES:
app_links:
:path: Flutter/ephemeral/.symlinks/plugins/app_links/macos
desktop_drop:
:path: Flutter/ephemeral/.symlinks/plugins/desktop_drop/macos
file_selector_macos:
:path: Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos
device_info_plus:
:path: Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos
FlutterMacOS:
:path: Flutter/ephemeral
macos_secure_bookmarks:
:path: Flutter/ephemeral/.symlinks/plugins/macos_secure_bookmarks/macos
package_info_plus:
:path: Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos
path_provider_macos:
:path: Flutter/ephemeral/.symlinks/plugins/path_provider_macos/macos
shared_preferences_macos:
:path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_macos/macos
path_provider_foundation:
:path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin
screen_retriever:
:path: Flutter/ephemeral/.symlinks/plugins/screen_retriever/macos
share_plus:
:path: Flutter/ephemeral/.symlinks/plugins/share_plus/macos
shared_preferences_foundation:
:path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin
tray_manager:
:path: Flutter/ephemeral/.symlinks/plugins/tray_manager/macos
url_launcher_macos:
:path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos
window_manager:
:path: Flutter/ephemeral/.symlinks/plugins/window_manager/macos
SPEC CHECKSUMS:
app_links: 10e0a0ab602ffaf34d142cd4862f29d34b303b2a
desktop_drop: 69eeff437544aa619c8db7f4481b3a65f7696898
file_selector_macos: f1b08a781e66103e3ba279fd5d4024a2478b3af6
FlutterMacOS: ae6af50a8ea7d6103d888583d46bd8328a7e9811
device_info_plus: 74e614483d05c89290d30a4c8feae15d555f7427
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
macos_secure_bookmarks: cff041c4b377fa00941bb901aaec4d27fac5edf2
package_info_plus: 02d7a575e80f194102bef286361c6c326e4c29ce
path_provider_macos: 3c0c3b4b0d4a76d2bf989a913c2de869c5641a19
shared_preferences_macos: a64dc611287ed6cbe28fd1297898db1336975727
url_launcher_macos: 597e05b8e514239626bcf4a850fcf9ef5c856ec3
package_info_plus: f5790acc797bf17c3e959e9d6cf162cc68ff7523
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
screen_retriever: 59634572a57080243dd1bf715e55b6c54f241a38
share_plus: fd717ef89a2801d3491e737630112b80c310640e
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
tray_manager: 9064e219c56d75c476e46b9a21182087930baf90
url_launcher_macos: c82c93949963e55b228a30115bd219499a6fe404
window_manager: 3a1844359a6295ab1e47659b1a777e36773cd6e8
PODFILE CHECKSUM: 6eac6b3292e5142cfc23bdeb71848a40ec51c14c
PODFILE CHECKSUM: 353c8bcc5d5b0994e508d035b5431cfe18c1dea7
COCOAPODS: 1.11.3
COCOAPODS: 1.15.2
@@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 51;
objectVersion = 54;
objects = {
/* Begin PBXAggregateTarget section */
@@ -206,7 +206,7 @@
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0920;
LastUpgradeCheck = 1300;
LastUpgradeCheck = 1510;
ORGANIZATIONNAME = "";
TargetAttributes = {
33CC10EC2044A3C60003C045 = {
@@ -259,6 +259,7 @@
/* Begin PBXShellScriptBuildPhase section */
3399D490228B24CF009A79C7 /* ShellScript */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
@@ -407,7 +408,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.11;
MACOSX_DEPLOYMENT_TARGET = 10.14;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = macosx;
SWIFT_COMPILATION_MODE = wholemodule;
@@ -490,7 +491,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.11;
MACOSX_DEPLOYMENT_TARGET = 10.14;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = macosx;
@@ -537,7 +538,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.11;
MACOSX_DEPLOYMENT_TARGET = 10.14;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = macosx;
SWIFT_COMPILATION_MODE = wholemodule;
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1300"
LastUpgradeVersion = "1510"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
+1 -1
View File
@@ -1,7 +1,7 @@
import Cocoa
import FlutterMacOS
@NSApplicationMain
@main
class AppDelegate: FlutterAppDelegate {
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
return false
Binary file not shown.

Before

Width:  |  Height:  |  Size: 191 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 709 B

After

Width:  |  Height:  |  Size: 592 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

+274 -140
View File
@@ -5,26 +5,55 @@ packages:
dependency: transitive
description:
name: _fe_analyzer_shared
sha256: eb376e9acf6938204f90eb3b1f00b578640d3188b4c8a8ec054f9f479af8d051
sha256: f256b0c0ba6c7577c15e2e4e114755640a875e885099367bf6e012b19314c834
url: "https://pub.dev"
source: hosted
version: "64.0.0"
version: "72.0.0"
_macros:
dependency: transitive
description: dart
source: sdk
version: "0.3.2"
analyzer:
dependency: transitive
description:
name: analyzer
sha256: "69f54f967773f6c26c7dcb13e93d7ccee8b17a641689da39e878d5cf13b06893"
sha256: b652861553cd3990d8ed361f7979dc6d7053a9ac8843fa73820ab68ce5410139
url: "https://pub.dev"
source: hosted
version: "6.2.0"
version: "6.7.0"
app_links:
dependency: "direct main"
description:
name: app_links
sha256: "3ced568a5d9e309e99af71285666f1f3117bddd0bd5b3317979dccc1a40cada4"
sha256: ad1a6d598e7e39b46a34f746f9a8b011ee147e4c275d407fa457e7a62f84dd99
url: "https://pub.dev"
source: hosted
version: "3.5.1"
version: "6.3.2"
app_links_linux:
dependency: transitive
description:
name: app_links_linux
sha256: f5f7173a78609f3dfd4c2ff2c95bd559ab43c80a87dc6a095921d96c05688c81
url: "https://pub.dev"
source: hosted
version: "1.0.3"
app_links_platform_interface:
dependency: transitive
description:
name: app_links_platform_interface
sha256: "05f5379577c513b534a29ddea68176a4d4802c46180ee8e2e966257158772a3f"
url: "https://pub.dev"
source: hosted
version: "2.0.2"
app_links_web:
dependency: transitive
description:
name: app_links_web
sha256: af060ed76183f9e2b87510a9480e56a5352b6c249778d07bd2c95fc35632a555
url: "https://pub.dev"
source: hosted
version: "1.0.4"
archive:
dependency: transitive
description:
@@ -37,10 +66,10 @@ packages:
dependency: "direct main"
description:
name: args
sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a"
sha256: bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6
url: "https://pub.dev"
source: hosted
version: "2.5.0"
version: "2.6.0"
async:
dependency: transitive
description:
@@ -93,10 +122,10 @@ packages:
dependency: transitive
description:
name: build_daemon
sha256: "0343061a33da9c5810b2d6cee51945127d8f4c060b7fbdd9d54917f0a3feaaa1"
sha256: "79b2aef6ac2ed00046867ed354c88778c9c0f029df8a20fe10b5436826721ef9"
url: "https://pub.dev"
source: hosted
version: "4.0.1"
version: "4.0.2"
build_resolvers:
dependency: transitive
description:
@@ -109,18 +138,18 @@ packages:
dependency: "direct dev"
description:
name: build_runner
sha256: "3ac61a79bfb6f6cc11f693591063a7f19a7af628dc52f141743edac5c16e8c22"
sha256: "028819cfb90051c6b5440c7e574d1896f8037e3c96cf17aaeb054c9311cfbf4d"
url: "https://pub.dev"
source: hosted
version: "2.4.9"
version: "2.4.13"
build_runner_core:
dependency: transitive
description:
name: build_runner_core
sha256: "4ae8ffe5ac758da294ecf1802f2aff01558d8b1b00616aa7538ea9a8a5d50799"
sha256: f8126682b87a7282a339b871298cc12009cb67109cfa1614d6436fb0289193e0
url: "https://pub.dev"
source: hosted
version: "7.3.0"
version: "7.3.2"
built_collection:
dependency: transitive
description:
@@ -197,26 +226,26 @@ packages:
dependency: transitive
description:
name: convert
sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592"
sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68
url: "https://pub.dev"
source: hosted
version: "3.1.1"
version: "3.1.2"
cross_file:
dependency: transitive
description:
name: cross_file
sha256: fedaadfa3a6996f75211d835aaeb8fede285dae94262485698afd832371b9a5e
sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670"
url: "https://pub.dev"
source: hosted
version: "0.3.3+8"
version: "0.3.4+2"
crypto:
dependency: transitive
description:
name: crypto
sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855"
url: "https://pub.dev"
source: hosted
version: "3.0.3"
version: "3.0.6"
cupertino_icons:
dependency: "direct main"
description:
@@ -229,10 +258,10 @@ packages:
dependency: transitive
description:
name: dart_style
sha256: "99e066ce75c89d6b29903d788a7bb9369cf754f7b24bf70bf4b6d6d6b26853b9"
sha256: "7856d364b589d1f08986e140938578ed36ed948581fbc3bc9aef1805039ac5ab"
url: "https://pub.dev"
source: hosted
version: "2.3.6"
version: "2.3.7"
desktop_drop:
dependency: "direct main"
description:
@@ -241,22 +270,38 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.4.4"
device_info_plus:
dependency: "direct main"
description:
name: device_info_plus
sha256: c4af09051b4f0508f6c1dc0a5c085bf014d5c9a4a0678ce1799c2b4d716387a0
url: "https://pub.dev"
source: hosted
version: "11.1.0"
device_info_plus_platform_interface:
dependency: transitive
description:
name: device_info_plus_platform_interface
sha256: "282d3cf731045a2feb66abfe61bbc40870ae50a3ed10a4d3d217556c35c8c2ba"
url: "https://pub.dev"
source: hosted
version: "7.0.1"
dio:
dependency: "direct main"
description:
name: dio
sha256: e17f6b3097b8c51b72c74c9f071a605c47bcc8893839bd66732457a5ebe73714
sha256: "5598aa796bbf4699afd5c67c0f5f6e2ed542afc956884b9cd58c306966efc260"
url: "https://pub.dev"
source: hosted
version: "5.5.0+1"
version: "5.7.0"
dio_web_adapter:
dependency: transitive
description:
name: dio_web_adapter
sha256: "36c5b2d79eb17cdae41e974b7a8284fec631651d2a6f39a8a2ff22327e90aeac"
sha256: "33259a9276d6cea88774a0000cfae0d861003497755969c92faa223108620dc8"
url: "https://pub.dev"
source: hosted
version: "1.0.1"
version: "2.0.0"
fake_async:
dependency: transitive
description:
@@ -269,10 +314,10 @@ packages:
dependency: transitive
description:
name: ffi
sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878"
sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6"
url: "https://pub.dev"
source: hosted
version: "2.1.0"
version: "2.1.3"
ffigen:
dependency: "direct dev"
description:
@@ -293,26 +338,26 @@ packages:
dependency: "direct main"
description:
name: file_picker
sha256: "9d6e95ec73abbd31ec54d0e0df8a961017e165aba1395e462e5b31ea0c165daf"
sha256: "167bb619cdddaa10ef2907609feb8a79c16dfa479d3afaf960f8e223f754bf12"
url: "https://pub.dev"
source: hosted
version: "5.3.1"
version: "8.1.2"
fixnum:
dependency: transitive
description:
name: fixnum
sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1"
sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
url: "https://pub.dev"
source: hosted
version: "1.1.0"
version: "1.1.1"
fluent_ui:
dependency: "direct main"
description:
name: fluent_ui
sha256: a8c76cb501303d108cb9bd33e516da7cfd078031ff427d68eab6069bf4492a2c
sha256: e7804bf3bbb3ecf9e77d5498181dc36375f5ca736ccfb3862fea17c50050eb89
url: "https://pub.dev"
source: hosted
version: "4.8.7"
version: "4.9.2"
flutter:
dependency: "direct main"
description: flutter
@@ -330,10 +375,10 @@ packages:
dependency: "direct main"
description:
name: flutter_form_builder
sha256: "560eb5e367d81170c6ade1e7ae63ecc5167936ae2cdfaae8a345e91bce19d2f2"
sha256: c278ef69b08957d484f83413f0e77b656a39b7a7bb4eb8a295da3a820ecc6545
url: "https://pub.dev"
source: hosted
version: "9.2.1"
version: "9.5.0"
flutter_launcher_icons:
dependency: "direct dev"
description:
@@ -359,10 +404,10 @@ packages:
dependency: transitive
description:
name: flutter_plugin_android_lifecycle
sha256: "8cf40eebf5dec866a6d1956ad7b4f7016e6c0cc69847ab946833b7d43743809f"
sha256: "9b78450b89f059e96c9ebb355fa6b3df1d6b330436e0b885fb49594c41721398"
url: "https://pub.dev"
source: hosted
version: "2.0.19"
version: "2.0.23"
flutter_test:
dependency: "direct dev"
description: flutter
@@ -377,10 +422,10 @@ packages:
dependency: "direct main"
description:
name: form_builder_validators
sha256: "19aa5282b7cede82d0025ab031a98d0554b84aa2ba40f12013471a3b3e22bf02"
sha256: c61ed7b1deecf0e1ebe49e2fa79e3283937c5a21c7e48e3ed9856a4a14e1191a
url: "https://pub.dev"
source: hosted
version: "9.1.0"
version: "11.0.0"
frontend_server_client:
dependency: transitive
description:
@@ -409,10 +454,10 @@ packages:
dependency: transitive
description:
name: graphs
sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19
sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0"
url: "https://pub.dev"
source: hosted
version: "2.3.1"
version: "2.3.2"
gtk:
dependency: transitive
description:
@@ -433,10 +478,10 @@ packages:
dependency: transitive
description:
name: http
sha256: a2bbf9d017fcced29139daa8ed2bba4ece450ab222871df93ca9eec6f80c34ba
sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010
url: "https://pub.dev"
source: hosted
version: "1.2.0"
version: "1.2.2"
http_multi_server:
dependency: transitive
description:
@@ -457,18 +502,18 @@ packages:
dependency: transitive
description:
name: image
sha256: "2237616a36c0d69aef7549ab439b833fb7f9fb9fc861af2cc9ac3eedddd69ca8"
sha256: f31d52537dc417fdcde36088fdf11d191026fd5e4fae742491ebd40e5a8bea7d
url: "https://pub.dev"
source: hosted
version: "4.2.0"
version: "4.3.0"
intl:
dependency: transitive
description:
name: intl
sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d"
sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf
url: "https://pub.dev"
source: hosted
version: "0.18.1"
version: "0.19.0"
io:
dependency: transitive
description:
@@ -505,10 +550,34 @@ packages:
dependency: "direct main"
description:
name: launch_at_startup
sha256: "93fc5638e088290004fae358bae691486673d469957d461d9dae5b12248593eb"
sha256: "1f8a75520913d1038630049e6c44a2575a23ffd28cc8b14fdf37401d1d21de84"
url: "https://pub.dev"
source: hosted
version: "0.2.2"
version: "0.3.1"
leak_tracker:
dependency: transitive
description:
name: leak_tracker
sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05"
url: "https://pub.dev"
source: hosted
version: "10.0.5"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806"
url: "https://pub.dev"
source: hosted
version: "3.0.5"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
url: "https://pub.dev"
source: hosted
version: "3.0.1"
lecle_downloads_path_provider:
dependency: "direct main"
description:
@@ -545,10 +614,10 @@ packages:
dependency: transitive
description:
name: logging
sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340"
sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
url: "https://pub.dev"
source: hosted
version: "1.2.0"
version: "1.3.0"
logging_appenders:
dependency: transitive
description:
@@ -557,14 +626,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.3.1"
lottie:
dependency: "direct main"
description:
name: lottie
sha256: "893da7a0022ec2fcaa616f34529a081f617e86cc501105b856e5a3184c58c7c2"
url: "https://pub.dev"
source: hosted
version: "1.4.3"
macos_secure_bookmarks:
dependency: "direct main"
description:
@@ -573,22 +634,30 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.2.1"
macros:
dependency: transitive
description:
name: macros
sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536"
url: "https://pub.dev"
source: hosted
version: "0.1.2-main.4"
matcher:
dependency: transitive
description:
name: matcher
sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e"
sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
url: "https://pub.dev"
source: hosted
version: "0.12.16"
version: "0.12.16+1"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41"
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
url: "https://pub.dev"
source: hosted
version: "0.5.0"
version: "0.11.1"
math_expressions:
dependency: transitive
description:
@@ -609,26 +678,26 @@ packages:
dependency: transitive
description:
name: meta
sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e
sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
url: "https://pub.dev"
source: hosted
version: "1.10.0"
version: "1.15.0"
mime:
dependency: transitive
description:
name: mime
sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2"
sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6"
url: "https://pub.dev"
source: hosted
version: "1.0.5"
version: "2.0.0"
open_filex:
dependency: "direct main"
description:
name: open_filex
sha256: "74e2280754cf8161e860746c3181db2c996d6c1909c7057b738ede4a469816b8"
sha256: ba425ea49affd0a98a234aa9344b9ea5d4c4f7625a1377961eae9fe194c3d523
url: "https://pub.dev"
source: hosted
version: "4.4.0"
version: "4.5.0"
package_config:
dependency: transitive
description:
@@ -641,26 +710,26 @@ packages:
dependency: "direct main"
description:
name: package_info_plus
sha256: "7e76fad405b3e4016cd39d08f455a4eb5199723cf594cd1b8916d47140d93017"
sha256: df3eb3e0aed5c1107bb0fdb80a8e82e778114958b1c5ac5644fb1ac9cae8a998
url: "https://pub.dev"
source: hosted
version: "4.2.0"
version: "8.1.0"
package_info_plus_platform_interface:
dependency: transitive
description:
name: package_info_plus_platform_interface
sha256: "9bc8ba46813a4cc42c66ab781470711781940780fd8beddd0c3da62506d3a6c6"
sha256: ac1f4a4847f1ade8e6a87d1f39f5d7c67490738642e2542f559ec38c37489a66
url: "https://pub.dev"
source: hosted
version: "2.0.1"
version: "3.0.1"
path:
dependency: "direct main"
description:
name: path
sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917"
sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
url: "https://pub.dev"
source: hosted
version: "1.8.3"
version: "1.9.0"
path_provider:
dependency: "direct main"
description:
@@ -673,18 +742,18 @@ packages:
dependency: transitive
description:
name: path_provider_android
sha256: a248d8146ee5983446bf03ed5ea8f6533129a12b11f12057ad1b4a67a2b3b41d
sha256: c464428172cb986b758c6d1724c603097febb8fb855aa265aeecc9280c294d4a
url: "https://pub.dev"
source: hosted
version: "2.2.4"
version: "2.2.12"
path_provider_foundation:
dependency: transitive
description:
name: path_provider_foundation
sha256: "5a7999be66e000916500be4f15a3633ebceb8302719b47b9cc49ce924125350f"
sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16
url: "https://pub.dev"
source: hosted
version: "2.3.2"
version: "2.4.0"
path_provider_linux:
dependency: transitive
description:
@@ -709,6 +778,55 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.3.0"
permission_handler:
dependency: "direct main"
description:
path: permission_handler
ref: "35fc72c30262b9b49e1965b48a7524b44ba9daa7"
resolved-ref: "35fc72c30262b9b49e1965b48a7524b44ba9daa7"
url: "https://github.com/monkeyWie/flutter-permission-handler.git"
source: git
version: "11.3.1"
permission_handler_android:
dependency: transitive
description:
name: permission_handler_android
sha256: "71bbecfee799e65aff7c744761a57e817e73b738fedf62ab7afd5593da21f9f1"
url: "https://pub.dev"
source: hosted
version: "12.0.13"
permission_handler_apple:
dependency: transitive
description:
name: permission_handler_apple
sha256: e6f6d73b12438ef13e648c4ae56bd106ec60d17e90a59c4545db6781229082a0
url: "https://pub.dev"
source: hosted
version: "9.4.5"
permission_handler_html:
dependency: transitive
description:
name: permission_handler_html
sha256: af26edbbb1f2674af65a8f4b56e1a6f526156bc273d0e65dd8075fab51c78851
url: "https://pub.dev"
source: hosted
version: "0.1.3+2"
permission_handler_platform_interface:
dependency: transitive
description:
name: permission_handler_platform_interface
sha256: e9c8eadee926c4532d0305dff94b85bf961f16759c3af791486613152af4b4f9
url: "https://pub.dev"
source: hosted
version: "4.2.3"
permission_handler_windows:
dependency: transitive
description:
name: permission_handler_windows
sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e"
url: "https://pub.dev"
source: hosted
version: "0.2.1"
petitparser:
dependency: transitive
description:
@@ -721,10 +839,10 @@ packages:
dependency: transitive
description:
name: platform
sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65"
sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
url: "https://pub.dev"
source: hosted
version: "3.1.5"
version: "3.1.6"
plugin_platform_interface:
dependency: transitive
description:
@@ -761,10 +879,10 @@ packages:
dependency: transitive
description:
name: quiver
sha256: b1c1ac5ce6688d77f65f3375a9abb9319b3cb32486bdc7a1e0fdf004d7ba4e47
sha256: ea0b925899e64ecdfbf9c7becb60d5b50e706ade44a85b2363be2a22d88117d2
url: "https://pub.dev"
source: hosted
version: "3.2.1"
version: "3.2.2"
recase:
dependency: transitive
description:
@@ -809,50 +927,50 @@ packages:
dependency: "direct main"
description:
name: share_plus
sha256: "3ef39599b00059db0990ca2e30fca0a29d8b37aae924d60063f8e0184cf20900"
sha256: "334fcdf0ef9c0df0e3b428faebcac9568f35c747d59831474b2fc56e156d244e"
url: "https://pub.dev"
source: hosted
version: "7.2.2"
version: "10.1.0"
share_plus_platform_interface:
dependency: transitive
description:
name: share_plus_platform_interface
sha256: "251eb156a8b5fa9ce033747d73535bf53911071f8d3b6f4f0b578505ce0d4496"
sha256: c57c0bbfec7142e3a0f55633be504b796af72e60e3c791b44d5a017b985f7a48
url: "https://pub.dev"
source: hosted
version: "3.4.0"
version: "5.0.1"
shared_preferences:
dependency: transitive
description:
name: shared_preferences
sha256: d3bbe5553a986e83980916ded2f0b435ef2e1893dfaa29d5a7a790d0eca12180
sha256: "746e5369a43170c25816cc472ee016d3a66bc13fcf430c0bc41ad7b4b2922051"
url: "https://pub.dev"
source: hosted
version: "2.2.3"
version: "2.3.2"
shared_preferences_android:
dependency: transitive
description:
name: shared_preferences_android
sha256: "1ee8bf911094a1b592de7ab29add6f826a7331fb854273d55918693d5364a1f2"
sha256: "3b9febd815c9ca29c9e3520d50ec32f49157711e143b7a4ca039eb87e8ade5ab"
url: "https://pub.dev"
source: hosted
version: "2.2.2"
version: "2.3.3"
shared_preferences_foundation:
dependency: transitive
description:
name: shared_preferences_foundation
sha256: "7708d83064f38060c7b39db12aefe449cb8cdc031d6062280087bc4cdb988f5c"
sha256: "07e050c7cd39bad516f8d64c455f04508d09df104be326d8c02551590a0d513d"
url: "https://pub.dev"
source: hosted
version: "2.3.5"
version: "2.5.3"
shared_preferences_linux:
dependency: transitive
description:
name: shared_preferences_linux
sha256: "2ba0510d3017f91655b7543e9ee46d48619de2a2af38e5c790423f7007c7ccc1"
sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f"
url: "https://pub.dev"
source: hosted
version: "2.4.0"
version: "2.4.1"
shared_preferences_platform_interface:
dependency: transitive
description:
@@ -865,18 +983,18 @@ packages:
dependency: transitive
description:
name: shared_preferences_web
sha256: "7b15ffb9387ea3e237bb7a66b8a23d2147663d391cafc5c8f37b2e7b4bde5d21"
sha256: d2ca4132d3946fec2184261726b355836a82c33d7d5b67af32692aff18a4684e
url: "https://pub.dev"
source: hosted
version: "2.2.2"
version: "2.4.2"
shared_preferences_windows:
dependency: transitive
description:
name: shared_preferences_windows
sha256: "398084b47b7f92110683cac45c6dc4aae853db47e470e5ddcd52cab7f7196ab2"
sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1"
url: "https://pub.dev"
source: hosted
version: "2.4.0"
version: "2.4.1"
shelf:
dependency: transitive
description:
@@ -889,10 +1007,10 @@ packages:
dependency: transitive
description:
name: shelf_web_socket
sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1"
sha256: "073c147238594ecd0d193f3456a5fe91c4b0abbcc68bf5cd95b36c4e194ac611"
url: "https://pub.dev"
source: hosted
version: "1.0.4"
version: "2.0.0"
shortid:
dependency: transitive
description:
@@ -990,10 +1108,10 @@ packages:
dependency: transitive
description:
name: test_api
sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b"
sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb"
url: "https://pub.dev"
source: hosted
version: "0.6.1"
version: "0.7.2"
timing:
dependency: transitive
description:
@@ -1014,66 +1132,66 @@ packages:
dependency: "direct main"
description:
name: tray_manager
sha256: c9a63fd88bd3546287a7eb8ccc978d707eef82c775397af17dda3a4f4c039e64
sha256: bdc3ac6c36f3d12d871459e4a9822705ce5a1165a17fa837103bc842719bf3f7
url: "https://pub.dev"
source: hosted
version: "0.2.3"
version: "0.2.4"
typed_data:
dependency: transitive
description:
name: typed_data
sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
url: "https://pub.dev"
source: hosted
version: "1.3.2"
version: "1.4.0"
uri_to_file:
dependency: "direct main"
description:
name: uri_to_file
sha256: "84afd633b1492fc465c768141e1a29edd519061bf99935b6b4d0d5de8ec7c108"
sha256: cbbb38f975d22311efacc9a53cdcb806708606666d8092314b81c5cb7983b700
url: "https://pub.dev"
source: hosted
version: "0.2.0"
version: "1.0.0"
url_launcher:
dependency: "direct main"
description:
name: url_launcher
sha256: "21b704ce5fa560ea9f3b525b43601c678728ba46725bab9b01187b4831377ed3"
sha256: "9d06212b1362abc2f0f0d78e6f09f726608c74e3b9462e8368bb03314aa8d603"
url: "https://pub.dev"
source: hosted
version: "6.3.0"
version: "6.3.1"
url_launcher_android:
dependency: transitive
description:
name: url_launcher_android
sha256: "17cd5e205ea615e2c6ea7a77323a11712dffa0720a8a90540db57a01347f9ad9"
sha256: "8fc3bae0b68c02c47c5c86fa8bfa74471d42687b0eded01b78de87872db745e2"
url: "https://pub.dev"
source: hosted
version: "6.3.2"
version: "6.3.12"
url_launcher_ios:
dependency: transitive
description:
name: url_launcher_ios
sha256: "75bb6fe3f60070407704282a2d295630cab232991eb52542b18347a8a941df03"
sha256: e43b677296fadce447e987a2f519dcf5f6d1e527dc35d01ffab4fff5b8a7063e
url: "https://pub.dev"
source: hosted
version: "6.2.4"
version: "6.3.1"
url_launcher_linux:
dependency: transitive
description:
name: url_launcher_linux
sha256: ab360eb661f8879369acac07b6bb3ff09d9471155357da8443fd5d3cf7363811
sha256: e2b9622b4007f97f504cd64c0128309dfb978ae66adbe944125ed9e1750f06af
url: "https://pub.dev"
source: hosted
version: "3.1.1"
version: "3.2.0"
url_launcher_macos:
dependency: transitive
description:
name: url_launcher_macos
sha256: "9a1a42d5d2d95400c795b2914c36fdcb525870c752569438e4ebb09a2b5d90de"
sha256: "769549c999acdb42b8bcfa7c43d72bf79a382ca7441ab18a808e101149daf672"
url: "https://pub.dev"
source: hosted
version: "3.2.0"
version: "3.2.1"
url_launcher_platform_interface:
dependency: transitive
description:
@@ -1086,26 +1204,26 @@ packages:
dependency: transitive
description:
name: url_launcher_web
sha256: fff0932192afeedf63cdd50ecbb1bc825d31aed259f02bb8dba0f3b729a5e88b
sha256: "772638d3b34c779ede05ba3d38af34657a05ac55b06279ea6edd409e323dca8e"
url: "https://pub.dev"
source: hosted
version: "2.2.3"
version: "2.3.3"
url_launcher_windows:
dependency: transitive
description:
name: url_launcher_windows
sha256: "49c10f879746271804767cb45551ec5592cdab00ee105c06dddde1a98f73b185"
sha256: "44cf3aabcedde30f2dba119a9dea3b0f2672fbe6fa96e85536251d678216b3c4"
url: "https://pub.dev"
source: hosted
version: "3.1.2"
version: "3.1.3"
uuid:
dependency: transitive
description:
name: uuid
sha256: "83d37c7ad7aaf9aa8e275490669535c8080377cfa7a7004c24dfac53afffaa90"
sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff
url: "https://pub.dev"
source: hosted
version: "4.4.2"
version: "4.5.1"
vector_math:
dependency: transitive
description:
@@ -1114,6 +1232,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.4"
vm_service:
dependency: transitive
description:
name: vm_service
sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d"
url: "https://pub.dev"
source: hosted
version: "14.2.5"
watcher:
dependency: transitive
description:
@@ -1126,50 +1252,58 @@ packages:
dependency: transitive
description:
name: web
sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152
sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb
url: "https://pub.dev"
source: hosted
version: "0.3.0"
version: "1.1.0"
web_socket:
dependency: transitive
description:
name: web_socket
sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83"
url: "https://pub.dev"
source: hosted
version: "0.1.6"
web_socket_channel:
dependency: transitive
description:
name: web_socket_channel
sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b
sha256: "9f187088ed104edd8662ca07af4b124465893caf063ba29758f97af57e61da8f"
url: "https://pub.dev"
source: hosted
version: "2.4.0"
version: "3.0.1"
win32:
dependency: transitive
description:
name: win32
sha256: "5a751eddf9db89b3e5f9d50c20ab8612296e4e8db69009788d6c8b060a84191c"
sha256: "2294c64768987ea280b43a3d8357d42d5679f3e2b5b69b602be45b2abbd165b0"
url: "https://pub.dev"
source: hosted
version: "4.1.4"
version: "5.6.1"
win32_registry:
dependency: transitive
description:
name: win32_registry
sha256: "1c52f994bdccb77103a6231ad4ea331a244dbcef5d1f37d8462f713143b0bfae"
sha256: "21ec76dfc731550fd3e2ce7a33a9ea90b828fdf19a5c3bcf556fa992cfa99852"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
version: "1.1.5"
window_manager:
dependency: "direct main"
description:
name: window_manager
sha256: "8699323b30da4cdbe2aa2e7c9de567a6abd8a97d9a5c850a3c86dcd0b34bbfbf"
sha256: ab8b2a7f97543d3db2b506c9d875e637149d48ee0c6a5cb5f5fd6e0dac463792
url: "https://pub.dev"
source: hosted
version: "0.3.9"
version: "0.4.2"
xdg_directories:
dependency: transitive
description:
name: xdg_directories
sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d
sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
url: "https://pub.dev"
source: hosted
version: "1.0.4"
version: "1.1.0"
xml:
dependency: transitive
description:
@@ -1195,5 +1329,5 @@ packages:
source: hosted
version: "2.2.1"
sdks:
dart: ">=3.2.0 <4.0.0"
flutter: ">=3.16.0"
dart: ">=3.5.0 <4.0.0"
flutter: ">=3.24.0"
+29 -23
View File
@@ -15,10 +15,10 @@ 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.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 1.5.8+1
version: 1.6.1+1
environment:
sdk: '>=3.0.0 <4.0.0'
sdk: ">=3.0.0 <4.0.0"
# Dependencies specify other packages that your package needs in order to work.
# To automatically upgrade your package dependencies to the latest versions
@@ -36,37 +36,43 @@ dependencies:
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.2
get: ^4.6.5
lottie: ^1.4.2
# lottie: ^1.4.2
styled_widget: ^0.4.0+3
context_menus: ^1.0.2
context_menus: ^1.2.0
json_annotation: ^4.8.1
dio: ^5.2.1
path_provider: ^2.1.1
file_picker: 5.3.1
path_provider: ^2.1.4
file_picker: ^8.1.2
macos_secure_bookmarks: ^0.2.1
rounded_loading_button_plus: ^3.0.1
url_launcher: ^6.1.6
logger: ^1.3.0
desktop_drop: ^0.4.3
package_info_plus: ^4.0.0
fluent_ui: ^4.4.0
path: any
url_launcher: ^6.3.1
logger: ^1.4.0
desktop_drop: ^0.4.4
package_info_plus: ^8.1.0
fluent_ui: ^4.9.2
path: ^1.9.0
badges: ^3.0.3
app_links: ^3.4.2
uri_to_file: ^0.2.0
window_manager: ^0.3.4
app_links: ^6.3.2
uri_to_file: ^1.0.0
window_manager: ^0.4.2
autoscale_tabbarview: ^1.0.2
share_plus: ^7.1.0
flutter_form_builder: ^9.1.1
form_builder_validators: ^9.0.0
share_plus: ^10.1.0
flutter_form_builder: ^9.5.0
form_builder_validators: ^11.0.0
flutter_foreground_task: ^8.2.0
open_filex: ^4.3.4
tray_manager: ^0.2.3
open_filex: ^4.5.0
tray_manager: ^0.2.4
lecle_downloads_path_provider: ^0.0.2+8
hive: ^2.2.3
launch_at_startup: ^0.2.2
launch_at_startup: ^0.3.1
args: ^2.5.0
toggle_switch: ^2.3.0
permission_handler:
git:
url: https://github.com/monkeyWie/flutter-permission-handler.git
ref: 35fc72c30262b9b49e1965b48a7524b44ba9daa7
path: permission_handler
device_info_plus: ^11.1.0
dev_dependencies:
flutter_test:
sdk: flutter
@@ -142,7 +148,7 @@ flutter_icons:
ffigen:
name: LibgopeedBind
description: Bindings to gopeed library.
output: 'lib/core/ffi/libgopeed_bind.dart'
output: "lib/core/ffi/libgopeed_bind.dart"
headers:
entry-points:
- 'include/libgopeed.h'
- "include/libgopeed.h"