mirror of
https://github.com/GopeedLab/gopeed.git
synced 2026-11-10 18:40:27 +00:00
Compare commits
15
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3521837964 | ||
|
|
b30cd78f86 | ||
|
|
0583e4aaa6 | ||
|
|
a89c55b5c4 | ||
|
|
68c40d7f3f | ||
|
|
42a0b89d95 | ||
|
|
1860f534cd | ||
|
|
5c2d2ad7b4 | ||
|
|
db6352ada8 | ||
|
|
d1a5ed8b3d | ||
|
|
be9583be0f | ||
|
|
d2c70b077b | ||
|
|
53b3eea4fc | ||
|
|
54347f41b6 | ||
|
|
cea454b015 |
No files matched your search
+38
-13
@@ -136,7 +136,7 @@ jobs:
|
|||||||
overwrite: true
|
overwrite: true
|
||||||
build-macos-arm64-lib:
|
build-macos-arm64-lib:
|
||||||
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'macos' }}
|
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'macos' }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: macos-latest
|
||||||
needs: [ get-release ]
|
needs: [ get-release ]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
@@ -147,16 +147,14 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
VERSION: ${{ needs.get-release.outputs.version }}
|
VERSION: ${{ needs.get-release.outputs.version }}
|
||||||
run: |
|
run: |
|
||||||
go install src.techknowlogick.com/xgo@latest
|
go build -tags nosqlite -ldflags="-w -s -X github.com/GopeedLab/gopeed/pkg/base.Version=$VERSION" -buildmode=c-shared -o libgopeed.dylib github.com/GopeedLab/gopeed/bind/desktop
|
||||||
xgo -go go-$GO_VERSION.x --targets=darwin/arm64 -tags="nosqlite" -ldflags="-w -s -X github.com/GopeedLab/gopeed/pkg/base.Version=$VERSION" -buildmode=c-shared -pkg=bind/desktop -out=libgopeed .
|
|
||||||
mv libgopeed-*.dylib libgopeed.dylib
|
|
||||||
- uses: actions/upload-artifact@v3
|
- uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: macos-arm64-lib
|
name: macos-arm64-lib
|
||||||
path: libgopeed.dylib
|
path: libgopeed.dylib
|
||||||
build-macos:
|
build-macos:
|
||||||
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'macos' }}
|
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'macos' }}
|
||||||
runs-on: macos-latest
|
runs-on: macos-13
|
||||||
needs: [ get-release, build-macos-arm64-lib ]
|
needs: [ get-release, build-macos-arm64-lib ]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
@@ -171,7 +169,7 @@ jobs:
|
|||||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||||
- name: Install appdmg
|
- name: Install appdmg
|
||||||
run: |
|
run: |
|
||||||
python3 -m pip install setuptools
|
python3 -m pip install setuptools --break-system-packages
|
||||||
npm install -g appdmg
|
npm install -g appdmg
|
||||||
- uses: actions/download-artifact@v3
|
- uses: actions/download-artifact@v3
|
||||||
with:
|
with:
|
||||||
@@ -260,9 +258,6 @@ jobs:
|
|||||||
- uses: actions/setup-go@v4
|
- uses: actions/setup-go@v4
|
||||||
with:
|
with:
|
||||||
go-version: ${{ env.GO_VERSION }}
|
go-version: ${{ env.GO_VERSION }}
|
||||||
- uses: subosito/flutter-action@v2
|
|
||||||
with:
|
|
||||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
|
||||||
- name: Setup LXD
|
- name: Setup LXD
|
||||||
uses: canonical/[email protected]
|
uses: canonical/[email protected]
|
||||||
with:
|
with:
|
||||||
@@ -300,20 +295,50 @@ jobs:
|
|||||||
|
|
||||||
apps:
|
apps:
|
||||||
gopeed:
|
gopeed:
|
||||||
command: gopeed
|
command: bin/gopeed
|
||||||
extensions: [gnome]
|
extensions: [gnome]
|
||||||
plugs:
|
plugs:
|
||||||
- network
|
- network
|
||||||
slots:
|
slots:
|
||||||
- dbus-gopeed
|
- dbus-gopeed
|
||||||
parts:
|
parts:
|
||||||
|
flutter-git:
|
||||||
|
source: https://github.com/flutter/flutter.git
|
||||||
|
source-tag: $FLUTTER_VERSION
|
||||||
|
source-depth: 1
|
||||||
|
plugin: nil
|
||||||
|
override-build: |
|
||||||
|
mkdir -p \$CRAFT_PART_INSTALL/usr/bin
|
||||||
|
mkdir -p \$CRAFT_PART_INSTALL/usr/libexec
|
||||||
|
cp -r \$CRAFT_PART_SRC \$CRAFT_PART_INSTALL/usr/libexec/flutter
|
||||||
|
ln -s \$CRAFT_PART_INSTALL/usr/libexec/flutter/bin/flutter \$CRAFT_PART_INSTALL/usr/bin/flutter
|
||||||
|
ln -s \$SNAPCRAFT_PART_INSTALL/usr/libexec/flutter/bin/dart \$SNAPCRAFT_PART_INSTALL/usr/bin/dart
|
||||||
|
\$CRAFT_PART_INSTALL/usr/bin/flutter doctor
|
||||||
|
build-packages:
|
||||||
|
- clang
|
||||||
|
- cmake
|
||||||
|
- curl
|
||||||
|
- libgtk-3-dev
|
||||||
|
- ninja-build
|
||||||
|
- unzip
|
||||||
|
- xz-utils
|
||||||
|
- zip
|
||||||
|
override-prime: ''
|
||||||
gopeed:
|
gopeed:
|
||||||
|
after: [flutter-git]
|
||||||
source: .
|
source: .
|
||||||
plugin: flutter
|
plugin: nil
|
||||||
stage-packages:
|
stage-packages:
|
||||||
- libgtk-3-dev
|
- libgtk-3-dev
|
||||||
- libappindicator3-dev
|
- libappindicator3-dev
|
||||||
flutter-target: lib/main.dart
|
override-build: |
|
||||||
|
# work around pub get stack overflow # https://github.com/dart-lang/sdk/issues/51068#issuecomment-1396588253
|
||||||
|
set +e
|
||||||
|
dart pub get
|
||||||
|
set -eux
|
||||||
|
flutter build linux --release -v
|
||||||
|
mkdir -p \$CRAFT_PART_INSTALL/bin/
|
||||||
|
cp -r build/linux/*/release/bundle/* \$CRAFT_PART_INSTALL/bin/
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
cp linux/assets/com.gopeed.Gopeed.desktop snap/gui/gopeed.desktop
|
cp linux/assets/com.gopeed.Gopeed.desktop snap/gui/gopeed.desktop
|
||||||
@@ -381,7 +406,7 @@ jobs:
|
|||||||
go install golang.org/x/mobile/cmd/gomobile@latest
|
go install golang.org/x/mobile/cmd/gomobile@latest
|
||||||
go get golang.org/x/mobile/bind
|
go get golang.org/x/mobile/bind
|
||||||
gomobile init
|
gomobile init
|
||||||
gomobile bind -tags nosqlite,netgo -ldflags="-w -s -X github.com/GopeedLab/gopeed/pkg/base.Version=$VERSION" -o ui/flutter/ios/Frameworks/Libgopeed.xcframework -target=ios github.com/GopeedLab/gopeed/bind/mobile
|
gomobile bind -tags nosqlite -ldflags="-w -s -X github.com/GopeedLab/gopeed/pkg/base.Version=$VERSION" -o ui/flutter/ios/Frameworks/Libgopeed.xcframework -target=ios github.com/GopeedLab/gopeed/bind/mobile
|
||||||
cd ui/flutter
|
cd ui/flutter
|
||||||
flutter build ios --no-codesign
|
flutter build ios --no-codesign
|
||||||
mkdir Payload
|
mkdir Payload
|
||||||
|
|||||||
@@ -12,9 +12,11 @@ require (
|
|||||||
github.com/gorilla/handlers v1.5.1
|
github.com/gorilla/handlers v1.5.1
|
||||||
github.com/gorilla/mux v1.8.0
|
github.com/gorilla/mux v1.8.0
|
||||||
github.com/matoous/go-nanoid/v2 v2.0.0
|
github.com/matoous/go-nanoid/v2 v2.0.0
|
||||||
|
github.com/mattn/go-ieproxy v0.0.11
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/rs/zerolog v1.31.0
|
github.com/rs/zerolog v1.31.0
|
||||||
github.com/virtuald/go-paniclog v0.0.0-20190812204905-43a7fa316459
|
github.com/virtuald/go-paniclog v0.0.0-20190812204905-43a7fa316459
|
||||||
|
github.com/xiaoqidun/setft v0.0.0-20220310121541-be86327699ad
|
||||||
go.etcd.io/bbolt v1.3.8
|
go.etcd.io/bbolt v1.3.8
|
||||||
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a
|
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a
|
||||||
golang.org/x/sync v0.6.0
|
golang.org/x/sync v0.6.0
|
||||||
@@ -96,7 +98,6 @@ require (
|
|||||||
github.com/stretchr/testify v1.8.4 // indirect
|
github.com/stretchr/testify v1.8.4 // indirect
|
||||||
github.com/tidwall/btree v1.7.0 // indirect
|
github.com/tidwall/btree v1.7.0 // indirect
|
||||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||||
github.com/xiaoqidun/setft v0.0.0-20220310121541-be86327699ad // indirect
|
|
||||||
go.opentelemetry.io/otel v1.22.0 // indirect
|
go.opentelemetry.io/otel v1.22.0 // indirect
|
||||||
go.opentelemetry.io/otel/metric v1.22.0 // indirect
|
go.opentelemetry.io/otel/metric v1.22.0 // indirect
|
||||||
go.opentelemetry.io/otel/trace v1.22.0 // indirect
|
go.opentelemetry.io/otel/trace v1.22.0 // indirect
|
||||||
@@ -115,3 +116,5 @@ require (
|
|||||||
modernc.org/sqlite v1.28.0 // indirect
|
modernc.org/sqlite v1.28.0 // indirect
|
||||||
zombiezen.com/go/sqlite v1.1.0 // indirect
|
zombiezen.com/go/sqlite v1.1.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
|
replace github.com/mattn/go-ieproxy => github.com/GopeedLab/go-ieproxy v0.0.0-20240508033154-86b7091da074
|
||||||
@@ -5,7 +5,10 @@ crawshaw.io/sqlite v0.3.2/go.mod h1:igAO5JulrQ1DbdZdtVq48mnZUBAPOeFzer7VhDWNtW4=
|
|||||||
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
|
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
|
||||||
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||||
filippo.io/edwards25519 v1.0.0-rc.1 h1:m0VOOB23frXZvAOK44usCgLWvtsxIoMCTBGJZlpmGfU=
|
filippo.io/edwards25519 v1.0.0-rc.1 h1:m0VOOB23frXZvAOK44usCgLWvtsxIoMCTBGJZlpmGfU=
|
||||||
|
filippo.io/edwards25519 v1.0.0-rc.1/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
|
github.com/GopeedLab/go-ieproxy v0.0.0-20240508033154-86b7091da074 h1:WTv/RQaUwPUWxX26kcRFp/kvSapYSKnw/viPtTH1ZTY=
|
||||||
|
github.com/GopeedLab/go-ieproxy v0.0.0-20240508033154-86b7091da074/go.mod h1:/NsJd+kxZBmjMc5hrJCKMbP57B84rvq9BiDRbtO9AS0=
|
||||||
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
|
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
|
||||||
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
|
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
|
||||||
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
|
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
|
||||||
@@ -23,9 +26,11 @@ github.com/acomagu/bufpipe v1.0.4/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ
|
|||||||
github.com/ajwerner/btree v0.0.0-20211221152037-f427b3e689c0 h1:byYvvbfSo3+9efR4IeReh77gVs4PnNDR3AMOE9NJ7a0=
|
github.com/ajwerner/btree v0.0.0-20211221152037-f427b3e689c0 h1:byYvvbfSo3+9efR4IeReh77gVs4PnNDR3AMOE9NJ7a0=
|
||||||
github.com/ajwerner/btree v0.0.0-20211221152037-f427b3e689c0/go.mod h1:q37NoqncT41qKc048STsifIt69LfUJ8SrWWcz/yam5k=
|
github.com/ajwerner/btree v0.0.0-20211221152037-f427b3e689c0/go.mod h1:q37NoqncT41qKc048STsifIt69LfUJ8SrWWcz/yam5k=
|
||||||
github.com/alecthomas/assert/v2 v2.0.0-alpha3 h1:pcHeMvQ3OMstAWgaeaXIAL8uzB9xMm2zlxt+/4ml8lk=
|
github.com/alecthomas/assert/v2 v2.0.0-alpha3 h1:pcHeMvQ3OMstAWgaeaXIAL8uzB9xMm2zlxt+/4ml8lk=
|
||||||
|
github.com/alecthomas/assert/v2 v2.0.0-alpha3/go.mod h1:+zD0lmDXTeQj7TgDgCt0ePWxb0hMC1G+PGTsTCv1B9o=
|
||||||
github.com/alecthomas/atomic v0.1.0-alpha2 h1:dqwXmax66gXvHhsOS4pGPZKqYOlTkapELkLb3MNdlH8=
|
github.com/alecthomas/atomic v0.1.0-alpha2 h1:dqwXmax66gXvHhsOS4pGPZKqYOlTkapELkLb3MNdlH8=
|
||||||
github.com/alecthomas/atomic v0.1.0-alpha2/go.mod h1:zD6QGEyw49HIq19caJDc2NMXAy8rNi9ROrxtMXATfyI=
|
github.com/alecthomas/atomic v0.1.0-alpha2/go.mod h1:zD6QGEyw49HIq19caJDc2NMXAy8rNi9ROrxtMXATfyI=
|
||||||
github.com/alecthomas/repr v0.0.0-20210801044451-80ca428c5142 h1:8Uy0oSf5co/NZXje7U1z8Mpep++QJOldL2hs/sBQf48=
|
github.com/alecthomas/repr v0.0.0-20210801044451-80ca428c5142 h1:8Uy0oSf5co/NZXje7U1z8Mpep++QJOldL2hs/sBQf48=
|
||||||
|
github.com/alecthomas/repr v0.0.0-20210801044451-80ca428c5142/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
|
||||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||||
@@ -86,6 +91,7 @@ github.com/anacrolix/upnp v0.1.3/go.mod h1:Qyhbqo69gwNWvEk1xNTXsS5j7hMHef9hdr984
|
|||||||
github.com/anacrolix/utp v0.2.0 h1:65Cdmr6q9WSw2KsM+rtJFu7rqDzLl2bdysf4KlNPcFI=
|
github.com/anacrolix/utp v0.2.0 h1:65Cdmr6q9WSw2KsM+rtJFu7rqDzLl2bdysf4KlNPcFI=
|
||||||
github.com/anacrolix/utp v0.2.0/go.mod h1:HGk4GYQw1O/3T1+yhqT/F6EcBd+AAwlo9dYErNy7mj8=
|
github.com/anacrolix/utp v0.2.0/go.mod h1:HGk4GYQw1O/3T1+yhqT/F6EcBd+AAwlo9dYErNy7mj8=
|
||||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
||||||
|
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
|
||||||
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
||||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
||||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||||
@@ -139,6 +145,7 @@ github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFP
|
|||||||
github.com/edsrzf/mmap-go v1.1.0 h1:6EUwBLQ/Mcr1EYLE4Tn1VdW1A4ckqCQWZBw8Hr0kjpQ=
|
github.com/edsrzf/mmap-go v1.1.0 h1:6EUwBLQ/Mcr1EYLE4Tn1VdW1A4ckqCQWZBw8Hr0kjpQ=
|
||||||
github.com/edsrzf/mmap-go v1.1.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q=
|
github.com/edsrzf/mmap-go v1.1.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q=
|
||||||
github.com/elazarl/goproxy v0.0.0-20221015165544-a0805db90819 h1:RIB4cRk+lBqKK3Oy0r2gRX4ui7tuhiZq2SuTtTCi0/0=
|
github.com/elazarl/goproxy v0.0.0-20221015165544-a0805db90819 h1:RIB4cRk+lBqKK3Oy0r2gRX4ui7tuhiZq2SuTtTCi0/0=
|
||||||
|
github.com/elazarl/goproxy v0.0.0-20221015165544-a0805db90819/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
|
||||||
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||||
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
||||||
github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ=
|
github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ=
|
||||||
@@ -146,9 +153,11 @@ github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSw
|
|||||||
github.com/frankban/quicktest v1.9.0/go.mod h1:ui7WezCLWMWxVWr1GETZY3smRy0G4KWq9vcPtJmFl7Y=
|
github.com/frankban/quicktest v1.9.0/go.mod h1:ui7WezCLWMWxVWr1GETZY3smRy0G4KWq9vcPtJmFl7Y=
|
||||||
github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||||
|
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||||
github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY=
|
github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY=
|
||||||
|
github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4=
|
||||||
github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE=
|
github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE=
|
||||||
github.com/glycerine/go-unsnap-stream v0.0.0-20181221182339-f9677308dec2/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE=
|
github.com/glycerine/go-unsnap-stream v0.0.0-20181221182339-f9677308dec2/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE=
|
||||||
github.com/glycerine/go-unsnap-stream v0.0.0-20190901134440-81cf024a9e0a/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE=
|
github.com/glycerine/go-unsnap-stream v0.0.0-20190901134440-81cf024a9e0a/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE=
|
||||||
@@ -160,6 +169,7 @@ github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmS
|
|||||||
github.com/go-git/go-billy/v5 v5.4.1 h1:Uwp5tDRkPr+l/TnbHOQzp+tmJfLceOlbVucgpTz8ix4=
|
github.com/go-git/go-billy/v5 v5.4.1 h1:Uwp5tDRkPr+l/TnbHOQzp+tmJfLceOlbVucgpTz8ix4=
|
||||||
github.com/go-git/go-billy/v5 v5.4.1/go.mod h1:vjbugF6Fz7JIflbVpl1hJsGjSHNltrSw45YK/ukIvQg=
|
github.com/go-git/go-billy/v5 v5.4.1/go.mod h1:vjbugF6Fz7JIflbVpl1hJsGjSHNltrSw45YK/ukIvQg=
|
||||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20230305113008-0c11038e723f h1:Pz0DHeFij3XFhoBRGUDPzSJ+w2UcK5/0JvF8DRI58r8=
|
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20230305113008-0c11038e723f h1:Pz0DHeFij3XFhoBRGUDPzSJ+w2UcK5/0JvF8DRI58r8=
|
||||||
|
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20230305113008-0c11038e723f/go.mod h1:8LHG1a3SRW71ettAD/jW13h8c6AqjVSeL11RAdgaqpo=
|
||||||
github.com/go-git/go-git/v5 v5.8.1 h1:Zo79E4p7TRk0xoRgMq0RShiTHGKcKI4+DI6BfJc/Q+A=
|
github.com/go-git/go-git/v5 v5.8.1 h1:Zo79E4p7TRk0xoRgMq0RShiTHGKcKI4+DI6BfJc/Q+A=
|
||||||
github.com/go-git/go-git/v5 v5.8.1/go.mod h1:FHFuoD6yGz5OSKEBK+aWN9Oah0q54Jxl0abmj6GnqAo=
|
github.com/go-git/go-git/v5 v5.8.1/go.mod h1:FHFuoD6yGz5OSKEBK+aWN9Oah0q54Jxl0abmj6GnqAo=
|
||||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
@@ -212,6 +222,7 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
|||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/pprof v0.0.0-20230207041349-798e818bf904/go.mod h1:uglQLonpP8qtYCYyzA+8c/9qtqgA3qsXGYqCPKARAFg=
|
github.com/google/pprof v0.0.0-20230207041349-798e818bf904/go.mod h1:uglQLonpP8qtYCYyzA+8c/9qtqgA3qsXGYqCPKARAFg=
|
||||||
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a h1:fEBsGL/sjAuJrgah5XqmmYsTLzJp/TO9Lhy39gkverk=
|
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a h1:fEBsGL/sjAuJrgah5XqmmYsTLzJp/TO9Lhy39gkverk=
|
||||||
@@ -233,6 +244,7 @@ github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/
|
|||||||
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
|
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
|
||||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
||||||
|
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
||||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||||
github.com/huandu/xstrings v1.0.0/go.mod h1:4qWG/gcEcfX4z/mBDHJ++3ReCw9ibxbsNJbcucJdbSo=
|
github.com/huandu/xstrings v1.0.0/go.mod h1:4qWG/gcEcfX4z/mBDHJ++3ReCw9ibxbsNJbcucJdbSo=
|
||||||
github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4=
|
github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4=
|
||||||
@@ -571,6 +583,7 @@ golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
|
|||||||
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
|
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
|
||||||
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||||
golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE=
|
golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE=
|
||||||
|
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/url"
|
"github.com/GopeedLab/gopeed/pkg/base"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Controller struct {
|
type Controller struct {
|
||||||
GetConfig func(v any) bool
|
GetConfig func(v any) bool
|
||||||
ProxyUrl *url.URL
|
ProxyConfig *base.DownloaderProxyConfig
|
||||||
FileController
|
FileController
|
||||||
//ContextDialer() (proxy.Dialer, error)
|
//ContextDialer() (proxy.Dialer, error)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import (
|
|||||||
"github.com/GopeedLab/gopeed/pkg/util"
|
"github.com/GopeedLab/gopeed/pkg/util"
|
||||||
"github.com/anacrolix/torrent"
|
"github.com/anacrolix/torrent"
|
||||||
"github.com/anacrolix/torrent/metainfo"
|
"github.com/anacrolix/torrent/metainfo"
|
||||||
"net/http"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -69,9 +68,7 @@ func (f *Fetcher) initClient() (err error) {
|
|||||||
cfg.Bep20 = fmt.Sprintf("-GP%s-", parseBep20())
|
cfg.Bep20 = fmt.Sprintf("-GP%s-", parseBep20())
|
||||||
cfg.ExtendedHandshakeClientVersion = fmt.Sprintf("Gopeed %s", base.Version)
|
cfg.ExtendedHandshakeClientVersion = fmt.Sprintf("Gopeed %s", base.Version)
|
||||||
cfg.ListenPort = f.config.ListenPort
|
cfg.ListenPort = f.config.ListenPort
|
||||||
if f.ctl.ProxyUrl != nil {
|
cfg.HTTPProxy = f.ctl.ProxyConfig.ToHandler()
|
||||||
cfg.HTTPProxy = http.ProxyURL(f.ctl.ProxyUrl)
|
|
||||||
}
|
|
||||||
cfg.DefaultStorage = newFileOpts(newFileClientOpts{
|
cfg.DefaultStorage = newFileOpts(newFileClientOpts{
|
||||||
ClientBaseDir: cfg.DataDir,
|
ClientBaseDir: cfg.DataDir,
|
||||||
HandleFileTorrent: func(infoHash metainfo.Hash, ft *fileTorrentImpl) {
|
HandleFileTorrent: func(infoHash metainfo.Hash, ft *fileTorrentImpl) {
|
||||||
@@ -86,10 +83,6 @@ func (f *Fetcher) initClient() (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f *Fetcher) Resolve(req *base.Request) error {
|
func (f *Fetcher) Resolve(req *base.Request) error {
|
||||||
if err := base.ParseReqExtra[bt.ReqExtra](req); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := f.addTorrent(req); err != nil {
|
if err := f.addTorrent(req); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -243,6 +236,9 @@ func (f *Fetcher) updateRes() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f *Fetcher) addTorrent(req *base.Request) (err error) {
|
func (f *Fetcher) addTorrent(req *base.Request) (err error) {
|
||||||
|
if err = base.ParseReqExtra[bt.ReqExtra](req); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
if err = f.initClient(); err != nil {
|
if err = f.initClient(); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,6 @@ import (
|
|||||||
"github.com/GopeedLab/gopeed/internal/test"
|
"github.com/GopeedLab/gopeed/internal/test"
|
||||||
"github.com/GopeedLab/gopeed/pkg/base"
|
"github.com/GopeedLab/gopeed/pkg/base"
|
||||||
"github.com/GopeedLab/gopeed/pkg/protocol/bt"
|
"github.com/GopeedLab/gopeed/pkg/protocol/bt"
|
||||||
"github.com/GopeedLab/gopeed/pkg/util"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -60,7 +58,14 @@ func TestFetcher_ResolveWithProxy(t *testing.T) {
|
|||||||
proxyListener := test.StartSocks5Server(usr, pwd)
|
proxyListener := test.StartSocks5Server(usr, pwd)
|
||||||
defer proxyListener.Close()
|
defer proxyListener.Close()
|
||||||
|
|
||||||
doResolve(t, buildConfigFetcher(util.BuildProxyUrl("socks5", proxyListener.Addr().String(), usr, pwd)))
|
doResolve(t, buildConfigFetcher(&base.DownloaderProxyConfig{
|
||||||
|
Enable: true,
|
||||||
|
System: false,
|
||||||
|
Scheme: "socks5",
|
||||||
|
Host: proxyListener.Addr().String(),
|
||||||
|
Usr: usr,
|
||||||
|
Pwd: pwd,
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
func doResolve(t *testing.T, fetcher fetcher.Fetcher) {
|
func doResolve(t *testing.T, fetcher fetcher.Fetcher) {
|
||||||
@@ -100,7 +105,7 @@ func buildFetcher() fetcher.Fetcher {
|
|||||||
return fetcher
|
return fetcher
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildConfigFetcher(proxyUrl *url.URL) fetcher.Fetcher {
|
func buildConfigFetcher(proxyConfig *base.DownloaderProxyConfig) fetcher.Fetcher {
|
||||||
fetcher := new(FetcherBuilder).Build()
|
fetcher := new(FetcherBuilder).Build()
|
||||||
newController := controller.NewController()
|
newController := controller.NewController()
|
||||||
mockCfg := config{
|
mockCfg := config{
|
||||||
@@ -114,7 +119,7 @@ func buildConfigFetcher(proxyUrl *url.URL) fetcher.Fetcher {
|
|||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
newController.ProxyUrl = proxyUrl
|
newController.ProxyConfig = proxyConfig
|
||||||
fetcher.Setup(newController)
|
fetcher.Setup(newController)
|
||||||
return fetcher
|
return fetcher
|
||||||
}
|
}
|
||||||
@@ -451,12 +451,11 @@ func (f *Fetcher) splitChunk() (chunks []*chunk) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f *Fetcher) buildClient() *http.Client {
|
func (f *Fetcher) buildClient() *http.Client {
|
||||||
transport := &http.Transport{}
|
transport := &http.Transport{
|
||||||
|
Proxy: f.ctl.ProxyConfig.ToHandler(),
|
||||||
|
}
|
||||||
// Cookie handle
|
// Cookie handle
|
||||||
jar, _ := cookiejar.New(nil)
|
jar, _ := cookiejar.New(nil)
|
||||||
if f.ctl.ProxyUrl != nil {
|
|
||||||
transport.Proxy = http.ProxyURL(f.ctl.ProxyUrl)
|
|
||||||
}
|
|
||||||
return &http.Client{
|
return &http.Client{
|
||||||
Transport: transport,
|
Transport: transport,
|
||||||
Jar: jar,
|
Jar: jar,
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import (
|
|||||||
"github.com/GopeedLab/gopeed/internal/test"
|
"github.com/GopeedLab/gopeed/internal/test"
|
||||||
"github.com/GopeedLab/gopeed/pkg/base"
|
"github.com/GopeedLab/gopeed/pkg/base"
|
||||||
"github.com/GopeedLab/gopeed/pkg/protocol/http"
|
"github.com/GopeedLab/gopeed/pkg/protocol/http"
|
||||||
"github.com/GopeedLab/gopeed/pkg/util"
|
|
||||||
"net"
|
"net"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@@ -338,7 +337,11 @@ func downloadResume(listener net.Listener, connections int, t *testing.T) {
|
|||||||
func downloadWithProxy(httpListener net.Listener, proxyListener net.Listener, t *testing.T) {
|
func downloadWithProxy(httpListener net.Listener, proxyListener net.Listener, t *testing.T) {
|
||||||
fetcher := downloadReady(httpListener, 4, t)
|
fetcher := downloadReady(httpListener, 4, t)
|
||||||
ctl := controller.NewController()
|
ctl := controller.NewController()
|
||||||
ctl.ProxyUrl = util.BuildProxyUrl("socks5", proxyListener.Addr().String(), "", "")
|
ctl.ProxyConfig = &base.DownloaderProxyConfig{
|
||||||
|
Enable: true,
|
||||||
|
Scheme: "socks5",
|
||||||
|
Host: proxyListener.Addr().String(),
|
||||||
|
}
|
||||||
fetcher.Setup(ctl)
|
fetcher.Setup(ctl)
|
||||||
err := fetcher.Start()
|
err := fetcher.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -24,6 +24,11 @@ const (
|
|||||||
Dir = "./"
|
Dir = "./"
|
||||||
BuildFile = Dir + BuildName
|
BuildFile = Dir + BuildName
|
||||||
|
|
||||||
|
ExternalDownloadUrl = "https://raw.githubusercontent.com/GopeedLab/gopeed/v1.5.6/_docs/img/banner.png"
|
||||||
|
ExternalDownloadName = "banner.png"
|
||||||
|
ExternalDownloadSize = 26416
|
||||||
|
//ExternalDownloadMd5 = "c67c6e3cae79a95342485676571e8a5c"
|
||||||
|
|
||||||
DownloadName = "download.data"
|
DownloadName = "download.data"
|
||||||
DownloadRename = "download (1).data"
|
DownloadRename = "download (1).data"
|
||||||
DownloadFile = Dir + DownloadName
|
DownloadFile = Dir + DownloadName
|
||||||
|
|||||||
@@ -3,7 +3,10 @@ package base
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/GopeedLab/gopeed/pkg/util"
|
"github.com/GopeedLab/gopeed/pkg/util"
|
||||||
|
"github.com/mattn/go-ieproxy"
|
||||||
"golang.org/x/exp/slices"
|
"golang.org/x/exp/slices"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -128,3 +131,88 @@ func ParseOptsExtra[E any](opts *Options) error {
|
|||||||
opts.Extra = &t
|
opts.Extra = &t
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DownloaderStoreConfig is the config that can restore the downloader.
|
||||||
|
type DownloaderStoreConfig struct {
|
||||||
|
FirstLoad bool `json:"-"` // FirstLoad is the flag that the config is first time init and not from store
|
||||||
|
|
||||||
|
DownloadDir string `json:"downloadDir"` // DownloadDir is the default directory to save the downloaded files
|
||||||
|
MaxRunning int `json:"maxRunning"` // MaxRunning is the max running download count
|
||||||
|
ProtocolConfig map[string]any `json:"protocolConfig"` // ProtocolConfig is special config for each protocol
|
||||||
|
Extra map[string]any `json:"extra"`
|
||||||
|
Proxy *DownloaderProxyConfig `json:"proxy"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *DownloaderStoreConfig) Init() *DownloaderStoreConfig {
|
||||||
|
if cfg.MaxRunning == 0 {
|
||||||
|
cfg.MaxRunning = 5
|
||||||
|
}
|
||||||
|
if cfg.Proxy == nil {
|
||||||
|
cfg.Proxy = &DownloaderProxyConfig{}
|
||||||
|
}
|
||||||
|
return cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
type DownloaderProxyConfig struct {
|
||||||
|
Enable bool `json:"enable"`
|
||||||
|
// System is the flag that use system proxy
|
||||||
|
System bool `json:"system"`
|
||||||
|
Scheme string `json:"scheme"`
|
||||||
|
Host string `json:"host"`
|
||||||
|
Usr string `json:"usr"`
|
||||||
|
Pwd string `json:"pwd"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *DownloaderProxyConfig) ToHandler() func(r *http.Request) (*url.URL, error) {
|
||||||
|
if cfg == nil || cfg.Enable == false {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if cfg.System {
|
||||||
|
ieproxy.ReloadConf()
|
||||||
|
return ieproxy.GetProxyFunc()
|
||||||
|
}
|
||||||
|
if cfg.Scheme == "" || cfg.Host == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return http.ProxyURL(util.BuildProxyUrl(cfg.Scheme, cfg.Host, cfg.Usr, cfg.Pwd))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToUrl returns the proxy url, just for git clone
|
||||||
|
func (cfg *DownloaderProxyConfig) ToUrl() *url.URL {
|
||||||
|
if cfg == nil || cfg.Enable == false {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if cfg.System {
|
||||||
|
ieproxy.ReloadConf()
|
||||||
|
static := ieproxy.GetConf().Static
|
||||||
|
if static.Active && len(static.Protocols) > 0 {
|
||||||
|
// If only one protocol, use it
|
||||||
|
if len(static.Protocols) == 1 {
|
||||||
|
for _, v := range static.Protocols {
|
||||||
|
return parseUrlSafe(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Check https
|
||||||
|
if v, ok := static.Protocols["https"]; ok {
|
||||||
|
return parseUrlSafe(v)
|
||||||
|
}
|
||||||
|
// Check http
|
||||||
|
if v, ok := static.Protocols["http"]; ok {
|
||||||
|
return parseUrlSafe(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if cfg.Scheme == "" || cfg.Host == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return util.BuildProxyUrl(cfg.Scheme, cfg.Host, cfg.Usr, cfg.Pwd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseUrlSafe(rawUrl string) *url.URL {
|
||||||
|
u, err := url.Parse(rawUrl)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return u
|
||||||
|
}
|
||||||
@@ -110,7 +110,7 @@ func (d *Downloader) Setup() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// load config from storage
|
// load config from storage
|
||||||
var cfg DownloaderStoreConfig
|
var cfg base.DownloaderStoreConfig
|
||||||
exist, err := d.storage.Get(bucketConfig, "config", &cfg)
|
exist, err := d.storage.Get(bucketConfig, "config", &cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -118,7 +118,7 @@ func (d *Downloader) Setup() error {
|
|||||||
if exist {
|
if exist {
|
||||||
d.cfg.DownloaderStoreConfig = &cfg
|
d.cfg.DownloaderStoreConfig = &cfg
|
||||||
} else {
|
} else {
|
||||||
d.cfg.DownloaderStoreConfig = &DownloaderStoreConfig{
|
d.cfg.DownloaderStoreConfig = &base.DownloaderStoreConfig{
|
||||||
FirstLoad: true,
|
FirstLoad: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -225,7 +225,7 @@ func (d *Downloader) setupFetcher(fetcher fetcher.Fetcher) {
|
|||||||
ctl.GetConfig = func(v any) bool {
|
ctl.GetConfig = func(v any) bool {
|
||||||
return d.getProtocolConfig(fetcher.Name(), v)
|
return d.getProtocolConfig(fetcher.Name(), v)
|
||||||
}
|
}
|
||||||
ctl.ProxyUrl = d.cfg.ProxyUrl()
|
ctl.ProxyConfig = d.cfg.Proxy
|
||||||
fetcher.Setup(ctl)
|
fetcher.Setup(ctl)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -301,6 +301,18 @@ func (d *Downloader) CreateDirect(req *base.Request, opts *base.Options) (taskId
|
|||||||
return d.doCreate(fetcher, opts)
|
return d.doCreate(fetcher, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *Downloader) CreateDirectBatch(reqs []*base.Request, opts *base.Options) (taskId []string, err error) {
|
||||||
|
taskIds := make([]string, 0)
|
||||||
|
for _, req := range reqs {
|
||||||
|
taskId, err := d.CreateDirect(req, opts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
taskIds = append(taskIds, taskId)
|
||||||
|
}
|
||||||
|
return taskIds, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (d *Downloader) Create(rrId string, opts *base.Options) (taskId string, err error) {
|
func (d *Downloader) Create(rrId string, opts *base.Options) (taskId string, err error) {
|
||||||
d.fetcherMapLock.RLock()
|
d.fetcherMapLock.RLock()
|
||||||
fetcher, ok := d.fetcherCache[rrId]
|
fetcher, ok := d.fetcherCache[rrId]
|
||||||
@@ -603,11 +615,11 @@ func (d *Downloader) GetTasksByStatues(statues []base.Status) []*Task {
|
|||||||
return tasks
|
return tasks
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Downloader) GetConfig() (*DownloaderStoreConfig, error) {
|
func (d *Downloader) GetConfig() (*base.DownloaderStoreConfig, error) {
|
||||||
return d.cfg.DownloaderStoreConfig, nil
|
return d.cfg.DownloaderStoreConfig, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Downloader) PutConfig(v *DownloaderStoreConfig) error {
|
func (d *Downloader) PutConfig(v *base.DownloaderStoreConfig) error {
|
||||||
d.cfg.DownloaderStoreConfig = v
|
d.cfg.DownloaderStoreConfig = v
|
||||||
return d.storage.Put(bucketConfig, "config", v)
|
return d.storage.Put(bucketConfig, "config", v)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"github.com/GopeedLab/gopeed/internal/test"
|
"github.com/GopeedLab/gopeed/internal/test"
|
||||||
"github.com/GopeedLab/gopeed/pkg/base"
|
"github.com/GopeedLab/gopeed/pkg/base"
|
||||||
"github.com/GopeedLab/gopeed/pkg/protocol/http"
|
"github.com/GopeedLab/gopeed/pkg/protocol/http"
|
||||||
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@@ -83,39 +84,95 @@ func TestDownloader_Create(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDownloader_CreateWithProxy(t *testing.T) {
|
func TestDownloader_CreateDirectBatch(t *testing.T) {
|
||||||
// No proxy
|
listener := test.StartTestFileServer()
|
||||||
doTestDownloaderCreateWithProxy(t, false, func(proxyCfg *DownloaderProxyConfig) *DownloaderProxyConfig {
|
defer listener.Close()
|
||||||
return nil
|
|
||||||
})
|
downloader := NewDownloader(nil)
|
||||||
// Disable proxy
|
if err := downloader.Setup(); err != nil {
|
||||||
doTestDownloaderCreateWithProxy(t, false, func(proxyCfg *DownloaderProxyConfig) *DownloaderProxyConfig {
|
t.Fatal(err)
|
||||||
proxyCfg.Enable = false
|
}
|
||||||
return proxyCfg
|
defer downloader.Clear()
|
||||||
})
|
|
||||||
// Invalid proxy scheme
|
reqs := make([]*base.Request, 0)
|
||||||
doTestDownloaderCreateWithProxy(t, false, func(proxyCfg *DownloaderProxyConfig) *DownloaderProxyConfig {
|
for i := 0; i < 6; i++ {
|
||||||
proxyCfg.Scheme = ""
|
req := &base.Request{
|
||||||
return proxyCfg
|
URL: "http://" + listener.Addr().String() + "/" + test.BuildName,
|
||||||
})
|
}
|
||||||
// Invalid proxy host
|
reqs = append(reqs, req)
|
||||||
doTestDownloaderCreateWithProxy(t, false, func(proxyCfg *DownloaderProxyConfig) *DownloaderProxyConfig {
|
}
|
||||||
proxyCfg.Host = ""
|
|
||||||
return proxyCfg
|
_, err := downloader.CreateDirectBatch(reqs, &base.Options{
|
||||||
})
|
Path: test.Dir,
|
||||||
// Use proxy without auth
|
Name: test.DownloadName,
|
||||||
doTestDownloaderCreateWithProxy(t, false, func(proxyCfg *DownloaderProxyConfig) *DownloaderProxyConfig {
|
Extra: http.OptsExtra{
|
||||||
return proxyCfg
|
Connections: 4,
|
||||||
})
|
},
|
||||||
// Use proxy with auth
|
|
||||||
doTestDownloaderCreateWithProxy(t, true, func(proxyCfg *DownloaderProxyConfig) *DownloaderProxyConfig {
|
|
||||||
return proxyCfg
|
|
||||||
})
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks := downloader.GetTasks()
|
||||||
|
if len(tasks) != len(reqs) {
|
||||||
|
t.Errorf("CreateDirectBatch() got = %v, want %v", len(tasks), len(reqs))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func doTestDownloaderCreateWithProxy(t *testing.T, auth bool, buildProxyConfig func(proxyCfg *DownloaderProxyConfig) *DownloaderProxyConfig) {
|
func TestDownloader_CreateWithProxy(t *testing.T) {
|
||||||
httpListener := test.StartTestFileServer()
|
// No proxy
|
||||||
defer httpListener.Close()
|
doTestDownloaderCreateWithProxy(t, false, func(proxyCfg *base.DownloaderProxyConfig) *base.DownloaderProxyConfig {
|
||||||
|
return nil
|
||||||
|
}, nil)
|
||||||
|
// Disable proxy
|
||||||
|
doTestDownloaderCreateWithProxy(t, false, 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 {
|
||||||
|
proxyCfg.System = true
|
||||||
|
return proxyCfg
|
||||||
|
}, nil)
|
||||||
|
// Enable proxy but error proxy environment variable
|
||||||
|
doTestDownloaderCreateWithProxy(t, false, 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
|
||||||
|
return proxyCfg
|
||||||
|
}, func(err error) {
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("doTestDownloaderCreateWithProxy() got = nil, want error")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// Enable system proxy and set proxy environment variable
|
||||||
|
doTestDownloaderCreateWithProxy(t, false, 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 {
|
||||||
|
proxyCfg.Scheme = ""
|
||||||
|
return proxyCfg
|
||||||
|
}, nil)
|
||||||
|
// Invalid proxy host
|
||||||
|
doTestDownloaderCreateWithProxy(t, false, func(proxyCfg *base.DownloaderProxyConfig) *base.DownloaderProxyConfig {
|
||||||
|
proxyCfg.Host = ""
|
||||||
|
return proxyCfg
|
||||||
|
}, nil)
|
||||||
|
// Use proxy without auth
|
||||||
|
doTestDownloaderCreateWithProxy(t, false, func(proxyCfg *base.DownloaderProxyConfig) *base.DownloaderProxyConfig {
|
||||||
|
return proxyCfg
|
||||||
|
}, nil)
|
||||||
|
// Use proxy with auth
|
||||||
|
doTestDownloaderCreateWithProxy(t, true, func(proxyCfg *base.DownloaderProxyConfig) *base.DownloaderProxyConfig {
|
||||||
|
return proxyCfg
|
||||||
|
}, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func doTestDownloaderCreateWithProxy(t *testing.T, auth bool, buildProxyConfig func(proxyCfg *base.DownloaderProxyConfig) *base.DownloaderProxyConfig, errHandler func(err error)) {
|
||||||
usr, pwd := "", ""
|
usr, pwd := "", ""
|
||||||
if auth {
|
if auth {
|
||||||
usr, pwd = "admin", "123"
|
usr, pwd = "admin", "123"
|
||||||
@@ -128,7 +185,7 @@ func doTestDownloaderCreateWithProxy(t *testing.T, auth bool, buildProxyConfig f
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
defer downloader.Clear()
|
defer downloader.Clear()
|
||||||
downloader.cfg.DownloaderStoreConfig.Proxy = buildProxyConfig(&DownloaderProxyConfig{
|
downloader.cfg.DownloaderStoreConfig.Proxy = buildProxyConfig(&base.DownloaderProxyConfig{
|
||||||
Enable: true,
|
Enable: true,
|
||||||
Scheme: "socks5",
|
Scheme: "socks5",
|
||||||
Host: proxyListener.Addr().String(),
|
Host: proxyListener.Addr().String(),
|
||||||
@@ -137,20 +194,24 @@ func doTestDownloaderCreateWithProxy(t *testing.T, auth bool, buildProxyConfig f
|
|||||||
})
|
})
|
||||||
|
|
||||||
req := &base.Request{
|
req := &base.Request{
|
||||||
URL: "http://" + httpListener.Addr().String() + "/" + test.BuildName,
|
URL: test.ExternalDownloadUrl,
|
||||||
}
|
}
|
||||||
rr, err := downloader.Resolve(req)
|
rr, err := downloader.Resolve(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
if errHandler == nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
errHandler(err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
want := &base.Resource{
|
want := &base.Resource{
|
||||||
Size: test.BuildSize,
|
Size: test.ExternalDownloadSize,
|
||||||
Range: true,
|
Range: true,
|
||||||
Files: []*base.FileInfo{
|
Files: []*base.FileInfo{
|
||||||
{
|
{
|
||||||
Name: test.BuildName,
|
Name: test.ExternalDownloadName,
|
||||||
Path: "",
|
Path: "",
|
||||||
Size: test.BuildSize,
|
Size: test.ExternalDownloadSize,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -285,7 +346,7 @@ func TestDownloader_Protocol_Config(t *testing.T) {
|
|||||||
t.Errorf("getProtocolConfig() got = %v, want %v", exits, false)
|
t.Errorf("getProtocolConfig() got = %v, want %v", exits, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
storeCfg := &DownloaderStoreConfig{
|
storeCfg := &base.DownloaderStoreConfig{
|
||||||
DownloadDir: "./downloads",
|
DownloadDir: "./downloads",
|
||||||
ProtocolConfig: map[string]any{
|
ProtocolConfig: map[string]any{
|
||||||
"http": map[string]any{
|
"http": map[string]any{
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package engine
|
|||||||
import (
|
import (
|
||||||
_ "embed"
|
_ "embed"
|
||||||
"errors"
|
"errors"
|
||||||
|
"github.com/GopeedLab/gopeed/pkg/base"
|
||||||
gojaerror "github.com/GopeedLab/gopeed/pkg/download/engine/inject/error"
|
gojaerror "github.com/GopeedLab/gopeed/pkg/download/engine/inject/error"
|
||||||
"github.com/GopeedLab/gopeed/pkg/download/engine/inject/file"
|
"github.com/GopeedLab/gopeed/pkg/download/engine/inject/file"
|
||||||
"github.com/GopeedLab/gopeed/pkg/download/engine/inject/formdata"
|
"github.com/GopeedLab/gopeed/pkg/download/engine/inject/formdata"
|
||||||
@@ -11,7 +12,6 @@ import (
|
|||||||
"github.com/dop251/goja"
|
"github.com/dop251/goja"
|
||||||
"github.com/dop251/goja_nodejs/eventloop"
|
"github.com/dop251/goja_nodejs/eventloop"
|
||||||
gojaurl "github.com/dop251/goja_nodejs/url"
|
gojaurl "github.com/dop251/goja_nodejs/url"
|
||||||
"net/url"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -110,7 +110,7 @@ func (e *Engine) Close() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
ProxyURL *url.URL
|
ProxyConfig *base.DownloaderProxyConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewEngine(cfg *Config) *Engine {
|
func NewEngine(cfg *Config) *Engine {
|
||||||
@@ -135,7 +135,7 @@ func NewEngine(cfg *Config) *Engine {
|
|||||||
if err := formdata.Enable(runtime); err != nil {
|
if err := formdata.Enable(runtime); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := xhr.Enable(runtime, cfg.ProxyURL); err != nil {
|
if err := xhr.Enable(runtime, cfg.ProxyConfig.ToHandler()); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if _, err := runtime.RunString(polyfillScript); err != nil {
|
if _, err := runtime.RunString(polyfillScript); err != nil {
|
||||||
|
|||||||
@@ -7,10 +7,10 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/GopeedLab/gopeed/internal/test"
|
"github.com/GopeedLab/gopeed/internal/test"
|
||||||
|
"github.com/GopeedLab/gopeed/pkg/base"
|
||||||
gojaerror "github.com/GopeedLab/gopeed/pkg/download/engine/inject/error"
|
gojaerror "github.com/GopeedLab/gopeed/pkg/download/engine/inject/error"
|
||||||
"github.com/GopeedLab/gopeed/pkg/download/engine/inject/file"
|
"github.com/GopeedLab/gopeed/pkg/download/engine/inject/file"
|
||||||
gojautil "github.com/GopeedLab/gopeed/pkg/download/engine/util"
|
gojautil "github.com/GopeedLab/gopeed/pkg/download/engine/util"
|
||||||
"github.com/GopeedLab/gopeed/pkg/util"
|
|
||||||
"github.com/dop251/goja"
|
"github.com/dop251/goja"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
@@ -217,8 +217,16 @@ func doTestFetchWithProxy(t *testing.T, usr, pwd string) {
|
|||||||
|
|
||||||
proxyListener := test.StartSocks5Server(usr, pwd)
|
proxyListener := test.StartSocks5Server(usr, pwd)
|
||||||
defer proxyListener.Close()
|
defer proxyListener.Close()
|
||||||
|
engine := NewEngine(&Config{
|
||||||
engine := NewEngine(&Config{ProxyURL: util.BuildProxyUrl("socks5", proxyListener.Addr().String(), usr, pwd)})
|
ProxyConfig: &base.DownloaderProxyConfig{
|
||||||
|
Enable: true,
|
||||||
|
System: false,
|
||||||
|
Scheme: "socks5",
|
||||||
|
Host: proxyListener.Addr().String(),
|
||||||
|
Usr: usr,
|
||||||
|
Pwd: pwd,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
if _, err := engine.RunString(fmt.Sprintf("var host = 'http://%s';", httpListener.Addr().String())); err != nil {
|
if _, err := engine.RunString(fmt.Sprintf("var host = 'http://%s';", httpListener.Addr().String())); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package xhr
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"errors"
|
||||||
"github.com/GopeedLab/gopeed/pkg/download/engine/inject/file"
|
"github.com/GopeedLab/gopeed/pkg/download/engine/inject/file"
|
||||||
"github.com/GopeedLab/gopeed/pkg/download/engine/inject/formdata"
|
"github.com/GopeedLab/gopeed/pkg/download/engine/inject/formdata"
|
||||||
"github.com/GopeedLab/gopeed/pkg/download/engine/util"
|
"github.com/GopeedLab/gopeed/pkg/download/engine/util"
|
||||||
@@ -120,7 +121,7 @@ type XMLHttpRequest struct {
|
|||||||
requestHeaders map[string]string
|
requestHeaders map[string]string
|
||||||
responseHeaders map[string]string
|
responseHeaders map[string]string
|
||||||
aborted bool
|
aborted bool
|
||||||
proxyUrl *url.URL
|
proxyHandler func(r *http.Request) (*url.URL, error)
|
||||||
|
|
||||||
Upload *XMLHttpRequestUpload `json:"upload"`
|
Upload *XMLHttpRequestUpload `json:"upload"`
|
||||||
Timeout int `json:"timeout"`
|
Timeout int `json:"timeout"`
|
||||||
@@ -202,9 +203,8 @@ func (xhr *XMLHttpRequest) Send(data goja.Value) {
|
|||||||
for k, v := range xhr.requestHeaders {
|
for k, v := range xhr.requestHeaders {
|
||||||
req.Header.Set(k, v)
|
req.Header.Set(k, v)
|
||||||
}
|
}
|
||||||
transport := &http.Transport{}
|
transport := &http.Transport{
|
||||||
if xhr.proxyUrl != nil {
|
Proxy: xhr.proxyHandler,
|
||||||
transport.Proxy = http.ProxyURL(xhr.proxyUrl)
|
|
||||||
}
|
}
|
||||||
client := &http.Client{
|
client := &http.Client{
|
||||||
Transport: transport,
|
Transport: transport,
|
||||||
@@ -213,7 +213,8 @@ func (xhr *XMLHttpRequest) Send(data goja.Value) {
|
|||||||
resp, err := client.Do(req)
|
resp, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// handle timeout error
|
// handle timeout error
|
||||||
if err, ok := err.(net.Error); ok && err.Timeout() {
|
var ne net.Error
|
||||||
|
if errors.As(err, &ne) && ne.Timeout() {
|
||||||
if xhr.Timeout > 0 {
|
if xhr.Timeout > 0 {
|
||||||
xhr.Upload.callOntimeout()
|
xhr.Upload.callOntimeout()
|
||||||
xhr.callOntimeout()
|
xhr.callOntimeout()
|
||||||
@@ -313,7 +314,7 @@ func (xhr *XMLHttpRequest) parseData(data goja.Value) any {
|
|||||||
return data.String()
|
return data.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func Enable(runtime *goja.Runtime, proxyUrl *url.URL) error {
|
func Enable(runtime *goja.Runtime, proxyHandler func(r *http.Request) (*url.URL, error)) error {
|
||||||
progressEvent := runtime.ToValue(func(call goja.ConstructorCall) *goja.Object {
|
progressEvent := runtime.ToValue(func(call goja.ConstructorCall) *goja.Object {
|
||||||
if len(call.Arguments) < 1 {
|
if len(call.Arguments) < 1 {
|
||||||
util.ThrowTypeError(runtime, "Failed to construct 'ProgressEvent': 1 argument required, but only 0 present.")
|
util.ThrowTypeError(runtime, "Failed to construct 'ProgressEvent': 1 argument required, but only 0 present.")
|
||||||
@@ -327,7 +328,7 @@ func Enable(runtime *goja.Runtime, proxyUrl *url.URL) error {
|
|||||||
})
|
})
|
||||||
xhr := runtime.ToValue(func(call goja.ConstructorCall) *goja.Object {
|
xhr := runtime.ToValue(func(call goja.ConstructorCall) *goja.Object {
|
||||||
instance := &XMLHttpRequest{
|
instance := &XMLHttpRequest{
|
||||||
proxyUrl: proxyUrl,
|
proxyHandler: proxyHandler,
|
||||||
Upload: &XMLHttpRequestUpload{
|
Upload: &XMLHttpRequestUpload{
|
||||||
EventProp: &EventProp{
|
EventProp: &EventProp{
|
||||||
eventListeners: make(map[string]func(event *ProgressEvent)),
|
eventListeners: make(map[string]func(event *ProgressEvent)),
|
||||||
|
|||||||
@@ -199,7 +199,7 @@ func (d *Downloader) fetchExtensionByGit(url string, handler func(tempExtPath st
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
proxyOptions := transport.ProxyOptions{}
|
proxyOptions := transport.ProxyOptions{}
|
||||||
proxyUrl := d.cfg.ProxyUrl()
|
proxyUrl := d.cfg.DownloaderStoreConfig.Proxy.ToUrl()
|
||||||
if proxyUrl != nil {
|
if proxyUrl != nil {
|
||||||
proxyOptions.URL = proxyUrl.Scheme + "://" + proxyUrl.Host
|
proxyOptions.URL = proxyUrl.Scheme + "://" + proxyUrl.Host
|
||||||
proxyOptions.Username = proxyUrl.User.Username()
|
proxyOptions.Username = proxyUrl.User.Username()
|
||||||
@@ -341,7 +341,7 @@ func doTrigger[T any](d *Downloader, event ActivationEvent, req *base.Request, c
|
|||||||
req.Labels = make(map[string]string)
|
req.Labels = make(map[string]string)
|
||||||
}
|
}
|
||||||
engine := engine.NewEngine(&engine.Config{
|
engine := engine.NewEngine(&engine.Config{
|
||||||
ProxyURL: d.cfg.ProxyUrl(),
|
ProxyConfig: d.cfg.Proxy,
|
||||||
})
|
})
|
||||||
defer engine.Close()
|
defer engine.Close()
|
||||||
err = engine.Runtime.Set("gopeed", gopeed)
|
err = engine.Runtime.Set("gopeed", gopeed)
|
||||||
|
|||||||
+1
-42
@@ -7,9 +7,7 @@ import (
|
|||||||
"github.com/GopeedLab/gopeed/internal/protocol/http"
|
"github.com/GopeedLab/gopeed/internal/protocol/http"
|
||||||
"github.com/GopeedLab/gopeed/pkg/base"
|
"github.com/GopeedLab/gopeed/pkg/base"
|
||||||
"github.com/GopeedLab/gopeed/pkg/util"
|
"github.com/GopeedLab/gopeed/pkg/util"
|
||||||
|
|
||||||
gonanoid "github.com/matoous/go-nanoid/v2"
|
gonanoid "github.com/matoous/go-nanoid/v2"
|
||||||
"net/url"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -87,7 +85,7 @@ type DownloaderConfig struct {
|
|||||||
|
|
||||||
ProductionMode bool
|
ProductionMode bool
|
||||||
|
|
||||||
*DownloaderStoreConfig
|
*base.DownloaderStoreConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *DownloaderConfig) Init() *DownloaderConfig {
|
func (cfg *DownloaderConfig) Init() *DownloaderConfig {
|
||||||
@@ -108,42 +106,3 @@ func (cfg *DownloaderConfig) Init() *DownloaderConfig {
|
|||||||
}
|
}
|
||||||
return cfg
|
return cfg
|
||||||
}
|
}
|
||||||
|
|
||||||
// DownloaderStoreConfig is the config that can restore the downloader.
|
|
||||||
type DownloaderStoreConfig struct {
|
|
||||||
FirstLoad bool `json:"-"` // FirstLoad is the flag that the config is first time init and not from store
|
|
||||||
|
|
||||||
DownloadDir string `json:"downloadDir"` // DownloadDir is the default directory to save the downloaded files
|
|
||||||
MaxRunning int `json:"maxRunning"` // MaxRunning is the max running download count
|
|
||||||
ProtocolConfig map[string]any `json:"protocolConfig"` // ProtocolConfig is special config for each protocol
|
|
||||||
Extra map[string]any `json:"extra"`
|
|
||||||
Proxy *DownloaderProxyConfig `json:"proxy"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *DownloaderStoreConfig) Init() *DownloaderStoreConfig {
|
|
||||||
if cfg.MaxRunning == 0 {
|
|
||||||
cfg.MaxRunning = 5
|
|
||||||
}
|
|
||||||
if cfg.Proxy == nil {
|
|
||||||
cfg.Proxy = &DownloaderProxyConfig{}
|
|
||||||
}
|
|
||||||
return cfg
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *DownloaderStoreConfig) ProxyUrl() *url.URL {
|
|
||||||
if cfg.Proxy == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if cfg.Proxy.Enable == false || cfg.Proxy.Scheme == "" || cfg.Proxy.Host == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return util.BuildProxyUrl(cfg.Proxy.Scheme, cfg.Proxy.Host, cfg.Proxy.Usr, cfg.Proxy.Pwd)
|
|
||||||
}
|
|
||||||
|
|
||||||
type DownloaderProxyConfig struct {
|
|
||||||
Enable bool `json:"enable"`
|
|
||||||
Scheme string `json:"scheme"`
|
|
||||||
Host string `json:"host"`
|
|
||||||
Usr string `json:"usr"`
|
|
||||||
Pwd string `json:"pwd"`
|
|
||||||
}
|
|
||||||
@@ -69,7 +69,9 @@ func (n *MemStorage) Setup(buckets []string) error {
|
|||||||
func (n *MemStorage) Put(bucket string, key string, v any) error {
|
func (n *MemStorage) Put(bucket string, key string, v any) error {
|
||||||
n.lock.Lock()
|
n.lock.Lock()
|
||||||
defer n.lock.Unlock()
|
defer n.lock.Unlock()
|
||||||
memData[bucket][key] = v
|
if bucketData, ok := memData[bucket]; ok {
|
||||||
|
bucketData[key] = v
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+30
-2
@@ -8,8 +8,20 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"runtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func Info(w http.ResponseWriter, r *http.Request) {
|
||||||
|
info := map[string]any{
|
||||||
|
"version": base.Version,
|
||||||
|
"runtime": runtime.Version(),
|
||||||
|
"os": runtime.GOOS,
|
||||||
|
"arch": runtime.GOARCH,
|
||||||
|
}
|
||||||
|
info["inDocker"] = base.InDocker == "true"
|
||||||
|
WriteJson(w, model.NewOkResult(info))
|
||||||
|
}
|
||||||
|
|
||||||
func Resolve(w http.ResponseWriter, r *http.Request) {
|
func Resolve(w http.ResponseWriter, r *http.Request) {
|
||||||
var req base.Request
|
var req base.Request
|
||||||
if ReadJson(r, w, &req) {
|
if ReadJson(r, w, &req) {
|
||||||
@@ -45,6 +57,22 @@ func CreateTask(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func CreateTaskBatch(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req model.CreateTaskBatch
|
||||||
|
if ReadJson(r, w, &req) {
|
||||||
|
if len(req.Reqs) == 0 {
|
||||||
|
WriteJson(w, model.NewErrorResult("param invalid: reqs", model.CodeInvalidParam))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
taskIds, err := Downloader.CreateDirectBatch(req.Reqs, req.Opt)
|
||||||
|
if err != nil {
|
||||||
|
WriteJson(w, model.NewErrorResult(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
WriteJson(w, model.NewOkResult(taskIds))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func PauseTask(w http.ResponseWriter, r *http.Request) {
|
func PauseTask(w http.ResponseWriter, r *http.Request) {
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
taskId := vars["id"]
|
taskId := vars["id"]
|
||||||
@@ -142,7 +170,7 @@ func GetConfig(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func PutConfig(w http.ResponseWriter, r *http.Request) {
|
func PutConfig(w http.ResponseWriter, r *http.Request) {
|
||||||
var cfg download.DownloaderStoreConfig
|
var cfg base.DownloaderStoreConfig
|
||||||
if ReadJson(r, w, &cfg) {
|
if ReadJson(r, w, &cfg) {
|
||||||
if err := Downloader.PutConfig(&cfg); err != nil {
|
if err := Downloader.PutConfig(&cfg); err != nil {
|
||||||
WriteJson(w, model.NewErrorResult(err.Error()))
|
WriteJson(w, model.NewErrorResult(err.Error()))
|
||||||
@@ -309,7 +337,7 @@ func writeError(w http.ResponseWriter, msg string) {
|
|||||||
w.Write([]byte(msg))
|
w.Write([]byte(msg))
|
||||||
}
|
}
|
||||||
|
|
||||||
func getServerConfig() *download.DownloaderStoreConfig {
|
func getServerConfig() *base.DownloaderStoreConfig {
|
||||||
cfg, _ := Downloader.GetConfig()
|
cfg, _ := Downloader.GetConfig()
|
||||||
return cfg
|
return cfg
|
||||||
}
|
}
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
package model
|
|
||||||
|
|
||||||
type Command struct {
|
|
||||||
Protocol string `json:"protocol"`
|
|
||||||
Action string `json:"action"`
|
|
||||||
Params any `json:"params"`
|
|
||||||
}
|
|
||||||
@@ -7,3 +7,8 @@ type CreateTask struct {
|
|||||||
Req *base.Request `json:"req"`
|
Req *base.Request `json:"req"`
|
||||||
Opt *base.Options `json:"opt"`
|
Opt *base.Options `json:"opt"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CreateTaskBatch struct {
|
||||||
|
Reqs []*base.Request `json:"reqs"`
|
||||||
|
Opt *base.Options `json:"opt"`
|
||||||
|
}
|
||||||
@@ -88,8 +88,10 @@ func BuildServer(startCfg *model.StartConfig) (*http.Server, net.Listener, error
|
|||||||
}
|
}
|
||||||
|
|
||||||
var r = mux.NewRouter()
|
var r = mux.NewRouter()
|
||||||
|
r.Methods(http.MethodGet).Path("/api/v1/info").HandlerFunc(Info)
|
||||||
r.Methods(http.MethodPost).Path("/api/v1/resolve").HandlerFunc(Resolve)
|
r.Methods(http.MethodPost).Path("/api/v1/resolve").HandlerFunc(Resolve)
|
||||||
r.Methods(http.MethodPost).Path("/api/v1/tasks").HandlerFunc(CreateTask)
|
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/{id}/pause").HandlerFunc(PauseTask)
|
||||||
r.Methods(http.MethodPut).Path("/api/v1/tasks/{id}/continue").HandlerFunc(ContinueTask)
|
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/pause").HandlerFunc(PauseAllTask)
|
||||||
|
|||||||
+29
-2
@@ -63,6 +63,18 @@ var (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestInfo(t *testing.T) {
|
||||||
|
matchKeys := []string{"version", "runtime", "os", "arch", "inDocker"}
|
||||||
|
doTest(func() {
|
||||||
|
resp := httpRequestCheckOk[map[string]any](http.MethodGet, "/api/v1/info", nil)
|
||||||
|
for _, key := range matchKeys {
|
||||||
|
if _, ok := resp[key]; !ok {
|
||||||
|
t.Errorf("Info() missing key = %v", key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestResolve(t *testing.T) {
|
func TestResolve(t *testing.T) {
|
||||||
doTest(func() {
|
doTest(func() {
|
||||||
resp := httpRequestCheckOk[*download.ResolveResult](http.MethodPost, "/api/v1/resolve", taskReq)
|
resp := httpRequestCheckOk[*download.ResolveResult](http.MethodPost, "/api/v1/resolve", taskReq)
|
||||||
@@ -125,6 +137,21 @@ func TestCreateDirectTask(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCreateDirectTaskBatch(t *testing.T) {
|
||||||
|
doTest(func() {
|
||||||
|
reqs := make([]*base.Request, 0)
|
||||||
|
for i := 0; i < 6; i++ {
|
||||||
|
reqs = append(reqs, createReq.Req)
|
||||||
|
}
|
||||||
|
taskIds := httpRequestCheckOk[[]string](http.MethodPost, "/api/v1/tasks/batch", &model.CreateTaskBatch{
|
||||||
|
Reqs: reqs,
|
||||||
|
})
|
||||||
|
if len(taskIds) != len(reqs) {
|
||||||
|
t.Errorf("CreateDirectTaskBatch() got = %v, want %v", len(taskIds), len(reqs))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestCreateDirectTaskWithResoled(t *testing.T) {
|
func TestCreateDirectTaskWithResoled(t *testing.T) {
|
||||||
doTest(func() {
|
doTest(func() {
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
@@ -324,7 +351,7 @@ func TestGetTasks(t *testing.T) {
|
|||||||
|
|
||||||
func TestGetAndPutConfig(t *testing.T) {
|
func TestGetAndPutConfig(t *testing.T) {
|
||||||
doTest(func() {
|
doTest(func() {
|
||||||
cfg := httpRequestCheckOk[*download.DownloaderStoreConfig](http.MethodGet, "/api/v1/config", nil)
|
cfg := httpRequestCheckOk[*base.DownloaderStoreConfig](http.MethodGet, "/api/v1/config", nil)
|
||||||
cfg.DownloadDir = "./download"
|
cfg.DownloadDir = "./download"
|
||||||
cfg.Extra = map[string]any{
|
cfg.Extra = map[string]any{
|
||||||
"serverConfig": &Config{
|
"serverConfig": &Config{
|
||||||
@@ -335,7 +362,7 @@ func TestGetAndPutConfig(t *testing.T) {
|
|||||||
}
|
}
|
||||||
httpRequestCheckOk[any](http.MethodPut, "/api/v1/config", cfg)
|
httpRequestCheckOk[any](http.MethodPut, "/api/v1/config", cfg)
|
||||||
|
|
||||||
newCfg := httpRequestCheckOk[*download.DownloaderStoreConfig](http.MethodGet, "/api/v1/config", nil)
|
newCfg := httpRequestCheckOk[*base.DownloaderStoreConfig](http.MethodGet, "/api/v1/config", nil)
|
||||||
if !test.JsonEqual(cfg, newCfg) {
|
if !test.JsonEqual(cfg, newCfg) {
|
||||||
t.Errorf("GetAndPutConfig() got = %v, want %v", test.ToJson(newCfg), test.ToJson(cfg))
|
t.Errorf("GetAndPutConfig() got = %v, want %v", test.ToJson(newCfg), test.ToJson(cfg))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package util
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -51,3 +52,14 @@ func BuildProxyUrl(scheme, host, usr, pwd string) *url.URL {
|
|||||||
Host: host,
|
Host: host,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ProxyUrlToHandler gets the proxy handler from the proxy url.
|
||||||
|
func ProxyUrlToHandler(proxyUrl *url.URL) func(*http.Request) (*url.URL, error) {
|
||||||
|
if proxyUrl == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if proxyUrl.Scheme == "system" {
|
||||||
|
return http.ProxyFromEnvironment
|
||||||
|
}
|
||||||
|
return http.ProxyURL(proxyUrl)
|
||||||
|
}
|
||||||
@@ -13,6 +13,7 @@
|
|||||||
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
||||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
||||||
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
||||||
|
B44F54F47E581A39546303C9 /* libresolv.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 029EF293EAA92EB3D18BBF19 /* libresolv.tbd */; };
|
||||||
D081C25C294826C0006EB10B /* libc++.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = D081C25B294826C0006EB10B /* libc++.tbd */; };
|
D081C25C294826C0006EB10B /* libc++.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = D081C25B294826C0006EB10B /* libc++.tbd */; };
|
||||||
D0E623A929482D160001185E /* Libgopeed.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0E623A729482D030001185E /* Libgopeed.xcframework */; };
|
D0E623A929482D160001185E /* Libgopeed.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0E623A729482D030001185E /* Libgopeed.xcframework */; };
|
||||||
D0E623AA29482D160001185E /* Libgopeed.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D0E623A729482D030001185E /* Libgopeed.xcframework */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
D0E623AA29482D160001185E /* Libgopeed.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D0E623A729482D030001185E /* Libgopeed.xcframework */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||||
@@ -34,6 +35,7 @@
|
|||||||
/* End PBXCopyFilesBuildPhase section */
|
/* End PBXCopyFilesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
|
029EF293EAA92EB3D18BBF19 /* libresolv.tbd */ = {isa = PBXFileReference; includeInIndex = 1; name = libresolv.tbd; path = usr/lib/libresolv.tbd; sourceTree = "<group>"; };
|
||||||
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
|
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
|
||||||
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
|
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
|
||||||
25B61E78A388C87913468FD6 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
|
25B61E78A388C87913468FD6 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
|
||||||
@@ -63,6 +65,7 @@
|
|||||||
D0E623A929482D160001185E /* Libgopeed.xcframework in Frameworks */,
|
D0E623A929482D160001185E /* Libgopeed.xcframework in Frameworks */,
|
||||||
D081C25C294826C0006EB10B /* libc++.tbd in Frameworks */,
|
D081C25C294826C0006EB10B /* libc++.tbd in Frameworks */,
|
||||||
DCA5B0FA95A8007ADEDACA1B /* Pods_Runner.framework in Frameworks */,
|
DCA5B0FA95A8007ADEDACA1B /* Pods_Runner.framework in Frameworks */,
|
||||||
|
B44F54F47E581A39546303C9 /* libresolv.tbd in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@@ -130,6 +133,7 @@
|
|||||||
D0E623A729482D030001185E /* Libgopeed.xcframework */,
|
D0E623A729482D030001185E /* Libgopeed.xcframework */,
|
||||||
D081C25B294826C0006EB10B /* libc++.tbd */,
|
D081C25B294826C0006EB10B /* libc++.tbd */,
|
||||||
70BC8D37FAC641C7F20125E8 /* Pods_Runner.framework */,
|
70BC8D37FAC641C7F20125E8 /* Pods_Runner.framework */,
|
||||||
|
029EF293EAA92EB3D18BBF19 /* libresolv.tbd */,
|
||||||
);
|
);
|
||||||
name = Frameworks;
|
name = Frameworks;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ class DownloaderConfig {
|
|||||||
|
|
||||||
factory DownloaderConfig.fromJson(Map<String, dynamic> json) =>
|
factory DownloaderConfig.fromJson(Map<String, dynamic> json) =>
|
||||||
_$DownloaderConfigFromJson(json);
|
_$DownloaderConfigFromJson(json);
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => _$DownloaderConfigToJson(this);
|
Map<String, dynamic> toJson() => _$DownloaderConfigToJson(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,6 +30,7 @@ class ProtocolConfig {
|
|||||||
|
|
||||||
factory ProtocolConfig.fromJson(Map<String, dynamic>? json) =>
|
factory ProtocolConfig.fromJson(Map<String, dynamic>? json) =>
|
||||||
json == null ? ProtocolConfig() : _$ProtocolConfigFromJson(json);
|
json == null ? ProtocolConfig() : _$ProtocolConfigFromJson(json);
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => _$ProtocolConfigToJson(this);
|
Map<String, dynamic> toJson() => _$ProtocolConfigToJson(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,6 +49,7 @@ class HttpConfig {
|
|||||||
|
|
||||||
factory HttpConfig.fromJson(Map<String, dynamic> json) =>
|
factory HttpConfig.fromJson(Map<String, dynamic> json) =>
|
||||||
_$HttpConfigFromJson(json);
|
_$HttpConfigFromJson(json);
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => _$HttpConfigToJson(this);
|
Map<String, dynamic> toJson() => _$HttpConfigToJson(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,25 +62,34 @@ class BtConfig {
|
|||||||
|
|
||||||
factory BtConfig.fromJson(Map<String, dynamic> json) =>
|
factory BtConfig.fromJson(Map<String, dynamic> json) =>
|
||||||
_$BtConfigFromJson(json);
|
_$BtConfigFromJson(json);
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => _$BtConfigToJson(this);
|
Map<String, dynamic> toJson() => _$BtConfigToJson(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonSerializable(explicitToJson: true)
|
@JsonSerializable(explicitToJson: true)
|
||||||
class ExtraConfig {
|
class ExtraConfig {
|
||||||
String themeMode = '';
|
String themeMode;
|
||||||
String locale = '';
|
String locale;
|
||||||
|
bool lastDeleteTaskKeep;
|
||||||
|
|
||||||
ExtraConfigBt bt = ExtraConfigBt();
|
ExtraConfigBt bt = ExtraConfigBt();
|
||||||
|
|
||||||
ExtraConfig();
|
ExtraConfig({
|
||||||
|
this.themeMode = '',
|
||||||
|
this.locale = '',
|
||||||
|
this.lastDeleteTaskKeep = false,
|
||||||
|
});
|
||||||
|
|
||||||
factory ExtraConfig.fromJson(Map<String, dynamic>? json) =>
|
factory ExtraConfig.fromJson(Map<String, dynamic>? json) =>
|
||||||
json == null ? ExtraConfig() : _$ExtraConfigFromJson(json);
|
json == null ? ExtraConfig() : _$ExtraConfigFromJson(json);
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => _$ExtraConfigToJson(this);
|
Map<String, dynamic> toJson() => _$ExtraConfigToJson(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
class ProxyConfig {
|
class ProxyConfig {
|
||||||
bool enable;
|
bool enable;
|
||||||
|
bool system;
|
||||||
String scheme;
|
String scheme;
|
||||||
String host;
|
String host;
|
||||||
String usr;
|
String usr;
|
||||||
@@ -85,6 +97,7 @@ class ProxyConfig {
|
|||||||
|
|
||||||
ProxyConfig({
|
ProxyConfig({
|
||||||
this.enable = false,
|
this.enable = false,
|
||||||
|
this.system = false,
|
||||||
this.scheme = '',
|
this.scheme = '',
|
||||||
this.host = '',
|
this.host = '',
|
||||||
this.usr = '',
|
this.usr = '',
|
||||||
@@ -110,5 +123,6 @@ class ExtraConfigBt {
|
|||||||
|
|
||||||
factory ExtraConfigBt.fromJson(Map<String, dynamic> json) =>
|
factory ExtraConfigBt.fromJson(Map<String, dynamic> json) =>
|
||||||
_$ExtraConfigBtFromJson(json);
|
_$ExtraConfigBtFromJson(json);
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => _$ExtraConfigBtToJson(this);
|
Map<String, dynamic> toJson() => _$ExtraConfigBtToJson(this);
|
||||||
}
|
}
|
||||||
@@ -60,20 +60,23 @@ Map<String, dynamic> _$BtConfigToJson(BtConfig instance) => <String, dynamic>{
|
|||||||
'trackers': instance.trackers,
|
'trackers': instance.trackers,
|
||||||
};
|
};
|
||||||
|
|
||||||
ExtraConfig _$ExtraConfigFromJson(Map<String, dynamic> json) => ExtraConfig()
|
ExtraConfig _$ExtraConfigFromJson(Map<String, dynamic> json) => ExtraConfig(
|
||||||
..themeMode = json['themeMode'] as String
|
themeMode: json['themeMode'] as String? ?? '',
|
||||||
..locale = json['locale'] as String
|
locale: json['locale'] as String? ?? '',
|
||||||
..bt = ExtraConfigBt.fromJson(json['bt'] as Map<String, dynamic>);
|
lastDeleteTaskKeep: json['lastDeleteTaskKeep'] as bool? ?? false,
|
||||||
|
)..bt = ExtraConfigBt.fromJson(json['bt'] as Map<String, dynamic>);
|
||||||
|
|
||||||
Map<String, dynamic> _$ExtraConfigToJson(ExtraConfig instance) =>
|
Map<String, dynamic> _$ExtraConfigToJson(ExtraConfig instance) =>
|
||||||
<String, dynamic>{
|
<String, dynamic>{
|
||||||
'themeMode': instance.themeMode,
|
'themeMode': instance.themeMode,
|
||||||
'locale': instance.locale,
|
'locale': instance.locale,
|
||||||
|
'lastDeleteTaskKeep': instance.lastDeleteTaskKeep,
|
||||||
'bt': instance.bt.toJson(),
|
'bt': instance.bt.toJson(),
|
||||||
};
|
};
|
||||||
|
|
||||||
ProxyConfig _$ProxyConfigFromJson(Map<String, dynamic> json) => ProxyConfig(
|
ProxyConfig _$ProxyConfigFromJson(Map<String, dynamic> json) => ProxyConfig(
|
||||||
enable: json['enable'] as bool? ?? false,
|
enable: json['enable'] as bool? ?? false,
|
||||||
|
system: json['system'] as bool? ?? false,
|
||||||
scheme: json['scheme'] as String? ?? '',
|
scheme: json['scheme'] as String? ?? '',
|
||||||
host: json['host'] as String? ?? '',
|
host: json['host'] as String? ?? '',
|
||||||
usr: json['usr'] as String? ?? '',
|
usr: json['usr'] as String? ?? '',
|
||||||
@@ -83,6 +86,7 @@ ProxyConfig _$ProxyConfigFromJson(Map<String, dynamic> json) => ProxyConfig(
|
|||||||
Map<String, dynamic> _$ProxyConfigToJson(ProxyConfig instance) =>
|
Map<String, dynamic> _$ProxyConfigToJson(ProxyConfig instance) =>
|
||||||
<String, dynamic>{
|
<String, dynamic>{
|
||||||
'enable': instance.enable,
|
'enable': instance.enable,
|
||||||
|
'system': instance.system,
|
||||||
'scheme': instance.scheme,
|
'scheme': instance.scheme,
|
||||||
'host': instance.host,
|
'host': instance.host,
|
||||||
'usr': instance.usr,
|
'usr': instance.usr,
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:app_links/app_links.dart';
|
import 'package:app_links/app_links.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_foreground_task/flutter_foreground_task.dart';
|
import 'package:flutter_foreground_task/flutter_foreground_task.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:gopeed/database/entity.dart';
|
import 'package:launch_at_startup/launch_at_startup.dart';
|
||||||
import 'package:lecle_downloads_path_provider/lecle_downloads_path_provider.dart';
|
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:tray_manager/tray_manager.dart';
|
import 'package:tray_manager/tray_manager.dart';
|
||||||
import 'package:uri_to_file/uri_to_file.dart';
|
import 'package:uri_to_file/uri_to_file.dart';
|
||||||
@@ -18,7 +18,9 @@ import '../../../../api/api.dart';
|
|||||||
import '../../../../api/model/downloader_config.dart';
|
import '../../../../api/model/downloader_config.dart';
|
||||||
import '../../../../core/common/start_config.dart';
|
import '../../../../core/common/start_config.dart';
|
||||||
import '../../../../database/database.dart';
|
import '../../../../database/database.dart';
|
||||||
|
import '../../../../database/entity.dart';
|
||||||
import '../../../../i18n/message.dart';
|
import '../../../../i18n/message.dart';
|
||||||
|
import '../../../../main.dart';
|
||||||
import '../../../../util/locale_manager.dart';
|
import '../../../../util/locale_manager.dart';
|
||||||
import '../../../../util/log_util.dart';
|
import '../../../../util/log_util.dart';
|
||||||
import '../../../../util/package_info.dart';
|
import '../../../../util/package_info.dart';
|
||||||
@@ -73,6 +75,7 @@ final allTrackerSubscribeUrlCdns = Map.fromIterable(allTrackerSubscribeUrls,
|
|||||||
class AppController extends GetxController with WindowListener, TrayListener {
|
class AppController extends GetxController with WindowListener, TrayListener {
|
||||||
static StartConfig? _defaultStartConfig;
|
static StartConfig? _defaultStartConfig;
|
||||||
|
|
||||||
|
final autoStartup = false.obs;
|
||||||
final startConfig = StartConfig().obs;
|
final startConfig = StartConfig().obs;
|
||||||
final runningPort = 0.obs;
|
final runningPort = 0.obs;
|
||||||
final downloaderConfig = DownloaderConfig().obs;
|
final downloaderConfig = DownloaderConfig().obs;
|
||||||
@@ -98,6 +101,9 @@ class AppController extends GetxController with WindowListener, TrayListener {
|
|||||||
|
|
||||||
_initTrackerUpdate().onError((error, stackTrace) =>
|
_initTrackerUpdate().onError((error, stackTrace) =>
|
||||||
logger.w("initTrackerUpdate error", error, stackTrace));
|
logger.w("initTrackerUpdate error", error, stackTrace));
|
||||||
|
|
||||||
|
_initLaunchAtStartup().onError((error, stackTrace) =>
|
||||||
|
logger.w("initLaunchAtStartup error", error, stackTrace));
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -114,6 +120,12 @@ class AppController extends GetxController with WindowListener, TrayListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// According to the system_manager document, make sure to call setState once on the onWindowFocus event.
|
||||||
|
@override
|
||||||
|
void onWindowFocus() {
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onTrayIconMouseDown() {
|
void onTrayIconMouseDown() {
|
||||||
windowManager.show();
|
windowManager.show();
|
||||||
@@ -185,6 +197,13 @@ class AppController extends GetxController with WindowListener, TrayListener {
|
|||||||
await trayManager.setIcon('assets/tray_icon/icon.png');
|
await trayManager.setIcon('assets/tray_icon/icon.png');
|
||||||
}
|
}
|
||||||
final menu = Menu(items: [
|
final menu = Menu(items: [
|
||||||
|
MenuItem(
|
||||||
|
label: "show".tr,
|
||||||
|
onClick: (menuItem) async => {
|
||||||
|
await windowManager.show(),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
MenuItem.separator(),
|
||||||
MenuItem(
|
MenuItem(
|
||||||
label: "create".tr,
|
label: "create".tr,
|
||||||
onClick: (menuItem) async => {
|
onClick: (menuItem) async => {
|
||||||
@@ -192,7 +211,6 @@ class AppController extends GetxController with WindowListener, TrayListener {
|
|||||||
await Get.rootDelegate.offAndToNamed(Routes.CREATE),
|
await Get.rootDelegate.offAndToNamed(Routes.CREATE),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
MenuItem.separator(),
|
|
||||||
MenuItem(
|
MenuItem(
|
||||||
label: "startAll".tr,
|
label: "startAll".tr,
|
||||||
onClick: (menuItem) async => {continueAllTasks()},
|
onClick: (menuItem) async => {continueAllTasks()},
|
||||||
@@ -274,7 +292,9 @@ class AppController extends GetxController with WindowListener, TrayListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _toCreate(Uri uri) async {
|
Future<void> _toCreate(Uri uri) async {
|
||||||
final path = (uri.scheme == "magnet" || uri.scheme == "http" || uri.scheme == "https")
|
final path = (uri.scheme == "magnet" ||
|
||||||
|
uri.scheme == "http" ||
|
||||||
|
uri.scheme == "https")
|
||||||
? uri.toString()
|
? uri.toString()
|
||||||
: (await toFile(uri.toString())).path;
|
: (await toFile(uri.toString())).path;
|
||||||
await Get.rootDelegate.offAndToNamed(Routes.CREATE, arguments: path);
|
await Get.rootDelegate.offAndToNamed(Routes.CREATE, arguments: path);
|
||||||
@@ -305,22 +325,24 @@ class AppController extends GetxController with WindowListener, TrayListener {
|
|||||||
return _defaultStartConfig!;
|
return _defaultStartConfig!;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> loadStartConfig() async {
|
Future<StartConfig> loadStartConfig() async {
|
||||||
final defaultCfg = await _initDefaultStartConfig();
|
final defaultCfg = await _initDefaultStartConfig();
|
||||||
final saveCfg = Database.instance.getStartConfig();
|
final saveCfg = Database.instance.getStartConfig();
|
||||||
startConfig.value.network = saveCfg?.network ?? defaultCfg.network;
|
startConfig.value.network = saveCfg?.network ?? defaultCfg.network;
|
||||||
startConfig.value.address = saveCfg?.address ?? defaultCfg.address;
|
startConfig.value.address = saveCfg?.address ?? defaultCfg.address;
|
||||||
startConfig.value.apiToken = saveCfg?.apiToken ?? defaultCfg.apiToken;
|
startConfig.value.apiToken = saveCfg?.apiToken ?? defaultCfg.apiToken;
|
||||||
|
return startConfig.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> loadDownloaderConfig() async {
|
Future<DownloaderConfig> loadDownloaderConfig() async {
|
||||||
try {
|
try {
|
||||||
downloaderConfig.value = await getConfig();
|
downloaderConfig.value = await getConfig();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.w("load downloader config fail", e);
|
logger.w("load downloader config fail", e, StackTrace.current);
|
||||||
downloaderConfig.value = DownloaderConfig();
|
downloaderConfig.value = DownloaderConfig();
|
||||||
}
|
}
|
||||||
await _initDownloaderConfig();
|
await _initDownloaderConfig();
|
||||||
|
return downloaderConfig.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> trackerUpdate() async {
|
Future<void> trackerUpdate() async {
|
||||||
@@ -413,12 +435,8 @@ class AppController extends GetxController with WindowListener, TrayListener {
|
|||||||
if (Util.isDesktop()) {
|
if (Util.isDesktop()) {
|
||||||
config.downloadDir = (await getDownloadsDirectory())?.path ?? "./";
|
config.downloadDir = (await getDownloadsDirectory())?.path ?? "./";
|
||||||
} else if (Util.isAndroid()) {
|
} else if (Util.isAndroid()) {
|
||||||
final downloadDir = (await DownloadsPath.downloadsDirectory())?.path;
|
config.downloadDir = (await getExternalStorageDirectory())?.path ??
|
||||||
if (downloadDir != null) {
|
(await getApplicationDocumentsDirectory()).path;
|
||||||
config.downloadDir = '$downloadDir/Gopeed';
|
|
||||||
} else {
|
|
||||||
config.downloadDir = (await getApplicationDocumentsDirectory()).path;
|
|
||||||
}
|
|
||||||
} else if (Util.isIOS()) {
|
} else if (Util.isIOS()) {
|
||||||
config.downloadDir = (await getApplicationDocumentsDirectory()).path;
|
config.downloadDir = (await getApplicationDocumentsDirectory()).path;
|
||||||
} else {
|
} else {
|
||||||
@@ -427,6 +445,17 @@ class AppController extends GetxController with WindowListener, TrayListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _initLaunchAtStartup() async {
|
||||||
|
if (!Util.isWindows() && !Util.isLinux()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
launchAtStartup.setup(
|
||||||
|
appName: packageInfo.appName,
|
||||||
|
appPath: Platform.resolvedExecutable,
|
||||||
|
args: ['--${Args.flagHidden}']);
|
||||||
|
autoStartup.value = await launchAtStartup.isEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> saveConfig() async {
|
Future<void> saveConfig() async {
|
||||||
Database.instance.saveStartConfig(StartConfigEntity(
|
Database.instance.saveStartConfig(StartConfigEntity(
|
||||||
network: startConfig.value.network,
|
network: startConfig.value.network,
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import '../../../../util/input_formatter.dart';
|
|||||||
import '../../../../util/message.dart';
|
import '../../../../util/message.dart';
|
||||||
import '../../../../util/util.dart';
|
import '../../../../util/util.dart';
|
||||||
import '../../../routes/app_pages.dart';
|
import '../../../routes/app_pages.dart';
|
||||||
|
import '../../../views/compact_checkbox.dart';
|
||||||
import '../../../views/directory_selector.dart';
|
import '../../../views/directory_selector.dart';
|
||||||
import '../../../views/file_list_view.dart';
|
import '../../../views/file_list_view.dart';
|
||||||
import '../../app/controllers/app_controller.dart';
|
import '../../app/controllers/app_controller.dart';
|
||||||
@@ -321,23 +322,13 @@ class CreateView extends GetView<CreateController> {
|
|||||||
Row(
|
Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
TextButton(
|
CompactCheckbox(
|
||||||
onPressed: () {
|
label: 'directDownload'.tr,
|
||||||
controller.directDownload.value =
|
value: controller.directDownload.value,
|
||||||
!controller.directDownload.value;
|
onChanged: (bool? value) {
|
||||||
},
|
controller.directDownload.value =
|
||||||
child: Row(children: [
|
value ?? false;
|
||||||
Obx(() => Checkbox(
|
}),
|
||||||
value:
|
|
||||||
controller.directDownload.value,
|
|
||||||
onChanged: (bool? value) {
|
|
||||||
controller.directDownload.value =
|
|
||||||
value ?? false;
|
|
||||||
},
|
|
||||||
)),
|
|
||||||
Text('directDownload'.tr),
|
|
||||||
]),
|
|
||||||
),
|
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
controller.showAdvanced.value =
|
controller.showAdvanced.value =
|
||||||
|
|||||||
@@ -1,15 +1,19 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:badges/badges.dart' as badges;
|
import 'package:badges/badges.dart' as badges;
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:launch_at_startup/launch_at_startup.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
|
import '../../../../api/model/downloader_config.dart';
|
||||||
import '../../../../i18n/message.dart';
|
import '../../../../i18n/message.dart';
|
||||||
import '../../../../util/input_formatter.dart';
|
import '../../../../util/input_formatter.dart';
|
||||||
import '../../../../util/locale_manager.dart';
|
import '../../../../util/locale_manager.dart';
|
||||||
|
import '../../../../util/log_util.dart';
|
||||||
import '../../../../util/message.dart';
|
import '../../../../util/message.dart';
|
||||||
import '../../../../util/package_info.dart';
|
import '../../../../util/package_info.dart';
|
||||||
import '../../../../util/util.dart';
|
import '../../../../util/util.dart';
|
||||||
@@ -33,13 +37,22 @@ class SettingView extends GetView<SettingController> {
|
|||||||
final startCfg = appController.startConfig;
|
final startCfg = appController.startConfig;
|
||||||
|
|
||||||
Timer? timer;
|
Timer? timer;
|
||||||
debounceSave({bool needRestart = false}) {
|
Future<bool> debounceSave(
|
||||||
var completer = Completer<void>();
|
{Future<String> Function()? check, bool needRestart = false}) {
|
||||||
|
var completer = Completer<bool>();
|
||||||
timer?.cancel();
|
timer?.cancel();
|
||||||
timer = Timer(const Duration(milliseconds: 1000), () {
|
timer = Timer(const Duration(milliseconds: 1000), () async {
|
||||||
|
if (check != null) {
|
||||||
|
final checkResult = await check();
|
||||||
|
if (checkResult.isNotEmpty) {
|
||||||
|
showErrorMessage(checkResult);
|
||||||
|
completer.complete(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
appController
|
appController
|
||||||
.saveConfig()
|
.saveConfig()
|
||||||
.then(completer.complete)
|
.then((_) => completer.complete(true))
|
||||||
.onError(completer.completeError);
|
.onError(completer.completeError);
|
||||||
if (needRestart) {
|
if (needRestart) {
|
||||||
showMessage('tip'.tr, 'effectAfterRestart'.tr);
|
showMessage('tip'.tr, 'effectAfterRestart'.tr);
|
||||||
@@ -66,6 +79,7 @@ class SettingView extends GetView<SettingController> {
|
|||||||
return DirectorySelector(
|
return DirectorySelector(
|
||||||
controller: downloadDirController,
|
controller: downloadDirController,
|
||||||
showLabel: false,
|
showLabel: false,
|
||||||
|
showAndoirdToggle: true,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
final buildMaxRunning = _buildConfigItem(
|
final buildMaxRunning = _buildConfigItem(
|
||||||
@@ -97,13 +111,57 @@ class SettingView extends GetView<SettingController> {
|
|||||||
buildBrowserExtension() {
|
buildBrowserExtension() {
|
||||||
return ListTile(
|
return ListTile(
|
||||||
title: Text('browserExtension'.tr),
|
title: Text('browserExtension'.tr),
|
||||||
subtitle: const OpenInNew(
|
subtitle: const Row(
|
||||||
text: "Chrome",
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
url:
|
children: [
|
||||||
"https://chromewebstore.google.com/detail/gopeed/mijpgljlfcapndmchhjffkpckknofcnd",
|
OpenInNew(
|
||||||
));
|
text: "Chrome",
|
||||||
|
url:
|
||||||
|
"https://chromewebstore.google.com/detail/gopeed/mijpgljlfcapndmchhjffkpckknofcnd",
|
||||||
|
),
|
||||||
|
SizedBox(width: 10),
|
||||||
|
OpenInNew(
|
||||||
|
text: "Edge",
|
||||||
|
url:
|
||||||
|
"https://microsoftedge.microsoft.com/addons/detail/dkajnckekendchdleoaenoophcobooce",
|
||||||
|
),
|
||||||
|
SizedBox(width: 10),
|
||||||
|
OpenInNew(
|
||||||
|
text: "Firefox",
|
||||||
|
url:
|
||||||
|
"https://addons.mozilla.org/zh-CN/firefox/addon/gopeed-extension",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).paddingOnly(top: 5));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Currently auto startup only support Windows and Linux
|
||||||
|
final buildAutoStartup = !Util.isWindows() && !Util.isLinux()
|
||||||
|
? () => null
|
||||||
|
: _buildConfigItem('launchAtStartup'.tr, () {
|
||||||
|
return appController.autoStartup.value ? 'on'.tr : 'off'.tr;
|
||||||
|
}, (Key key) {
|
||||||
|
return Container(
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
child: Switch(
|
||||||
|
value: appController.autoStartup.value,
|
||||||
|
onChanged: (bool value) async {
|
||||||
|
try {
|
||||||
|
if (value) {
|
||||||
|
await launchAtStartup.enable();
|
||||||
|
} else {
|
||||||
|
await launchAtStartup.disable();
|
||||||
|
}
|
||||||
|
appController.autoStartup.value = value;
|
||||||
|
} catch (e) {
|
||||||
|
showErrorMessage(e);
|
||||||
|
logger.e('launchAtStartup fail', e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
// http config items start
|
// http config items start
|
||||||
final httpConfig = downloaderCfg.value.protocolConfig.http;
|
final httpConfig = downloaderCfg.value.protocolConfig.http;
|
||||||
final buildHttpUa =
|
final buildHttpUa =
|
||||||
@@ -408,29 +466,53 @@ class SettingView extends GetView<SettingController> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// advanced config proxy items start
|
// advanced config proxy items start
|
||||||
|
final proxy = downloaderCfg.value.proxy;
|
||||||
final buildProxy = _buildConfigItem(
|
final buildProxy = _buildConfigItem(
|
||||||
'proxy',
|
'proxy',
|
||||||
() => downloaderCfg.value.proxy.enable
|
() {
|
||||||
? '${downloaderCfg.value.proxy.scheme}://${downloaderCfg.value.proxy.host}'
|
switch (proxy.proxyMode) {
|
||||||
: 'notSet'.tr,
|
case ProxyModeEnum.noProxy:
|
||||||
|
return 'noProxy'.tr;
|
||||||
|
case ProxyModeEnum.systemProxy:
|
||||||
|
return 'systemProxy'.tr;
|
||||||
|
case ProxyModeEnum.customProxy:
|
||||||
|
return '${downloaderCfg.value.proxy.scheme}://${downloaderCfg.value.proxy.host}';
|
||||||
|
}
|
||||||
|
},
|
||||||
(Key key) {
|
(Key key) {
|
||||||
final proxy = downloaderCfg.value.proxy;
|
final mode = SizedBox(
|
||||||
|
width: 150,
|
||||||
|
child: DropdownButtonFormField<ProxyModeEnum>(
|
||||||
|
value: proxy.proxyMode,
|
||||||
|
onChanged: (value) async {
|
||||||
|
if (value != null && value != proxy.proxyMode) {
|
||||||
|
proxy.proxyMode = value;
|
||||||
|
downloaderCfg.update((val) {
|
||||||
|
val!.proxy = proxy;
|
||||||
|
});
|
||||||
|
|
||||||
final switcher = Switch(
|
await debounceSave();
|
||||||
value: proxy.enable,
|
}
|
||||||
onChanged: (bool value) async {
|
},
|
||||||
if (value != proxy.enable) {
|
items: [
|
||||||
downloaderCfg.update((val) {
|
DropdownMenuItem<ProxyModeEnum>(
|
||||||
val!.proxy.enable = value;
|
value: ProxyModeEnum.noProxy,
|
||||||
});
|
child: Text('noProxy'.tr),
|
||||||
|
),
|
||||||
await debounceSave();
|
DropdownMenuItem<ProxyModeEnum>(
|
||||||
}
|
value: ProxyModeEnum.systemProxy,
|
||||||
},
|
child: Text('systemProxy'.tr),
|
||||||
|
),
|
||||||
|
DropdownMenuItem<ProxyModeEnum>(
|
||||||
|
value: ProxyModeEnum.customProxy,
|
||||||
|
child: Text('customProxy'.tr),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
final scheme = SizedBox(
|
final scheme = SizedBox(
|
||||||
width: 100,
|
width: 150,
|
||||||
child: DropdownButtonFormField<String>(
|
child: DropdownButtonFormField<String>(
|
||||||
value: proxy.scheme,
|
value: proxy.scheme,
|
||||||
onChanged: (value) async {
|
onChanged: (value) async {
|
||||||
@@ -478,7 +560,7 @@ class SettingView extends GetView<SettingController> {
|
|||||||
|
|
||||||
ipController.addListener(updateAddress);
|
ipController.addListener(updateAddress);
|
||||||
portController.addListener(updateAddress);
|
portController.addListener(updateAddress);
|
||||||
final server = [
|
final server = Row(children: [
|
||||||
Flexible(
|
Flexible(
|
||||||
child: TextFormField(
|
child: TextFormField(
|
||||||
controller: ipController,
|
controller: ipController,
|
||||||
@@ -503,12 +585,12 @@ class SettingView extends GetView<SettingController> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
];
|
]);
|
||||||
|
|
||||||
final usrController = TextEditingController(text: proxy.usr);
|
final usrController = TextEditingController(text: proxy.usr);
|
||||||
final pwdController = TextEditingController(text: proxy.pwd);
|
final pwdController = TextEditingController(text: proxy.pwd);
|
||||||
|
|
||||||
final auth = [
|
final auth = Row(children: [
|
||||||
Flexible(
|
Flexible(
|
||||||
child: TextFormField(
|
child: TextFormField(
|
||||||
controller: usrController,
|
controller: usrController,
|
||||||
@@ -528,20 +610,21 @@ class SettingView extends GetView<SettingController> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
];
|
]);
|
||||||
|
|
||||||
|
List<Widget> customView() {
|
||||||
|
if (proxy.proxyMode != ProxyModeEnum.customProxy) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return [scheme, server, auth];
|
||||||
|
}
|
||||||
|
|
||||||
return Form(
|
return Form(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: _addPadding([
|
children: _addPadding([
|
||||||
switcher,
|
mode,
|
||||||
scheme,
|
...customView(),
|
||||||
Row(
|
|
||||||
children: server,
|
|
||||||
),
|
|
||||||
Row(
|
|
||||||
children: auth,
|
|
||||||
),
|
|
||||||
]),
|
]),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -596,11 +679,41 @@ class SettingView extends GetView<SettingController> {
|
|||||||
final ipController = TextEditingController(text: ip);
|
final ipController = TextEditingController(text: ip);
|
||||||
final portController = TextEditingController(text: port);
|
final portController = TextEditingController(text: port);
|
||||||
updateAddress() async {
|
updateAddress() async {
|
||||||
|
if (ipController.text.isEmpty || portController.text.isEmpty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
final newAddress = '${ipController.text}:${portController.text}';
|
final newAddress = '${ipController.text}:${portController.text}';
|
||||||
if (newAddress != startCfg.value.address) {
|
if (newAddress != startCfg.value.address) {
|
||||||
startCfg.value.address = newAddress;
|
startCfg.value.address = newAddress;
|
||||||
|
|
||||||
await debounceSave(needRestart: true);
|
final saved = await debounceSave(
|
||||||
|
check: () async {
|
||||||
|
// Check if address already in use
|
||||||
|
final configIp = ipController.text;
|
||||||
|
final configPort = int.parse(portController.text);
|
||||||
|
if (configPort == 0) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
final socket = await Socket.connect(configIp, configPort,
|
||||||
|
timeout: const Duration(seconds: 3));
|
||||||
|
socket.close();
|
||||||
|
return 'portInUse'
|
||||||
|
.trParams({'port': configPort.toString()});
|
||||||
|
} catch (e) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
needRestart: true);
|
||||||
|
|
||||||
|
// If save failed, restore the old address
|
||||||
|
if (!saved) {
|
||||||
|
final oldAddress =
|
||||||
|
(await appController.loadStartConfig()).address;
|
||||||
|
startCfg.update((val) async {
|
||||||
|
val!.address = oldAddress;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -700,7 +813,8 @@ class SettingView extends GetView<SettingController> {
|
|||||||
children: _addDivider([
|
children: _addDivider([
|
||||||
buildDownloadDir(),
|
buildDownloadDir(),
|
||||||
buildMaxRunning(),
|
buildMaxRunning(),
|
||||||
buildBrowserExtension()
|
buildBrowserExtension(),
|
||||||
|
buildAutoStartup(),
|
||||||
]),
|
]),
|
||||||
)),
|
)),
|
||||||
const Text('HTTP'),
|
const Text('HTTP'),
|
||||||
@@ -857,3 +971,37 @@ class SettingView extends GetView<SettingController> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum ProxyModeEnum {
|
||||||
|
noProxy,
|
||||||
|
systemProxy,
|
||||||
|
customProxy,
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ProxyMode on ProxyConfig {
|
||||||
|
ProxyModeEnum get proxyMode {
|
||||||
|
if (!enable) {
|
||||||
|
return ProxyModeEnum.noProxy;
|
||||||
|
}
|
||||||
|
if (system) {
|
||||||
|
return ProxyModeEnum.systemProxy;
|
||||||
|
}
|
||||||
|
return ProxyModeEnum.customProxy;
|
||||||
|
}
|
||||||
|
|
||||||
|
set proxyMode(ProxyModeEnum value) {
|
||||||
|
switch (value) {
|
||||||
|
case ProxyModeEnum.noProxy:
|
||||||
|
enable = false;
|
||||||
|
break;
|
||||||
|
case ProxyModeEnum.systemProxy:
|
||||||
|
enable = true;
|
||||||
|
system = true;
|
||||||
|
break;
|
||||||
|
case ProxyModeEnum.customProxy:
|
||||||
|
enable = true;
|
||||||
|
system = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -89,7 +89,7 @@ class TaskFilesView extends GetView<TaskFilesController> {
|
|||||||
return [
|
return [
|
||||||
IconButton(
|
IconButton(
|
||||||
icon:
|
icon:
|
||||||
const Icon(Icons.play_circle),
|
const Icon(Icons.open_in_new),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
launchUrl(Uri.parse(accessUrl),
|
launchUrl(Uri.parse(accessUrl),
|
||||||
webOnlyWindowName:
|
webOnlyWindowName:
|
||||||
@@ -106,12 +106,7 @@ class TaskFilesView extends GetView<TaskFilesController> {
|
|||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.play_circle),
|
icon: const Icon(Icons.play_circle),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
print(filePath);
|
await OpenFilex.open(filePath);
|
||||||
try {
|
|
||||||
await OpenFilex.open(filePath);
|
|
||||||
} catch (e) {
|
|
||||||
print(e);
|
|
||||||
}
|
|
||||||
}),
|
}),
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.share),
|
icon: const Icon(Icons.share),
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
import 'package:open_filex/open_filex.dart';
|
||||||
import 'package:path/path.dart' as path;
|
import 'package:path/path.dart' as path;
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
|
||||||
@@ -11,6 +12,7 @@ import '../../util/file_icon.dart';
|
|||||||
import '../../util/icons.dart';
|
import '../../util/icons.dart';
|
||||||
import '../../util/message.dart';
|
import '../../util/message.dart';
|
||||||
import '../../util/util.dart';
|
import '../../util/util.dart';
|
||||||
|
import '../modules/app/controllers/app_controller.dart';
|
||||||
import '../routes/app_pages.dart';
|
import '../routes/app_pages.dart';
|
||||||
|
|
||||||
class BuildTaskListView extends GetView {
|
class BuildTaskListView extends GetView {
|
||||||
@@ -57,8 +59,12 @@ class BuildTaskListView extends GetView {
|
|||||||
return task.status == Status.running;
|
return task.status == Status.running;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isFolderTask() {
|
||||||
|
return task.meta.res?.name.isNotEmpty ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
String buildExplorerUrl(Task task) {
|
String buildExplorerUrl(Task task) {
|
||||||
if (task.meta.res!.name.isEmpty) {
|
if (!isFolderTask()) {
|
||||||
return path.join(Util.safeDir(task.meta.opts.path),
|
return path.join(Util.safeDir(task.meta.opts.path),
|
||||||
Util.safeDir(task.meta.res!.files[0].path), fileName(task.meta));
|
Util.safeDir(task.meta.res!.files[0].path), fileName(task.meta));
|
||||||
} else {
|
} else {
|
||||||
@@ -68,7 +74,7 @@ class BuildTaskListView extends GetView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> showDeleteDialog(String id) {
|
Future<void> showDeleteDialog(String id) {
|
||||||
final keep = true.obs;
|
final appController = Get.find<AppController>();
|
||||||
|
|
||||||
final context = Get.context!;
|
final context = Get.context!;
|
||||||
|
|
||||||
@@ -78,11 +84,14 @@ class BuildTaskListView extends GetView {
|
|||||||
builder: (_) => AlertDialog(
|
builder: (_) => AlertDialog(
|
||||||
title: Text('deleteTask'.tr),
|
title: Text('deleteTask'.tr),
|
||||||
content: Obx(() => CheckboxListTile(
|
content: Obx(() => CheckboxListTile(
|
||||||
value: keep.value,
|
value: appController
|
||||||
|
.downloaderConfig.value.extra.lastDeleteTaskKeep,
|
||||||
title: Text('deleteTaskTip'.tr,
|
title: Text('deleteTaskTip'.tr,
|
||||||
style: context.textTheme.bodyLarge),
|
style: context.textTheme.bodyLarge),
|
||||||
onChanged: (v) {
|
onChanged: (v) {
|
||||||
keep.value = v!;
|
appController.downloaderConfig.update((val) {
|
||||||
|
val!.extra.lastDeleteTaskKeep = v!;
|
||||||
|
});
|
||||||
})),
|
})),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
@@ -96,7 +105,10 @@ class BuildTaskListView extends GetView {
|
|||||||
),
|
),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
try {
|
try {
|
||||||
await deleteTask(id, !keep.value);
|
final force = !appController
|
||||||
|
.downloaderConfig.value.extra.lastDeleteTaskKeep;
|
||||||
|
await appController.saveConfig();
|
||||||
|
await deleteTask(id, force);
|
||||||
Get.back();
|
Get.back();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
showErrorMessage(e);
|
showErrorMessage(e);
|
||||||
@@ -107,18 +119,22 @@ class BuildTaskListView extends GetView {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toTaskFilesView() {
|
||||||
|
if (Util.isDesktop()) {
|
||||||
|
FileExplorer.openAndSelectFile(buildExplorerUrl(task));
|
||||||
|
} else {
|
||||||
|
Get.rootDelegate
|
||||||
|
.toNamed(Routes.TASK_FILES, parameters: {'id': task.id});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
List<Widget> buildActions() {
|
List<Widget> buildActions() {
|
||||||
final list = <Widget>[];
|
final list = <Widget>[];
|
||||||
if (isDone()) {
|
if (isDone()) {
|
||||||
list.add(IconButton(
|
list.add(IconButton(
|
||||||
icon: const Icon(Icons.folder_open),
|
icon: const Icon(Icons.folder_open),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
if (Util.isDesktop()) {
|
toTaskFilesView();
|
||||||
FileExplorer.openAndSelectFile(buildExplorerUrl(task));
|
|
||||||
} else {
|
|
||||||
Get.rootDelegate
|
|
||||||
.toNamed(Routes.TASK_FILES, parameters: {'id': task.id});
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
@@ -176,14 +192,23 @@ class BuildTaskListView extends GetView {
|
|||||||
elevation: 4.0,
|
elevation: 4.0,
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: () {},
|
onTap: () {},
|
||||||
|
onDoubleTap: () {
|
||||||
|
if (isDone()) {
|
||||||
|
if (isFolderTask()) {
|
||||||
|
toTaskFilesView();
|
||||||
|
} else {
|
||||||
|
OpenFilex.open(buildExplorerUrl(task));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text(fileName(task.meta)),
|
title: Text(fileName(task.meta)),
|
||||||
leading: (task.meta.res?.name.isNotEmpty ?? false
|
leading: isFolderTask()
|
||||||
? const Icon(FaIcons.folder)
|
? const Icon(FaIcons.folder)
|
||||||
: Icon(FaIcons.allIcons[findIcon(fileName(task.meta))]))),
|
: Icon(FaIcons.allIcons[findIcon(fileName(task.meta))])),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class CompactCheckbox extends StatefulWidget {
|
||||||
|
final String label;
|
||||||
|
final bool value;
|
||||||
|
final ValueChanged<bool?>? onChanged;
|
||||||
|
final double? scale;
|
||||||
|
final TextStyle? textStyle;
|
||||||
|
|
||||||
|
const CompactCheckbox({
|
||||||
|
super.key,
|
||||||
|
required this.label,
|
||||||
|
required this.value,
|
||||||
|
this.onChanged,
|
||||||
|
this.scale,
|
||||||
|
this.textStyle,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<CompactCheckbox> createState() => _CompactCheckboxState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CompactCheckboxState extends State<CompactCheckbox> {
|
||||||
|
late bool _value;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_value = widget.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
valueChanged(bool? value) {
|
||||||
|
setState(() {
|
||||||
|
_value = value!;
|
||||||
|
});
|
||||||
|
widget.onChanged?.call(_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final checkbox = Checkbox(
|
||||||
|
value: _value,
|
||||||
|
onChanged: valueChanged,
|
||||||
|
);
|
||||||
|
|
||||||
|
return TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
valueChanged(!_value);
|
||||||
|
},
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
widget.scale == null
|
||||||
|
? checkbox
|
||||||
|
: Transform.scale(
|
||||||
|
scale: widget.scale,
|
||||||
|
child: checkbox,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
widget.label,
|
||||||
|
style: widget.textStyle,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,16 +1,26 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:file_picker/file_picker.dart';
|
import 'package:file_picker/file_picker.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.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:toggle_switch/toggle_switch.dart';
|
||||||
|
|
||||||
import '../../util/mac_secure_util.dart';
|
import '../../util/mac_secure_util.dart';
|
||||||
|
import '../../util/message.dart';
|
||||||
import '../../util/util.dart';
|
import '../../util/util.dart';
|
||||||
|
|
||||||
class DirectorySelector extends StatefulWidget {
|
class DirectorySelector extends StatefulWidget {
|
||||||
final TextEditingController controller;
|
final TextEditingController controller;
|
||||||
final bool showLabel;
|
final bool showLabel;
|
||||||
|
final bool showAndoirdToggle;
|
||||||
|
|
||||||
const DirectorySelector(
|
const DirectorySelector(
|
||||||
{Key? key, required this.controller, this.showLabel = true})
|
{Key? key,
|
||||||
|
required this.controller,
|
||||||
|
this.showLabel = true,
|
||||||
|
this.showAndoirdToggle = false})
|
||||||
: super(key: key);
|
: super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -20,6 +30,68 @@ class DirectorySelector extends StatefulWidget {
|
|||||||
class _DirectorySelectorState extends State<DirectorySelector> {
|
class _DirectorySelectorState extends State<DirectorySelector> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
Widget? buildSelectWidget() {
|
||||||
|
if (Util.isDesktop()) {
|
||||||
|
return IconButton(
|
||||||
|
icon: const Icon(Icons.folder_open),
|
||||||
|
onPressed: () async {
|
||||||
|
var dir = await FilePicker.platform.getDirectoryPath();
|
||||||
|
if (dir != null) {
|
||||||
|
widget.controller.text = dir;
|
||||||
|
MacSecureUtil.saveBookmark(dir);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// After Android 11, access to external storage is increasingly restricted, so it no longer supports selecting the download directory. However, if you do not download in external storage, all downloaded files will be deleted after the application is uninstalled.
|
||||||
|
// Fortunately, so far, most Android devices can still access the system download directory.
|
||||||
|
// For the sake of user experience, it is decided to only support selecting the application's internal directory and the system download directory. Also, a test for file write permission is performed when selecting the system download directory. If it cannot be written, selection is not allowed.
|
||||||
|
if (Util.isAndroid() && widget.showAndoirdToggle) {
|
||||||
|
final isSwitchToDownloadDir =
|
||||||
|
widget.controller.text.endsWith('/Gopeed');
|
||||||
|
|
||||||
|
return ToggleSwitch(
|
||||||
|
initialLabelIndex: isSwitchToDownloadDir ? 1 : 0,
|
||||||
|
totalSwitches: 2,
|
||||||
|
icons: const [Icons.home, Icons.download],
|
||||||
|
customWidths: const [50, 50],
|
||||||
|
onToggle: (index) async {
|
||||||
|
if (index == 0) {
|
||||||
|
widget.controller.text =
|
||||||
|
(await getExternalStorageDirectory())?.path ??
|
||||||
|
(await getApplicationDocumentsDirectory()).path;
|
||||||
|
} else {
|
||||||
|
widget.controller.text =
|
||||||
|
'${(await DownloadsPath.downloadsDirectory())!.path}/Gopeed';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
cancelToggle: (index) async {
|
||||||
|
if (index == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Check write permission
|
||||||
|
final downloadDir =
|
||||||
|
(await DownloadsPath.downloadsDirectory())?.path;
|
||||||
|
if (downloadDir == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
final fileRandomeName =
|
||||||
|
"test_${DateTime.now().millisecondsSinceEpoch}.tmp";
|
||||||
|
final testFile = File('$downloadDir/Gopeed/$fileRandomeName');
|
||||||
|
try {
|
||||||
|
await testFile.create(recursive: true);
|
||||||
|
await testFile.writeAsString('test');
|
||||||
|
await testFile.delete();
|
||||||
|
return false;
|
||||||
|
} catch (e) {
|
||||||
|
showErrorMessage(e);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
).marginOnly(left: 10);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return Row(
|
return Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
@@ -35,17 +107,7 @@ class _DirectorySelectorState extends State<DirectorySelector> {
|
|||||||
return v!.trim().isNotEmpty ? null : 'downloadDirValid'.tr;
|
return v!.trim().isNotEmpty ? null : 'downloadDirValid'.tr;
|
||||||
},
|
},
|
||||||
)),
|
)),
|
||||||
!Util.isWeb() && !Util.isAndroid() && !Util.isIOS()
|
buildSelectWidget()
|
||||||
? IconButton(
|
|
||||||
icon: const Icon(Icons.folder_open),
|
|
||||||
onPressed: () async {
|
|
||||||
var dir = await FilePicker.platform.getDirectoryPath();
|
|
||||||
if (dir != null) {
|
|
||||||
widget.controller.text = dir;
|
|
||||||
MacSecureUtil.saveBookmark(dir);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
: null
|
|
||||||
].where((e) => e != null).map((e) => e!).toList(),
|
].where((e) => e != null).map((e) => e!).toList(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,7 +58,9 @@ const enUS = {
|
|||||||
'apiToken': 'API Token',
|
'apiToken': 'API Token',
|
||||||
'notSet': 'NS',
|
'notSet': 'NS',
|
||||||
'set': 'SET',
|
'set': 'SET',
|
||||||
|
'portInUse': 'Port [@port] is in use, please change the port',
|
||||||
'effectAfterRestart': 'Effect after restart',
|
'effectAfterRestart': 'Effect after restart',
|
||||||
|
'show': 'Show',
|
||||||
'startAll': 'Start All',
|
'startAll': 'Start All',
|
||||||
'pauseAll': 'Pause All',
|
'pauseAll': 'Pause All',
|
||||||
'deleteTask': 'Delete Task',
|
'deleteTask': 'Delete Task',
|
||||||
@@ -82,6 +84,9 @@ const enUS = {
|
|||||||
'serviceText': 'Running',
|
'serviceText': 'Running',
|
||||||
'network': 'Network',
|
'network': 'Network',
|
||||||
'proxy': 'Proxy',
|
'proxy': 'Proxy',
|
||||||
|
'noProxy': 'No Proxy',
|
||||||
|
'systemProxy': 'System Proxy',
|
||||||
|
'customProxy': 'Custom Proxy',
|
||||||
'server': 'Server',
|
'server': 'Server',
|
||||||
'username': 'Username',
|
'username': 'Username',
|
||||||
'password': 'Password',
|
'password': 'Password',
|
||||||
@@ -89,5 +94,6 @@ const enUS = {
|
|||||||
'thanksDesc':
|
'thanksDesc':
|
||||||
'Thanks to all the contributors who have helped build and develop the Gopeed community!',
|
'Thanks to all the contributors who have helped build and develop the Gopeed community!',
|
||||||
'browserExtension': 'Browser Extension',
|
'browserExtension': 'Browser Extension',
|
||||||
|
'launchAtStartup': 'Launch at Startup',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
const plPL = {
|
||||||
|
'pl_PL': {
|
||||||
|
'label': 'Polski',
|
||||||
|
'error': 'Błąd',
|
||||||
|
'tip': 'Tip',
|
||||||
|
'confirm': 'Potwierdź',
|
||||||
|
'cancel': 'Anuluj',
|
||||||
|
'on': 'Włącz',
|
||||||
|
'off': 'Wyłącz',
|
||||||
|
'selectAll': 'Zaznacz wszystko',
|
||||||
|
'task': 'Zadania',
|
||||||
|
'downloading': 'Pobierane',
|
||||||
|
'downloaded': 'Pobrane',
|
||||||
|
'setting': 'Ustawienia',
|
||||||
|
'donate': 'Wesprzyj',
|
||||||
|
'exit': 'Wyjście',
|
||||||
|
'create': 'Utwórz zadanie',
|
||||||
|
'directDownload': 'Pobieranie bezpośrednie',
|
||||||
|
'advancedOptions': 'Zaawansowane',
|
||||||
|
'downloadLink': 'Pobierz z linku',
|
||||||
|
'downloadLinkValid': 'Otwórz link pobierania',
|
||||||
|
'downloadLinkHit':
|
||||||
|
'Otwórz link pobierania, HTTP/HTTPS/MAGNET supported@append',
|
||||||
|
'downloadLinkHitDesktop': ', lub przenieś bezpośrednio plik torrent',
|
||||||
|
'download': 'Pobiers',
|
||||||
|
'noFileSelected': 'Wybierz conajmniej jeden plik aby kontynuować.',
|
||||||
|
'noStoragePermission': 'Przyznaj uprawnienia do zapisu',
|
||||||
|
'selectFile': 'Wybierz plik',
|
||||||
|
'rename': 'Zmień nazwę',
|
||||||
|
'basic': 'Zwykły',
|
||||||
|
'advanced': 'Zaawansowane',
|
||||||
|
'general': 'Ogólne',
|
||||||
|
'downloadDir': 'Folder Pobierania',
|
||||||
|
'downloadDirValid': 'Wskaż folder pobierania',
|
||||||
|
'connections': 'Połączenia',
|
||||||
|
'useServerCtime': 'Użyj czasu serwera do utworzenia pliku',
|
||||||
|
'maxRunning': 'Maksymalna liczba zadań',
|
||||||
|
'items': '@count items',
|
||||||
|
'subscribeTracker': 'Subskrybuj tracker',
|
||||||
|
'subscribeFail': 'Błąd subskrypcji, sprawdź łączność z internetem',
|
||||||
|
'update': 'Aktualizacja',
|
||||||
|
'updateDaily': 'Aktualizuj codziennie',
|
||||||
|
'lastUpdate': 'Ostatnia aktualizacja: @time',
|
||||||
|
'addTracker': 'Dodaj tracker',
|
||||||
|
'addTrackerHit': 'Wpisz adres trackera w jednej linii',
|
||||||
|
'ui': 'Wygląd',
|
||||||
|
'theme': 'Schemat kolorów',
|
||||||
|
'themeSystem': 'Systemowy',
|
||||||
|
'themeLight': 'Jasny',
|
||||||
|
'themeDark': 'Ciemny',
|
||||||
|
'locale': 'Język',
|
||||||
|
'about': 'Info',
|
||||||
|
'homepage': 'Strona WWW',
|
||||||
|
'version': 'Wersja',
|
||||||
|
'protocol': 'Protokół',
|
||||||
|
'port': 'Port',
|
||||||
|
'apiToken': 'API Token',
|
||||||
|
'notSet': 'NS',
|
||||||
|
'set': 'SET',
|
||||||
|
'portInUse': 'Port [@port] w użyciu, proszę zmień port',
|
||||||
|
'effectAfterRestart': 'Zmiany po restarcie',
|
||||||
|
'show': 'Pokaż',
|
||||||
|
'startAll': 'Zacznij wszystkie',
|
||||||
|
'pauseAll': 'Zatrzymaj wszystkie',
|
||||||
|
'deleteTask': 'Usuń zadanie',
|
||||||
|
'deleteTaskTip': 'Zachowaj pobrane pliki',
|
||||||
|
'delete': 'Usuń',
|
||||||
|
'newVersionTitle': 'Sprawdź aktualizację @version',
|
||||||
|
'newVersionUpdate': 'Aktualizuj teraz',
|
||||||
|
'newVersionLater': 'Później',
|
||||||
|
'extensions': 'Wtyczki',
|
||||||
|
'extensionInstallUrl': 'Instaluj z url',
|
||||||
|
'extensionInstallSuccess': 'Instalacja zakończona',
|
||||||
|
'extensionUpdateSuccess': 'Aktualizacja zakończona',
|
||||||
|
'extensionDelete': 'Usuń wtyczkę',
|
||||||
|
'extensionAlreadyLatest': 'Wtyczka w aktualnej wersji',
|
||||||
|
'extensionFind': 'Wyszukaj wtyczkę',
|
||||||
|
'extensionDevelop': 'Stwórz wtyczkę',
|
||||||
|
'history': 'Historia',
|
||||||
|
'clearHistory': 'Wyczyść historię',
|
||||||
|
'noHistoryFound': 'Brak historii',
|
||||||
|
'serviceTitle': 'Usługa pobierania',
|
||||||
|
'serviceText': 'Trwa',
|
||||||
|
'network': 'Sieć',
|
||||||
|
'proxy': 'Proxy',
|
||||||
|
'noProxy': 'Bez Proxy',
|
||||||
|
'systemProxy': 'Systemowe Proxy',
|
||||||
|
'customProxy': 'Własne Proxy',
|
||||||
|
'server': 'Serwer',
|
||||||
|
'username': 'Użytkownik',
|
||||||
|
'password': 'Hasło',
|
||||||
|
'thanks': 'Dzięki',
|
||||||
|
'thanksDesc':
|
||||||
|
'Dziękuję wszystkim za pomoc przy powstawaniu aplikacji Gopeed!',
|
||||||
|
'browserExtension': 'Wtyczka dla przeglądarki',
|
||||||
|
'launchAtStartup': 'Uruchom na starcie',
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -56,7 +56,9 @@ const zhCN = {
|
|||||||
'apiToken': '接口令牌',
|
'apiToken': '接口令牌',
|
||||||
'notSet': '未设置',
|
'notSet': '未设置',
|
||||||
'set': '已设置',
|
'set': '已设置',
|
||||||
|
'portInUse': '端口[@port]已被占用,请更换端口',
|
||||||
'effectAfterRestart': '此配置项将在重启应用后生效',
|
'effectAfterRestart': '此配置项将在重启应用后生效',
|
||||||
|
'show': '显示',
|
||||||
'startAll': '全部开始',
|
'startAll': '全部开始',
|
||||||
'pauseAll': '全部暂停',
|
'pauseAll': '全部暂停',
|
||||||
'deleteTask': '删除任务',
|
'deleteTask': '删除任务',
|
||||||
@@ -80,11 +82,15 @@ const zhCN = {
|
|||||||
'serviceText': '运行中',
|
'serviceText': '运行中',
|
||||||
'network': '网络',
|
'network': '网络',
|
||||||
'proxy': '代理',
|
'proxy': '代理',
|
||||||
|
'noProxy': '不使用代理',
|
||||||
|
'systemProxy': '系统代理',
|
||||||
|
'customProxy': '自定义代理',
|
||||||
'server': '服务器',
|
'server': '服务器',
|
||||||
'username': '用户名',
|
'username': '用户名',
|
||||||
'password': '密码',
|
'password': '密码',
|
||||||
'thanks': '鸣谢',
|
'thanks': '鸣谢',
|
||||||
'thanksDesc': '感谢所有为 Gopeed 社区建设添砖加瓦的贡献者们!',
|
'thanksDesc': '感谢所有为 Gopeed 社区建设添砖加瓦的贡献者们!',
|
||||||
'browserExtension': '浏览器扩展',
|
'browserExtension': '浏览器扩展',
|
||||||
|
'launchAtStartup': '开机自动运行',
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -56,7 +56,9 @@ const zhTW = {
|
|||||||
'apiToken': 'API令牌',
|
'apiToken': 'API令牌',
|
||||||
'notSet': '未設定',
|
'notSet': '未設定',
|
||||||
'set': '已設定',
|
'set': '已設定',
|
||||||
|
'portInUse': '端口[@port]已被占用,請更換端口',
|
||||||
'effectAfterRestart': '重啟後生效',
|
'effectAfterRestart': '重啟後生效',
|
||||||
|
'show': '顯示',
|
||||||
'startAll': '全部開始',
|
'startAll': '全部開始',
|
||||||
'pauseAll': '全部暫停',
|
'pauseAll': '全部暫停',
|
||||||
'deleteTask': '刪除任務',
|
'deleteTask': '刪除任務',
|
||||||
@@ -80,11 +82,15 @@ const zhTW = {
|
|||||||
'serviceText': '執行中',
|
'serviceText': '執行中',
|
||||||
'network': '網路',
|
'network': '網路',
|
||||||
'proxy': '代理',
|
'proxy': '代理',
|
||||||
|
'noProxy': '不使用代理',
|
||||||
|
'systemProxy': '系統代理',
|
||||||
|
'customProxy': '自定義代理',
|
||||||
'server': '伺服器',
|
'server': '伺服器',
|
||||||
'username': '名稱',
|
'username': '名稱',
|
||||||
'password': '密碼',
|
'password': '密碼',
|
||||||
'thanks': '感謝',
|
'thanks': '感謝',
|
||||||
'thanksDesc': '感謝所有為 Gopeed 社區建設添磚加瓦的貢獻者們!',
|
'thanksDesc': '感謝所有為 Gopeed 社區建設添磚加瓦的貢獻者們!',
|
||||||
'browserExtension': '瀏覽器擴充程式',
|
'browserExtension': '瀏覽器擴充程式',
|
||||||
|
'launchAtStartup': '開機自動運行',
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -1,13 +1,14 @@
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:gopeed/i18n/langs/fa_ir.dart';
|
|
||||||
import 'package:gopeed/i18n/langs/ja_jp.dart';
|
|
||||||
import 'package:gopeed/i18n/langs/zh_tw.dart';
|
|
||||||
|
|
||||||
import 'langs/en_us.dart';
|
import 'langs/en_us.dart';
|
||||||
|
import 'langs/fa_ir.dart';
|
||||||
|
import 'langs/ja_jp.dart';
|
||||||
|
import 'langs/pl_pl.dart';
|
||||||
import 'langs/ru_ru.dart';
|
import 'langs/ru_ru.dart';
|
||||||
import 'langs/zh_cn.dart';
|
|
||||||
import 'langs/vi_vn.dart';
|
|
||||||
import 'langs/tr_tr.dart';
|
import 'langs/tr_tr.dart';
|
||||||
|
import 'langs/vi_vn.dart';
|
||||||
|
import 'langs/zh_cn.dart';
|
||||||
|
import 'langs/zh_tw.dart';
|
||||||
|
|
||||||
final messages = _Messages();
|
final messages = _Messages();
|
||||||
|
|
||||||
@@ -23,5 +24,6 @@ class _Messages extends Translations {
|
|||||||
...jaJP,
|
...jaJP,
|
||||||
...viVN,
|
...viVN,
|
||||||
...trTR,
|
...trTR,
|
||||||
|
...plPL,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
+28
-11
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:args/args.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_foreground_task/flutter_foreground_task.dart';
|
import 'package:flutter_foreground_task/flutter_foreground_task.dart';
|
||||||
@@ -17,14 +18,28 @@ import 'util/mac_secure_util.dart';
|
|||||||
import 'util/package_info.dart';
|
import 'util/package_info.dart';
|
||||||
import 'util/util.dart';
|
import 'util/util.dart';
|
||||||
|
|
||||||
void main() async {
|
class Args {
|
||||||
await init();
|
static const flagHidden = "hidden";
|
||||||
|
|
||||||
|
bool hidden = false;
|
||||||
|
|
||||||
|
Args.parse(List<String> args) {
|
||||||
|
final parser = ArgParser();
|
||||||
|
parser.addFlag(flagHidden);
|
||||||
|
final results = parser.parse(args);
|
||||||
|
hidden = results.flag(flagHidden);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void main(List<String> arguments) async {
|
||||||
|
final args = Args.parse(arguments);
|
||||||
|
await init(args);
|
||||||
onStart();
|
onStart();
|
||||||
|
|
||||||
runApp(const AppView());
|
runApp(const AppView());
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> init() async {
|
Future<void> init(Args args) async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
await Util.initStorageDir();
|
await Util.initStorageDir();
|
||||||
await Database.instance.init();
|
await Database.instance.init();
|
||||||
@@ -37,8 +52,10 @@ Future<void> init() async {
|
|||||||
skipTaskbar: false,
|
skipTaskbar: false,
|
||||||
);
|
);
|
||||||
await windowManager.waitUntilReadyToShow(windowOptions, () async {
|
await windowManager.waitUntilReadyToShow(windowOptions, () async {
|
||||||
await windowManager.show();
|
if (!args.hidden) {
|
||||||
await windowManager.focus();
|
await windowManager.show();
|
||||||
|
await windowManager.focus();
|
||||||
|
}
|
||||||
await windowManager.setPreventClose(true);
|
await windowManager.setPreventClose(true);
|
||||||
// windows_manager has a bug where when window to be maximized, it will be unmaximized immediately, so can't implement this feature currently.
|
// windows_manager has a bug where when window to be maximized, it will be unmaximized immediately, so can't implement this feature currently.
|
||||||
// https://github.com/leanflutter/window_manager/issues/412
|
// https://github.com/leanflutter/window_manager/issues/412
|
||||||
@@ -50,6 +67,12 @@ Future<void> init() async {
|
|||||||
|
|
||||||
initLogger();
|
initLogger();
|
||||||
|
|
||||||
|
try {
|
||||||
|
await initPackageInfo();
|
||||||
|
} catch (e) {
|
||||||
|
logger.e("init package info fail", e);
|
||||||
|
}
|
||||||
|
|
||||||
final controller = Get.put(AppController());
|
final controller = Get.put(AppController());
|
||||||
try {
|
try {
|
||||||
await controller.loadStartConfig();
|
await controller.loadStartConfig();
|
||||||
@@ -81,12 +104,6 @@ Future<void> init() async {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.e("load config fail", e);
|
logger.e("load config fail", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
|
||||||
await initPackageInfo();
|
|
||||||
} catch (e) {
|
|
||||||
logger.e("init package info fail", e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> onStart() async {
|
Future<void> onStart() async {
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ class Util {
|
|||||||
static Future<void> initStorageDir() async {
|
static Future<void> initStorageDir() async {
|
||||||
var storageDir = "";
|
var storageDir = "";
|
||||||
if (Util.isWindows()) {
|
if (Util.isWindows()) {
|
||||||
storageDir = "./";
|
storageDir = File(Platform.resolvedExecutable).parent.path;
|
||||||
} else if (!Util.isWeb()) {
|
} else if (!Util.isWeb()) {
|
||||||
if (Util.isLinux()) {
|
if (Util.isLinux()) {
|
||||||
storageDir = File(Platform.resolvedExecutable).parent.path;
|
storageDir = File(Platform.resolvedExecutable).parent.path;
|
||||||
|
|||||||
@@ -48,7 +48,8 @@ static void my_application_activate(GApplication* application) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
gtk_window_set_default_size(window, 1280, 720);
|
gtk_window_set_default_size(window, 1280, 720);
|
||||||
gtk_widget_show(GTK_WIDGET(window));
|
//gtk_widget_show(GTK_WIDGET(window));
|
||||||
|
gtk_widget_realize(GTK_WIDGET(window));
|
||||||
|
|
||||||
g_autoptr(FlDartProject) project = fl_dart_project_new();
|
g_autoptr(FlDartProject) project = fl_dart_project_new();
|
||||||
fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments);
|
fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import Cocoa
|
import Cocoa
|
||||||
import FlutterMacOS
|
import FlutterMacOS
|
||||||
|
import window_manager
|
||||||
|
|
||||||
class MainFlutterWindow: NSWindow {
|
class MainFlutterWindow: NSWindow {
|
||||||
override func awakeFromNib() {
|
override func awakeFromNib() {
|
||||||
@@ -12,4 +13,9 @@ class MainFlutterWindow: NSWindow {
|
|||||||
|
|
||||||
super.awakeFromNib()
|
super.awakeFromNib()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override public func order(_ place: NSWindow.OrderingMode, relativeTo otherWin: Int) {
|
||||||
|
super.order(place, relativeTo: otherWin)
|
||||||
|
hiddenWindowAtLaunch()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+27
-3
@@ -34,13 +34,13 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "3.4.10"
|
version: "3.4.10"
|
||||||
args:
|
args:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: args
|
name: args
|
||||||
sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596
|
sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.2"
|
version: "2.5.0"
|
||||||
async:
|
async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -493,6 +493,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.7.1"
|
version: "6.7.1"
|
||||||
|
launch_at_startup:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: launch_at_startup
|
||||||
|
sha256: "93fc5638e088290004fae358bae691486673d469957d461d9dae5b12248593eb"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.2.2"
|
||||||
lecle_downloads_path_provider:
|
lecle_downloads_path_provider:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -994,6 +1002,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.1"
|
version: "1.0.1"
|
||||||
|
toggle_switch:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: toggle_switch
|
||||||
|
sha256: dca04512d7c23ed320d6c5ede1211a404f177d54d353bf785b07d15546a86ce5
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.3.0"
|
||||||
tray_manager:
|
tray_manager:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -1130,6 +1146,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.1.4"
|
version: "4.1.4"
|
||||||
|
win32_registry:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: win32_registry
|
||||||
|
sha256: "1c52f994bdccb77103a6231ad4ea331a244dbcef5d1f37d8462f713143b0bfae"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.0"
|
||||||
window_manager:
|
window_manager:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
|
|||||||
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
||||||
# Read more about iOS versioning at
|
# Read more about iOS versioning at
|
||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||||
version: 1.5.6+1
|
version: 1.5.7+1
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=3.0.0 <4.0.0'
|
sdk: '>=3.0.0 <4.0.0'
|
||||||
@@ -64,6 +64,9 @@ dependencies:
|
|||||||
tray_manager: ^0.2.1
|
tray_manager: ^0.2.1
|
||||||
lecle_downloads_path_provider: ^0.0.2+8
|
lecle_downloads_path_provider: ^0.0.2+8
|
||||||
hive: ^2.2.3
|
hive: ^2.2.3
|
||||||
|
launch_at_startup: ^0.2.2
|
||||||
|
args: ^2.5.0
|
||||||
|
toggle_switch: ^2.3.0
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ bool FlutterWindow::OnCreate() {
|
|||||||
SetChildContent(flutter_controller_->view()->GetNativeWindow());
|
SetChildContent(flutter_controller_->view()->GetNativeWindow());
|
||||||
|
|
||||||
flutter_controller_->engine()->SetNextFrameCallback([&]() {
|
flutter_controller_->engine()->SetNextFrameCallback([&]() {
|
||||||
this->Show();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Flutter can complete the first frame before the "show window" callback is
|
// Flutter can complete the first frame before the "show window" callback is
|
||||||
|
|||||||
Reference in new issue
Block a user