mirror of
https://github.com/GopeedLab/gopeed.git
synced 2026-11-10 18:40:27 +00:00
Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c7fefd2b9b | ||
|
|
b8e36b483e | ||
|
|
91ac841b9d | ||
|
|
5134446c3f | ||
|
|
82b6ebd6f1 | ||
|
|
f8ea009fe9 |
No files matched your search
@@ -1,16 +0,0 @@
|
|||||||
.git
|
|
||||||
|
|
||||||
*.data
|
|
||||||
*.log
|
|
||||||
|
|
||||||
node_modules
|
|
||||||
**/node_modules
|
|
||||||
|
|
||||||
Dockerfile
|
|
||||||
.dockerignore
|
|
||||||
|
|
||||||
.github
|
|
||||||
_docs
|
|
||||||
_examples
|
|
||||||
bin
|
|
||||||
ui
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
* @monkeyWie
|
|
||||||
@@ -11,9 +11,6 @@ categories:
|
|||||||
- 'fix'
|
- 'fix'
|
||||||
- 'bugfix'
|
- 'bugfix'
|
||||||
- 'bug'
|
- 'bug'
|
||||||
- title: '🔧 Performance Improvements'
|
|
||||||
labels:
|
|
||||||
- 'perf'
|
|
||||||
- title: '🧪 Tests'
|
- title: '🧪 Tests'
|
||||||
label: 'test'
|
label: 'test'
|
||||||
- title: '🧰 Maintenance'
|
- title: '🧰 Maintenance'
|
||||||
|
|||||||
+43
-119
@@ -4,17 +4,17 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
platform:
|
platform:
|
||||||
description: "Build platform"
|
description: 'Build platform'
|
||||||
required: true
|
required: true
|
||||||
default: "all"
|
default: 'all'
|
||||||
test:
|
test:
|
||||||
description: "Test mode"
|
description: 'Test mode'
|
||||||
required: true
|
required: true
|
||||||
default: "false"
|
default: "false"
|
||||||
|
|
||||||
env:
|
env:
|
||||||
GO_VERSION: "1.23"
|
GO_VERSION: "1.21"
|
||||||
FLUTTER_VERSION: "3.24.5"
|
FLUTTER_VERSION: "3.16.8"
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
@@ -35,7 +35,7 @@ jobs:
|
|||||||
build-windows:
|
build-windows:
|
||||||
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'windows' }}
|
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'windows' }}
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
needs: [get-release]
|
needs: [ get-release ]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-go@v4
|
- uses: actions/setup-go@v4
|
||||||
@@ -51,7 +51,7 @@ jobs:
|
|||||||
Invoke-WebRequest -Uri "https://github.com/jrsoftware/issrc/raw/main/Files/Languages/Unofficial/ChineseSimplified.isl" -OutFile "ChineseSimplified.isl"
|
Invoke-WebRequest -Uri "https://github.com/jrsoftware/issrc/raw/main/Files/Languages/Unofficial/ChineseSimplified.isl" -OutFile "ChineseSimplified.isl"
|
||||||
mv ChineseSimplified.isl "C:\Program Files (x86)\Inno Setup 6\Languages\"
|
mv ChineseSimplified.isl "C:\Program Files (x86)\Inno Setup 6\Languages\"
|
||||||
|
|
||||||
go build -tags nosqlite -ldflags="-w -s -X github.com/GopeedLab/gopeed/pkg/base.Version=$env:VERSION" -buildmode=c-shared -o ui/flutter/windows/libgopeed.dll github.com/GopeedLab/gopeed/bind/desktop
|
go build -tags nosqlite -ldflags="-w -s -X github.com/GopeedLab/gopeed/pkg/base.Version=$VERSION" -buildmode=c-shared -o ui/flutter/windows/libgopeed.dll github.com/GopeedLab/gopeed/bind/desktop
|
||||||
cd ui/flutter
|
cd ui/flutter
|
||||||
$TAG = "v$env:VERSION"
|
$TAG = "v$env:VERSION"
|
||||||
flutter build windows
|
flutter build windows
|
||||||
@@ -97,13 +97,11 @@ jobs:
|
|||||||
PrivilegesRequired=lowest
|
PrivilegesRequired=lowest
|
||||||
OutputBaseFilename=gopeed
|
OutputBaseFilename=gopeed
|
||||||
SetupIconFile=..\..\assets\icon\icon.ico
|
SetupIconFile=..\..\assets\icon\icon.ico
|
||||||
UninstallDisplayIcon={app}\{#MyAppExeName}
|
|
||||||
Compression=lzma
|
Compression=lzma
|
||||||
SolidCompression=yes
|
SolidCompression=yes
|
||||||
WizardStyle=modern
|
WizardStyle=modern
|
||||||
LanguageDetectionMethod=uilanguage
|
LanguageDetectionMethod=uilanguage
|
||||||
ShowLanguageDialog=yes
|
ShowLanguageDialog=yes
|
||||||
CloseApplications=force
|
|
||||||
|
|
||||||
[Languages]
|
[Languages]
|
||||||
Name: "english"; MessagesFile: "compiler:Default.isl"
|
Name: "english"; MessagesFile: "compiler:Default.isl"
|
||||||
@@ -113,7 +111,7 @@ jobs:
|
|||||||
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
|
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
|
||||||
|
|
||||||
[Files]
|
[Files]
|
||||||
Source: ".\x64\runner\Release\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs;
|
Source: ".\x64\runner\Release\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs
|
||||||
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
|
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
|
||||||
|
|
||||||
[UninstallDelete]
|
[UninstallDelete]
|
||||||
@@ -122,7 +120,7 @@ jobs:
|
|||||||
[Icons]
|
[Icons]
|
||||||
Name: "{autoprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
|
Name: "{autoprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
|
||||||
Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
|
Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
|
||||||
|
|
||||||
[Run]
|
[Run]
|
||||||
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent
|
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent
|
||||||
"@ > setup.iss
|
"@ > setup.iss
|
||||||
@@ -138,8 +136,8 @@ 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: macos-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [get-release]
|
needs: [ get-release ]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-go@v4
|
- uses: actions/setup-go@v4
|
||||||
@@ -149,15 +147,17 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
VERSION: ${{ needs.get-release.outputs.version }}
|
VERSION: ${{ needs.get-release.outputs.version }}
|
||||||
run: |
|
run: |
|
||||||
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
|
go install src.techknowlogick.com/xgo@latest
|
||||||
|
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-13
|
runs-on: macos-latest
|
||||||
needs: [get-release, build-macos-arm64-lib]
|
needs: [ get-release, build-macos-arm64-lib ]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-go@v4
|
- uses: actions/setup-go@v4
|
||||||
@@ -171,7 +171,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 --break-system-packages
|
python3 -m pip install setuptools
|
||||||
npm install -g appdmg
|
npm install -g appdmg
|
||||||
- uses: actions/download-artifact@v3
|
- uses: actions/download-artifact@v3
|
||||||
with:
|
with:
|
||||||
@@ -216,7 +216,7 @@ jobs:
|
|||||||
build-linux:
|
build-linux:
|
||||||
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'linux' }}
|
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'linux' }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [get-release]
|
needs: [ get-release ]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-go@v4
|
- uses: actions/setup-go@v4
|
||||||
@@ -254,12 +254,15 @@ jobs:
|
|||||||
build-snap:
|
build-snap:
|
||||||
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'snap' }}
|
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'snap' }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [get-release]
|
needs: [ get-release ]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- 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:
|
||||||
@@ -270,87 +273,52 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
go build -tags nosqlite -ldflags="-w -s -X github.com/GopeedLab/gopeed/pkg/base.Version=$VERSION" -buildmode=c-shared -o ui/flutter/linux/bundle/lib/libgopeed.so github.com/GopeedLab/gopeed/bind/desktop
|
go build -tags nosqlite -ldflags="-w -s -X github.com/GopeedLab/gopeed/pkg/base.Version=$VERSION" -buildmode=c-shared -o ui/flutter/linux/bundle/lib/libgopeed.so github.com/GopeedLab/gopeed/bind/desktop
|
||||||
cd ui/flutter
|
cd ui/flutter
|
||||||
|
|
||||||
sudo snap install snapcraft --classic
|
sudo snap install snapcraft --classic
|
||||||
mkdir -p snap/gui
|
mkdir -p snap/gui
|
||||||
cp assets/icon/icon.svg snap/gui/gopeed.svg
|
cp assets/icon/icon.svg snap/gui/gopeed.svg
|
||||||
|
|
||||||
cat>snap/snapcraft.yaml<<EOF
|
cat>snap/snapcraft.yaml<<EOF
|
||||||
name: gopeed
|
name: gopeed
|
||||||
version: $VERSION
|
version: $VERSION
|
||||||
summary: A modern download manager
|
summary: A modern download manager
|
||||||
description: High speed downloader that supports all platforms.
|
description: High speed downloader that supports all platforms.
|
||||||
|
|
||||||
confinement: strict
|
confinement: strict
|
||||||
base: core22
|
base: core22
|
||||||
grade: stable
|
grade: stable
|
||||||
|
|
||||||
architectures:
|
architectures:
|
||||||
- build-on: amd64
|
- build-on: amd64
|
||||||
build-for: amd64
|
build-for: amd64
|
||||||
|
|
||||||
slots:
|
slots:
|
||||||
dbus-gopeed:
|
dbus-gopeed:
|
||||||
interface: dbus
|
interface: dbus
|
||||||
bus: session
|
bus: session
|
||||||
name: com.gopeed.gopeed
|
name: com.gopeed.gopeed
|
||||||
|
|
||||||
apps:
|
apps:
|
||||||
gopeed:
|
gopeed:
|
||||||
command: bin/gopeed
|
command: gopeed
|
||||||
extensions: [gnome]
|
extensions: [gnome]
|
||||||
plugs:
|
plugs:
|
||||||
- network
|
- network
|
||||||
- home
|
|
||||||
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: ''
|
|
||||||
zenity:
|
|
||||||
plugin: nil
|
|
||||||
stage-packages:
|
|
||||||
- zenity
|
|
||||||
gopeed:
|
gopeed:
|
||||||
after: [flutter-git,zenity]
|
|
||||||
source: .
|
source: .
|
||||||
plugin: nil
|
plugin: flutter
|
||||||
stage-packages:
|
stage-packages:
|
||||||
- libgtk-3-dev
|
- libgtk-3-dev
|
||||||
- libappindicator3-dev
|
- libappindicator3-dev
|
||||||
override-build: |
|
flutter-target: lib/main.dart
|
||||||
# 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
|
||||||
sed -i 's/Icon=com.gopeed.Gopeed/Icon=\${SNAP}\/meta\/gui\/gopeed.svg/g' snap/gui/gopeed.desktop
|
sed -i 's/Icon=com.gopeed.Gopeed/Icon=\${SNAP}\/meta\/gui\/gopeed.svg/g' snap/gui/gopeed.desktop
|
||||||
|
|
||||||
snapcraft --use-lxd
|
snapcraft --use-lxd
|
||||||
|
|
||||||
# Snapcraft login
|
# Snapcraft login
|
||||||
@@ -359,7 +327,7 @@ jobs:
|
|||||||
build-android:
|
build-android:
|
||||||
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'android' }}
|
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'android' }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [get-release]
|
needs: [ get-release ]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-go@v4
|
- uses: actions/setup-go@v4
|
||||||
@@ -368,7 +336,7 @@ jobs:
|
|||||||
- uses: actions/setup-java@v3
|
- uses: actions/setup-java@v3
|
||||||
with:
|
with:
|
||||||
distribution: "zulu"
|
distribution: "zulu"
|
||||||
java-version: "17"
|
java-version: "11"
|
||||||
- uses: subosito/flutter-action@v2
|
- uses: subosito/flutter-action@v2
|
||||||
with:
|
with:
|
||||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||||
@@ -382,7 +350,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 -ldflags="-w -s -checklinkname=0 -X github.com/GopeedLab/gopeed/pkg/base.Version=$VERSION" -o ui/flutter/android/app/libs/libgopeed.aar -target=android -androidapi 21 -javapkg=com.gopeed 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/android/app/libs/libgopeed.aar -target=android -androidapi 19 -javapkg=com.gopeed github.com/GopeedLab/gopeed/bind/mobile
|
||||||
cd ui/flutter
|
cd ui/flutter
|
||||||
echo $APK_KEYSTORE | base64 -di > android/app/upload-keystore.jks
|
echo $APK_KEYSTORE | base64 -di > android/app/upload-keystore.jks
|
||||||
flutter build apk
|
flutter build apk
|
||||||
@@ -397,7 +365,7 @@ jobs:
|
|||||||
build-ios:
|
build-ios:
|
||||||
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'ios' }}
|
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'ios' }}
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
needs: [get-release]
|
needs: [ get-release ]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-go@v4
|
- uses: actions/setup-go@v4
|
||||||
@@ -413,7 +381,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 -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,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
|
||||||
cd ui/flutter
|
cd ui/flutter
|
||||||
flutter build ios --no-codesign
|
flutter build ios --no-codesign
|
||||||
mkdir Payload
|
mkdir Payload
|
||||||
@@ -430,7 +398,7 @@ jobs:
|
|||||||
build-web:
|
build-web:
|
||||||
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'web' }}
|
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'web' }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [get-release]
|
needs: [ get-release ]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-go@v4
|
- uses: actions/setup-go@v4
|
||||||
@@ -443,13 +411,12 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
VERSION: ${{ needs.get-release.outputs.version }}
|
VERSION: ${{ needs.get-release.outputs.version }}
|
||||||
run: |
|
run: |
|
||||||
VERSION=${VERSION:-$(date +%y%m%d%H%M)} # Use current time as version if not specified
|
|
||||||
cd ui/flutter
|
cd ui/flutter
|
||||||
flutter build web --no-web-resources-cdn
|
flutter build web --web-renderer html
|
||||||
cd ../../
|
cd ../../
|
||||||
cp -r ui/flutter/build/web cmd/web/dist
|
cp -r ui/flutter/build/web cmd/web/dist
|
||||||
mkdir -p dist/zip
|
mkdir -p dist/zip
|
||||||
|
|
||||||
goos_arr=(windows darwin linux)
|
goos_arr=(windows darwin linux)
|
||||||
goarch_arr=(386 amd64 arm64)
|
goarch_arr=(386 amd64 arm64)
|
||||||
export CGO_ENABLED=0
|
export CGO_ENABLED=0
|
||||||
@@ -471,39 +438,6 @@ jobs:
|
|||||||
|| true
|
|| true
|
||||||
done
|
done
|
||||||
done
|
done
|
||||||
sudo apt update -y
|
|
||||||
sudo apt install -y pv python2 bsdmainutils
|
|
||||||
wget -O qdk2_0.32.bionic_amd64.deb "https://github.com/qnap-dev/qdk2/releases/download/v0.32/qdk2_0.32.bionic_amd64.deb"
|
|
||||||
dpkg -X qdk2_0.32.bionic_amd64.deb qdk2 # Direct installs will fail due to missing dependencies!
|
|
||||||
[[ -d qdk2 ]] || exit 1
|
|
||||||
export PATH=$(pwd)/qdk2/usr/bin:$(pwd)/qdk2/usr/share/qdk2/QDK/bin:${PATH}
|
|
||||||
wget -O Gopeed.template.tar.gz "https://github.com/GopeedLab/QpkgBuild/raw/refs/heads/master/template/Gopeed.template.tar.gz"
|
|
||||||
tar -zxf Gopeed.template.tar.gz
|
|
||||||
[[ -d Gopeed ]] || exit 1
|
|
||||||
goos=linux
|
|
||||||
goarch_arr=(amd64 arm64)
|
|
||||||
for goarch in "${goarch_arr[@]}"; do
|
|
||||||
qarch=x86_64
|
|
||||||
[[ "${goarch}" == "arm64" ]] && qarch=arm_64
|
|
||||||
name=gopeed-web-v${VERSION}-${goos}-${goarch}
|
|
||||||
cp dist/${name}/${name}/* Gopeed/${qarch}/
|
|
||||||
done
|
|
||||||
cd Gopeed
|
|
||||||
sed -i -e 's/__QPKG_VER__/${VERSION}/g' qpkg.cfg
|
|
||||||
qbuild || exit 1
|
|
||||||
for f in build/*; do echo ">>> build=[${f}]"; done
|
|
||||||
goos=qnap
|
|
||||||
goarch_arr=(amd64 arm64)
|
|
||||||
for goarch in "${goarch_arr[@]}"; do
|
|
||||||
qarch=x86_64
|
|
||||||
[[ "${goarch}" == "arm64" ]] && qarch=arm_64
|
|
||||||
sname=Gopeed_${VERSION}_${qarch}.qpkg
|
|
||||||
dname=gopeed-${goos}-v${VERSION}-${goarch}.qpkg
|
|
||||||
echo "sname=${sname}, dname=${dname}"
|
|
||||||
[[ -f build/${sname} ]] && cp -ra build/${sname} ../dist/zip/${dname}
|
|
||||||
done
|
|
||||||
for f in ../dist/zip/*; do echo ">>> dist=[${f}]"; done
|
|
||||||
cd ..
|
|
||||||
- name: Upload
|
- name: Upload
|
||||||
uses: shogo82148/actions-upload-release-asset@v1
|
uses: shogo82148/actions-upload-release-asset@v1
|
||||||
with:
|
with:
|
||||||
@@ -513,15 +447,12 @@ jobs:
|
|||||||
build-docker:
|
build-docker:
|
||||||
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'docker' }}
|
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'docker' }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [get-release]
|
needs: [ get-release ]
|
||||||
steps:
|
steps:
|
||||||
- name: Remove unnecessary files
|
- name: Remove unnecessary files
|
||||||
run: |
|
run: |
|
||||||
sudo rm -rf /usr/share/dotnet
|
sudo rm -rf /usr/share/dotnet
|
||||||
sudo rm -rf "$AGENT_TOOLSDIRECTORY"
|
sudo rm -rf "$AGENT_TOOLSDIRECTORY"
|
||||||
- uses: subosito/flutter-action@v2
|
|
||||||
with:
|
|
||||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v2
|
uses: docker/setup-qemu-action@v2
|
||||||
@@ -532,19 +463,11 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
username: liwei2633
|
username: liwei2633
|
||||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
- name: Build flutter web
|
|
||||||
run: |
|
|
||||||
cd ui/flutter
|
|
||||||
flutter build web --no-web-resources-cdn
|
|
||||||
cd ../../
|
|
||||||
rm -rf cmd/web/dist
|
|
||||||
cp -r ui/flutter/build/web cmd/web/dist
|
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
uses: docker/build-push-action@v2
|
uses: docker/build-push-action@v2
|
||||||
env:
|
env:
|
||||||
VERSION: ${{ needs.get-release.outputs.version }}
|
VERSION: ${{ needs.get-release.outputs.version }}
|
||||||
with:
|
with:
|
||||||
context: .
|
|
||||||
push: ${{ github.event.inputs.test == 'true' && 'false' || 'true' }}
|
push: ${{ github.event.inputs.test == 'true' && 'false' || 'true' }}
|
||||||
build-args: |
|
build-args: |
|
||||||
VERSION=${{ env.VERSION }}
|
VERSION=${{ env.VERSION }}
|
||||||
@@ -554,6 +477,7 @@ jobs:
|
|||||||
linux/amd64
|
linux/amd64
|
||||||
linux/arm64
|
linux/arm64
|
||||||
linux/arm/v7
|
linux/arm/v7
|
||||||
|
linux/arm64/v8
|
||||||
tags: |
|
tags: |
|
||||||
liwei2633/gopeed:latest
|
liwei2633/gopeed:latest
|
||||||
liwei2633/gopeed:v${{ env.VERSION }}
|
liwei2633/gopeed:v${{ env.VERSION }}
|
||||||
@@ -17,7 +17,7 @@ on:
|
|||||||
- "Dockerfile"
|
- "Dockerfile"
|
||||||
|
|
||||||
env:
|
env:
|
||||||
GO_VERSION: "1.23"
|
GO_VERSION: "1.19"
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
|
|||||||
+12
-99
@@ -27,8 +27,8 @@ on:
|
|||||||
- "go.sum"
|
- "go.sum"
|
||||||
|
|
||||||
env:
|
env:
|
||||||
GO_VERSION: "1.23"
|
GO_VERSION: "1.21"
|
||||||
FLUTTER_VERSION: "3.24.5"
|
FLUTTER_VERSION: "3.16.8"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
# lint:
|
# lint:
|
||||||
@@ -54,49 +54,14 @@ jobs:
|
|||||||
go-version: ${{ env.GO_VERSION }}
|
go-version: ${{ env.GO_VERSION }}
|
||||||
- name: Run Unit tests.
|
- name: Run Unit tests.
|
||||||
run: go test $(go list ./... | grep -v /cmd/ | grep -v /bind/ ) -v -covermode=count -coverprofile=coverage.txt
|
run: go test $(go list ./... | grep -v /cmd/ | grep -v /bind/ ) -v -covermode=count -coverprofile=coverage.txt
|
||||||
- uses: codecov/codecov-action@v4
|
- uses: codecov/codecov-action@v3
|
||||||
with:
|
with:
|
||||||
files: ./coverage.txt
|
files: ./coverage.txt
|
||||||
token: ${{ secrets.CODECOV_UPLOAD_TOKEN }}
|
build:
|
||||||
build-desktop:
|
name: Build check
|
||||||
name: Build desktop check
|
runs-on: ubuntu-latest
|
||||||
runs-on: ${{ matrix.os }}
|
# needs: [lint, test]
|
||||||
strategy:
|
needs: [test]
|
||||||
matrix:
|
|
||||||
os: [ windows-latest, macos-latest, ubuntu-latest ]
|
|
||||||
needs: [ test ]
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- uses: actions/setup-go@v4
|
|
||||||
with:
|
|
||||||
go-version: ${{ env.GO_VERSION }}
|
|
||||||
- uses: subosito/flutter-action@v2
|
|
||||||
with:
|
|
||||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
|
||||||
- if: runner.os == 'windows-latest'
|
|
||||||
run: |
|
|
||||||
go build -tags nosqlite -ldflags="-w -s" -buildmode=c-shared -o ui/flutter/windows/libgopeed.dll github.com/GopeedLab/gopeed/bind/desktop
|
|
||||||
cd ui/flutter
|
|
||||||
flutter build windows
|
|
||||||
- if: runner.os == 'macos-latest'
|
|
||||||
run: |
|
|
||||||
go build -tags nosqlite -ldflags="-w -s" -buildmode=c-shared -o ui/flutter/macos/Frameworks/libgopeed.dylib github.com/GopeedLab/gopeed/bind/desktop
|
|
||||||
cd ui/flutter
|
|
||||||
flutter build macos
|
|
||||||
- if: runner.os == 'ubuntu-latest'
|
|
||||||
run: |
|
|
||||||
sudo apt-get update -y
|
|
||||||
sudo apt-get install -y ninja-build libgtk-3-dev libayatana-appindicator3-dev
|
|
||||||
go build -tags nosqlite -ldflags="-w -s" -buildmode=c-shared -o ui/flutter/linux/bundle/lib/libgopeed.so github.com/GopeedLab/gopeed/bind/desktop
|
|
||||||
cd ui/flutter
|
|
||||||
flutter build linux
|
|
||||||
build-mobile:
|
|
||||||
name: Build mobile check
|
|
||||||
runs-on: ${{ matrix.os }}
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
os: [ macos-latest, ubuntu-latest ]
|
|
||||||
needs: [ test ]
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-go@v4
|
- uses: actions/setup-go@v4
|
||||||
@@ -106,62 +71,10 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||||
- run: |
|
- run: |
|
||||||
go install golang.org/x/mobile/cmd/gomobile@latest
|
sudo apt-get update -y
|
||||||
go get golang.org/x/mobile/bind
|
sudo apt-get install -y ninja-build libgtk-3-dev libayatana-appindicator3-dev
|
||||||
gomobile init
|
|
||||||
- if: runner.os == 'macOS'
|
|
||||||
run: |
|
|
||||||
gomobile bind -tags nosqlite -ldflags="-w -s" -o ui/flutter/ios/Frameworks/Libgopeed.xcframework -target=ios github.com/GopeedLab/gopeed/bind/mobile
|
|
||||||
cd ui/flutter
|
|
||||||
flutter build ipa --no-codesign
|
|
||||||
- if: runner.os == 'Linux'
|
|
||||||
uses: actions/setup-java@v3
|
|
||||||
with:
|
|
||||||
distribution: "zulu"
|
|
||||||
java-version: "17"
|
|
||||||
- if: runner.os == 'Linux'
|
|
||||||
run: |
|
|
||||||
gomobile bind -tags nosqlite -ldflags="-w -s -checklinkname=0" -o ui/flutter/android/app/libs/libgopeed.aar -target=android -androidapi 21 -javapkg=com.gopeed github.com/GopeedLab/gopeed/bind/mobile
|
|
||||||
cd ui/flutter
|
|
||||||
flutter build apk
|
|
||||||
|
|
||||||
build-web:
|
|
||||||
name: Build web check
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: [ test ]
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- uses: actions/setup-go@v4
|
|
||||||
with:
|
|
||||||
go-version: ${{ env.GO_VERSION }}
|
|
||||||
- uses: subosito/flutter-action@v2
|
|
||||||
with:
|
|
||||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
|
go build -tags nosqlite -ldflags="-w -s" -buildmode=c-shared -o ui/flutter/linux/bundle/lib/libgopeed.so github.com/GopeedLab/gopeed/bind/desktop
|
||||||
cd ui/flutter
|
cd ui/flutter
|
||||||
flutter build web --no-web-resources-cdn
|
flutter build linux
|
||||||
cd ../../
|
|
||||||
rm -rf cmd/web/dist
|
|
||||||
cp -r ui/flutter/build/web cmd/web/dist
|
|
||||||
go build -tags nosqlite,web -ldflags="-s -w" github.com/GopeedLab/gopeed/cmd/web
|
|
||||||
build-docker:
|
|
||||||
name: Build docker check
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: [ test ]
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- uses: actions/setup-go@v4
|
|
||||||
with:
|
|
||||||
go-version: ${{ env.GO_VERSION }}
|
|
||||||
- uses: subosito/flutter-action@v2
|
|
||||||
with:
|
|
||||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
|
||||||
- name: Build
|
|
||||||
run: |
|
|
||||||
cd ui/flutter
|
|
||||||
flutter build web --no-web-resources-cdn
|
|
||||||
cd ../../
|
|
||||||
rm -rf cmd/web/dist
|
|
||||||
cp -r ui/flutter/build/web cmd/web/dist
|
|
||||||
docker build -t gopeed .
|
|
||||||
@@ -21,7 +21,6 @@ The internationalization files of Gopeed are located in the `ui/flutter/lib/i18n
|
|||||||
You only need to add the corresponding language file in this directory.
|
You only need to add the corresponding language file in this directory.
|
||||||
|
|
||||||
Please refer to `en_us.dart` for translation.
|
Please refer to `en_us.dart` for translation.
|
||||||
words prefixed with `@` are not meant to be translated.
|
|
||||||
|
|
||||||
## flutter development
|
## flutter development
|
||||||
|
|
||||||
|
|||||||
+12
-3
@@ -1,20 +1,29 @@
|
|||||||
FROM golang:1.23.3 AS go
|
FROM instrumentisto/flutter:3.16.8 AS flutter
|
||||||
|
WORKDIR /app
|
||||||
|
COPY ./ui/flutter/pubspec.yaml ./ui/flutter/pubspec.lock ./
|
||||||
|
RUN flutter pub get
|
||||||
|
COPY ./ui/flutter ./
|
||||||
|
RUN flutter build web --web-renderer html
|
||||||
|
|
||||||
|
FROM golang:1.21.6 AS go
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY ./go.mod ./go.sum ./
|
COPY ./go.mod ./go.sum ./
|
||||||
RUN go mod download
|
RUN go mod download
|
||||||
COPY . .
|
COPY . .
|
||||||
|
COPY --from=flutter /app/build/web ./cmd/web/dist
|
||||||
ARG VERSION=dev
|
ARG VERSION=dev
|
||||||
RUN CGO_ENABLED=0 go build -tags nosqlite,web \
|
RUN CGO_ENABLED=0 go build -tags nosqlite,web \
|
||||||
-ldflags="-s -w -X github.com/GopeedLab/gopeed/pkg/base.Version=$VERSION -X github.com/GopeedLab/gopeed/pkg/base.InDocker=true" \
|
-ldflags="-s -w -X github.com/GopeedLab/gopeed/pkg/base.Version=$VERSION -X github.com/GopeedLab/gopeed/pkg/base.InDocker=true" \
|
||||||
-o dist/gopeed github.com/GopeedLab/gopeed/cmd/web
|
-o dist/gopeed github.com/GopeedLab/gopeed/cmd/web
|
||||||
|
|
||||||
FROM alpine:3.18
|
FROM alpine:3.14.2
|
||||||
LABEL maintainer="monkeyWie"
|
LABEL maintainer="monkeyWie"
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY --from=go /app/dist/gopeed ./
|
COPY --from=go /app/dist/gopeed ./
|
||||||
COPY entrypoint.sh ./entrypoint.sh
|
COPY entrypoint.sh ./entrypoint.sh
|
||||||
RUN apk update && \
|
RUN apk update && \
|
||||||
apk add --no-cache su-exec ; \
|
apk upgrade --no-cache && \
|
||||||
|
apk add --no-cache bash su-exec; \
|
||||||
chmod +x ./entrypoint.sh && \
|
chmod +x ./entrypoint.sh && \
|
||||||
rm -rf /var/cache/apk/*
|
rm -rf /var/cache/apk/*
|
||||||
VOLUME ["/app/storage"]
|
VOLUME ["/app/storage"]
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
# [](https://gopeed.com)
|
|
||||||
|
|
||||||
[](https://github.com/GopeedLab/gopeed/actions?query=workflow%3Atest)
|
[](https://github.com/GopeedLab/gopeed/actions?query=workflow%3Atest)
|
||||||
[](https://codecov.io/gh/GopeedLab/gopeed)
|
[](https://codecov.io/gh/GopeedLab/gopeed)
|
||||||
[](https://github.com/GopeedLab/gopeed/releases)
|
[](https://github.com/GopeedLab/gopeed/releases)
|
||||||
@@ -7,7 +5,7 @@
|
|||||||
[](https://docs.gopeed.com/donate.html)
|
[](https://docs.gopeed.com/donate.html)
|
||||||
[](https://discord.gg/ZUJqJrwCGB)
|
[](https://discord.gg/ZUJqJrwCGB)
|
||||||
|
|
||||||
<a href="https://trendshift.io/repositories/7953" target="_blank"><img src="https://trendshift.io/api/badge/repositories/7953" alt="GopeedLab%2Fgopeed | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
|

|
||||||
|
|
||||||
[](https://ko-fi.com/R6R6IJGN6)
|
[](https://ko-fi.com/R6R6IJGN6)
|
||||||
|
|
||||||
@@ -71,11 +69,6 @@ Visit ✈ [Official Website](https://gopeed.com) | 📖 [Official Docs](https://
|
|||||||
<td><code>IPA</code></td>
|
<td><code>IPA</code></td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-ios.ipa">Link</a></td>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-ios.ipa">Link</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
|
||||||
<td>Qnap</td>
|
|
||||||
<td><code>QPKG</code></td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=gopeed-qnap-$version-amd64.qpkg">Link</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
<tr>
|
||||||
<td>Web</td>
|
<td>Web</td>
|
||||||
<td></td>
|
<td></td>
|
||||||
@@ -119,8 +112,8 @@ This project is divided into two parts, the front end uses `flutter`, the back e
|
|||||||
|
|
||||||
### Environment
|
### Environment
|
||||||
|
|
||||||
1. Golang 1.23+
|
1. Golang 1.21+
|
||||||
2. Flutter 3.24+
|
2. Flutter 3.16+
|
||||||
|
|
||||||
### Clone
|
### Clone
|
||||||
|
|
||||||
@@ -179,7 +172,7 @@ command:
|
|||||||
- android
|
- android
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
gomobile bind -tags nosqlite -ldflags="-w -s -checklinkname=0" -o ui/flutter/android/app/libs/libgopeed.aar -target=android -androidapi 21 -javapkg="com.gopeed" github.com/GopeedLab/gopeed/bind/mobile
|
gomobile bind -tags nosqlite -ldflags="-w -s" -o ui/flutter/android/app/libs/libgopeed.aar -target=android -androidapi 19 -javapkg="com.gopeed" github.com/GopeedLab/gopeed/bind/mobile
|
||||||
cd ui/flutter
|
cd ui/flutter
|
||||||
flutter build apk
|
flutter build apk
|
||||||
```
|
```
|
||||||
|
|||||||
+4
-11
@@ -1,5 +1,3 @@
|
|||||||
# [](https://gopeed.com)
|
|
||||||
|
|
||||||
[](https://github.com/GopeedLab/gopeed/actions?query=workflow%3Atest)
|
[](https://github.com/GopeedLab/gopeed/actions?query=workflow%3Atest)
|
||||||
[](https://codecov.io/gh/GopeedLab/gopeed)
|
[](https://codecov.io/gh/GopeedLab/gopeed)
|
||||||
[](https://github.com/GopeedLab/gopeed/releases)
|
[](https://github.com/GopeedLab/gopeed/releases)
|
||||||
@@ -7,7 +5,7 @@
|
|||||||
[](https://docs.gopeed.com/donate.html)
|
[](https://docs.gopeed.com/donate.html)
|
||||||
[](https://discord.gg/ZUJqJrwCGB)
|
[](https://discord.gg/ZUJqJrwCGB)
|
||||||
|
|
||||||
<a href="https://trendshift.io/repositories/7953" target="_blank"><img src="https://trendshift.io/api/badge/repositories/7953" alt="GopeedLab%2Fgopeed | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
|

|
||||||
|
|
||||||
[](https://ko-fi.com/R6R6IJGN6)
|
[](https://ko-fi.com/R6R6IJGN6)
|
||||||
|
|
||||||
@@ -71,11 +69,6 @@ Gopeed (正式名 Go Speed) は `Golang` + `Flutter` によって開発された
|
|||||||
<td><code>IPA</code></td>
|
<td><code>IPA</code></td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-ios.ipa">Link</a></td>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-ios.ipa">Link</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
|
||||||
<td>Qnap</td>
|
|
||||||
<td><code>QPKG</code></td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=gopeed-qnap-$version-amd64.qpkg">Link</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
<tr>
|
||||||
<td>Web</td>
|
<td>Web</td>
|
||||||
<td></td>
|
<td></td>
|
||||||
@@ -107,8 +100,8 @@ Gopeed (正式名 Go Speed) は `Golang` + `Flutter` によって開発された
|
|||||||
|
|
||||||
### 環境
|
### 環境
|
||||||
|
|
||||||
1. Go 言語 1.23+
|
1. Go 言語 1.21+
|
||||||
2. Flutter 3.24+
|
2. Flutter 3.16+
|
||||||
|
|
||||||
### クローン
|
### クローン
|
||||||
|
|
||||||
@@ -167,7 +160,7 @@ gomobile init
|
|||||||
- android
|
- android
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
gomobile bind -tags nosqlite -ldflags="-w -s -checklinkname=0" -o ui/flutter/android/app/libs/libgopeed.aar -target=android -androidapi 21 -javapkg="com.gopeed" github.com/GopeedLab/gopeed/bind/mobile
|
gomobile bind -tags nosqlite -ldflags="-w -s" -o ui/flutter/android/app/libs/libgopeed.aar -target=android -androidapi 19 -javapkg="com.gopeed" github.com/GopeedLab/gopeed/bind/mobile
|
||||||
cd ui/flutter
|
cd ui/flutter
|
||||||
flutter build apk
|
flutter build apk
|
||||||
```
|
```
|
||||||
|
|||||||
+5
-12
@@ -1,5 +1,3 @@
|
|||||||
# [](https://gopeed.com)
|
|
||||||
|
|
||||||
[](https://github.com/GopeedLab/gopeed/actions?query=workflow%3Atest)
|
[](https://github.com/GopeedLab/gopeed/actions?query=workflow%3Atest)
|
||||||
[](https://codecov.io/gh/GopeedLab/gopeed)
|
[](https://codecov.io/gh/GopeedLab/gopeed)
|
||||||
[](https://github.com/GopeedLab/gopeed/releases)
|
[](https://github.com/GopeedLab/gopeed/releases)
|
||||||
@@ -7,7 +5,7 @@
|
|||||||
[](https://docs.gopeed.com/donate.html)
|
[](https://docs.gopeed.com/donate.html)
|
||||||
[](https://discord.gg/ZUJqJrwCGB)
|
[](https://discord.gg/ZUJqJrwCGB)
|
||||||
|
|
||||||
<a href="https://trendshift.io/repositories/7953" target="_blank"><img src="https://trendshift.io/api/badge/repositories/7953" alt="GopeedLab%2Fgopeed | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
|

|
||||||
|
|
||||||
[](https://ko-fi.com/R6R6IJGN6)
|
[](https://ko-fi.com/R6R6IJGN6)
|
||||||
|
|
||||||
@@ -71,11 +69,6 @@ Truy cập ✈ [Trang web chính thức](https://gopeed.com) | 📖 [Tài liệu
|
|||||||
<td><code>IPA</code></td>
|
<td><code>IPA</code></td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-ios.ipa">Liên kết</a></td>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-ios.ipa">Liên kết</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
|
||||||
<td>Qnap</td>
|
|
||||||
<td><code>QPKG</code></td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=gopeed-qnap-$version-amd64.qpkg">Liên kết</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
<tr>
|
||||||
<td>Web</td>
|
<td>Web</td>
|
||||||
<td></td>
|
<td></td>
|
||||||
@@ -115,8 +108,8 @@ Dự án này được chia thành hai phần, phần giao diện sử dụng `f
|
|||||||
|
|
||||||
### Environment
|
### Environment
|
||||||
|
|
||||||
1. Golang 1.23+
|
1. Golang 1.21+
|
||||||
2. Flutter 3.24+
|
2. Flutter 3.16+
|
||||||
|
|
||||||
### Clone
|
### Clone
|
||||||
|
|
||||||
@@ -175,7 +168,7 @@ command:
|
|||||||
- android
|
- android
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
gomobile bind -tags nosqlite -ldflags="-w -s -checklinkname=0" -o ui/flutter/android/app/libs/libgopeed.aar -target=android -androidapi 21 -javapkg="com.gopeed" github.com/GopeedLab/gopeed/bind/mobile
|
gomobile bind -tags nosqlite -ldflags="-w -s" -o ui/flutter/android/app/libs/libgopeed.aar -target=android -androidapi 19 -javapkg="com.gopeed" github.com/GopeedLab/gopeed/bind/mobile
|
||||||
cd ui/flutter
|
cd ui/flutter
|
||||||
flutter build apk
|
flutter build apk
|
||||||
```
|
```
|
||||||
@@ -215,4 +208,4 @@ go build -tags nosqlite,web -ldflags="-s -w" -o bin/ github.com/GopeedLab/gopeed
|
|||||||
|
|
||||||
## Giấy phép
|
## Giấy phép
|
||||||
|
|
||||||
[GPLv3](LICENSE)
|
[GPLv3](LICENSE)
|
||||||
+4
-11
@@ -1,5 +1,3 @@
|
|||||||
# [](https://gopeed.com)
|
|
||||||
|
|
||||||
[](https://github.com/GopeedLab/gopeed/actions?query=workflow%3Atest)
|
[](https://github.com/GopeedLab/gopeed/actions?query=workflow%3Atest)
|
||||||
[](https://codecov.io/gh/GopeedLab/gopeed)
|
[](https://codecov.io/gh/GopeedLab/gopeed)
|
||||||
[](https://github.com/GopeedLab/gopeed/releases)
|
[](https://github.com/GopeedLab/gopeed/releases)
|
||||||
@@ -7,7 +5,7 @@
|
|||||||
[](https://docs.gopeed.com/zh/donate.html)
|
[](https://docs.gopeed.com/zh/donate.html)
|
||||||
[](https://discord.gg/ZUJqJrwCGB)
|
[](https://discord.gg/ZUJqJrwCGB)
|
||||||
|
|
||||||
<a href="https://trendshift.io/repositories/7953" target="_blank"><img src="https://trendshift.io/api/badge/repositories/7953" alt="GopeedLab%2Fgopeed | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
|

|
||||||
|
|
||||||
[](https://ko-fi.com/R6R6IJGN6)
|
[](https://ko-fi.com/R6R6IJGN6)
|
||||||
|
|
||||||
@@ -71,11 +69,6 @@ Gopeed(全称 Go Speed),直译过来中文名叫做`够快下载器`(不
|
|||||||
<td><code>IPA</code></td>
|
<td><code>IPA</code></td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-ios.ipa">前往</a></td>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-ios.ipa">前往</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
|
||||||
<td>Qnap</td>
|
|
||||||
<td><code>QPKG</code></td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=gopeed-qnap-$version-amd64.qpkg">前往</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
<tr>
|
||||||
<td>Web</td>
|
<td>Web</td>
|
||||||
<td></td>
|
<td></td>
|
||||||
@@ -119,8 +112,8 @@ Gopeed 还提供了浏览器扩展用于接管浏览器下载,支持 Chrome、
|
|||||||
|
|
||||||
### 环境要求
|
### 环境要求
|
||||||
|
|
||||||
1. Golang 1.23+
|
1. Golang 1.21+
|
||||||
2. Flutter 3.24+
|
2. Flutter 3.16+
|
||||||
|
|
||||||
### 克隆项目
|
### 克隆项目
|
||||||
|
|
||||||
@@ -179,7 +172,7 @@ gomobile init
|
|||||||
- android
|
- android
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
gomobile bind -tags nosqlite -ldflags="-w -s -checklinkname=0" -o ui/flutter/android/app/libs/libgopeed.aar -target=android -androidapi 21 -javapkg="com.gopeed" github.com/GopeedLab/gopeed/bind/mobile
|
gomobile bind -tags nosqlite -ldflags="-w -s" -o ui/flutter/android/app/libs/libgopeed.aar -target=android -androidapi 19 -javapkg="com.gopeed" github.com/GopeedLab/gopeed/bind/mobile
|
||||||
cd ui/flutter
|
cd ui/flutter
|
||||||
flutter build apk
|
flutter build apk
|
||||||
```
|
```
|
||||||
|
|||||||
+5
-12
@@ -1,5 +1,3 @@
|
|||||||
# [](https://gopeed.com)
|
|
||||||
|
|
||||||
[](https://github.com/GopeedLab/gopeed/actions?query=workflow%3Atest)
|
[](https://github.com/GopeedLab/gopeed/actions?query=workflow%3Atest)
|
||||||
[](https://codecov.io/gh/GopeedLab/gopeed)
|
[](https://codecov.io/gh/GopeedLab/gopeed)
|
||||||
[](https://github.com/GopeedLab/gopeed/releases)
|
[](https://github.com/GopeedLab/gopeed/releases)
|
||||||
@@ -7,7 +5,7 @@
|
|||||||
[](https://docs.gopeed.com/donate.html)
|
[](https://docs.gopeed.com/donate.html)
|
||||||
[](https://discord.gg/ZUJqJrwCGB)
|
[](https://discord.gg/ZUJqJrwCGB)
|
||||||
|
|
||||||
<a href="https://trendshift.io/repositories/7953" target="_blank"><img src="https://trendshift.io/api/badge/repositories/7953" alt="GopeedLab%2Fgopeed | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
|

|
||||||
|
|
||||||
[](https://ko-fi.com/R6R6IJGN6)
|
[](https://ko-fi.com/R6R6IJGN6)
|
||||||
|
|
||||||
@@ -71,11 +69,6 @@ Gopeed(全稱 Go Speed),是一款使用`Golang`+`Flutter`編寫的高速
|
|||||||
<td><code>IPA</code></td>
|
<td><code>IPA</code></td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-ios.ipa">前往</a></td>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-ios.ipa">前往</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
|
||||||
<td>Qnap</td>
|
|
||||||
<td><code>QPKG</code></td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=gopeed-qnap-$version-amd64.qpkg">前往</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
<tr>
|
||||||
<td>Web</td>
|
<td>Web</td>
|
||||||
<td></td>
|
<td></td>
|
||||||
@@ -91,7 +84,7 @@ Gopeed(全稱 Go Speed),是一款使用`Golang`+`Flutter`編寫的高速
|
|||||||
|
|
||||||
更多關於安裝的內容請參考[安裝文檔](https://docs.gopeed.com/zh/install.html)
|
更多關於安裝的內容請參考[安裝文檔](https://docs.gopeed.com/zh/install.html)
|
||||||
|
|
||||||
### 使用 CLI 安裝
|
### 使用CLI安裝
|
||||||
|
|
||||||
使用`go install`安裝:
|
使用`go install`安裝:
|
||||||
|
|
||||||
@@ -115,8 +108,8 @@ go install github.com/GopeedLab/gopeed/cmd/gopeed@latest
|
|||||||
|
|
||||||
### 開發環境
|
### 開發環境
|
||||||
|
|
||||||
1. Golang 1.23+
|
1. Golang 1.21+
|
||||||
2. Flutter 3.24+
|
2. Flutter 3.16+
|
||||||
|
|
||||||
### 克隆項目
|
### 克隆項目
|
||||||
|
|
||||||
@@ -175,7 +168,7 @@ gomobile init
|
|||||||
- android
|
- android
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
gomobile bind -tags nosqlite -ldflags="-w -s -checklinkname=0" -o ui/flutter/android/app/libs/libgopeed.aar -target=android -androidapi 21 -javapkg="com.gopeed" github.com/GopeedLab/gopeed/bind/mobile
|
gomobile bind -tags nosqlite -ldflags="-w -s" -o ui/flutter/android/app/libs/libgopeed.aar -target=android -androidapi 19 -javapkg="com.gopeed" github.com/GopeedLab/gopeed/bind/mobile
|
||||||
cd ui/flutter
|
cd ui/flutter
|
||||||
flutter build apk
|
flutter build apk
|
||||||
```
|
```
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 26 KiB |
+11
-36
@@ -8,9 +8,7 @@ import (
|
|||||||
"github.com/GopeedLab/gopeed/pkg/rest/model"
|
"github.com/GopeedLab/gopeed/pkg/rest/model"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"syscall"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed banner.txt
|
//go:embed banner.txt
|
||||||
@@ -27,46 +25,23 @@ func Start(cfg *model.StartConfig) {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
if downloadCfg.FirstLoad {
|
if downloadCfg.FirstLoad {
|
||||||
// Set default download config
|
|
||||||
if cfg.DownloadConfig != nil {
|
|
||||||
cfg.DownloadConfig.Merge(downloadCfg)
|
|
||||||
// TODO Use PatchConfig
|
|
||||||
rest.Downloader.PutConfig(cfg.DownloadConfig)
|
|
||||||
downloadCfg = cfg.DownloadConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
downloadDir := downloadCfg.DownloadDir
|
|
||||||
// Set default download dir, in docker, it will be ${exe}/Downloads, else it will be ${user}/Downloads
|
// Set default download dir, in docker, it will be ${exe}/Downloads, else it will be ${user}/Downloads
|
||||||
if downloadDir == "" {
|
var downloadDir string
|
||||||
if base.InDocker == "true" {
|
if base.InDocker == "true" {
|
||||||
downloadDir = filepath.Join(filepath.Dir(cfg.StorageDir), "Downloads")
|
downloadDir = filepath.Join(filepath.Dir(cfg.StorageDir), "Downloads")
|
||||||
} else {
|
} else {
|
||||||
userDir, err := os.UserHomeDir()
|
userDir, err := os.UserHomeDir()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
downloadDir = filepath.Join(userDir, "Downloads")
|
downloadDir = filepath.Join(userDir, "Downloads")
|
||||||
}
|
|
||||||
}
|
|
||||||
if downloadDir != "" {
|
|
||||||
downloadCfg.DownloadDir = downloadDir
|
|
||||||
rest.Downloader.PutConfig(downloadCfg)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if downloadDir != "" {
|
||||||
|
downloadCfg.DownloadDir = downloadDir
|
||||||
|
rest.Downloader.PutConfig(downloadCfg)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
watchExit()
|
|
||||||
|
|
||||||
fmt.Printf("Server start success on http://%s\n", listener.Addr().String())
|
fmt.Printf("Server start success on http://%s\n", listener.Addr().String())
|
||||||
if err := srv.Serve(listener); err != nil && err != http.ErrServerClosed {
|
if err := srv.Serve(listener); err != nil && err != http.ErrServerClosed {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func watchExit() {
|
|
||||||
sigs := make(chan os.Signal, 1)
|
|
||||||
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
|
|
||||||
go func() {
|
|
||||||
sig := <-sigs
|
|
||||||
fmt.Printf("Server is shutting down due to signal: %s\n", sig)
|
|
||||||
rest.Downloader.Close()
|
|
||||||
os.Exit(0)
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
+2
-6
@@ -4,7 +4,6 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/GopeedLab/gopeed/pkg/base"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
)
|
)
|
||||||
@@ -16,8 +15,6 @@ type args struct {
|
|||||||
Password *string `json:"password"`
|
Password *string `json:"password"`
|
||||||
ApiToken *string `json:"apiToken"`
|
ApiToken *string `json:"apiToken"`
|
||||||
StorageDir *string `json:"storageDir"`
|
StorageDir *string `json:"storageDir"`
|
||||||
// DownloadConfig when the first time to start the server, it will be configured as initial value
|
|
||||||
DownloadConfig *base.DownloaderStoreConfig `json:"downloadConfig"`
|
|
||||||
|
|
||||||
configPath *string
|
configPath *string
|
||||||
}
|
}
|
||||||
@@ -28,7 +25,7 @@ func parse() *args {
|
|||||||
cliArgs.Port = flag.Int("P", 9999, "Bind Port")
|
cliArgs.Port = flag.Int("P", 9999, "Bind Port")
|
||||||
cliArgs.Username = flag.String("u", "gopeed", "HTTP Basic Auth Username")
|
cliArgs.Username = flag.String("u", "gopeed", "HTTP Basic Auth Username")
|
||||||
cliArgs.Password = flag.String("p", "", "HTTP Basic Auth Pwd")
|
cliArgs.Password = flag.String("p", "", "HTTP Basic Auth Pwd")
|
||||||
cliArgs.ApiToken = flag.String("T", "", "API token, it must be configured when using HTTP API in the case of enabling basic authentication")
|
cliArgs.ApiToken = flag.String("T", "", "API token, that can only be used when basic authentication is enabled.")
|
||||||
cliArgs.StorageDir = flag.String("d", "", "Storage directory")
|
cliArgs.StorageDir = flag.String("d", "", "Storage directory")
|
||||||
cliArgs.configPath = flag.String("c", "./config.json", "Config file path")
|
cliArgs.configPath = flag.String("c", "./config.json", "Config file path")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
@@ -60,9 +57,8 @@ func loadConfig(path string) *args {
|
|||||||
var args args
|
var args args
|
||||||
|
|
||||||
if !filepath.IsAbs(path) {
|
if !filepath.IsAbs(path) {
|
||||||
dir, err := os.Getwd()
|
dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("config dir get failed, reason:" + err.Error())
|
|
||||||
return &args
|
return &args
|
||||||
}
|
}
|
||||||
path = filepath.Join(dir, path)
|
path = filepath.Join(dir, path)
|
||||||
|
|||||||
@@ -48,7 +48,6 @@ func main() {
|
|||||||
Storage: model.StorageBolt,
|
Storage: model.StorageBolt,
|
||||||
StorageDir: filepath.Join(dir, "storage"),
|
StorageDir: filepath.Join(dir, "storage"),
|
||||||
ApiToken: *args.ApiToken,
|
ApiToken: *args.ApiToken,
|
||||||
DownloadConfig: args.DownloadConfig,
|
|
||||||
ProductionMode: true,
|
ProductionMode: true,
|
||||||
WebEnable: true,
|
WebEnable: true,
|
||||||
WebFS: sub,
|
WebFS: sub,
|
||||||
|
|||||||
+11
-14
@@ -1,15 +1,12 @@
|
|||||||
|
version: '3'
|
||||||
|
|
||||||
services:
|
services:
|
||||||
gopeed:
|
gopeed:
|
||||||
container_name: gopeed
|
container_name: gopeed
|
||||||
ports:
|
ports:
|
||||||
- 9999:9999 # HTTP port (host:container)
|
- "9999:9999" # HTTP port (host:container)
|
||||||
environment:
|
image: liwei2633/gopeed
|
||||||
- PUID=0
|
volumes:
|
||||||
- PGID=0
|
- ~/gopeed/Downloads:/root/Downloads # mount download path
|
||||||
- UMASK=022
|
#- ~/gopeed/storage:/app/storage # if you need to mount storage path, uncomment this line
|
||||||
volumes:
|
restart: unless-stopped
|
||||||
- ~/gopeed/Downloads:/app/Downloads # mount download path
|
|
||||||
#- ~/gopeed/storage:/app/storage # if you need to mount storage path, uncomment this line
|
|
||||||
restart: unless-stopped
|
|
||||||
image: liwei2633/gopeed
|
|
||||||
# command: -u Username -p Password # optional authentication
|
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
#!/bin/sh
|
#!/bin/bash
|
||||||
|
|
||||||
chown -R ${PUID}:${PGID} /app
|
chown -R ${PUID}:${PGID} /app
|
||||||
|
|
||||||
|
|||||||
@@ -1,127 +1,118 @@
|
|||||||
module github.com/GopeedLab/gopeed
|
module github.com/GopeedLab/gopeed
|
||||||
|
|
||||||
go 1.23.0
|
go 1.21
|
||||||
|
|
||||||
toolchain go1.23.1
|
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/anacrolix/missinggo/v2 v2.8.0
|
github.com/anacrolix/missinggo/v2 v2.7.3
|
||||||
github.com/anacrolix/torrent v1.57.2-0.20241017235801-4d8437a05621
|
github.com/anacrolix/torrent v1.53.3
|
||||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5
|
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5
|
||||||
github.com/dop251/goja v0.0.0-20240919115326-6c7d1df7ff05
|
github.com/dop251/goja v0.0.0-20231027120936-b396bb4c349d
|
||||||
github.com/dop251/goja_nodejs v0.0.0-20240728170619-29b559befffc
|
github.com/dop251/goja_nodejs v0.0.0-20231122114759-e84d9a924c5c
|
||||||
github.com/go-git/go-git/v5 v5.8.1
|
github.com/go-git/go-git/v5 v5.8.1
|
||||||
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.12
|
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/rs/dnscache v0.0.0-20230804202142-fc85eb664529
|
|
||||||
github.com/rs/zerolog v1.31.0
|
github.com/rs/zerolog v1.31.0
|
||||||
github.com/xiaoqidun/setft v0.0.0-20220310121541-be86327699ad
|
github.com/virtuald/go-paniclog v0.0.0-20190812204905-43a7fa316459
|
||||||
go.etcd.io/bbolt v1.3.11
|
go.etcd.io/bbolt v1.3.8
|
||||||
golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f
|
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a
|
||||||
golang.org/x/sync v0.9.0
|
golang.org/x/sync v0.6.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
dario.cat/mergo v1.0.0 // indirect
|
dario.cat/mergo v1.0.0 // indirect
|
||||||
github.com/Microsoft/go-winio v0.6.1 // indirect
|
github.com/Microsoft/go-winio v0.6.1 // indirect
|
||||||
github.com/ProtonMail/go-crypto v0.0.0-20230717121422-5aa5874ade95 // indirect
|
github.com/ProtonMail/go-crypto v0.0.0-20230717121422-5aa5874ade95 // indirect
|
||||||
github.com/RoaringBitmap/roaring v1.9.4 // indirect
|
github.com/RoaringBitmap/roaring v1.7.0 // indirect
|
||||||
github.com/acomagu/bufpipe v1.0.4 // indirect
|
github.com/acomagu/bufpipe v1.0.4 // indirect
|
||||||
github.com/ajwerner/btree v0.0.0-20211221152037-f427b3e689c0 // indirect
|
github.com/ajwerner/btree v0.0.0-20211221152037-f427b3e689c0 // indirect
|
||||||
github.com/alecthomas/atomic v0.1.0-alpha2 // indirect
|
github.com/alecthomas/atomic v0.1.0-alpha2 // indirect
|
||||||
github.com/anacrolix/chansync v0.6.0 // indirect
|
github.com/anacrolix/chansync v0.4.0 // indirect
|
||||||
github.com/anacrolix/dht/v2 v2.22.0 // indirect
|
github.com/anacrolix/dht/v2 v2.21.0 // indirect
|
||||||
github.com/anacrolix/envpprof v1.4.0 // indirect
|
github.com/anacrolix/envpprof v1.3.0 // indirect
|
||||||
github.com/anacrolix/generics v0.0.3-0.20240902042256-7fb2702ef0ca // indirect
|
github.com/anacrolix/generics v0.0.0-20230911070922-5dd7545c6b13 // indirect
|
||||||
github.com/anacrolix/go-libutp v1.3.1 // indirect
|
github.com/anacrolix/go-libutp v1.3.1 // indirect
|
||||||
github.com/anacrolix/log v0.16.0 // indirect
|
github.com/anacrolix/log v0.14.6-0.20231202035202-ed7a02cad0b4 // indirect
|
||||||
github.com/anacrolix/missinggo v1.3.0 // indirect
|
github.com/anacrolix/missinggo v1.3.0 // indirect
|
||||||
github.com/anacrolix/missinggo/perf v1.0.0 // indirect
|
github.com/anacrolix/missinggo/perf v1.0.0 // indirect
|
||||||
github.com/anacrolix/mmsg v1.1.1 // indirect
|
github.com/anacrolix/mmsg v1.0.0 // indirect
|
||||||
github.com/anacrolix/multiless v0.4.0 // indirect
|
github.com/anacrolix/multiless v0.3.1-0.20221221005021-2d12701f83f7 // indirect
|
||||||
github.com/anacrolix/stm v0.5.0 // indirect
|
github.com/anacrolix/stm v0.5.0 // indirect
|
||||||
github.com/anacrolix/sync v0.5.3 // indirect
|
github.com/anacrolix/sync v0.5.1 // indirect
|
||||||
github.com/anacrolix/upnp v0.1.4 // indirect
|
github.com/anacrolix/upnp v0.1.3 // indirect
|
||||||
github.com/anacrolix/utp v0.2.0 // indirect
|
github.com/anacrolix/utp v0.2.0 // indirect
|
||||||
github.com/bahlo/generic-list-go v0.2.0 // indirect
|
github.com/bahlo/generic-list-go v0.2.0 // indirect
|
||||||
github.com/benbjohnson/immutable v0.4.3 // indirect
|
github.com/benbjohnson/immutable v0.4.3 // indirect
|
||||||
github.com/bits-and-blooms/bitset v1.15.0 // indirect
|
github.com/bits-and-blooms/bitset v1.13.0 // indirect
|
||||||
github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 // indirect
|
github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 // indirect
|
||||||
github.com/cespare/xxhash v1.1.0 // indirect
|
|
||||||
github.com/cloudflare/circl v1.3.3 // indirect
|
github.com/cloudflare/circl v1.3.3 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/dlclark/regexp2 v1.11.4 // indirect
|
github.com/dlclark/regexp2 v1.10.0 // indirect
|
||||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
github.com/edsrzf/mmap-go v1.2.0 // indirect
|
github.com/edsrzf/mmap-go v1.1.0 // indirect
|
||||||
github.com/emirpasic/gods v1.18.1 // indirect
|
github.com/emirpasic/gods v1.18.1 // indirect
|
||||||
github.com/felixge/httpsnoop v1.0.1 // indirect
|
github.com/felixge/httpsnoop v1.0.1 // indirect
|
||||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||||
github.com/go-git/go-billy/v5 v5.4.1 // indirect
|
github.com/go-git/go-billy/v5 v5.4.1 // indirect
|
||||||
github.com/go-llsqlite/adapter v0.1.0 // indirect
|
github.com/go-llsqlite/adapter v0.1.0 // indirect
|
||||||
github.com/go-llsqlite/crawshaw v0.5.5 // indirect
|
github.com/go-llsqlite/crawshaw v0.5.0 // indirect
|
||||||
github.com/go-logr/logr v1.4.2 // indirect
|
github.com/go-logr/logr v1.4.1 // indirect
|
||||||
github.com/go-logr/stdr v1.2.2 // indirect
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
github.com/go-sourcemap/sourcemap v2.1.4+incompatible // indirect
|
github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect
|
||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||||
github.com/google/btree v1.1.3 // indirect
|
github.com/google/btree v1.1.2 // indirect
|
||||||
github.com/google/pprof v0.0.0-20240910150728-a0b0bb1d4134 // indirect
|
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a // indirect
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
github.com/google/uuid v1.5.0 // indirect
|
||||||
github.com/gorilla/websocket v1.5.3 // indirect
|
github.com/gorilla/websocket v1.5.1 // indirect
|
||||||
github.com/huandu/xstrings v1.5.0 // indirect
|
github.com/huandu/xstrings v1.4.0 // indirect
|
||||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||||
github.com/kevinburke/ssh_config v1.2.0 // indirect
|
github.com/kevinburke/ssh_config v1.2.0 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.9 // indirect
|
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
|
github.com/mattn/go-ieproxy v0.0.11 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/minio/sha256-simd v1.0.1 // indirect
|
|
||||||
github.com/mr-tron/base58 v1.2.0 // indirect
|
|
||||||
github.com/mschoch/smat v0.2.0 // indirect
|
github.com/mschoch/smat v0.2.0 // indirect
|
||||||
github.com/multiformats/go-multihash v0.2.3 // indirect
|
github.com/pion/datachannel v1.5.5 // indirect
|
||||||
github.com/multiformats/go-varint v0.0.7 // indirect
|
github.com/pion/dtls/v2 v2.2.9 // indirect
|
||||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
github.com/pion/ice/v2 v2.3.12 // indirect
|
||||||
github.com/pion/datachannel v1.5.9 // indirect
|
github.com/pion/interceptor v0.1.25 // indirect
|
||||||
github.com/pion/dtls/v3 v3.0.4 // indirect
|
|
||||||
github.com/pion/ice/v4 v4.0.2 // indirect
|
|
||||||
github.com/pion/interceptor v0.1.37 // indirect
|
|
||||||
github.com/pion/logging v0.2.2 // indirect
|
github.com/pion/logging v0.2.2 // indirect
|
||||||
github.com/pion/mdns/v2 v2.0.7 // indirect
|
github.com/pion/mdns v0.0.9 // indirect
|
||||||
github.com/pion/randutil v0.1.0 // indirect
|
github.com/pion/randutil v0.1.0 // indirect
|
||||||
github.com/pion/rtcp v1.2.14 // indirect
|
github.com/pion/rtcp v1.2.13 // indirect
|
||||||
github.com/pion/rtp v1.8.9 // indirect
|
github.com/pion/rtp v1.8.3 // indirect
|
||||||
github.com/pion/sctp v1.8.33 // indirect
|
github.com/pion/sctp v1.8.9 // indirect
|
||||||
github.com/pion/sdp/v3 v3.0.9 // indirect
|
github.com/pion/sdp/v3 v3.0.6 // indirect
|
||||||
github.com/pion/srtp/v3 v3.0.4 // indirect
|
github.com/pion/srtp/v2 v2.0.18 // indirect
|
||||||
github.com/pion/stun/v3 v3.0.0 // indirect
|
github.com/pion/stun v0.6.1 // indirect
|
||||||
github.com/pion/transport/v3 v3.0.7 // indirect
|
github.com/pion/transport/v2 v2.2.4 // indirect
|
||||||
github.com/pion/turn/v4 v4.0.0 // indirect
|
github.com/pion/turn/v2 v2.1.4 // indirect
|
||||||
github.com/pion/webrtc/v4 v4.0.2 // indirect
|
github.com/pion/webrtc/v3 v3.2.24 // indirect
|
||||||
github.com/pjbgf/sha1cd v0.3.0 // indirect
|
github.com/pjbgf/sha1cd v0.3.0 // indirect
|
||||||
github.com/protolambda/ctxlock v0.1.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||||
|
github.com/rs/dnscache v0.0.0-20230804202142-fc85eb664529 // indirect
|
||||||
github.com/sergi/go-diff v1.1.0 // indirect
|
github.com/sergi/go-diff v1.1.0 // indirect
|
||||||
github.com/skeema/knownhosts v1.2.0 // indirect
|
github.com/skeema/knownhosts v1.2.0 // indirect
|
||||||
github.com/spaolacci/murmur3 v1.1.0 // 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/wlynxg/anet v0.0.5 // indirect
|
|
||||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||||
go.opentelemetry.io/otel v1.32.0 // indirect
|
github.com/xiaoqidun/setft v0.0.0-20220310121541-be86327699ad // indirect
|
||||||
go.opentelemetry.io/otel/metric v1.32.0 // indirect
|
go.opentelemetry.io/otel v1.22.0 // indirect
|
||||||
go.opentelemetry.io/otel/trace v1.32.0 // indirect
|
go.opentelemetry.io/otel/metric v1.22.0 // indirect
|
||||||
golang.org/x/crypto v0.29.0 // indirect
|
go.opentelemetry.io/otel/trace v1.22.0 // indirect
|
||||||
golang.org/x/mod v0.22.0 // indirect
|
golang.org/x/crypto v0.18.0 // indirect
|
||||||
golang.org/x/net v0.31.0 // indirect
|
golang.org/x/mod v0.14.0 // indirect
|
||||||
golang.org/x/sys v0.27.0 // indirect
|
golang.org/x/net v0.20.0 // indirect
|
||||||
golang.org/x/text v0.20.0 // indirect
|
golang.org/x/sys v0.16.0 // indirect
|
||||||
golang.org/x/time v0.8.0 // indirect
|
golang.org/x/text v0.14.0 // indirect
|
||||||
golang.org/x/tools v0.27.0 // indirect
|
golang.org/x/time v0.5.0 // indirect
|
||||||
|
golang.org/x/tools v0.17.0 // indirect
|
||||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||||
lukechampine.com/blake3 v1.3.0 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
modernc.org/libc v1.61.0 // indirect
|
modernc.org/libc v1.40.6 // indirect
|
||||||
modernc.org/mathutil v1.6.0 // indirect
|
modernc.org/mathutil v1.6.0 // indirect
|
||||||
modernc.org/memory v1.8.0 // indirect
|
modernc.org/memory v1.7.2 // indirect
|
||||||
modernc.org/sqlite v1.33.1 // indirect
|
modernc.org/sqlite v1.28.0 // indirect
|
||||||
zombiezen.com/go/sqlite v1.4.0 // indirect
|
zombiezen.com/go/sqlite v1.1.0 // indirect
|
||||||
)
|
)
|
||||||
@@ -5,22 +5,17 @@ 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/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0=
|
|
||||||
github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
|
|
||||||
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=
|
||||||
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
|
|
||||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
|
||||||
github.com/ProtonMail/go-crypto v0.0.0-20230717121422-5aa5874ade95 h1:KLq8BE0KwCL+mmXnjLWEAOYO+2l2AE4YMmqG1ZpZHBs=
|
github.com/ProtonMail/go-crypto v0.0.0-20230717121422-5aa5874ade95 h1:KLq8BE0KwCL+mmXnjLWEAOYO+2l2AE4YMmqG1ZpZHBs=
|
||||||
github.com/ProtonMail/go-crypto v0.0.0-20230717121422-5aa5874ade95/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
|
github.com/ProtonMail/go-crypto v0.0.0-20230717121422-5aa5874ade95/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
|
||||||
github.com/RoaringBitmap/roaring v0.4.7/go.mod h1:8khRDP4HmeXns4xIj9oGrKSz7XTQiJx2zgh7AcNke4w=
|
github.com/RoaringBitmap/roaring v0.4.7/go.mod h1:8khRDP4HmeXns4xIj9oGrKSz7XTQiJx2zgh7AcNke4w=
|
||||||
github.com/RoaringBitmap/roaring v0.4.17/go.mod h1:D3qVegWTmfCaX4Bl5CrBE9hfrSrrXIr8KVNvRsDi1NI=
|
github.com/RoaringBitmap/roaring v0.4.17/go.mod h1:D3qVegWTmfCaX4Bl5CrBE9hfrSrrXIr8KVNvRsDi1NI=
|
||||||
github.com/RoaringBitmap/roaring v0.4.23/go.mod h1:D0gp8kJQgE1A4LQ5wFLggQEyvDi06Mq5mKs52e1TwOo=
|
github.com/RoaringBitmap/roaring v0.4.23/go.mod h1:D0gp8kJQgE1A4LQ5wFLggQEyvDi06Mq5mKs52e1TwOo=
|
||||||
github.com/RoaringBitmap/roaring v1.9.4 h1:yhEIoH4YezLYT04s1nHehNO64EKFTop/wBhxv2QzDdQ=
|
github.com/RoaringBitmap/roaring v1.7.0 h1:OZF303tJCER1Tj3x+aArx/S5X7hrT186ri6JjrGvG68=
|
||||||
github.com/RoaringBitmap/roaring v1.9.4/go.mod h1:6AXUsoIEzDTFFQCe1RbGA6uFONMhvejWj5rqITANK90=
|
github.com/RoaringBitmap/roaring v1.7.0/go.mod h1:6AXUsoIEzDTFFQCe1RbGA6uFONMhvejWj5rqITANK90=
|
||||||
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
|
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
|
||||||
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
|
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
|
||||||
github.com/acomagu/bufpipe v1.0.4 h1:e3H4WUzM3npvo5uv95QuJM3cQspFNtFBzvJ2oNjKIDQ=
|
github.com/acomagu/bufpipe v1.0.4 h1:e3H4WUzM3npvo5uv95QuJM3cQspFNtFBzvJ2oNjKIDQ=
|
||||||
@@ -28,36 +23,33 @@ 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=
|
||||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||||
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
|
github.com/anacrolix/chansync v0.4.0 h1:Md0HM7zYCAO4KwNwgcIRgxNsMxiRuk7D1Ha0Uo+2y60=
|
||||||
github.com/anacrolix/chansync v0.6.0 h1:/aQVvZ1yLRhmqEYrr9dC92JwzNBQ/SNnFi4uk+fTkQY=
|
github.com/anacrolix/chansync v0.4.0/go.mod h1:DZsatdsdXxD0WiwcGl0nJVwyjCKMDv+knl1q2iBjA2k=
|
||||||
github.com/anacrolix/chansync v0.6.0/go.mod h1:DZsatdsdXxD0WiwcGl0nJVwyjCKMDv+knl1q2iBjA2k=
|
github.com/anacrolix/dht/v2 v2.21.0 h1:8nzI+faaynY9jOKmVgdmBZVrTo8B7ZE/LKEgN3Vl/Bs=
|
||||||
github.com/anacrolix/dht/v2 v2.22.0 h1:wat5FLdT25vltHsjX377GBrpK9o6L2QVn541bIguCYo=
|
github.com/anacrolix/dht/v2 v2.21.0/go.mod h1:SDGC+sEs1pnO2sJGYuhvIis7T8749dDHNfcjtdH4e3g=
|
||||||
github.com/anacrolix/dht/v2 v2.22.0/go.mod h1:shbBjhgvezqsJoE+hMo/ezHYQFF18V9jUllNIP5xV9k=
|
|
||||||
github.com/anacrolix/envpprof v0.0.0-20180404065416-323002cec2fa/go.mod h1:KgHhUaQMc8cC0+cEflSgCFNFbKwi5h54gqtVn8yhP7c=
|
github.com/anacrolix/envpprof v0.0.0-20180404065416-323002cec2fa/go.mod h1:KgHhUaQMc8cC0+cEflSgCFNFbKwi5h54gqtVn8yhP7c=
|
||||||
github.com/anacrolix/envpprof v1.0.0/go.mod h1:KgHhUaQMc8cC0+cEflSgCFNFbKwi5h54gqtVn8yhP7c=
|
github.com/anacrolix/envpprof v1.0.0/go.mod h1:KgHhUaQMc8cC0+cEflSgCFNFbKwi5h54gqtVn8yhP7c=
|
||||||
github.com/anacrolix/envpprof v1.1.0/go.mod h1:My7T5oSqVfEn4MD4Meczkw/f5lSIndGAKu/0SM/rkf4=
|
github.com/anacrolix/envpprof v1.1.0/go.mod h1:My7T5oSqVfEn4MD4Meczkw/f5lSIndGAKu/0SM/rkf4=
|
||||||
github.com/anacrolix/envpprof v1.4.0 h1:QHeIcrgHcRChhnxR8l6rlaLlRQx9zd7Q2NII6Zbt83w=
|
github.com/anacrolix/envpprof v1.3.0 h1:WJt9bpuT7A/CDCxPOv/eeZqHWlle/Y0keJUvc6tcJDk=
|
||||||
github.com/anacrolix/envpprof v1.4.0/go.mod h1:7QIG4CaX1uexQ3tqd5+BRa/9e2D02Wcertl6Yh0jCB0=
|
github.com/anacrolix/envpprof v1.3.0/go.mod h1:7QIG4CaX1uexQ3tqd5+BRa/9e2D02Wcertl6Yh0jCB0=
|
||||||
github.com/anacrolix/generics v0.0.0-20230113004304-d6428d516633/go.mod h1:ff2rHB/joTV03aMSSn/AZNnaIpUw0h3njetGsaXcMy8=
|
github.com/anacrolix/generics v0.0.0-20230113004304-d6428d516633/go.mod h1:ff2rHB/joTV03aMSSn/AZNnaIpUw0h3njetGsaXcMy8=
|
||||||
github.com/anacrolix/generics v0.0.3-0.20240902042256-7fb2702ef0ca h1:aiiGqSQWjtVNdi8zUMfA//IrM8fPkv2bWwZVPbDe0wg=
|
github.com/anacrolix/generics v0.0.0-20230911070922-5dd7545c6b13 h1:qwOprPTDMM3BASJRf84mmZnTXRsPGGJ8xoHKQS7m3so=
|
||||||
github.com/anacrolix/generics v0.0.3-0.20240902042256-7fb2702ef0ca/go.mod h1:MN3ve08Z3zSV/rTuX/ouI4lNdlfTxgdafQJiLzyNRB8=
|
github.com/anacrolix/generics v0.0.0-20230911070922-5dd7545c6b13/go.mod h1:ff2rHB/joTV03aMSSn/AZNnaIpUw0h3njetGsaXcMy8=
|
||||||
github.com/anacrolix/go-libutp v1.3.1 h1:idJzreNLl+hNjGC3ZnUOjujEaryeOGgkwHLqSGoige0=
|
github.com/anacrolix/go-libutp v1.3.1 h1:idJzreNLl+hNjGC3ZnUOjujEaryeOGgkwHLqSGoige0=
|
||||||
github.com/anacrolix/go-libutp v1.3.1/go.mod h1:heF41EC8kN0qCLMokLBVkB8NXiLwx3t8R8810MTNI5o=
|
github.com/anacrolix/go-libutp v1.3.1/go.mod h1:heF41EC8kN0qCLMokLBVkB8NXiLwx3t8R8810MTNI5o=
|
||||||
github.com/anacrolix/log v0.3.0/go.mod h1:lWvLTqzAnCWPJA08T2HCstZi0L1y2Wyvm3FJgwU9jwU=
|
github.com/anacrolix/log v0.3.0/go.mod h1:lWvLTqzAnCWPJA08T2HCstZi0L1y2Wyvm3FJgwU9jwU=
|
||||||
github.com/anacrolix/log v0.6.0/go.mod h1:lWvLTqzAnCWPJA08T2HCstZi0L1y2Wyvm3FJgwU9jwU=
|
github.com/anacrolix/log v0.6.0/go.mod h1:lWvLTqzAnCWPJA08T2HCstZi0L1y2Wyvm3FJgwU9jwU=
|
||||||
github.com/anacrolix/log v0.13.1/go.mod h1:D4+CvN8SnruK6zIFS/xPoRJmtvtnxs+CSfDQ+BFxZ68=
|
github.com/anacrolix/log v0.13.1/go.mod h1:D4+CvN8SnruK6zIFS/xPoRJmtvtnxs+CSfDQ+BFxZ68=
|
||||||
github.com/anacrolix/log v0.14.2/go.mod h1:1OmJESOtxQGNMlUO5rcv96Vpp9mfMqXXbe2RdinFLdY=
|
github.com/anacrolix/log v0.14.2/go.mod h1:1OmJESOtxQGNMlUO5rcv96Vpp9mfMqXXbe2RdinFLdY=
|
||||||
github.com/anacrolix/log v0.16.0 h1:DSuyb5kAJwl3Y0X1TRcStVrTS9ST9b0BHW+7neE4Xho=
|
github.com/anacrolix/log v0.14.6-0.20231202035202-ed7a02cad0b4 h1:CdVK9IoqoqklXQQ4+L2aew64xsz14KdOD+rnKdTQajg=
|
||||||
github.com/anacrolix/log v0.16.0/go.mod h1:m0poRtlr41mriZlXBQ9SOVZ8yZBkLjOkDhd5Li5pITA=
|
github.com/anacrolix/log v0.14.6-0.20231202035202-ed7a02cad0b4/go.mod h1:1OmJESOtxQGNMlUO5rcv96Vpp9mfMqXXbe2RdinFLdY=
|
||||||
github.com/anacrolix/lsan v0.0.0-20211126052245-807000409a62 h1:P04VG6Td13FHMgS5ZBcJX23NPC/fiC4cp9bXwYujdYM=
|
github.com/anacrolix/lsan v0.0.0-20211126052245-807000409a62 h1:P04VG6Td13FHMgS5ZBcJX23NPC/fiC4cp9bXwYujdYM=
|
||||||
github.com/anacrolix/lsan v0.0.0-20211126052245-807000409a62/go.mod h1:66cFKPCO7Sl4vbFnAaSq7e4OXtdMhRSBagJGWgmpJbM=
|
github.com/anacrolix/lsan v0.0.0-20211126052245-807000409a62/go.mod h1:66cFKPCO7Sl4vbFnAaSq7e4OXtdMhRSBagJGWgmpJbM=
|
||||||
github.com/anacrolix/missinggo v0.0.0-20180725070939-60ef2fbf63df/go.mod h1:kwGiTUTZ0+p4vAz3VbAI5a30t2YbvemcmspjKwrAz5s=
|
github.com/anacrolix/missinggo v0.0.0-20180725070939-60ef2fbf63df/go.mod h1:kwGiTUTZ0+p4vAz3VbAI5a30t2YbvemcmspjKwrAz5s=
|
||||||
@@ -70,32 +62,30 @@ github.com/anacrolix/missinggo/perf v1.0.0 h1:7ZOGYziGEBytW49+KmYGTaNfnwUqP1HBsy
|
|||||||
github.com/anacrolix/missinggo/perf v1.0.0/go.mod h1:ljAFWkBuzkO12MQclXzZrosP5urunoLS0Cbvb4V0uMQ=
|
github.com/anacrolix/missinggo/perf v1.0.0/go.mod h1:ljAFWkBuzkO12MQclXzZrosP5urunoLS0Cbvb4V0uMQ=
|
||||||
github.com/anacrolix/missinggo/v2 v2.2.0/go.mod h1:o0jgJoYOyaoYQ4E2ZMISVa9c88BbUBVQQW4QeRkNCGY=
|
github.com/anacrolix/missinggo/v2 v2.2.0/go.mod h1:o0jgJoYOyaoYQ4E2ZMISVa9c88BbUBVQQW4QeRkNCGY=
|
||||||
github.com/anacrolix/missinggo/v2 v2.5.1/go.mod h1:WEjqh2rmKECd0t1VhQkLGTdIWXO6f6NLjp5GlMZ+6FA=
|
github.com/anacrolix/missinggo/v2 v2.5.1/go.mod h1:WEjqh2rmKECd0t1VhQkLGTdIWXO6f6NLjp5GlMZ+6FA=
|
||||||
|
github.com/anacrolix/missinggo/v2 v2.7.3 h1:Ee//CmZBMadeNiYB/hHo9ly2PFOEZ4Fhsbnug3rDAIE=
|
||||||
github.com/anacrolix/missinggo/v2 v2.7.3/go.mod h1:mIEtp9pgaXqt8VQ3NQxFOod/eQ1H0D1XsZzKUQfwtac=
|
github.com/anacrolix/missinggo/v2 v2.7.3/go.mod h1:mIEtp9pgaXqt8VQ3NQxFOod/eQ1H0D1XsZzKUQfwtac=
|
||||||
github.com/anacrolix/missinggo/v2 v2.8.0 h1:6pGnVOlR6TWL9JM5Msyezij8YHU3+oHO7r82Eql/kpA=
|
|
||||||
github.com/anacrolix/missinggo/v2 v2.8.0/go.mod h1:vVO5FEziQm+NFmJesc7StpkquZk+WJFCaL0Wp//2sa0=
|
|
||||||
github.com/anacrolix/mmsg v0.0.0-20180515031531-a4a3ba1fc8bb/go.mod h1:x2/ErsYUmT77kezS63+wzZp8E3byYB0gzirM/WMBLfw=
|
github.com/anacrolix/mmsg v0.0.0-20180515031531-a4a3ba1fc8bb/go.mod h1:x2/ErsYUmT77kezS63+wzZp8E3byYB0gzirM/WMBLfw=
|
||||||
github.com/anacrolix/mmsg v1.1.1 h1:4ce/3I5kM7qSF6T5A8MOmDCfac3UqYlk5Bzh5XsWebM=
|
github.com/anacrolix/mmsg v1.0.0 h1:btC7YLjOn29aTUAExJiVUhQOuf/8rhm+/nWCMAnL3Hg=
|
||||||
github.com/anacrolix/mmsg v1.1.1/go.mod h1:lPCXEN1eDDQtKktdKEzdw+roswx6wWPpeXAl/WpWVDU=
|
github.com/anacrolix/mmsg v1.0.0/go.mod h1:x8kRaJY/dCrY9Al0PEcj1mb/uFHwP6GCJ9fLl4thEPc=
|
||||||
github.com/anacrolix/multiless v0.4.0 h1:lqSszHkliMsZd2hsyrDvHOw4AbYWa+ijQ66LzbjqWjM=
|
github.com/anacrolix/multiless v0.3.1-0.20221221005021-2d12701f83f7 h1:lOtCD+LzoD1g7bowhYJNR++uV+FyY5bTZXKwnPex9S8=
|
||||||
github.com/anacrolix/multiless v0.4.0/go.mod h1:zJv1JF9AqdZiHwxqPgjuOZDGWER6nyE48WBCi/OOrMM=
|
github.com/anacrolix/multiless v0.3.1-0.20221221005021-2d12701f83f7/go.mod h1:zJv1JF9AqdZiHwxqPgjuOZDGWER6nyE48WBCi/OOrMM=
|
||||||
github.com/anacrolix/stm v0.2.0/go.mod h1:zoVQRvSiGjGoTmbM0vSLIiaKjWtNPeTvXUSdJQA4hsg=
|
github.com/anacrolix/stm v0.2.0/go.mod h1:zoVQRvSiGjGoTmbM0vSLIiaKjWtNPeTvXUSdJQA4hsg=
|
||||||
github.com/anacrolix/stm v0.5.0 h1:9df1KBpttF0TzLgDq51Z+TEabZKMythqgx89f1FQJt8=
|
github.com/anacrolix/stm v0.5.0 h1:9df1KBpttF0TzLgDq51Z+TEabZKMythqgx89f1FQJt8=
|
||||||
github.com/anacrolix/stm v0.5.0/go.mod h1:MOwrSy+jCm8Y7HYfMAwPj7qWVu7XoVvjOiYwJmpeB/M=
|
github.com/anacrolix/stm v0.5.0/go.mod h1:MOwrSy+jCm8Y7HYfMAwPj7qWVu7XoVvjOiYwJmpeB/M=
|
||||||
github.com/anacrolix/sync v0.0.0-20180808010631-44578de4e778/go.mod h1:s735Etp3joe/voe2sdaXLcqDdJSay1O0OPnM0ystjqk=
|
github.com/anacrolix/sync v0.0.0-20180808010631-44578de4e778/go.mod h1:s735Etp3joe/voe2sdaXLcqDdJSay1O0OPnM0ystjqk=
|
||||||
github.com/anacrolix/sync v0.3.0/go.mod h1:BbecHL6jDSExojhNtgTFSBcdGerzNc64tz3DCOj/I0g=
|
github.com/anacrolix/sync v0.3.0/go.mod h1:BbecHL6jDSExojhNtgTFSBcdGerzNc64tz3DCOj/I0g=
|
||||||
github.com/anacrolix/sync v0.5.3 h1:iyMUiRRzTnD15+YQYxmDoobGdIbmUxdD4TNO67aCMYI=
|
github.com/anacrolix/sync v0.5.1 h1:FbGju6GqSjzVoTgcXTUKkF041lnZkG5P0C3T5RL3SGc=
|
||||||
github.com/anacrolix/sync v0.5.3/go.mod h1:S51SPBetDsINK/KSNJtZkfM78s5rMeiO11/GNK6Rmh4=
|
github.com/anacrolix/sync v0.5.1/go.mod h1:BbecHL6jDSExojhNtgTFSBcdGerzNc64tz3DCOj/I0g=
|
||||||
github.com/anacrolix/tagflag v0.0.0-20180109131632-2146c8d41bf0/go.mod h1:1m2U/K6ZT+JZG0+bdMK6qauP49QT4wE5pmhJXOKKCHw=
|
github.com/anacrolix/tagflag v0.0.0-20180109131632-2146c8d41bf0/go.mod h1:1m2U/K6ZT+JZG0+bdMK6qauP49QT4wE5pmhJXOKKCHw=
|
||||||
github.com/anacrolix/tagflag v1.0.0/go.mod h1:1m2U/K6ZT+JZG0+bdMK6qauP49QT4wE5pmhJXOKKCHw=
|
github.com/anacrolix/tagflag v1.0.0/go.mod h1:1m2U/K6ZT+JZG0+bdMK6qauP49QT4wE5pmhJXOKKCHw=
|
||||||
github.com/anacrolix/tagflag v1.1.0/go.mod h1:Scxs9CV10NQatSmbyjqmqmeQNwGzlNe0CMUMIxqHIG8=
|
github.com/anacrolix/tagflag v1.1.0/go.mod h1:Scxs9CV10NQatSmbyjqmqmeQNwGzlNe0CMUMIxqHIG8=
|
||||||
github.com/anacrolix/torrent v1.57.2-0.20241017235801-4d8437a05621 h1:h8YDIq0VGMrFVBqdd1FgBzF6AXbSaTyosk40++Ub9dE=
|
github.com/anacrolix/torrent v1.53.3 h1:Abb3J8p/JWUjOzHaii8kixuDOGMXz+Oh8+hwc/zA2lA=
|
||||||
github.com/anacrolix/torrent v1.57.2-0.20241017235801-4d8437a05621/go.mod h1:n3SjHIE8oHXeH0Px0d5FXQ7cU4IgbEfTroen6B9KWJk=
|
github.com/anacrolix/torrent v1.53.3/go.mod h1:is8GNob5qDeZ5Kq+pKPiE2xqYUi1ms7IgSB+CftZETk=
|
||||||
github.com/anacrolix/upnp v0.1.4 h1:+2t2KA6QOhm/49zeNyeVwDu1ZYS9dB9wfxyVvh/wk7U=
|
github.com/anacrolix/upnp v0.1.3 h1:NlYEhE75adz2npEJKjbqyqnyW9qU4STookvSNXBJ5ao=
|
||||||
github.com/anacrolix/upnp v0.1.4/go.mod h1:Qyhbqo69gwNWvEk1xNTXsS5j7hMHef9hdr984+9fIic=
|
github.com/anacrolix/upnp v0.1.3/go.mod h1:Qyhbqo69gwNWvEk1xNTXsS5j7hMHef9hdr984+9fIic=
|
||||||
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=
|
||||||
@@ -108,15 +98,13 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24
|
|||||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||||
github.com/bits-and-blooms/bitset v1.12.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
|
github.com/bits-and-blooms/bitset v1.12.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
|
||||||
github.com/bits-and-blooms/bitset v1.15.0 h1:DiCRMscZsGyYePE9AR3sVhKqUXCt5IZvkX5AfAc5xLQ=
|
github.com/bits-and-blooms/bitset v1.13.0 h1:bAQ9OPNFYbGHV6Nez0tmNI0RiEu7/hxlYJRUA0wFAVE=
|
||||||
github.com/bits-and-blooms/bitset v1.15.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
|
github.com/bits-and-blooms/bitset v1.13.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
|
||||||
github.com/bradfitz/iter v0.0.0-20140124041915-454541ec3da2/go.mod h1:PyRFw1Lt2wKX4ZVSQ2mk+PeDa1rxyObEDlApuIsUKuo=
|
github.com/bradfitz/iter v0.0.0-20140124041915-454541ec3da2/go.mod h1:PyRFw1Lt2wKX4ZVSQ2mk+PeDa1rxyObEDlApuIsUKuo=
|
||||||
github.com/bradfitz/iter v0.0.0-20190303215204-33e6a9893b0c/go.mod h1:PyRFw1Lt2wKX4ZVSQ2mk+PeDa1rxyObEDlApuIsUKuo=
|
github.com/bradfitz/iter v0.0.0-20190303215204-33e6a9893b0c/go.mod h1:PyRFw1Lt2wKX4ZVSQ2mk+PeDa1rxyObEDlApuIsUKuo=
|
||||||
github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 h1:GKTyiRCL6zVf5wWaqKnf+7Qs6GbEPfd4iMOitWzXJx8=
|
github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 h1:GKTyiRCL6zVf5wWaqKnf+7Qs6GbEPfd4iMOitWzXJx8=
|
||||||
github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8/go.mod h1:spo1JLcs67NmW1aVLEgtA8Yy1elc+X8y5SRW1sFW4Og=
|
github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8/go.mod h1:spo1JLcs67NmW1aVLEgtA8Yy1elc+X8y5SRW1sFW4Og=
|
||||||
github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
|
github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
|
||||||
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
|
||||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
|
||||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4MedaY=
|
github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4MedaY=
|
||||||
github.com/chzyer/readline v1.5.0/go.mod h1:x22KAscuvRqlLoK9CsoYsmxoXZMMFVyOl86cAH8qUic=
|
github.com/chzyer/readline v1.5.0/go.mod h1:x22KAscuvRqlLoK9CsoYsmxoXZMMFVyOl86cAH8qUic=
|
||||||
@@ -129,13 +117,18 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
|
|||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo=
|
github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||||
github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||||
|
github.com/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0=
|
||||||
|
github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||||
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
|
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
|
||||||
github.com/dop251/goja v0.0.0-20240919115326-6c7d1df7ff05 h1:oK4+QcKsczZjHYTHD0JAdkvq5w74JEkG95J0XNBx/BI=
|
github.com/dop251/goja v0.0.0-20211022113120-dc8c55024d06/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk=
|
||||||
github.com/dop251/goja v0.0.0-20240919115326-6c7d1df7ff05/go.mod h1:MxLav0peU43GgvwVgNbLAj1s/bSGboKkhuULvq/7hx4=
|
github.com/dop251/goja v0.0.0-20231027120936-b396bb4c349d h1:wi6jN5LVt/ljaBG4ue79Ekzb12QfJ52L9Q98tl8SWhw=
|
||||||
github.com/dop251/goja_nodejs v0.0.0-20240728170619-29b559befffc h1:MKYt39yZJi0Z9xEeRmDX2L4ocE0ETKcHKw6MVL3R+co=
|
github.com/dop251/goja v0.0.0-20231027120936-b396bb4c349d/go.mod h1:QMWlm50DNe14hD7t24KEqZuUdC9sOTy8W6XbCU1mlw4=
|
||||||
github.com/dop251/goja_nodejs v0.0.0-20240728170619-29b559befffc/go.mod h1:VULptt4Q/fNzQUJlqY/GP3qHyU7ZH46mFkBZe0ZTokU=
|
github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y=
|
||||||
|
github.com/dop251/goja_nodejs v0.0.0-20211022123610-8dd9abb0616d/go.mod h1:DngW8aVqWbuLRMHItjPUyqdj+HWPvnQe8V8y1nDpIbM=
|
||||||
|
github.com/dop251/goja_nodejs v0.0.0-20231122114759-e84d9a924c5c h1:hLoodLRD4KLWIH8eyAQCLcH8EqIrjac7fCkp/fHnvuQ=
|
||||||
|
github.com/dop251/goja_nodejs v0.0.0-20231122114759-e84d9a924c5c/go.mod h1:bhGPmCgCCTSRfiMYWjpS46IDo9EUZXlsuUaPXSWGbv0=
|
||||||
github.com/dustin/go-humanize v0.0.0-20180421182945-02af3965c54e/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
github.com/dustin/go-humanize v0.0.0-20180421182945-02af3965c54e/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||||
@@ -143,10 +136,9 @@ github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+m
|
|||||||
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
|
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
|
||||||
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
|
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
|
||||||
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
|
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
|
||||||
github.com/edsrzf/mmap-go v1.2.0 h1:hXLYlkbaPzt1SaQk+anYwKSRNhufIDCchSPkUD6dD84=
|
github.com/edsrzf/mmap-go v1.1.0 h1:6EUwBLQ/Mcr1EYLE4Tn1VdW1A4ckqCQWZBw8Hr0kjpQ=
|
||||||
github.com/edsrzf/mmap-go v1.2.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=
|
||||||
@@ -154,10 +146,9 @@ 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/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=
|
||||||
@@ -169,29 +160,25 @@ 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=
|
||||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
|
|
||||||
github.com/go-llsqlite/adapter v0.1.0 h1:wGSQNsu/rtYeu/lqZNZQMjwUdEF3OW66xTLvsFwJQUw=
|
github.com/go-llsqlite/adapter v0.1.0 h1:wGSQNsu/rtYeu/lqZNZQMjwUdEF3OW66xTLvsFwJQUw=
|
||||||
github.com/go-llsqlite/adapter v0.1.0/go.mod h1:DADrR88ONKPPeSGjFp5iEN55Arx3fi2qXZeKCYDpbmU=
|
github.com/go-llsqlite/adapter v0.1.0/go.mod h1:DADrR88ONKPPeSGjFp5iEN55Arx3fi2qXZeKCYDpbmU=
|
||||||
github.com/go-llsqlite/crawshaw v0.5.5 h1:sXnRkiV26MBv++lbPbzp+ZzFcTqzVMxftO8yHyFvwUA=
|
github.com/go-llsqlite/crawshaw v0.5.0 h1:Olbqkth53vkkh4WvmkYjrtfOBcxXD3rMYBYuk6FNH3E=
|
||||||
github.com/go-llsqlite/crawshaw v0.5.5/go.mod h1:/YJdV7uBQaYDE0fwe4z3wwJIZBJxdYzd38ICggWqtaE=
|
github.com/go-llsqlite/crawshaw v0.5.0/go.mod h1:/YJdV7uBQaYDE0fwe4z3wwJIZBJxdYzd38ICggWqtaE=
|
||||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||||
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
|
||||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
|
||||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
|
github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU=
|
||||||
github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
|
github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
|
||||||
github.com/go-sourcemap/sourcemap v2.1.4+incompatible h1:a+iTbH5auLKxaNwQFg0B+TCYl6lbukKPc7b5x0n1s6Q=
|
|
||||||
github.com/go-sourcemap/sourcemap v2.1.4+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
|
|
||||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||||
|
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
@@ -210,30 +197,28 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU
|
|||||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
|
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
github.com/google/btree v0.0.0-20180124185431-e89373fe6b4a/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
github.com/google/btree v0.0.0-20180124185431-e89373fe6b4a/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
|
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
|
||||||
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.5.4/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.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-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
|
github.com/google/pprof v0.0.0-20230207041349-798e818bf904/go.mod h1:uglQLonpP8qtYCYyzA+8c/9qtqgA3qsXGYqCPKARAFg=
|
||||||
github.com/google/pprof v0.0.0-20240910150728-a0b0bb1d4134 h1:c5FlPPgxOn7kJz3VoPLkQYQXGBS3EklQ4Zfi57uOuqQ=
|
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a h1:fEBsGL/sjAuJrgah5XqmmYsTLzJp/TO9Lhy39gkverk=
|
||||||
github.com/google/pprof v0.0.0-20240910150728-a0b0bb1d4134/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
|
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
|
||||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20190309154008-847fc94819f9/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
github.com/gopherjs/gopherjs v0.0.0-20190309154008-847fc94819f9/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||||
@@ -244,39 +229,29 @@ github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv
|
|||||||
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||||
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
||||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
|
||||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
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=
|
||||||
github.com/huandu/xstrings v1.3.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
github.com/huandu/xstrings v1.3.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||||
github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||||
github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU=
|
||||||
github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI=
|
github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||||
github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
|
github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
|
||||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
||||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
||||||
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
|
||||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
|
||||||
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
|
||||||
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||||
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
|
||||||
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
|
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
|
||||||
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
|
|
||||||
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
|
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
|
||||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
@@ -295,72 +270,84 @@ github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A=
|
|||||||
github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
|
github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
|
||||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||||
github.com/mattn/go-ieproxy v0.0.12 h1:OZkUFJC3ESNZPQ+6LzC3VJIFSnreeFLQyqvBWtvfL2M=
|
github.com/mattn/go-ieproxy v0.0.11 h1:MQ/5BuGSgDAHZOJe6YY80IF2UVCfGkwfo6AeD7HtHYo=
|
||||||
github.com/mattn/go-ieproxy v0.0.12/go.mod h1:Vn+N61199DAnVeTgaF8eoB9PvLO8P3OBnG95ENh7B7c=
|
github.com/mattn/go-ieproxy v0.0.11/go.mod h1:/NsJd+kxZBmjMc5hrJCKMbP57B84rvq9BiDRbtO9AS0=
|
||||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||||
github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM=
|
|
||||||
github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8=
|
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
|
|
||||||
github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
|
|
||||||
github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg=
|
github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg=
|
||||||
github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM=
|
github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM=
|
||||||
github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw=
|
github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw=
|
||||||
github.com/multiformats/go-multihash v0.2.3 h1:7Lyc8XfX/IY2jWb/gI7JP+o7JEq9hOa7BFvVU9RSh+U=
|
|
||||||
github.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM=
|
|
||||||
github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8=
|
|
||||||
github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU=
|
|
||||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
|
||||||
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
|
||||||
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||||
|
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||||
|
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
|
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||||
|
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
|
||||||
|
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
|
||||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||||
|
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||||
|
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||||
|
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
||||||
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
|
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
|
||||||
github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
|
github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
|
||||||
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||||
github.com/pion/datachannel v1.5.9 h1:LpIWAOYPyDrXtU+BW7X0Yt/vGtYxtXQ8ql7dFfYUVZA=
|
github.com/pion/datachannel v1.5.5 h1:10ef4kwdjije+M9d7Xm9im2Y3O6A6ccQb0zcqZcJew8=
|
||||||
github.com/pion/datachannel v1.5.9/go.mod h1:kDUuk4CU4Uxp82NH4LQZbISULkX/HtzKa4P7ldf9izE=
|
github.com/pion/datachannel v1.5.5/go.mod h1:iMz+lECmfdCMqFRhXhcA/219B0SQlbpoR2V118yimL0=
|
||||||
github.com/pion/dtls/v3 v3.0.4 h1:44CZekewMzfrn9pmGrj5BNnTMDCFwr+6sLH+cCuLM7U=
|
github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s=
|
||||||
github.com/pion/dtls/v3 v3.0.4/go.mod h1:R373CsjxWqNPf6MEkfdy3aSe9niZvL/JaKlGeFphtMg=
|
github.com/pion/dtls/v2 v2.2.9 h1:K+D/aVf9/REahQvqk6G5JavdrD8W1PWDKC11UlwN7ts=
|
||||||
github.com/pion/ice/v4 v4.0.2 h1:1JhBRX8iQLi0+TfcavTjPjI6GO41MFn4CeTBX+Y9h5s=
|
github.com/pion/dtls/v2 v2.2.9/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s=
|
||||||
github.com/pion/ice/v4 v4.0.2/go.mod h1:DCdqyzgtsDNYN6/3U8044j3U7qsJ9KFJC92VnOWHvXg=
|
github.com/pion/ice/v2 v2.3.11/go.mod h1:hPcLC3kxMa+JGRzMHqQzjoSj3xtE9F+eoncmXLlCL4E=
|
||||||
github.com/pion/interceptor v0.1.37 h1:aRA8Zpab/wE7/c0O3fh1PqY0AJI3fCSEM5lRWJVorwI=
|
github.com/pion/ice/v2 v2.3.12 h1:NWKW2b3+oSZS3klbQMIEWQ0i52Kuo0KBg505a5kQv4s=
|
||||||
github.com/pion/interceptor v0.1.37/go.mod h1:JzxbJ4umVTlZAf+/utHzNesY8tmRkM2lVmkS82TTj8Y=
|
github.com/pion/ice/v2 v2.3.12/go.mod h1:hPcLC3kxMa+JGRzMHqQzjoSj3xtE9F+eoncmXLlCL4E=
|
||||||
|
github.com/pion/interceptor v0.1.25 h1:pwY9r7P6ToQ3+IF0bajN0xmk/fNw/suTgaTdlwTDmhc=
|
||||||
|
github.com/pion/interceptor v0.1.25/go.mod h1:wkbPYAak5zKsfpVDYMtEfWEy8D4zL+rpxCxPImLOg3Y=
|
||||||
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
|
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
|
||||||
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
|
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
|
||||||
github.com/pion/mdns/v2 v2.0.7 h1:c9kM8ewCgjslaAmicYMFQIde2H9/lrZpjBkN8VwoVtM=
|
github.com/pion/mdns v0.0.8/go.mod h1:hYE72WX8WDveIhg7fmXgMKivD3Puklk0Ymzog0lSyaI=
|
||||||
github.com/pion/mdns/v2 v2.0.7/go.mod h1:vAdSYNAT0Jy3Ru0zl2YiW3Rm/fJCwIeM0nToenfOJKA=
|
github.com/pion/mdns v0.0.9 h1:7Ue5KZsqq8EuqStnpPWV33vYYEH0+skdDN5L7EiEsI4=
|
||||||
|
github.com/pion/mdns v0.0.9/go.mod h1:2JA5exfxwzXiCihmxpTKgFUpiQws2MnipoPK09vecIc=
|
||||||
github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
|
github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
|
||||||
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
|
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
|
||||||
github.com/pion/rtcp v1.2.14 h1:KCkGV3vJ+4DAJmvP0vaQShsb0xkRfWkO540Gy102KyE=
|
github.com/pion/rtcp v1.2.10/go.mod h1:ztfEwXZNLGyF1oQDttz/ZKIBaeeg/oWbRYqzBM9TL1I=
|
||||||
github.com/pion/rtcp v1.2.14/go.mod h1:sn6qjxvnwyAkkPzPULIbVqSKI5Dv54Rv7VG0kNxh9L4=
|
github.com/pion/rtcp v1.2.12/go.mod h1:sn6qjxvnwyAkkPzPULIbVqSKI5Dv54Rv7VG0kNxh9L4=
|
||||||
github.com/pion/rtp v1.8.9 h1:E2HX740TZKaqdcPmf4pw6ZZuG8u5RlMMt+l3dxeu6Wk=
|
github.com/pion/rtcp v1.2.13 h1:+EQijuisKwm/8VBs8nWllr0bIndR7Lf7cZG200mpbNo=
|
||||||
github.com/pion/rtp v1.8.9/go.mod h1:pBGHaFt/yW7bf1jjWAoUjpSNoDnw98KTMg+jWWvziqU=
|
github.com/pion/rtcp v1.2.13/go.mod h1:sn6qjxvnwyAkkPzPULIbVqSKI5Dv54Rv7VG0kNxh9L4=
|
||||||
github.com/pion/sctp v1.8.33 h1:dSE4wX6uTJBcNm8+YlMg7lw1wqyKHggsP5uKbdj+NZw=
|
github.com/pion/rtp v1.8.2/go.mod h1:pBGHaFt/yW7bf1jjWAoUjpSNoDnw98KTMg+jWWvziqU=
|
||||||
github.com/pion/sctp v1.8.33/go.mod h1:beTnqSzewI53KWoG3nqB282oDMGrhNxBdb+JZnkCwRM=
|
github.com/pion/rtp v1.8.3 h1:VEHxqzSVQxCkKDSHro5/4IUUG1ea+MFdqR2R3xSpNU8=
|
||||||
github.com/pion/sdp/v3 v3.0.9 h1:pX++dCHoHUwq43kuwf3PyJfHlwIj4hXA7Vrifiq0IJY=
|
github.com/pion/rtp v1.8.3/go.mod h1:pBGHaFt/yW7bf1jjWAoUjpSNoDnw98KTMg+jWWvziqU=
|
||||||
github.com/pion/sdp/v3 v3.0.9/go.mod h1:B5xmvENq5IXJimIO4zfp6LAe1fD9N+kFv+V/1lOdz8M=
|
github.com/pion/sctp v1.8.5/go.mod h1:SUFFfDpViyKejTAdwD1d/HQsCu+V/40cCs2nZIvC3s0=
|
||||||
github.com/pion/srtp/v3 v3.0.4 h1:2Z6vDVxzrX3UHEgrUyIGM4rRouoC7v+NiF1IHtp9B5M=
|
github.com/pion/sctp v1.8.8/go.mod h1:igF9nZBrjh5AtmKc7U30jXltsFHicFCXSmWA2GWRaWs=
|
||||||
github.com/pion/srtp/v3 v3.0.4/go.mod h1:1Jx3FwDoxpRaTh1oRV8A/6G1BnFL+QI82eK4ms8EEJQ=
|
github.com/pion/sctp v1.8.9 h1:TP5ZVxV5J7rz7uZmbyvnUvsn7EJ2x/5q9uhsTtXbI3g=
|
||||||
github.com/pion/stun/v3 v3.0.0 h1:4h1gwhWLWuZWOJIJR9s2ferRO+W3zA/b6ijOI6mKzUw=
|
github.com/pion/sctp v1.8.9/go.mod h1:cMLT45jqw3+jiJCrtHVwfQLnfR0MGZ4rgOJwUOIqLkI=
|
||||||
github.com/pion/stun/v3 v3.0.0/go.mod h1:HvCN8txt8mwi4FBvS3EmDghW6aQJ24T+y+1TKjB5jyU=
|
github.com/pion/sdp/v3 v3.0.6 h1:WuDLhtuFUUVpTfus9ILC4HRyHsW6TdugjEX/QY9OiUw=
|
||||||
github.com/pion/transport/v3 v3.0.7 h1:iRbMH05BzSNwhILHoBoAPxoB9xQgOaJk+591KC9P1o0=
|
github.com/pion/sdp/v3 v3.0.6/go.mod h1:iiFWFpQO8Fy3S5ldclBkpXqmWy02ns78NOKoLLL0YQw=
|
||||||
github.com/pion/transport/v3 v3.0.7/go.mod h1:YleKiTZ4vqNxVwh77Z0zytYi7rXHl7j6uPLGhhz9rwo=
|
github.com/pion/srtp/v2 v2.0.18 h1:vKpAXfawO9RtTRKZJbG4y0v1b11NZxQnxRl85kGuUlo=
|
||||||
github.com/pion/turn/v4 v4.0.0 h1:qxplo3Rxa9Yg1xXDxxH8xaqcyGUtbHYw4QSCvmFWvhM=
|
github.com/pion/srtp/v2 v2.0.18/go.mod h1:0KJQjA99A6/a0DOVTu1PhDSw0CXF2jTkqOoMg3ODqdA=
|
||||||
github.com/pion/turn/v4 v4.0.0/go.mod h1:MuPDkm15nYSklKpN8vWJ9W2M0PlyQZqYt1McGuxG7mA=
|
github.com/pion/stun v0.6.1 h1:8lp6YejULeHBF8NmV8e2787BogQhduZugh5PdhDyyN4=
|
||||||
github.com/pion/webrtc/v4 v4.0.2 h1:fBwm5/hqSUybrCWl0DDBSTDrpbkcgkqpeLmXw9CsBQA=
|
github.com/pion/stun v0.6.1/go.mod h1:/hO7APkX4hZKu/D0f2lHzNyvdkTGtIy3NDmLR7kSz/8=
|
||||||
github.com/pion/webrtc/v4 v4.0.2/go.mod h1:moylBT2A4dNoEaYBCdV1nThM3TLwRHzWszIG+eSPaqQ=
|
github.com/pion/transport v0.14.1 h1:XSM6olwW+o8J4SCmOBb/BpwZypkHeyM0PGFCxNQBr40=
|
||||||
|
github.com/pion/transport v0.14.1/go.mod h1:4tGmbk00NeYA3rUa9+n+dzCCoKkcy3YlYb99Jn2fNnI=
|
||||||
|
github.com/pion/transport/v2 v2.2.1/go.mod h1:cXXWavvCnFF6McHTft3DWS9iic2Mftcz1Aq29pGcU5g=
|
||||||
|
github.com/pion/transport/v2 v2.2.2/go.mod h1:OJg3ojoBJopjEeECq2yJdXH9YVrUJ1uQ++NjXLOUorc=
|
||||||
|
github.com/pion/transport/v2 v2.2.3/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0=
|
||||||
|
github.com/pion/transport/v2 v2.2.4 h1:41JJK6DZQYSeVLxILA2+F4ZkKb4Xd/tFJZRFZQ9QAlo=
|
||||||
|
github.com/pion/transport/v2 v2.2.4/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0=
|
||||||
|
github.com/pion/transport/v3 v3.0.1 h1:gDTlPJwROfSfz6QfSi0ZmeCSkFcnWWiiR9ES0ouANiM=
|
||||||
|
github.com/pion/transport/v3 v3.0.1/go.mod h1:UY7kiITrlMv7/IKgd5eTUcaahZx5oUN3l9SzK5f5xE0=
|
||||||
|
github.com/pion/turn/v2 v2.1.3/go.mod h1:huEpByKKHix2/b9kmTAM3YoX6MKP+/D//0ClgUYR2fY=
|
||||||
|
github.com/pion/turn/v2 v2.1.4 h1:2xn8rduI5W6sCZQkEnIUDAkrBQNl2eYIBCHMZ3QMmP8=
|
||||||
|
github.com/pion/turn/v2 v2.1.4/go.mod h1:huEpByKKHix2/b9kmTAM3YoX6MKP+/D//0ClgUYR2fY=
|
||||||
|
github.com/pion/webrtc/v3 v3.2.24 h1:MiFL5DMo2bDaaIFWr0DDpwiV/L4EGbLZb+xoRvfEo1Y=
|
||||||
|
github.com/pion/webrtc/v3 v3.2.24/go.mod h1:1CaT2fcZzZ6VZA+O1i9yK2DU4EOcXVvSbWG9pr5jefs=
|
||||||
github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4=
|
github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4=
|
||||||
github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI=
|
github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI=
|
||||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||||
@@ -374,8 +361,6 @@ github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXP
|
|||||||
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
|
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
|
||||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||||
github.com/prometheus/client_golang v1.5.1/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
|
github.com/prometheus/client_golang v1.5.1/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
|
||||||
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
|
||||||
github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
|
|
||||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||||
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
@@ -383,26 +368,18 @@ github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6T
|
|||||||
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||||
github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
|
github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
|
||||||
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
|
|
||||||
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
|
|
||||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||||
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||||
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
|
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
|
||||||
github.com/prometheus/procfs v0.0.11/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
github.com/prometheus/procfs v0.0.11/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||||
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
|
||||||
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
|
||||||
github.com/protolambda/ctxlock v0.1.0 h1:rCUY3+vRdcdZXqT07iXgyr744J2DU2LCBIXowYAjBCE=
|
|
||||||
github.com/protolambda/ctxlock v0.1.0/go.mod h1:vefhX6rIZH8rsg5ZpOJfEDYQOppZi19SfPiGOFrNnwM=
|
|
||||||
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||||
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
||||||
|
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||||
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
|
||||||
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
|
||||||
github.com/rs/dnscache v0.0.0-20230804202142-fc85eb664529 h1:18kd+8ZUlt/ARXhljq+14TwAoKa61q6dX8jtwOf6DH8=
|
github.com/rs/dnscache v0.0.0-20230804202142-fc85eb664529 h1:18kd+8ZUlt/ARXhljq+14TwAoKa61q6dX8jtwOf6DH8=
|
||||||
github.com/rs/dnscache v0.0.0-20230804202142-fc85eb664529/go.mod h1:qe5TWALJ8/a1Lqznoc5BDHpYX/8HU60Hm2AwRmqzxqA=
|
github.com/rs/dnscache v0.0.0-20230804202142-fc85eb664529/go.mod h1:qe5TWALJ8/a1Lqznoc5BDHpYX/8HU60Hm2AwRmqzxqA=
|
||||||
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||||
@@ -410,11 +387,11 @@ github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A=
|
|||||||
github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
|
github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
|
||||||
github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46 h1:GHRpF1pTW19a8tTFrMLUcfWwyC0pnifVo2ClaLq+hP8=
|
github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46 h1:GHRpF1pTW19a8tTFrMLUcfWwyC0pnifVo2ClaLq+hP8=
|
||||||
github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46/go.mod h1:uAQ5PCi+MFsC7HjREoAz1BU+Mq60+05gifQSsHSDG/8=
|
github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46/go.mod h1:uAQ5PCi+MFsC7HjREoAz1BU+Mq60+05gifQSsHSDG/8=
|
||||||
|
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
|
||||||
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
|
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
|
||||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
|
||||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||||
github.com/skeema/knownhosts v1.2.0 h1:h9r9cf0+u7wSE+M183ZtMGgOJKiL96brpaz5ekfJCpM=
|
github.com/skeema/knownhosts v1.2.0 h1:h9r9cf0+u7wSE+M183ZtMGgOJKiL96brpaz5ekfJCpM=
|
||||||
github.com/skeema/knownhosts v1.2.0/go.mod h1:g4fPeYpque7P0xefxtGzV81ihjC8sX2IqpAoNkjxbMo=
|
github.com/skeema/knownhosts v1.2.0/go.mod h1:g4fPeYpque7P0xefxtGzV81ihjC8sX2IqpAoNkjxbMo=
|
||||||
@@ -422,35 +399,32 @@ github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1
|
|||||||
github.com/smartystreets/assertions v0.0.0-20190215210624-980c5ac6f3ac/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
github.com/smartystreets/assertions v0.0.0-20190215210624-980c5ac6f3ac/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||||
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
|
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
|
||||||
github.com/smartystreets/goconvey v0.0.0-20190306220146-200a235640ff/go.mod h1:KSQcGKpxUMHk3nbYzs/tIBAM2iDooCn0BmttHOJEbLs=
|
github.com/smartystreets/goconvey v0.0.0-20190306220146-200a235640ff/go.mod h1:KSQcGKpxUMHk3nbYzs/tIBAM2iDooCn0BmttHOJEbLs=
|
||||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
|
||||||
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
|
|
||||||
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
|
||||||
github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
|
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
|
||||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
|
||||||
github.com/tidwall/btree v1.7.0 h1:L1fkJH/AuEh5zBnnBbmTwQ5Lt+bRJ5A8EWecslvo9iI=
|
github.com/tidwall/btree v1.7.0 h1:L1fkJH/AuEh5zBnnBbmTwQ5Lt+bRJ5A8EWecslvo9iI=
|
||||||
github.com/tidwall/btree v1.7.0/go.mod h1:twD9XRA5jj9VUQGELzDO4HPQTNJsoWWfYEL+EUQ2cKY=
|
github.com/tidwall/btree v1.7.0/go.mod h1:twD9XRA5jj9VUQGELzDO4HPQTNJsoWWfYEL+EUQ2cKY=
|
||||||
github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
|
github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
|
||||||
github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
|
github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
|
||||||
github.com/tinylib/msgp v1.1.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
|
github.com/tinylib/msgp v1.1.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
|
||||||
|
github.com/virtuald/go-paniclog v0.0.0-20190812204905-43a7fa316459 h1:x9pIfbdIjnw+Ylb2vE27Gtqb7BDmfR+nLcJwvbJh98U=
|
||||||
|
github.com/virtuald/go-paniclog v0.0.0-20190812204905-43a7fa316459/go.mod h1:nFvuG3SWu3VWqobG3cX8nt57wXU0OOFapeCs/8axIuM=
|
||||||
github.com/willf/bitset v1.1.9/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
|
github.com/willf/bitset v1.1.9/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
|
||||||
github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
|
github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
|
||||||
github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU=
|
|
||||||
github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA=
|
|
||||||
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
|
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
|
||||||
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
|
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
|
||||||
github.com/xiaoqidun/setft v0.0.0-20220310121541-be86327699ad h1:QtuWk6dsrNXc/ugwCBEN0jG52q/4tXRzMvZm4fH6T9g=
|
github.com/xiaoqidun/setft v0.0.0-20220310121541-be86327699ad h1:QtuWk6dsrNXc/ugwCBEN0jG52q/4tXRzMvZm4fH6T9g=
|
||||||
@@ -458,19 +432,17 @@ github.com/xiaoqidun/setft v0.0.0-20220310121541-be86327699ad/go.mod h1:Jj8p9bgK
|
|||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0=
|
go.etcd.io/bbolt v1.3.8 h1:xs88BrvEv273UsB79e0hcVrlUWmS0a8upikMFhSyAtA=
|
||||||
go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I=
|
go.etcd.io/bbolt v1.3.8/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw=
|
||||||
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
||||||
go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
||||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
go.opentelemetry.io/otel v1.32.0 h1:WnBN+Xjcteh0zdk01SVqV55d/m62NJLJdIyb4y/WO5U=
|
go.opentelemetry.io/otel v1.22.0 h1:xS7Ku+7yTFvDfDraDIJVpw7XPyuHlB9MCiqqX5mcJ6Y=
|
||||||
go.opentelemetry.io/otel v1.32.0/go.mod h1:00DCVSB0RQcnzlwyTfqtxSm+DRr9hpYrHjNGiBHVQIg=
|
go.opentelemetry.io/otel v1.22.0/go.mod h1:eoV4iAi3Ea8LkAEI9+GFT44O6T/D0GWAVFyZVCC6pMI=
|
||||||
go.opentelemetry.io/otel/metric v1.32.0 h1:xV2umtmNcThh2/a/aCP+h64Xx5wsj8qqnkYZktzNa0M=
|
go.opentelemetry.io/otel/metric v1.22.0 h1:lypMQnGyJYeuYPhOM/bgjbFM6WE44W1/T45er4d8Hhg=
|
||||||
go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzauciCRLoc/SyMv8=
|
go.opentelemetry.io/otel/metric v1.22.0/go.mod h1:evJGjVpZv0mQ5QBRJoBF64yMuOf4xCWdXjK8pzFvliY=
|
||||||
go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM=
|
go.opentelemetry.io/otel/trace v1.22.0 h1:Hg6pPujv0XG9QaVbGOBVHunyuLcCC3jN7WEhPx83XD0=
|
||||||
go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8=
|
go.opentelemetry.io/otel/trace v1.22.0/go.mod h1:RbbHXVqKES9QhzZq/fE5UnOSILqRt40a21sPw2He1xo=
|
||||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
|
||||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
|
||||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
@@ -479,12 +451,17 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
|
|||||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||||
golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ=
|
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
|
||||||
golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg=
|
golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
|
||||||
|
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
|
||||||
|
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
|
||||||
|
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||||
|
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
|
||||||
|
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20220428152302-39d4317da171/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE=
|
golang.org/x/exp v0.0.0-20220428152302-39d4317da171/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE=
|
||||||
golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f h1:XdNn9LlyWAhLVp6P/i8QYBW+hlyhrhei9uErw2B5GJo=
|
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a h1:Q8/wZp0KX97QFTc2ywcOE0YRjZPVIx+MXInMzdvQqcA=
|
||||||
golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f/go.mod h1:D5SMRVC3C2/4+F/DB1wZsLRnSNimn2Sp/NPsCrsv8ak=
|
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08=
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
@@ -494,8 +471,8 @@ golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
|||||||
golang.org/x/mod v0.6.0-dev.0.20211013180041-c96bc1413d57/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
|
golang.org/x/mod v0.6.0-dev.0.20211013180041-c96bc1413d57/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
|
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
|
||||||
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
@@ -507,18 +484,26 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn
|
|||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||||
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
|
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
||||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||||
golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo=
|
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||||
golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM=
|
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||||
|
golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
|
||||||
|
golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
|
||||||
|
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
|
||||||
|
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||||
|
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
|
||||||
|
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
@@ -528,12 +513,11 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ
|
|||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ=
|
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
||||||
golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
@@ -543,18 +527,19 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
|
|||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
@@ -562,32 +547,50 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
|
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
|
||||||
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
|
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||||
golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU=
|
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
||||||
golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E=
|
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||||
|
golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo=
|
||||||
|
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.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||||
|
golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE=
|
||||||
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=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
|
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=
|
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
|
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
|
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
|
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
|
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||||
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
|
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||||
|
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
@@ -595,12 +598,12 @@ golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3
|
|||||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
golang.org/x/tools v0.1.8-0.20211029000441-d6a9af8af023/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
|
golang.org/x/tools v0.1.8-0.20211029000441-d6a9af8af023/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
golang.org/x/tools v0.27.0 h1:qEKojBykQkQ4EynWy4S8Weg69NumxKdn40Fce3uc/8o=
|
golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc=
|
||||||
golang.org/x/tools v0.27.0/go.mod h1:sUi0ZgbwW9ZPAq26Ekut+weQPR5eIM6GQLQ1Yjm1H0Q=
|
golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
@@ -621,6 +624,7 @@ google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miE
|
|||||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
|
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
@@ -644,58 +648,13 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
|||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
lukechampine.com/blake3 v1.3.0 h1:sJ3XhFINmHSrYCgl958hscfIa3bw8x4DqMP3u1YvoYE=
|
modernc.org/libc v1.40.6 h1:141JHq3SjhOOCjECBgD4K8VgTFOy19CnHwroC08DAig=
|
||||||
lukechampine.com/blake3 v1.3.0/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=
|
modernc.org/libc v1.40.6/go.mod h1:YAXkAZ8ktnkCKaN9sw/UDeUVkGYJ/YquGO4FTi5nmHE=
|
||||||
lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
|
|
||||||
lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
|
|
||||||
modernc.org/cc/v3 v3.37.0/go.mod h1:vtL+3mdHx/wcj3iEGz84rQa8vEqR6XM84v5Lcvfph20=
|
|
||||||
modernc.org/cc/v3 v3.38.1/go.mod h1:vtL+3mdHx/wcj3iEGz84rQa8vEqR6XM84v5Lcvfph20=
|
|
||||||
modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0=
|
|
||||||
modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ=
|
|
||||||
modernc.org/cc/v4 v4.21.4/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
|
|
||||||
modernc.org/ccgo/v3 v3.0.0-20220904174949-82d86e1b6d56/go.mod h1:YSXjPL62P2AMSxBphRHPn7IkzhVHqkvOnRKAKh+W6ZI=
|
|
||||||
modernc.org/ccgo/v3 v3.0.0-20220910160915-348f15de615a/go.mod h1:8p47QxPkdugex9J4n9P2tLZ9bK01yngIVp00g4nomW0=
|
|
||||||
modernc.org/ccgo/v3 v3.16.13-0.20221017192402-261537637ce8/go.mod h1:fUB3Vn0nVPReA+7IG7yZDfjv1TMWjhQP8gCxrFAtL5g=
|
|
||||||
modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY=
|
|
||||||
modernc.org/ccgo/v4 v4.21.0 h1:kKPI3dF7RIag8YcToh5ZwDcVMIv6VGa0ED5cvh0LMW4=
|
|
||||||
modernc.org/ccgo/v4 v4.21.0/go.mod h1:h6kt6H/A2+ew/3MW/p6KEoQmrq/i3pr0J/SiwiaF/g0=
|
|
||||||
modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
|
|
||||||
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
|
|
||||||
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
|
|
||||||
modernc.org/gc/v2 v2.5.0 h1:bJ9ChznK1L1mUtAQtxi0wi5AtAs5jQuw4PrPHO5pb6M=
|
|
||||||
modernc.org/gc/v2 v2.5.0/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU=
|
|
||||||
modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
|
|
||||||
modernc.org/libc v1.17.4/go.mod h1:WNg2ZH56rDEwdropAJeZPQkXmDwh+JCA1s/htl6r2fA=
|
|
||||||
modernc.org/libc v1.18.0/go.mod h1:vj6zehR5bfc98ipowQOM2nIDUZnVew/wNC/2tOGS+q0=
|
|
||||||
modernc.org/libc v1.19.0/go.mod h1:ZRfIaEkgrYgZDl6pa4W39HgN5G/yDW+NRmNKZBDFrk0=
|
|
||||||
modernc.org/libc v1.20.3/go.mod h1:ZRfIaEkgrYgZDl6pa4W39HgN5G/yDW+NRmNKZBDFrk0=
|
|
||||||
modernc.org/libc v1.21.4/go.mod h1:przBsL5RDOZajTVslkugzLBj1evTue36jEomFQOoYuI=
|
|
||||||
modernc.org/libc v1.21.5/go.mod h1:przBsL5RDOZajTVslkugzLBj1evTue36jEomFQOoYuI=
|
|
||||||
modernc.org/libc v1.61.0 h1:eGFcvWpqlnoGwzZeZe3PWJkkKbM/3SUGyk1DVZQ0TpE=
|
|
||||||
modernc.org/libc v1.61.0/go.mod h1:DvxVX89wtGTu+r72MLGhygpfi3aUGgZRdAYGCAVVud0=
|
|
||||||
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
|
||||||
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
|
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
|
||||||
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
|
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
|
||||||
modernc.org/memory v1.3.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
|
modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E=
|
||||||
modernc.org/memory v1.4.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
|
modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E=
|
||||||
modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
|
modernc.org/sqlite v1.28.0 h1:Zx+LyDDmXczNnEQdvPuEfcFVA2ZPyaD7UCZDjef3BHQ=
|
||||||
modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU=
|
modernc.org/sqlite v1.28.0/go.mod h1:Qxpazz0zH8Z1xCFyi5GSL3FzbtZ3fvbjmywNogldEW0=
|
||||||
modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
zombiezen.com/go/sqlite v1.1.0 h1:2BIABTFCE221++xR90rV0mE6T5gFF8o0MUcKDNUrIVc=
|
||||||
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
|
zombiezen.com/go/sqlite v1.1.0/go.mod h1:rZEFEJSoeXeiwuAb6HugOJLhtDabl+EekT92DCEKOb0=
|
||||||
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
|
||||||
modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc=
|
|
||||||
modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss=
|
|
||||||
modernc.org/sqlite v1.20.0/go.mod h1:EsYz8rfOvLCiYTy5ZFsOYzoCcRMu98YYkwAcCw5YIYw=
|
|
||||||
modernc.org/sqlite v1.33.1 h1:trb6Z3YYoeM9eDL1O8do81kP+0ejv+YzgyFo+Gwy0nM=
|
|
||||||
modernc.org/sqlite v1.33.1/go.mod h1:pXV2xHxhzXZsgT/RtTFAPY6JJDEvOTcTdwADQCCWD4k=
|
|
||||||
modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw=
|
|
||||||
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
|
|
||||||
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
|
|
||||||
modernc.org/tcl v1.15.0/go.mod h1:xRoGotBZ6dU+Zo2tca+2EqVEeMmOUBzHnhIwq4YrVnE=
|
|
||||||
modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
|
||||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
|
||||||
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
|
||||||
modernc.org/z v1.7.0/go.mod h1:hVdgNMh8ggTuRG1rGU8x+xGRFfiQUIAw0ZqlPy8+HyQ=
|
|
||||||
zombiezen.com/go/sqlite v0.12.0/go.mod h1:RKdRR9xoQDSnB47yy7G4PtrjGZJtupb/SyEbJZLaRes=
|
|
||||||
zombiezen.com/go/sqlite v1.4.0 h1:N1s3RIljwtp4541Y8rM880qgGIgq3fTD2yks1xftnKU=
|
|
||||||
zombiezen.com/go/sqlite v1.4.0/go.mod h1:0w9F1DN9IZj9AcLS9YDKMboubCACkwYCGkzoy3eG5ik=
|
|
||||||
@@ -2,15 +2,13 @@ package controller
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/GopeedLab/gopeed/pkg/base"
|
"github.com/GopeedLab/gopeed/pkg/base"
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Controller struct {
|
type Controller struct {
|
||||||
GetConfig func(v any)
|
GetConfig func(v any) bool
|
||||||
GetProxy func(requestProxy *base.RequestProxy) func(*http.Request) (*url.URL, error)
|
ProxyConfig *base.DownloaderProxyConfig
|
||||||
FileController
|
FileController
|
||||||
//ContextDialer() (proxy.Dialer, error)
|
//ContextDialer() (proxy.Dialer, error)
|
||||||
}
|
}
|
||||||
@@ -24,9 +22,10 @@ type DefaultFileController struct {
|
|||||||
|
|
||||||
func NewController() *Controller {
|
func NewController() *Controller {
|
||||||
return &Controller{
|
return &Controller{
|
||||||
GetConfig: func(v any) {},
|
|
||||||
GetProxy: func(requestProxy *base.RequestProxy) func(*http.Request) (*url.URL, error) { return nil },
|
|
||||||
FileController: &DefaultFileController{},
|
FileController: &DefaultFileController{},
|
||||||
|
GetConfig: func(v any) bool {
|
||||||
|
return false
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,12 +4,14 @@ import (
|
|||||||
"github.com/GopeedLab/gopeed/internal/controller"
|
"github.com/GopeedLab/gopeed/internal/controller"
|
||||||
"github.com/GopeedLab/gopeed/pkg/base"
|
"github.com/GopeedLab/gopeed/pkg/base"
|
||||||
"path"
|
"path"
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Fetcher defines the interface for a download protocol.
|
// Fetcher defines the interface for a download protocol.
|
||||||
// Each download task will have a corresponding Fetcher instance for the management of the download task
|
// One fetcher for each download task
|
||||||
type Fetcher interface {
|
type Fetcher interface {
|
||||||
|
// Name return the name of the protocol.
|
||||||
|
Name() string
|
||||||
|
|
||||||
Setup(ctl *controller.Controller)
|
Setup(ctl *controller.Controller)
|
||||||
// Resolve resource info from request
|
// Resolve resource info from request
|
||||||
Resolve(req *base.Request) error
|
Resolve(req *base.Request) error
|
||||||
@@ -29,12 +31,6 @@ type Fetcher interface {
|
|||||||
Wait() error
|
Wait() error
|
||||||
}
|
}
|
||||||
|
|
||||||
type Uploader interface {
|
|
||||||
Upload() error
|
|
||||||
UploadedBytes() int64
|
|
||||||
WaitUpload() error
|
|
||||||
}
|
|
||||||
|
|
||||||
// FetcherMeta defines the meta information of a fetcher.
|
// FetcherMeta defines the meta information of a fetcher.
|
||||||
type FetcherMeta struct {
|
type FetcherMeta struct {
|
||||||
Req *base.Request `json:"req"`
|
Req *base.Request `json:"req"`
|
||||||
@@ -72,57 +68,17 @@ func (m *FetcherMeta) RootDirPath() string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type FilterType int
|
// FetcherBuilder defines the interface for a fetcher builder.
|
||||||
|
type FetcherBuilder interface {
|
||||||
const (
|
// Schemes returns the schemes supported by the fetcher.
|
||||||
// FilterTypeUrl url type, pattern is the scheme, e.g. http://github.com -> http
|
Schemes() []string
|
||||||
FilterTypeUrl FilterType = iota
|
|
||||||
// FilterTypeFile file type, pattern is the file extension name, e.g. test.torrent -> torrent
|
|
||||||
FilterTypeFile
|
|
||||||
// FilterTypeBase64 base64 data type, pattern is the data mime type, e.g. data:application/x-bittorrent;base64 -> application/x-bittorrent
|
|
||||||
FilterTypeBase64
|
|
||||||
)
|
|
||||||
|
|
||||||
type SchemeFilter struct {
|
|
||||||
Type FilterType
|
|
||||||
Pattern string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SchemeFilter) Match(uri string) bool {
|
|
||||||
uriUpper := strings.ToUpper(uri)
|
|
||||||
patternUpper := strings.ToUpper(s.Pattern)
|
|
||||||
switch s.Type {
|
|
||||||
case FilterTypeUrl:
|
|
||||||
return strings.HasPrefix(uriUpper, patternUpper+":")
|
|
||||||
case FilterTypeFile:
|
|
||||||
return strings.HasSuffix(uriUpper, "."+patternUpper)
|
|
||||||
case FilterTypeBase64:
|
|
||||||
return strings.HasPrefix(uriUpper, "DATA:"+patternUpper+";BASE64,")
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// FetcherManager manage and control the fetcher
|
|
||||||
type FetcherManager interface {
|
|
||||||
// Name return the name of the protocol.
|
|
||||||
Name() string
|
|
||||||
// Filters registers the supported schemes.
|
|
||||||
Filters() []*SchemeFilter
|
|
||||||
// Build returns a new fetcher.
|
// Build returns a new fetcher.
|
||||||
Build() Fetcher
|
Build() Fetcher
|
||||||
// ParseName name displayed when the task is not yet resolved, parsed from the request URL
|
|
||||||
ParseName(u string) string
|
|
||||||
// AutoRename returns whether the fetcher need renaming the download file when has the same name file.
|
|
||||||
AutoRename() bool
|
|
||||||
|
|
||||||
// DefaultConfig returns the default configuration of the protocol.
|
|
||||||
DefaultConfig() any
|
|
||||||
// Store fetcher
|
// Store fetcher
|
||||||
Store(fetcher Fetcher) (any, error)
|
Store(fetcher Fetcher) (any, error)
|
||||||
// Restore fetcher
|
// Restore fetcher
|
||||||
Restore() (v any, f func(meta *FetcherMeta, v any) Fetcher)
|
Restore() (v any, f func(meta *FetcherMeta, v any) Fetcher)
|
||||||
// Close the fetcher manager, release resources.
|
|
||||||
Close() error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type DefaultFetcher struct {
|
type DefaultFetcher struct {
|
||||||
|
|||||||
@@ -1,97 +0,0 @@
|
|||||||
package fetcher
|
|
||||||
|
|
||||||
import "testing"
|
|
||||||
|
|
||||||
func TestSchemeFilter_Match(t *testing.T) {
|
|
||||||
type fields struct {
|
|
||||||
Type FilterType
|
|
||||||
Pattern string
|
|
||||||
}
|
|
||||||
type args struct {
|
|
||||||
uri string
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
fields fields
|
|
||||||
args args
|
|
||||||
want bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "url match",
|
|
||||||
fields: fields{
|
|
||||||
Type: FilterTypeUrl,
|
|
||||||
Pattern: "https",
|
|
||||||
},
|
|
||||||
args: args{
|
|
||||||
uri: "https://github.com",
|
|
||||||
},
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "url not match",
|
|
||||||
fields: fields{
|
|
||||||
Type: FilterTypeUrl,
|
|
||||||
Pattern: "https",
|
|
||||||
},
|
|
||||||
args: args{
|
|
||||||
uri: "ftp://github.com",
|
|
||||||
},
|
|
||||||
want: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "file match",
|
|
||||||
fields: fields{
|
|
||||||
Type: FilterTypeFile,
|
|
||||||
Pattern: "torrent",
|
|
||||||
},
|
|
||||||
args: args{
|
|
||||||
uri: "d:/temp/test.torrent",
|
|
||||||
},
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "file not match",
|
|
||||||
fields: fields{
|
|
||||||
Type: FilterTypeFile,
|
|
||||||
Pattern: "torrent",
|
|
||||||
},
|
|
||||||
args: args{
|
|
||||||
uri: "d:/temp/test.txt",
|
|
||||||
},
|
|
||||||
want: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "base64 match",
|
|
||||||
fields: fields{
|
|
||||||
Type: FilterTypeBase64,
|
|
||||||
Pattern: "application/x-bittorrent",
|
|
||||||
},
|
|
||||||
args: args{
|
|
||||||
uri: "data:application/x-bittorrent;base64,xxx",
|
|
||||||
},
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "base64 not match",
|
|
||||||
fields: fields{
|
|
||||||
Type: FilterTypeBase64,
|
|
||||||
Pattern: "application/x-bittorrent",
|
|
||||||
},
|
|
||||||
args: args{
|
|
||||||
uri: "data:application/javascript;base64,xxx",
|
|
||||||
},
|
|
||||||
want: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
s := &SchemeFilter{
|
|
||||||
Type: tt.fields.Type,
|
|
||||||
Pattern: tt.fields.Pattern,
|
|
||||||
}
|
|
||||||
if got := s.Match(tt.args.uri); got != tt.want {
|
|
||||||
t.Errorf("Match() = %v, want %v", got, tt.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,10 +3,4 @@ package bt
|
|||||||
type config struct {
|
type config struct {
|
||||||
ListenPort int `json:"listenPort"`
|
ListenPort int `json:"listenPort"`
|
||||||
Trackers []string `json:"trackers"`
|
Trackers []string `json:"trackers"`
|
||||||
// SeedKeep is always keep seeding after downloading is complete, unless manually stopped.
|
|
||||||
SeedKeep bool `json:"seedKeep`
|
|
||||||
// SeedRatio is the ratio of uploaded data to downloaded data to seed.
|
|
||||||
SeedRatio float64 `json:"seedRatio"`
|
|
||||||
// SeedTime is the time in seconds to seed after downloading is complete.
|
|
||||||
SeedTime int64 `json:"seedTime"`
|
|
||||||
}
|
}
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
package bt
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/rs/dnscache"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DnsCacheResolver resolves DNS requests for an HTTP client using an in-memory cache.
|
|
||||||
type DnsCacheResolver struct {
|
|
||||||
RefreshTimeout time.Duration
|
|
||||||
|
|
||||||
resolver dnscache.Resolver
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DnsCacheResolver) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
|
|
||||||
host, port, err := net.SplitHostPort(address)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
ips, err := r.resolver.LookupHost(ctx, host)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var conn net.Conn
|
|
||||||
for _, ip := range ips {
|
|
||||||
var dialer net.Dialer
|
|
||||||
conn, err = dialer.DialContext(ctx, network, net.JoinHostPort(ip, port))
|
|
||||||
if err == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return conn, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DnsCacheResolver) Run(ctx context.Context) {
|
|
||||||
ticker := time.NewTicker(r.RefreshTimeout)
|
|
||||||
defer ticker.Stop()
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return
|
|
||||||
case <-ticker.C:
|
|
||||||
r.resolver.Refresh(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+93
-320
@@ -2,7 +2,6 @@ package bt
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/GopeedLab/gopeed/internal/controller"
|
"github.com/GopeedLab/gopeed/internal/controller"
|
||||||
"github.com/GopeedLab/gopeed/internal/fetcher"
|
"github.com/GopeedLab/gopeed/internal/fetcher"
|
||||||
@@ -11,9 +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"
|
||||||
"io"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -26,8 +22,6 @@ var (
|
|||||||
lock sync.Mutex
|
lock sync.Mutex
|
||||||
torrentDirMap = make(map[string]string)
|
torrentDirMap = make(map[string]string)
|
||||||
ftMap = make(map[string]*fileTorrentImpl)
|
ftMap = make(map[string]*fileTorrentImpl)
|
||||||
closeCtx context.Context
|
|
||||||
closeFunc func()
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Fetcher struct {
|
type Fetcher struct {
|
||||||
@@ -36,13 +30,15 @@ type Fetcher struct {
|
|||||||
|
|
||||||
torrent *torrent.Torrent
|
torrent *torrent.Torrent
|
||||||
meta *fetcher.FetcherMeta
|
meta *fetcher.FetcherMeta
|
||||||
data *fetcherData
|
|
||||||
|
|
||||||
torrentReady atomic.Bool
|
torrentReady atomic.Bool
|
||||||
torrentUpload atomic.Bool
|
torrentDrop atomic.Bool
|
||||||
torrentDropCtx context.Context
|
create atomic.Bool
|
||||||
torrentDropFunc func()
|
progress fetcher.Progress
|
||||||
uploadDoneCh chan any
|
}
|
||||||
|
|
||||||
|
func (f *Fetcher) Name() string {
|
||||||
|
return "bt"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Fetcher) Setup(ctl *controller.Controller) {
|
func (f *Fetcher) Setup(ctl *controller.Controller) {
|
||||||
@@ -50,12 +46,13 @@ func (f *Fetcher) Setup(ctl *controller.Controller) {
|
|||||||
if f.meta == nil {
|
if f.meta == nil {
|
||||||
f.meta = &fetcher.FetcherMeta{}
|
f.meta = &fetcher.FetcherMeta{}
|
||||||
}
|
}
|
||||||
if f.data == nil {
|
exist := f.ctl.GetConfig(&f.config)
|
||||||
f.data = &fetcherData{}
|
if !exist {
|
||||||
|
f.config = &config{
|
||||||
|
ListenPort: 0,
|
||||||
|
Trackers: []string{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
f.uploadDoneCh = make(chan any, 1)
|
|
||||||
f.torrentDropCtx, f.torrentDropFunc = context.WithCancel(context.Background())
|
|
||||||
f.ctl.GetConfig(&f.config)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,16 +63,12 @@ func (f *Fetcher) initClient() (err error) {
|
|||||||
if client != nil {
|
if client != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if closeCtx == nil {
|
|
||||||
closeCtx, closeFunc = context.WithCancel(context.Background())
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg := torrent.NewDefaultClientConfig()
|
cfg := torrent.NewDefaultClientConfig()
|
||||||
cfg.Seed = true
|
|
||||||
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
|
||||||
cfg.HTTPProxy = f.ctl.GetProxy(f.meta.Req.Proxy)
|
cfg.HTTPProxy = f.ctl.ProxyConfig.ToHandler()
|
||||||
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) {
|
||||||
@@ -85,17 +78,7 @@ func (f *Fetcher) initClient() (err error) {
|
|||||||
ftMap[infoHash.String()] = ft
|
ftMap[infoHash.String()] = ft
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
dnsResolver := &DnsCacheResolver{RefreshTimeout: 5 * time.Minute}
|
|
||||||
cfg.TrackerDialContext = dnsResolver.DialContext
|
|
||||||
client, err = torrent.NewClient(cfg)
|
client, err = torrent.NewClient(cfg)
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
closeCtx, closeFunc = context.WithCancel(context.Background())
|
|
||||||
go func() {
|
|
||||||
dnsResolver.Run(closeCtx)
|
|
||||||
}()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,42 +86,42 @@ func (f *Fetcher) Resolve(req *base.Request) error {
|
|||||||
if err := base.ParseReqExtra[bt.ReqExtra](req); err != nil {
|
if err := base.ParseReqExtra[bt.ReqExtra](req); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
f.meta.Req = req
|
|
||||||
if err := f.addTorrent(req, false); err != nil {
|
if err := f.addTorrent(req); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
f.updateRes()
|
f.updateRes()
|
||||||
|
f.meta.Req = req
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Fetcher) Create(opts *base.Options) (err error) {
|
func (f *Fetcher) Create(opts *base.Options) (err error) {
|
||||||
|
f.create.Store(true)
|
||||||
f.meta.Opts = opts
|
f.meta.Opts = opts
|
||||||
if f.meta.Res != nil {
|
if f.meta.Res != nil {
|
||||||
torrentDirMap[f.meta.Res.Hash] = opts.Path
|
torrentDirMap[f.meta.Res.Hash] = f.meta.FolderPath()
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Fetcher) Start() (err error) {
|
func (f *Fetcher) Start() (err error) {
|
||||||
if !f.torrentReady.Load() {
|
if !f.torrentReady.Load() {
|
||||||
if err = f.addTorrent(f.meta.Req, false); err != nil {
|
if err = f.addTorrent(f.meta.Req); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ft, ok := ftMap[f.meta.Res.Hash]; ok {
|
if ft, ok := ftMap[f.meta.Res.Hash]; ok {
|
||||||
ft.setTorrentDir(f.meta.Opts.Path)
|
ft.setTorrentDir(f.meta.FolderPath())
|
||||||
}
|
}
|
||||||
files := f.torrent.Files()
|
files := f.torrent.Files()
|
||||||
// If the user does not specify the file to download, all files will be downloaded by default
|
// If the user does not specify the file to download, all files will be downloaded by default
|
||||||
if f.data.Progress == nil {
|
if len(f.meta.Opts.SelectFiles) == 0 {
|
||||||
if len(f.meta.Opts.SelectFiles) == 0 {
|
f.meta.Opts.SelectFiles = make([]int, len(files))
|
||||||
f.meta.Opts.SelectFiles = make([]int, len(files))
|
for i := range files {
|
||||||
for i := range files {
|
f.meta.Opts.SelectFiles[i] = i
|
||||||
f.meta.Opts.SelectFiles[i] = i
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
f.data.Progress = make(fetcher.Progress, len(f.meta.Opts.SelectFiles))
|
|
||||||
}
|
}
|
||||||
|
f.progress = make(fetcher.Progress, len(f.meta.Opts.SelectFiles))
|
||||||
if len(f.meta.Opts.SelectFiles) == len(files) {
|
if len(f.meta.Opts.SelectFiles) == len(files) {
|
||||||
f.torrent.DownloadAll()
|
f.torrent.DownloadAll()
|
||||||
} else {
|
} else {
|
||||||
@@ -147,6 +130,7 @@ func (f *Fetcher) Start() (err error) {
|
|||||||
file.Download()
|
file.Download()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,12 +141,8 @@ func (f *Fetcher) Pause() (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f *Fetcher) Close() (err error) {
|
func (f *Fetcher) Close() (err error) {
|
||||||
|
f.torrentDrop.Store(false)
|
||||||
f.safeDrop()
|
f.safeDrop()
|
||||||
f.torrentDropFunc()
|
|
||||||
f.uploadDoneCh <- nil
|
|
||||||
if len(client.Torrents()) == 0 {
|
|
||||||
err = closeClient()
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,94 +155,80 @@ func (f *Fetcher) safeDrop() {
|
|||||||
f.torrent.Drop()
|
f.torrent.Drop()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *Fetcher) Wait() (err error) {
|
||||||
|
for {
|
||||||
|
if f.torrentDrop.Load() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if f.torrentReady.Load() && len(f.meta.Opts.SelectFiles) > 0 {
|
||||||
|
done := true
|
||||||
|
for _, selectIndex := range f.meta.Opts.SelectFiles {
|
||||||
|
file := f.torrent.Files()[selectIndex]
|
||||||
|
if file.BytesCompleted() < file.Length() {
|
||||||
|
done = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if done {
|
||||||
|
// remove unselected files
|
||||||
|
for i, file := range f.torrent.Files() {
|
||||||
|
selected := false
|
||||||
|
for _, selectIndex := range f.meta.Opts.SelectFiles {
|
||||||
|
if i == selectIndex {
|
||||||
|
selected = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !selected {
|
||||||
|
util.SafeRemove(filepath.Join(f.meta.Opts.Path, f.meta.Res.Name, file.Path()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
time.Sleep(time.Millisecond * 500)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (f *Fetcher) Meta() *fetcher.FetcherMeta {
|
func (f *Fetcher) Meta() *fetcher.FetcherMeta {
|
||||||
return f.meta
|
return f.meta
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Fetcher) Stats() any {
|
func (f *Fetcher) Stats() any {
|
||||||
var stats torrent.TorrentStats
|
stats := f.torrent.Stats()
|
||||||
if f.torrent != nil {
|
baseStats := &bt.Stats{
|
||||||
stats = f.torrent.Stats()
|
|
||||||
} else {
|
|
||||||
stats = torrent.TorrentStats{}
|
|
||||||
}
|
|
||||||
return &bt.Stats{
|
|
||||||
TotalPeers: stats.TotalPeers,
|
TotalPeers: stats.TotalPeers,
|
||||||
ActivePeers: stats.ActivePeers,
|
ActivePeers: stats.ActivePeers,
|
||||||
ConnectedSeeders: stats.ConnectedSeeders,
|
ConnectedSeeders: stats.ConnectedSeeders,
|
||||||
SeedBytes: f.data.SeedBytes,
|
|
||||||
SeedRatio: f.seedRadio(),
|
|
||||||
SeedTime: f.data.SeedTime,
|
|
||||||
}
|
}
|
||||||
|
return baseStats
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Fetcher) Progress() fetcher.Progress {
|
func (f *Fetcher) Progress() fetcher.Progress {
|
||||||
if !f.torrentReady.Load() {
|
if !f.torrentReady.Load() {
|
||||||
return f.data.Progress
|
return f.progress
|
||||||
}
|
}
|
||||||
for i := range f.data.Progress {
|
for i := range f.progress {
|
||||||
selectIndex := f.meta.Opts.SelectFiles[i]
|
selectIndex := f.meta.Opts.SelectFiles[i]
|
||||||
file := f.torrent.Files()[selectIndex]
|
file := f.torrent.Files()[selectIndex]
|
||||||
f.data.Progress[i] = file.BytesCompleted()
|
f.progress[i] = file.BytesCompleted()
|
||||||
}
|
}
|
||||||
return f.data.Progress
|
return f.progress
|
||||||
}
|
|
||||||
|
|
||||||
func (f *Fetcher) Wait() (err error) {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-f.torrentDropCtx.Done():
|
|
||||||
return
|
|
||||||
case <-time.After(time.Second):
|
|
||||||
if f.torrentReady.Load() && len(f.meta.Opts.SelectFiles) > 0 {
|
|
||||||
if f.isDone() {
|
|
||||||
// remove unselected files
|
|
||||||
for i, file := range f.torrent.Files() {
|
|
||||||
selected := false
|
|
||||||
for _, selectIndex := range f.meta.Opts.SelectFiles {
|
|
||||||
if i == selectIndex {
|
|
||||||
selected = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !selected {
|
|
||||||
util.SafeRemove(filepath.Join(f.meta.Opts.Path, f.meta.Res.Name, file.Path()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *Fetcher) isDone() bool {
|
|
||||||
if f.meta.Opts == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for _, selectIndex := range f.meta.Opts.SelectFiles {
|
|
||||||
file := f.torrent.Files()[selectIndex]
|
|
||||||
if file.BytesCompleted() < file.Length() {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Fetcher) updateRes() {
|
func (f *Fetcher) updateRes() {
|
||||||
res := &base.Resource{
|
res := &base.Resource{
|
||||||
|
Name: f.torrent.Name(),
|
||||||
Range: true,
|
Range: true,
|
||||||
Files: make([]*base.FileInfo, len(f.torrent.Files())),
|
Files: make([]*base.FileInfo, len(f.torrent.Files())),
|
||||||
Hash: f.torrent.InfoHash().String(),
|
Hash: f.torrent.InfoHash().String(),
|
||||||
}
|
}
|
||||||
// Directory torrent
|
f.torrent.PeerConns()
|
||||||
if f.torrent.Info().Length == 0 {
|
|
||||||
res.Name = f.torrent.Name()
|
|
||||||
}
|
|
||||||
for i, file := range f.torrent.Files() {
|
for i, file := range f.torrent.Files() {
|
||||||
res.Files[i] = &base.FileInfo{
|
res.Files[i] = &base.FileInfo{
|
||||||
Name: filepath.Base(file.DisplayPath()),
|
Name: filepath.Base(file.DisplayPath()),
|
||||||
Path: util.Dir(file.DisplayPath()),
|
Path: util.Dir(file.Path()),
|
||||||
Size: file.Length(),
|
Size: file.Length(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -273,122 +239,24 @@ func (f *Fetcher) updateRes() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Fetcher) Upload() (err error) {
|
func (f *Fetcher) addTorrent(req *base.Request) (err error) {
|
||||||
return f.addTorrent(f.meta.Req, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *Fetcher) doUpload(fromUpload bool) {
|
|
||||||
if !f.torrentUpload.CompareAndSwap(false, true) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check and update seed data
|
|
||||||
lastData := &fetcherData{
|
|
||||||
SeedBytes: f.data.SeedBytes,
|
|
||||||
SeedTime: f.data.SeedTime,
|
|
||||||
}
|
|
||||||
var doneTime int64 = 0
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-f.torrentDropCtx.Done():
|
|
||||||
return
|
|
||||||
case <-time.After(time.Second):
|
|
||||||
if !f.torrentReady.Load() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
stats := f.torrentStats()
|
|
||||||
f.data.SeedBytes = lastData.SeedBytes + stats.BytesWrittenData.Int64()
|
|
||||||
|
|
||||||
// Check is download complete, if not don't check and stop seeding
|
|
||||||
if !fromUpload && !f.isDone() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if doneTime == 0 {
|
|
||||||
doneTime = time.Now().Unix()
|
|
||||||
}
|
|
||||||
f.data.SeedTime = lastData.SeedTime + time.Now().Unix() - doneTime
|
|
||||||
|
|
||||||
// If the seed forever is true, keep seeding
|
|
||||||
if f.config.SeedKeep {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the seed ratio is reached, stop seeding
|
|
||||||
if f.config.SeedRatio > 0 {
|
|
||||||
seedRadio := f.seedRadio()
|
|
||||||
if seedRadio >= f.config.SeedRatio {
|
|
||||||
f.Close()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the seed time is reached, stop seeding
|
|
||||||
if f.config.SeedTime > 0 {
|
|
||||||
if f.data.SeedTime >= f.config.SeedTime {
|
|
||||||
f.Close()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get torrent stats maybe panic, see https://github.com/anacrolix/torrent/issues/972
|
|
||||||
func (f *Fetcher) torrentStats() torrent.TorrentStats {
|
|
||||||
defer func() {
|
|
||||||
if r := recover(); r != nil {
|
|
||||||
// ignore panic
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
return f.torrent.Stats()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *Fetcher) UploadedBytes() int64 {
|
|
||||||
return f.data.SeedBytes
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *Fetcher) WaitUpload() (err error) {
|
|
||||||
<-f.uploadDoneCh
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *Fetcher) addTorrent(req *base.Request, fromUpload bool) (err error) {
|
|
||||||
if err = f.initClient(); err != nil {
|
if err = f.initClient(); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
schema := util.ParseSchema(req.URL)
|
schema := util.ParseSchema(req.URL)
|
||||||
if schema == "MAGNET" {
|
if schema == "MAGNET" {
|
||||||
f.torrent, err = client.AddMagnet(req.URL)
|
f.torrent, err = client.AddMagnet(req.URL)
|
||||||
} else {
|
} else if schema == "APPLICATION/X-BITTORRENT" {
|
||||||
var reader io.Reader
|
_, data := util.ParseDataUri(req.URL)
|
||||||
if schema == "FILE" {
|
buf := bytes.NewBuffer(data)
|
||||||
fileUrl, _ := url.Parse(req.URL)
|
|
||||||
filePath := fileUrl.Path[1:]
|
|
||||||
reader, err = os.Open(filePath)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else if schema == "DATA" {
|
|
||||||
_, data := util.ParseDataUri(req.URL)
|
|
||||||
reader = bytes.NewBuffer(data)
|
|
||||||
} else {
|
|
||||||
reader, err = os.Open(req.URL)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer reader.(io.Closer).Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
var metaInfo *metainfo.MetaInfo
|
var metaInfo *metainfo.MetaInfo
|
||||||
metaInfo, err = metainfo.Load(reader)
|
metaInfo, err = metainfo.Load(buf)
|
||||||
// Hotfix for https://github.com/anacrolix/torrent/issues/992, ignore "expected EOF" error
|
if err != nil {
|
||||||
// TODO remove this after the issue is fixed
|
|
||||||
if err != nil && !strings.Contains(err.Error(), "expected EOF") {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
f.torrent, err = client.AddTorrent(metaInfo)
|
f.torrent, err = client.AddTorrent(metaInfo)
|
||||||
|
} else {
|
||||||
|
f.torrent, err = client.AddTorrentFromFile(req.URL)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@@ -418,129 +286,34 @@ func (f *Fetcher) addTorrent(req *base.Request, fromUpload bool) (err error) {
|
|||||||
}
|
}
|
||||||
<-f.torrent.GotInfo()
|
<-f.torrent.GotInfo()
|
||||||
f.torrentReady.Store(true)
|
f.torrentReady.Store(true)
|
||||||
|
|
||||||
go f.doUpload(fromUpload)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Fetcher) seedRadio() float64 {
|
type FetcherBuilder struct {
|
||||||
var bytesRead int64
|
|
||||||
if f.Meta().Res != nil {
|
|
||||||
bytesRead = f.Meta().Res.Size
|
|
||||||
} else {
|
|
||||||
bytesRead = 0
|
|
||||||
}
|
|
||||||
if bytesRead <= 0 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
return float64(f.data.SeedBytes) / float64(bytesRead)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type fetcherData struct {
|
var schemes = []string{"FILE", "MAGNET", "APPLICATION/X-BITTORRENT"}
|
||||||
Progress fetcher.Progress
|
|
||||||
SeedBytes int64
|
func (fb *FetcherBuilder) Schemes() []string {
|
||||||
// SeedTime is the time in seconds to seed after downloading is complete.
|
return schemes
|
||||||
SeedTime int64
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func closeClient() error {
|
func (fb *FetcherBuilder) Build() fetcher.Fetcher {
|
||||||
lock.Lock()
|
|
||||||
defer lock.Unlock()
|
|
||||||
|
|
||||||
if closeFunc != nil {
|
|
||||||
closeFunc()
|
|
||||||
}
|
|
||||||
if client != nil {
|
|
||||||
errs := client.Close()
|
|
||||||
if len(errs) > 0 {
|
|
||||||
return errs[0]
|
|
||||||
}
|
|
||||||
client = nil
|
|
||||||
closeCtx = nil
|
|
||||||
closeFunc = nil
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type FetcherManager struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fm *FetcherManager) Name() string {
|
|
||||||
return "bt"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fm *FetcherManager) Filters() []*fetcher.SchemeFilter {
|
|
||||||
return []*fetcher.SchemeFilter{
|
|
||||||
{
|
|
||||||
Type: fetcher.FilterTypeUrl,
|
|
||||||
Pattern: "MAGNET",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Type: fetcher.FilterTypeFile,
|
|
||||||
Pattern: "TORRENT",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Type: fetcher.FilterTypeBase64,
|
|
||||||
Pattern: "APPLICATION/X-BITTORRENT",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fm *FetcherManager) Build() fetcher.Fetcher {
|
|
||||||
return &Fetcher{}
|
return &Fetcher{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fm *FetcherManager) ParseName(u string) string {
|
func (fb *FetcherBuilder) Store(f fetcher.Fetcher) (data any, err error) {
|
||||||
var name string
|
return nil, nil
|
||||||
url, err := url.Parse(u)
|
|
||||||
if err != nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
params := url.Query()
|
|
||||||
if params.Get("dn") != "" {
|
|
||||||
return params.Get("dn")
|
|
||||||
}
|
|
||||||
if params.Get("xt") != "" {
|
|
||||||
xt := strings.Split(params.Get("xt"), ":")
|
|
||||||
return xt[len(xt)-1]
|
|
||||||
}
|
|
||||||
return name
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fm *FetcherManager) AutoRename() bool {
|
func (fb *FetcherBuilder) Restore() (v any, f func(meta *fetcher.FetcherMeta, v any) fetcher.Fetcher) {
|
||||||
return false
|
return nil, func(meta *fetcher.FetcherMeta, v any) fetcher.Fetcher {
|
||||||
}
|
|
||||||
|
|
||||||
func (fm *FetcherManager) DefaultConfig() any {
|
|
||||||
return &config{
|
|
||||||
ListenPort: 0,
|
|
||||||
Trackers: []string{},
|
|
||||||
SeedKeep: false,
|
|
||||||
SeedRatio: 1.0,
|
|
||||||
SeedTime: 120 * 60,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fm *FetcherManager) Store(f fetcher.Fetcher) (data any, err error) {
|
|
||||||
_f := f.(*Fetcher)
|
|
||||||
return _f.data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fm *FetcherManager) Restore() (v any, f func(meta *fetcher.FetcherMeta, v any) fetcher.Fetcher) {
|
|
||||||
return &fetcherData{}, func(meta *fetcher.FetcherMeta, v any) fetcher.Fetcher {
|
|
||||||
return &Fetcher{
|
return &Fetcher{
|
||||||
meta: meta,
|
meta: meta,
|
||||||
data: v.(*fetcherData),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fm *FetcherManager) Close() error {
|
|
||||||
return closeClient()
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse version to bep20 format, fixed length 4, if not enough, fill 0
|
// parse version to bep20 format, fixed length 4, if not enough, fill 0
|
||||||
func parseBep20() string {
|
func parseBep20() string {
|
||||||
s := strings.ReplaceAll(base.Version, ".", "")
|
s := strings.ReplaceAll(base.Version, ".", "")
|
||||||
|
|||||||
@@ -8,10 +8,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/bt"
|
"github.com/GopeedLab/gopeed/pkg/protocol/bt"
|
||||||
gohttp "net/http"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
@@ -36,6 +33,7 @@ func TestFetcher_Resolve_DataUri_Torrent(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
want := &base.Resource{
|
want := &base.Resource{
|
||||||
|
Name: "ubuntu-22.04-live-server-amd64.iso",
|
||||||
Size: 1466714112,
|
Size: 1466714112,
|
||||||
Range: true,
|
Range: true,
|
||||||
Files: []*base.FileInfo{
|
Files: []*base.FileInfo{
|
||||||
@@ -71,170 +69,57 @@ func TestFetcher_ResolveWithProxy(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func doResolve(t *testing.T, fetcher fetcher.Fetcher) {
|
func doResolve(t *testing.T, fetcher fetcher.Fetcher) {
|
||||||
t.Run("Resolve Single File", func(t *testing.T) {
|
err := fetcher.Resolve(&base.Request{
|
||||||
err := fetcher.Resolve(&base.Request{
|
URL: "./testdata/ubuntu-22.04-live-server-amd64.iso.torrent",
|
||||||
URL: "./testdata/ubuntu-22.04-live-server-amd64.iso.torrent",
|
Extra: bt.ReqExtra{
|
||||||
Extra: bt.ReqExtra{
|
Trackers: []string{
|
||||||
Trackers: []string{
|
"udp://tracker.birkenwald.de:6969/announce",
|
||||||
"udp://tracker.birkenwald.de:6969/announce",
|
"udp://tracker.bitsearch.to:1337/announce",
|
||||||
"udp://tracker.bitsearch.to:1337/announce",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
})
|
},
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
want := &base.Resource{
|
|
||||||
Size: 1466714112,
|
|
||||||
Range: true,
|
|
||||||
Files: []*base.FileInfo{
|
|
||||||
{
|
|
||||||
Name: "ubuntu-22.04-live-server-amd64.iso",
|
|
||||||
Size: 1466714112,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Hash: "8a55cfbd5ca5d11507364765936c4f9e55b253ed",
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(want, fetcher.Meta().Res) {
|
|
||||||
t.Errorf("Resolve Single File Resolve() got = %v, want %v", fetcher.Meta().Res, want)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
if err != nil {
|
||||||
t.Run("Resolve Multi Files", func(t *testing.T) {
|
panic(err)
|
||||||
err := fetcher.Resolve(&base.Request{
|
|
||||||
URL: "./testdata/test.torrent",
|
|
||||||
Extra: bt.ReqExtra{
|
|
||||||
Trackers: []string{
|
|
||||||
"udp://tracker.birkenwald.de:6969/announce",
|
|
||||||
"udp://tracker.bitsearch.to:1337/announce",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
want := &base.Resource{
|
|
||||||
Name: "test",
|
|
||||||
Size: 107484864,
|
|
||||||
Range: true,
|
|
||||||
Files: []*base.FileInfo{
|
|
||||||
{
|
|
||||||
Name: "c.txt",
|
|
||||||
Path: "path",
|
|
||||||
Size: 98501754,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "b.txt",
|
|
||||||
Size: 8904996,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "a.txt",
|
|
||||||
Size: 78114,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Hash: "ccbc92b0cd8deec16a2ef4be242a8c9243b1cedb",
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(want, fetcher.Meta().Res) {
|
|
||||||
t.Errorf("Resolve Multi Files Resolve() got = %v, want %v", fetcher.Meta().Res, want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Resolve Unclean Torrent", func(t *testing.T) {
|
|
||||||
err := fetcher.Resolve(&base.Request{
|
|
||||||
URL: "./testdata/test.unclean.torrent",
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Resolve Unclean Torrent Resolve() got = %v, want nil", err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Resolve file scheme Torrent", func(t *testing.T) {
|
|
||||||
file, _ := filepath.Abs("./testdata/test.unclean.torrent")
|
|
||||||
uri := "file:///" + file
|
|
||||||
err := fetcher.Resolve(&base.Request{
|
|
||||||
URL: uri,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Resolve file scheme Torrent Resolve() got = %v, want nil", err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFetcherManager_ParseName(t *testing.T) {
|
|
||||||
type args struct {
|
|
||||||
u string
|
|
||||||
}
|
}
|
||||||
tests := []struct {
|
|
||||||
name string
|
want := &base.Resource{
|
||||||
args args
|
Name: "ubuntu-22.04-live-server-amd64.iso",
|
||||||
want string
|
Size: 1466714112,
|
||||||
}{
|
Range: true,
|
||||||
{
|
Files: []*base.FileInfo{
|
||||||
name: "broken url",
|
{
|
||||||
args: args{
|
Name: "ubuntu-22.04-live-server-amd64.iso",
|
||||||
u: "magnet://!@#%github.com",
|
Size: 1466714112,
|
||||||
},
|
},
|
||||||
want: "",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "dn",
|
|
||||||
args: args{
|
|
||||||
u: "magnet:?xt=urn:btih:8a55cfbd5ca5d11507364765936c4f9e55b253ed&dn=ubuntu-22.04-live-server-amd64.iso",
|
|
||||||
},
|
|
||||||
want: "ubuntu-22.04-live-server-amd64.iso",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "no dn",
|
|
||||||
args: args{
|
|
||||||
u: "magnet:?xt=urn:btih:8a55cfbd5ca5d11507364765936c4f9e55b253ed",
|
|
||||||
},
|
|
||||||
want: "8a55cfbd5ca5d11507364765936c4f9e55b253ed",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "non standard magnet",
|
|
||||||
args: args{
|
|
||||||
u: "magnet:?xxt=abcd",
|
|
||||||
},
|
|
||||||
want: "",
|
|
||||||
},
|
},
|
||||||
|
Hash: "8a55cfbd5ca5d11507364765936c4f9e55b253ed",
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
if !reflect.DeepEqual(want, fetcher.Meta().Res) {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Errorf("Resolve() got = %v, want %v", fetcher.Meta().Res, want)
|
||||||
fm := &FetcherManager{}
|
|
||||||
if got := fm.ParseName(tt.args.u); got != tt.want {
|
|
||||||
t.Errorf("ParseName() = %v, want %v", got, tt.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildFetcher() fetcher.Fetcher {
|
func buildFetcher() fetcher.Fetcher {
|
||||||
fb := new(FetcherManager)
|
fetcher := new(FetcherBuilder).Build()
|
||||||
fetcher := fb.Build()
|
fetcher.Setup(controller.NewController())
|
||||||
newController := controller.NewController()
|
|
||||||
newController.GetConfig = func(v any) {
|
|
||||||
json.Unmarshal([]byte(test.ToJson(fb.DefaultConfig())), v)
|
|
||||||
}
|
|
||||||
fetcher.Setup(newController)
|
|
||||||
return fetcher
|
return fetcher
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildConfigFetcher(proxyConfig *base.DownloaderProxyConfig) fetcher.Fetcher {
|
func buildConfigFetcher(proxyConfig *base.DownloaderProxyConfig) fetcher.Fetcher {
|
||||||
fetcher := new(FetcherManager).Build()
|
fetcher := new(FetcherBuilder).Build()
|
||||||
newController := controller.NewController()
|
newController := controller.NewController()
|
||||||
mockCfg := config{
|
mockCfg := config{
|
||||||
Trackers: []string{
|
Trackers: []string{
|
||||||
"udp://tracker.birkenwald.de:6969/announce",
|
"udp://tracker.birkenwald.de:6969/announce",
|
||||||
"udp://tracker.bitsearch.to:1337/announce",
|
"udp://tracker.bitsearch.to:1337/announce",
|
||||||
}}
|
}}
|
||||||
newController.GetConfig = func(v any) {
|
newController.GetConfig = func(v any) bool {
|
||||||
json.Unmarshal([]byte(test.ToJson(mockCfg)), v)
|
if err := json.Unmarshal([]byte(test.ToJson(mockCfg)), v); err != nil {
|
||||||
}
|
return false
|
||||||
newController.GetProxy = func(requestProxy *base.RequestProxy) func(*gohttp.Request) (*url.URL, error) {
|
}
|
||||||
return proxyConfig.ToHandler()
|
return true
|
||||||
}
|
}
|
||||||
|
newController.ProxyConfig = proxyConfig
|
||||||
fetcher.Setup(newController)
|
fetcher.Setup(newController)
|
||||||
return fetcher
|
return fetcher
|
||||||
}
|
}
|
||||||
@@ -3,7 +3,6 @@
|
|||||||
package bt
|
package bt
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/anacrolix/torrent/storage"
|
"github.com/anacrolix/torrent/storage"
|
||||||
"io"
|
"io"
|
||||||
@@ -54,7 +53,7 @@ func (fs fileClientImpl) Close() error {
|
|||||||
return fs.opts.PieceCompletion.Close()
|
return fs.opts.PieceCompletion.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fs fileClientImpl) OpenTorrent(ctx context.Context, info *metainfo.Info, infoHash metainfo.Hash) (_ storage.TorrentImpl, err error) {
|
func (fs fileClientImpl) OpenTorrent(info *metainfo.Info, infoHash metainfo.Hash) (_ storage.TorrentImpl, err error) {
|
||||||
upvertedFiles := info.UpvertedFiles()
|
upvertedFiles := info.UpvertedFiles()
|
||||||
files := make([]file, 0, len(upvertedFiles))
|
files := make([]file, 0, len(upvertedFiles))
|
||||||
for _, fileInfo := range upvertedFiles {
|
for _, fileInfo := range upvertedFiles {
|
||||||
|
|||||||
BIN
Binary file not shown.
Binary file not shown.
@@ -3,12 +3,16 @@ package http
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/GopeedLab/gopeed/internal/controller"
|
||||||
|
"github.com/GopeedLab/gopeed/internal/fetcher"
|
||||||
|
"github.com/GopeedLab/gopeed/pkg/base"
|
||||||
|
fhttp "github.com/GopeedLab/gopeed/pkg/protocol/http"
|
||||||
|
"github.com/xiaoqidun/setft"
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
"io"
|
"io"
|
||||||
"mime"
|
"mime"
|
||||||
"net"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/cookiejar"
|
"net/http/cookiejar"
|
||||||
"net/url"
|
"net/url"
|
||||||
@@ -17,18 +21,8 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/GopeedLab/gopeed/internal/controller"
|
|
||||||
"github.com/GopeedLab/gopeed/internal/fetcher"
|
|
||||||
"github.com/GopeedLab/gopeed/pkg/base"
|
|
||||||
fhttp "github.com/GopeedLab/gopeed/pkg/protocol/http"
|
|
||||||
"github.com/xiaoqidun/setft"
|
|
||||||
"golang.org/x/sync/errgroup"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const connectTimeout = 15 * time.Second
|
|
||||||
const readTimeout = 15 * time.Second
|
|
||||||
|
|
||||||
type RequestError struct {
|
type RequestError struct {
|
||||||
Code int
|
Code int
|
||||||
Msg string
|
Msg string
|
||||||
@@ -70,13 +64,23 @@ type Fetcher struct {
|
|||||||
eg *errgroup.Group
|
eg *errgroup.Group
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *Fetcher) Name() string {
|
||||||
|
return "http"
|
||||||
|
}
|
||||||
|
|
||||||
func (f *Fetcher) Setup(ctl *controller.Controller) {
|
func (f *Fetcher) Setup(ctl *controller.Controller) {
|
||||||
f.ctl = ctl
|
f.ctl = ctl
|
||||||
f.doneCh = make(chan error, 1)
|
f.doneCh = make(chan error, 1)
|
||||||
if f.meta == nil {
|
if f.meta == nil {
|
||||||
f.meta = &fetcher.FetcherMeta{}
|
f.meta = &fetcher.FetcherMeta{}
|
||||||
}
|
}
|
||||||
f.ctl.GetConfig(&f.config)
|
exist := f.ctl.GetConfig(&f.config)
|
||||||
|
if !exist {
|
||||||
|
f.config = &config{
|
||||||
|
UserAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36",
|
||||||
|
Connections: 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,7 +88,6 @@ func (f *Fetcher) Resolve(req *base.Request) error {
|
|||||||
if err := base.ParseReqExtra[fhttp.ReqExtra](req); err != nil {
|
if err := base.ParseReqExtra[fhttp.ReqExtra](req); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
f.meta.Req = req
|
|
||||||
httpReq, err := f.buildRequest(nil, req)
|
httpReq, err := f.buildRequest(nil, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -105,11 +108,11 @@ func (f *Fetcher) Resolve(req *base.Request) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if base.HttpCodePartialContent == httpResp.StatusCode || (base.HttpCodeOK == httpResp.StatusCode && httpResp.Header.Get(base.HttpHeaderAcceptRanges) == base.HttpHeaderBytes && strings.HasPrefix(httpResp.Header.Get(base.HttpHeaderContentRange), base.HttpHeaderBytes)) {
|
if base.HttpCodePartialContent == httpResp.StatusCode || (base.HttpCodeOK == httpResp.StatusCode && httpResp.Header.Get(base.HttpHeaderAcceptRanges) == base.HttpHeaderBytes && strings.HasPrefix(httpResp.Header.Get(base.HttpHeaderContentRange), base.HttpHeaderBytes)) {
|
||||||
// response 206 status code, support breakpoint continuation
|
// 1.返回206响应码表示支持断点下载 2.不返回206但是Accept-Ranges首部并且等于bytes也表示支持断点下载
|
||||||
res.Range = true
|
res.Range = true
|
||||||
// parse content length from Content-Range header, eg: bytes 0-1000/1001 or bytes 0-0/*
|
// 解析资源大小: bytes 0-1000/1001 => 1001
|
||||||
contentTotal := path.Base(httpResp.Header.Get(base.HttpHeaderContentRange))
|
contentTotal := path.Base(httpResp.Header.Get(base.HttpHeaderContentRange))
|
||||||
if contentTotal != "" && contentTotal != "*" {
|
if contentTotal != "" {
|
||||||
parse, err := strconv.ParseInt(contentTotal, 10, 64)
|
parse, err := strconv.ParseInt(contentTotal, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -117,8 +120,7 @@ func (f *Fetcher) Resolve(req *base.Request) error {
|
|||||||
res.Size = parse
|
res.Size = parse
|
||||||
}
|
}
|
||||||
} else if base.HttpCodeOK == httpResp.StatusCode {
|
} else if base.HttpCodeOK == httpResp.StatusCode {
|
||||||
// response 200 status code, not support breakpoint continuation, get file size by Content-Length header
|
// 返回200响应码,不支持断点下载,通过Content-Length头获取文件大小,获取不到的话可能是chunked编码
|
||||||
// if not found, maybe chunked encoding
|
|
||||||
contentLength := httpResp.Header.Get(base.HttpHeaderContentLength)
|
contentLength := httpResp.Header.Get(base.HttpHeaderContentLength)
|
||||||
if contentLength != "" {
|
if contentLength != "" {
|
||||||
parse, err := strconv.ParseInt(contentLength, 10, 64)
|
parse, err := strconv.ParseInt(contentLength, 10, 64)
|
||||||
@@ -150,7 +152,7 @@ func (f *Fetcher) Resolve(req *base.Request) error {
|
|||||||
file.Name = filename
|
file.Name = filename
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// get file filePath by URL
|
// Get file filePath by URL
|
||||||
if file.Name == "" {
|
if file.Name == "" {
|
||||||
file.Name = path.Base(httpReq.URL.Path)
|
file.Name = path.Base(httpReq.URL.Path)
|
||||||
}
|
}
|
||||||
@@ -159,6 +161,7 @@ func (f *Fetcher) Resolve(req *base.Request) error {
|
|||||||
file.Name = httpReq.URL.Hostname()
|
file.Name = httpReq.URL.Hostname()
|
||||||
}
|
}
|
||||||
res.Files = append(res.Files, file)
|
res.Files = append(res.Files, file)
|
||||||
|
f.meta.Req = req
|
||||||
f.meta.Res = res
|
f.meta.Res = res
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -166,6 +169,9 @@ func (f *Fetcher) Resolve(req *base.Request) error {
|
|||||||
func (f *Fetcher) Create(opts *base.Options) error {
|
func (f *Fetcher) Create(opts *base.Options) error {
|
||||||
f.meta.Opts = opts
|
f.meta.Opts = opts
|
||||||
|
|
||||||
|
if err := base.ParseReqExtra[fhttp.ReqExtra](f.meta.Req); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if err := base.ParseOptsExtra[fhttp.OptsExtra](f.meta.Opts); err != nil {
|
if err := base.ParseOptsExtra[fhttp.OptsExtra](f.meta.Opts); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -173,12 +179,8 @@ func (f *Fetcher) Create(opts *base.Options) error {
|
|||||||
opts.Extra = &fhttp.OptsExtra{}
|
opts.Extra = &fhttp.OptsExtra{}
|
||||||
}
|
}
|
||||||
extra := opts.Extra.(*fhttp.OptsExtra)
|
extra := opts.Extra.(*fhttp.OptsExtra)
|
||||||
if extra.Connections <= 0 {
|
if extra.Connections == 0 {
|
||||||
extra.Connections = f.config.Connections
|
extra.Connections = f.config.Connections
|
||||||
// Avoid zero connections configuration
|
|
||||||
if extra.Connections <= 0 {
|
|
||||||
extra.Connections = 1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -199,12 +201,6 @@ func (f *Fetcher) Start() (err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Avoid request extra modified by extension
|
|
||||||
if err = base.ParseReqExtra[fhttp.ReqExtra](f.meta.Req); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if f.chunks == nil {
|
if f.chunks == nil {
|
||||||
f.chunks = f.splitChunk()
|
f.chunks = f.splitChunk()
|
||||||
}
|
}
|
||||||
@@ -254,42 +250,23 @@ func (f *Fetcher) Wait() (err error) {
|
|||||||
return <-f.doneCh
|
return <-f.doneCh
|
||||||
}
|
}
|
||||||
|
|
||||||
type fetchResult struct {
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *Fetcher) fetch() {
|
func (f *Fetcher) fetch() {
|
||||||
var ctx context.Context
|
var ctx context.Context
|
||||||
ctx, f.cancel = context.WithCancel(context.Background())
|
ctx, f.cancel = context.WithCancel(context.Background())
|
||||||
f.eg, _ = errgroup.WithContext(ctx)
|
f.eg, _ = errgroup.WithContext(ctx)
|
||||||
chunkErrs := make([]error, len(f.chunks))
|
|
||||||
for i := 0; i < len(f.chunks); i++ {
|
for i := 0; i < len(f.chunks); i++ {
|
||||||
i := i
|
i := i
|
||||||
f.eg.Go(func() error {
|
f.eg.Go(func() error {
|
||||||
err := f.fetchChunk(i, ctx)
|
return f.fetchChunk(i, ctx)
|
||||||
// if canceled, fail fast
|
|
||||||
if errors.Is(err, context.Canceled) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
chunkErrs[i] = err
|
|
||||||
return nil
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
err := f.eg.Wait()
|
err := f.eg.Wait()
|
||||||
// error returned only if canceled, just return
|
// check if canceled
|
||||||
if err != nil {
|
if errors.Is(err, context.Canceled) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// check all fetch results, if any error, return
|
|
||||||
for _, chunkErr := range chunkErrs {
|
|
||||||
if chunkErr != nil {
|
|
||||||
err = chunkErr
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
f.file.Close()
|
f.file.Close()
|
||||||
// Update file last modified time
|
// Update file last modified time
|
||||||
if f.config.UseServerCtime && f.meta.Res.Files[0].Ctime != nil {
|
if f.config.UseServerCtime && f.meta.Res.Files[0].Ctime != nil {
|
||||||
@@ -312,11 +289,24 @@ func (f *Fetcher) fetchChunk(index int, ctx context.Context) (err error) {
|
|||||||
for {
|
for {
|
||||||
// if chunk is completed, return
|
// if chunk is completed, return
|
||||||
if f.meta.Res.Range && chunk.Downloaded >= chunk.End-chunk.Begin+1 {
|
if f.meta.Res.Range && chunk.Downloaded >= chunk.End-chunk.Begin+1 {
|
||||||
return nil
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if chunk.retryTimes >= maxRetries {
|
if chunk.retryTimes >= maxRetries {
|
||||||
return
|
if !f.meta.Res.Range {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// check if all failed
|
||||||
|
allFailed := true
|
||||||
|
for _, c := range f.chunks {
|
||||||
|
if chunk.Downloaded < chunk.End-chunk.Begin+1 && c.retryTimes < maxRetries {
|
||||||
|
allFailed = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if allFailed {
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -343,9 +333,10 @@ func (f *Fetcher) fetchChunk(index int, ctx context.Context) (err error) {
|
|||||||
err = NewRequestError(resp.StatusCode, resp.Status)
|
err = NewRequestError(resp.StatusCode, resp.Status)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
reader := NewTimeoutReader(resp.Body, readTimeout)
|
// Http request success, reset retry times
|
||||||
|
chunk.retryTimes = 0
|
||||||
for {
|
for {
|
||||||
n, err := reader.Read(buf)
|
n, err := resp.Body.Read(buf)
|
||||||
if n > 0 {
|
if n > 0 {
|
||||||
_, err := f.file.WriteAt(buf[:n], chunk.Begin+chunk.Downloaded)
|
_, err := f.file.WriteAt(buf[:n], chunk.Begin+chunk.Downloaded)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -360,6 +351,7 @@ func (f *Fetcher) fetchChunk(index int, ctx context.Context) (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}()
|
}()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// If canceled, do not retry
|
// If canceled, do not retry
|
||||||
@@ -394,8 +386,7 @@ func (f *Fetcher) buildRequest(ctx context.Context, req *base.Request) (httpReq
|
|||||||
method string
|
method string
|
||||||
body io.Reader
|
body io.Reader
|
||||||
)
|
)
|
||||||
|
headers := make(map[string][]string)
|
||||||
headers := http.Header{}
|
|
||||||
if req.Extra == nil {
|
if req.Extra == nil {
|
||||||
method = http.MethodGet
|
method = http.MethodGet
|
||||||
} else {
|
} else {
|
||||||
@@ -407,7 +398,7 @@ func (f *Fetcher) buildRequest(ctx context.Context, req *base.Request) (httpReq
|
|||||||
}
|
}
|
||||||
if len(extra.Header) > 0 {
|
if len(extra.Header) > 0 {
|
||||||
for k, v := range extra.Header {
|
for k, v := range extra.Header {
|
||||||
headers.Set(k, v)
|
headers[k] = []string{v}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if extra.Body != "" {
|
if extra.Body != "" {
|
||||||
@@ -415,7 +406,8 @@ func (f *Fetcher) buildRequest(ctx context.Context, req *base.Request) (httpReq
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if _, ok := headers[base.HttpHeaderUserAgent]; !ok {
|
if _, ok := headers[base.HttpHeaderUserAgent]; !ok {
|
||||||
headers.Set(base.HttpHeaderUserAgent, f.config.UserAgent)
|
// load user agent from config
|
||||||
|
headers[base.HttpHeaderUserAgent] = []string{f.config.UserAgent}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx != nil {
|
if ctx != nil {
|
||||||
@@ -427,10 +419,6 @@ func (f *Fetcher) buildRequest(ctx context.Context, req *base.Request) (httpReq
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
httpReq.Header = headers
|
httpReq.Header = headers
|
||||||
// Override Host header
|
|
||||||
if host := headers.Get(base.HttpHeaderHost); host != "" {
|
|
||||||
httpReq.Host = host
|
|
||||||
}
|
|
||||||
return httpReq, nil
|
return httpReq, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -464,13 +452,7 @@ func (f *Fetcher) splitChunk() (chunks []*chunk) {
|
|||||||
|
|
||||||
func (f *Fetcher) buildClient() *http.Client {
|
func (f *Fetcher) buildClient() *http.Client {
|
||||||
transport := &http.Transport{
|
transport := &http.Transport{
|
||||||
DialContext: (&net.Dialer{
|
Proxy: f.ctl.ProxyConfig.ToHandler(),
|
||||||
Timeout: connectTimeout,
|
|
||||||
}).DialContext,
|
|
||||||
Proxy: f.ctl.GetProxy(f.meta.Req.Proxy),
|
|
||||||
TLSClientConfig: &tls.Config{
|
|
||||||
InsecureSkipVerify: f.meta.Req.SkipVerifyCert,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
// Cookie handle
|
// Cookie handle
|
||||||
jar, _ := cookiejar.New(nil)
|
jar, _ := cookiejar.New(nil)
|
||||||
@@ -484,67 +466,30 @@ type fetcherData struct {
|
|||||||
Chunks []*chunk
|
Chunks []*chunk
|
||||||
}
|
}
|
||||||
|
|
||||||
type FetcherManager struct {
|
type FetcherBuilder struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fm *FetcherManager) Name() string {
|
var schemes = []string{"HTTP", "HTTPS"}
|
||||||
return "http"
|
|
||||||
|
func (fb *FetcherBuilder) Schemes() []string {
|
||||||
|
return schemes
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fm *FetcherManager) Filters() []*fetcher.SchemeFilter {
|
func (fb *FetcherBuilder) Build() fetcher.Fetcher {
|
||||||
return []*fetcher.SchemeFilter{
|
|
||||||
{
|
|
||||||
Type: fetcher.FilterTypeUrl,
|
|
||||||
Pattern: "HTTP",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Type: fetcher.FilterTypeUrl,
|
|
||||||
Pattern: "HTTPS",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fm *FetcherManager) Build() fetcher.Fetcher {
|
|
||||||
return &Fetcher{}
|
return &Fetcher{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fm *FetcherManager) ParseName(u string) string {
|
func (fb *FetcherBuilder) Store(f fetcher.Fetcher) (data any, err error) {
|
||||||
var name string
|
|
||||||
url, err := url.Parse(u)
|
|
||||||
if err != nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
// Get filePath by URL
|
|
||||||
name = path.Base(url.Path)
|
|
||||||
// If file name is empty, use host name
|
|
||||||
if name == "" || name == "/" || name == "." {
|
|
||||||
name = url.Hostname()
|
|
||||||
}
|
|
||||||
return name
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fm *FetcherManager) AutoRename() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fm *FetcherManager) DefaultConfig() any {
|
|
||||||
return &config{
|
|
||||||
UserAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36",
|
|
||||||
Connections: 16,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fm *FetcherManager) Store(f fetcher.Fetcher) (data any, err error) {
|
|
||||||
_f := f.(*Fetcher)
|
_f := f.(*Fetcher)
|
||||||
return &fetcherData{
|
return &fetcherData{
|
||||||
Chunks: _f.chunks,
|
Chunks: _f.chunks,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fm *FetcherManager) Restore() (v any, f func(meta *fetcher.FetcherMeta, v any) fetcher.Fetcher) {
|
func (fb *FetcherBuilder) Restore() (v any, f func(meta *fetcher.FetcherMeta, v any) fetcher.Fetcher) {
|
||||||
return &fetcherData{}, func(meta *fetcher.FetcherMeta, v any) fetcher.Fetcher {
|
return &fetcherData{}, func(meta *fetcher.FetcherMeta, v any) fetcher.Fetcher {
|
||||||
fd := v.(*fetcherData)
|
fd := v.(*fetcherData)
|
||||||
fb := &FetcherManager{}
|
fb := &FetcherBuilder{}
|
||||||
fetcher := fb.Build().(*Fetcher)
|
fetcher := fb.Build().(*Fetcher)
|
||||||
fetcher.meta = meta
|
fetcher.meta = meta
|
||||||
base.ParseReqExtra[fhttp.ReqExtra](fetcher.meta.Req)
|
base.ParseReqExtra[fhttp.ReqExtra](fetcher.meta.Req)
|
||||||
@@ -555,7 +500,3 @@ func (fm *FetcherManager) Restore() (v any, f func(meta *fetcher.FetcherMeta, v
|
|||||||
return fetcher
|
return fetcher
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fm *FetcherManager) Close() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -9,9 +9,6 @@ import (
|
|||||||
"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"
|
||||||
"net"
|
"net"
|
||||||
gohttp "net/http"
|
|
||||||
"net/url"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -60,21 +57,6 @@ func TestFetcher_Resolve(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFetcher_ResolveWithHostHeader(t *testing.T) {
|
|
||||||
fetcher := buildFetcher()
|
|
||||||
err := fetcher.Resolve(&base.Request{
|
|
||||||
URL: "https://bing.com",
|
|
||||||
Extra: &http.ReqExtra{
|
|
||||||
Header: map[string]string{
|
|
||||||
"Host": "test",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err == nil || !strings.Contains(err.Error(), "400") {
|
|
||||||
t.Errorf("Resolve() got = %v, want %v", err, "400")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func testResolve(startTestServer func() net.Listener, path string, want *base.Resource, t *testing.T) {
|
func testResolve(startTestServer func() net.Listener, path string, want *base.Resource, t *testing.T) {
|
||||||
listener := startTestServer()
|
listener := startTestServer()
|
||||||
defer listener.Close()
|
defer listener.Close()
|
||||||
@@ -148,14 +130,6 @@ func TestFetcher_DownloadLimit(t *testing.T) {
|
|||||||
downloadNormal(listener, 8, t)
|
downloadNormal(listener, 8, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFetcher_DownloadResponseBodyReadTimeout(t *testing.T) {
|
|
||||||
listener := test.StartTestLimitServer(16, readTimeout.Milliseconds()+5000)
|
|
||||||
defer listener.Close()
|
|
||||||
|
|
||||||
downloadError(listener, 1, t)
|
|
||||||
downloadError(listener, 4, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFetcher_DownloadResume(t *testing.T) {
|
func TestFetcher_DownloadResume(t *testing.T) {
|
||||||
listener := test.StartTestFileServer()
|
listener := test.StartTestFileServer()
|
||||||
defer listener.Close()
|
defer listener.Close()
|
||||||
@@ -218,54 +192,6 @@ func TestFetcher_ConfigUseServerCtime(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFetcherManager_ParseName(t *testing.T) {
|
|
||||||
type args struct {
|
|
||||||
u string
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
args args
|
|
||||||
want string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "broken url",
|
|
||||||
args: args{
|
|
||||||
u: "https://!@#%github.com",
|
|
||||||
},
|
|
||||||
want: "",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "file path",
|
|
||||||
args: args{
|
|
||||||
u: "https://github.com/index.html",
|
|
||||||
},
|
|
||||||
want: "index.html",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "file path with query and hash",
|
|
||||||
args: args{
|
|
||||||
u: "https://github.com/a/b/index.html/#list?name=1",
|
|
||||||
},
|
|
||||||
want: "index.html",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "no file path",
|
|
||||||
args: args{
|
|
||||||
u: "https://github.com",
|
|
||||||
},
|
|
||||||
want: "github.com",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
fm := &FetcherManager{}
|
|
||||||
if got := fm.ParseName(tt.args.u); got != tt.want {
|
|
||||||
t.Errorf("ParseName() = %v, want %v", got, tt.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func downloadReady(listener net.Listener, connections int, t *testing.T) fetcher.Fetcher {
|
func downloadReady(listener net.Listener, connections int, t *testing.T) fetcher.Fetcher {
|
||||||
return doDownloadReady(buildFetcher(), listener, connections, t)
|
return doDownloadReady(buildFetcher(), listener, connections, t)
|
||||||
}
|
}
|
||||||
@@ -380,7 +306,7 @@ func downloadResume(listener net.Listener, connections int, t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fb := new(FetcherManager)
|
fb := new(FetcherBuilder)
|
||||||
time.Sleep(time.Millisecond * 50)
|
time.Sleep(time.Millisecond * 50)
|
||||||
data, err := fb.Store(fetcher)
|
data, err := fb.Store(fetcher)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -411,12 +337,10 @@ 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.GetProxy = func(requestProxy *base.RequestProxy) func(*gohttp.Request) (*url.URL, error) {
|
ctl.ProxyConfig = &base.DownloaderProxyConfig{
|
||||||
return (&base.DownloaderProxyConfig{
|
Enable: true,
|
||||||
Enable: true,
|
Scheme: "socks5",
|
||||||
Scheme: "socks5",
|
Host: proxyListener.Addr().String(),
|
||||||
Host: proxyListener.Addr().String(),
|
|
||||||
}).ToHandler()
|
|
||||||
}
|
}
|
||||||
fetcher.Setup(ctl)
|
fetcher.Setup(ctl)
|
||||||
err := fetcher.Start()
|
err := fetcher.Start()
|
||||||
@@ -435,21 +359,19 @@ func downloadWithProxy(httpListener net.Listener, proxyListener net.Listener, t
|
|||||||
}
|
}
|
||||||
|
|
||||||
func buildFetcher() *Fetcher {
|
func buildFetcher() *Fetcher {
|
||||||
fm := new(FetcherManager)
|
fetcher := new(FetcherBuilder).Build()
|
||||||
fetcher := fm.Build()
|
fetcher.Setup(controller.NewController())
|
||||||
newController := controller.NewController()
|
|
||||||
newController.GetConfig = func(v any) {
|
|
||||||
json.Unmarshal([]byte(test.ToJson(fm.DefaultConfig())), v)
|
|
||||||
}
|
|
||||||
fetcher.Setup(newController)
|
|
||||||
return fetcher.(*Fetcher)
|
return fetcher.(*Fetcher)
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildConfigFetcher(cfg config) fetcher.Fetcher {
|
func buildConfigFetcher(cfg config) fetcher.Fetcher {
|
||||||
fetcher := new(FetcherManager).Build()
|
fetcher := new(FetcherBuilder).Build()
|
||||||
newController := controller.NewController()
|
newController := controller.NewController()
|
||||||
newController.GetConfig = func(v any) {
|
newController.GetConfig = func(v any) bool {
|
||||||
json.Unmarshal([]byte(test.ToJson(cfg)), v)
|
if err := json.Unmarshal([]byte(test.ToJson(cfg)), v); err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
fetcher.Setup(newController)
|
fetcher.Setup(newController)
|
||||||
return fetcher
|
return fetcher
|
||||||
|
|||||||
@@ -1,40 +0,0 @@
|
|||||||
package http
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"io"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type TimeoutReader struct {
|
|
||||||
reader io.Reader
|
|
||||||
timeout time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewTimeoutReader(r io.Reader, timeout time.Duration) *TimeoutReader {
|
|
||||||
return &TimeoutReader{
|
|
||||||
reader: r,
|
|
||||||
timeout: timeout,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tr *TimeoutReader) Read(p []byte) (n int, err error) {
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), tr.timeout)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
done := make(chan struct{})
|
|
||||||
var readErr error
|
|
||||||
var bytesRead int
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
bytesRead, readErr = tr.reader.Read(p)
|
|
||||||
close(done)
|
|
||||||
}()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-done:
|
|
||||||
return bytesRead, readErr
|
|
||||||
case <-ctx.Done():
|
|
||||||
return 0, ctx.Err()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
package http
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestTimeoutReader_Read(t *testing.T) {
|
|
||||||
data := []byte("Hello, World!")
|
|
||||||
reader := bytes.NewReader(data)
|
|
||||||
timeoutReader := NewTimeoutReader(reader, 1*time.Second)
|
|
||||||
|
|
||||||
buf := make([]byte, len(data))
|
|
||||||
n, err := timeoutReader.Read(buf)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("expected no error, got %v", err)
|
|
||||||
}
|
|
||||||
if n != len(data) {
|
|
||||||
t.Fatalf("expected to read %d bytes, read %d", len(data), n)
|
|
||||||
}
|
|
||||||
if !bytes.Equal(buf, data) {
|
|
||||||
t.Fatalf("expected %s, got %s", data, buf)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTimeoutReader_ReadTimeout(t *testing.T) {
|
|
||||||
reader := &slowReader{delay: 2 * time.Second}
|
|
||||||
timeoutReader := NewTimeoutReader(reader, 1*time.Second)
|
|
||||||
|
|
||||||
buf := make([]byte, 8192)
|
|
||||||
_, err := timeoutReader.Read(buf)
|
|
||||||
if err == nil {
|
|
||||||
t.Fatal("expected timeout error, got nil")
|
|
||||||
}
|
|
||||||
if !errors.Is(err, context.DeadlineExceeded) {
|
|
||||||
t.Fatalf("expected %v, got %v", context.DeadlineExceeded, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type slowReader struct {
|
|
||||||
delay time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sr *slowReader) Read(p []byte) (n int, err error) {
|
|
||||||
time.Sleep(sr.delay)
|
|
||||||
return 0, io.EOF
|
|
||||||
}
|
|
||||||
+20
-31
@@ -2,13 +2,12 @@ package test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/rand"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/GopeedLab/gopeed/pkg/base"
|
"github.com/GopeedLab/gopeed/pkg/base"
|
||||||
"github.com/armon/go-socks5"
|
"github.com/armon/go-socks5"
|
||||||
"io"
|
"io"
|
||||||
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
@@ -37,7 +36,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func StartTestFileServer() net.Listener {
|
func StartTestFileServer() net.Listener {
|
||||||
return startTestServer(func(sl *shutdownListener) http.Handler {
|
return startTestServer(func() http.Handler {
|
||||||
return http.FileServer(http.Dir(Dir))
|
return http.FileServer(http.Dir(Dir))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -53,7 +52,7 @@ func (s *SlowFileServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func StartTestSlowFileServer(delay time.Duration) net.Listener {
|
func StartTestSlowFileServer(delay time.Duration) net.Listener {
|
||||||
return startTestServer(func(sl *shutdownListener) http.Handler {
|
return startTestServer(func() http.Handler {
|
||||||
return &SlowFileServer{
|
return &SlowFileServer{
|
||||||
delay: delay,
|
delay: delay,
|
||||||
handler: http.FileServer(http.Dir(Dir)),
|
handler: http.FileServer(http.Dir(Dir)),
|
||||||
@@ -62,7 +61,7 @@ func StartTestSlowFileServer(delay time.Duration) net.Listener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func StartTestCustomServer() net.Listener {
|
func StartTestCustomServer() net.Listener {
|
||||||
return startTestServer(func(sl *shutdownListener) http.Handler {
|
return startTestServer(func() http.Handler {
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
mux.HandleFunc("/"+BuildName, func(writer http.ResponseWriter, request *http.Request) {
|
mux.HandleFunc("/"+BuildName, func(writer http.ResponseWriter, request *http.Request) {
|
||||||
file, err := os.Open(BuildFile)
|
file, err := os.Open(BuildFile)
|
||||||
@@ -89,7 +88,7 @@ func StartTestCustomServer() net.Listener {
|
|||||||
|
|
||||||
func StartTestRetryServer() net.Listener {
|
func StartTestRetryServer() net.Listener {
|
||||||
counter := 0
|
counter := 0
|
||||||
return startTestServer(func(sl *shutdownListener) http.Handler {
|
return startTestServer(func() http.Handler {
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
mux.HandleFunc("/"+BuildName, func(writer http.ResponseWriter, request *http.Request) {
|
mux.HandleFunc("/"+BuildName, func(writer http.ResponseWriter, request *http.Request) {
|
||||||
counter++
|
counter++
|
||||||
@@ -109,7 +108,7 @@ func StartTestRetryServer() net.Listener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func StartTestPostServer() net.Listener {
|
func StartTestPostServer() net.Listener {
|
||||||
return startTestServer(func(sl *shutdownListener) http.Handler {
|
return startTestServer(func() http.Handler {
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
mux.HandleFunc("/"+BuildName, func(writer http.ResponseWriter, request *http.Request) {
|
mux.HandleFunc("/"+BuildName, func(writer http.ResponseWriter, request *http.Request) {
|
||||||
if request.Method == "POST" && request.Header.Get("Authorization") != "" {
|
if request.Method == "POST" && request.Header.Get("Authorization") != "" {
|
||||||
@@ -133,7 +132,7 @@ func StartTestPostServer() net.Listener {
|
|||||||
|
|
||||||
func StartTestErrorServer() net.Listener {
|
func StartTestErrorServer() net.Listener {
|
||||||
counter := 0
|
counter := 0
|
||||||
return startTestServer(func(sl *shutdownListener) http.Handler {
|
return startTestServer(func() http.Handler {
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
mux.HandleFunc("/"+BuildName, func(writer http.ResponseWriter, request *http.Request) {
|
mux.HandleFunc("/"+BuildName, func(writer http.ResponseWriter, request *http.Request) {
|
||||||
counter++
|
counter++
|
||||||
@@ -150,7 +149,7 @@ func StartTestErrorServer() net.Listener {
|
|||||||
func StartTestLimitServer(maxConnections int32, delay int64) net.Listener {
|
func StartTestLimitServer(maxConnections int32, delay int64) net.Listener {
|
||||||
var connections atomic.Int32
|
var connections atomic.Int32
|
||||||
|
|
||||||
return startTestServer(func(sl *shutdownListener) http.Handler {
|
return startTestServer(func() http.Handler {
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
mux.HandleFunc("/"+BuildName, func(writer http.ResponseWriter, request *http.Request) {
|
mux.HandleFunc("/"+BuildName, func(writer http.ResponseWriter, request *http.Request) {
|
||||||
defer func() {
|
defer func() {
|
||||||
@@ -166,14 +165,12 @@ func StartTestLimitServer(maxConnections int32, delay int64) net.Listener {
|
|||||||
if r == "" {
|
if r == "" {
|
||||||
writer.Header().Set("Content-Length", fmt.Sprintf("%d", BuildSize))
|
writer.Header().Set("Content-Length", fmt.Sprintf("%d", BuildSize))
|
||||||
writer.WriteHeader(200)
|
writer.WriteHeader(200)
|
||||||
(writer.(http.Flusher)).Flush()
|
|
||||||
|
|
||||||
file, err := os.Open(BuildFile)
|
file, err := os.Open(BuildFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
slowCopy(sl, writer, file, delay)
|
slowCopy(writer, file, delay)
|
||||||
} else {
|
} else {
|
||||||
// split range
|
// split range
|
||||||
s := strings.Split(r, "=")
|
s := strings.Split(r, "=")
|
||||||
@@ -207,8 +204,6 @@ func StartTestLimitServer(maxConnections int32, delay int64) net.Listener {
|
|||||||
writer.Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", start, end, BuildSize))
|
writer.Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", start, end, BuildSize))
|
||||||
writer.Header().Set("Accept-Ranges", "bytes")
|
writer.Header().Set("Accept-Ranges", "bytes")
|
||||||
writer.WriteHeader(206)
|
writer.WriteHeader(206)
|
||||||
(writer.(http.Flusher)).Flush()
|
|
||||||
|
|
||||||
file, err := os.Open(BuildFile)
|
file, err := os.Open(BuildFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writer.WriteHeader(500)
|
writer.WriteHeader(500)
|
||||||
@@ -216,7 +211,7 @@ func StartTestLimitServer(maxConnections int32, delay int64) net.Listener {
|
|||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
file.Seek(start, 0)
|
file.Seek(start, 0)
|
||||||
slowCopyN(sl, writer, file, end-start+1, delay)
|
slowCopyN(writer, file, end-start+1, delay)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return mux
|
return mux
|
||||||
@@ -224,12 +219,9 @@ func StartTestLimitServer(maxConnections int32, delay int64) net.Listener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// slowCopyN copies n bytes from src to dst, speed limit is bytes per second
|
// slowCopyN copies n bytes from src to dst, speed limit is bytes per second
|
||||||
func slowCopy(sl *shutdownListener, dst io.Writer, src io.Reader, delay int64) (written int64, err error) {
|
func slowCopy(dst io.Writer, src io.Reader, delay int64) (written int64, err error) {
|
||||||
buf := make([]byte, 32*1024)
|
buf := make([]byte, 32*1024)
|
||||||
for {
|
for {
|
||||||
if sl.isShutdown {
|
|
||||||
return 0, errors.New("server shutdown")
|
|
||||||
}
|
|
||||||
nr, er := src.Read(buf)
|
nr, er := src.Read(buf)
|
||||||
if nr > 0 {
|
if nr > 0 {
|
||||||
nw, ew := dst.Write(buf[0:nr])
|
nw, ew := dst.Write(buf[0:nr])
|
||||||
@@ -258,8 +250,8 @@ func slowCopy(sl *shutdownListener, dst io.Writer, src io.Reader, delay int64) (
|
|||||||
return written, err
|
return written, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func slowCopyN(sl *shutdownListener, dst io.Writer, src io.Reader, n int64, delay int64) (written int64, err error) {
|
func slowCopyN(dst io.Writer, src io.Reader, n int64, delay int64) (written int64, err error) {
|
||||||
written, err = slowCopy(sl, dst, io.LimitReader(src, n), delay)
|
written, err = slowCopy(dst, io.LimitReader(src, n), delay)
|
||||||
if written == n {
|
if written == n {
|
||||||
return n, nil
|
return n, nil
|
||||||
}
|
}
|
||||||
@@ -270,7 +262,7 @@ func slowCopyN(sl *shutdownListener, dst io.Writer, src io.Reader, n int64, dela
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func startTestServer(serverHandle func(sl *shutdownListener) http.Handler) net.Listener {
|
func startTestServer(serverHandle func() http.Handler) net.Listener {
|
||||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@@ -280,7 +272,7 @@ func startTestServer(serverHandle func(sl *shutdownListener) http.Handler) net.L
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
// Write random data
|
// 随机生成一个文件
|
||||||
l := int64(8192)
|
l := int64(8192)
|
||||||
buf := make([]byte, l)
|
buf := make([]byte, l)
|
||||||
size := int64(0)
|
size := int64(0)
|
||||||
@@ -297,24 +289,21 @@ func startTestServer(serverHandle func(sl *shutdownListener) http.Handler) net.L
|
|||||||
size += l
|
size += l
|
||||||
}
|
}
|
||||||
server := &http.Server{}
|
server := &http.Server{}
|
||||||
sl := &shutdownListener{
|
server.Handler = serverHandle()
|
||||||
|
go server.Serve(listener)
|
||||||
|
|
||||||
|
return &shutdownListener{
|
||||||
server: server,
|
server: server,
|
||||||
Listener: listener,
|
Listener: listener,
|
||||||
}
|
}
|
||||||
server.Handler = serverHandle(sl)
|
|
||||||
go server.Serve(listener)
|
|
||||||
|
|
||||||
return sl
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type shutdownListener struct {
|
type shutdownListener struct {
|
||||||
server *http.Server
|
server *http.Server
|
||||||
isShutdown bool
|
|
||||||
net.Listener
|
net.Listener
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *shutdownListener) Close() error {
|
func (c *shutdownListener) Close() error {
|
||||||
c.isShutdown = true
|
|
||||||
closeErr := c.server.Shutdown(context.Background())
|
closeErr := c.server.Shutdown(context.Background())
|
||||||
if err := ifExistAndRemove(BuildFile); err != nil {
|
if err := ifExistAndRemove(BuildFile); err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ const (
|
|||||||
HttpCodeOK = 200
|
HttpCodeOK = 200
|
||||||
HttpCodePartialContent = 206
|
HttpCodePartialContent = 206
|
||||||
|
|
||||||
HttpHeaderHost = "Host"
|
|
||||||
HttpHeaderRange = "Range"
|
HttpHeaderRange = "Range"
|
||||||
HttpHeaderAcceptRanges = "Accept-Ranges"
|
HttpHeaderAcceptRanges = "Accept-Ranges"
|
||||||
HttpHeaderContentLength = "Content-Length"
|
HttpHeaderContentLength = "Content-Length"
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package base
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
var (
|
||||||
|
NotFound = errors.New("not found")
|
||||||
|
BadParams = errors.New("bad params")
|
||||||
|
)
|
||||||
+8
-73
@@ -7,7 +7,6 @@ import (
|
|||||||
"golang.org/x/exp/slices"
|
"golang.org/x/exp/slices"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -17,10 +16,6 @@ type Request struct {
|
|||||||
Extra any `json:"extra"`
|
Extra any `json:"extra"`
|
||||||
// Labels is used to mark the download task
|
// Labels is used to mark the download task
|
||||||
Labels map[string]string `json:"labels"`
|
Labels map[string]string `json:"labels"`
|
||||||
// Proxy is special proxy config for request
|
|
||||||
Proxy *RequestProxy `json:"proxy"`
|
|
||||||
// SkipVerifyCert is the flag that skip verify cert
|
|
||||||
SkipVerifyCert bool `json:"skipVerifyCert"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Request) Validate() error {
|
func (r *Request) Validate() error {
|
||||||
@@ -30,37 +25,6 @@ func (r *Request) Validate() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type RequestProxyMode string
|
|
||||||
|
|
||||||
const (
|
|
||||||
// RequestProxyModeFollow follow setting proxy
|
|
||||||
RequestProxyModeFollow RequestProxyMode = "follow"
|
|
||||||
// RequestProxyModeNone not use proxy
|
|
||||||
RequestProxyModeNone RequestProxyMode = "none"
|
|
||||||
// RequestProxyModeCustom custom proxy
|
|
||||||
RequestProxyModeCustom RequestProxyMode = "custom"
|
|
||||||
)
|
|
||||||
|
|
||||||
type RequestProxy struct {
|
|
||||||
Mode RequestProxyMode `json:"mode"`
|
|
||||||
Scheme string `json:"scheme"`
|
|
||||||
Host string `json:"host"`
|
|
||||||
Usr string `json:"usr"`
|
|
||||||
Pwd string `json:"pwd"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *RequestProxy) ToHandler() func(r *http.Request) (*url.URL, error) {
|
|
||||||
if p == nil || p.Mode != RequestProxyModeCustom {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if p.Scheme == "" || p.Host == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return http.ProxyURL(util.BuildProxyUrl(p.Scheme, p.Host, p.Usr, p.Pwd))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resource download resource
|
// Resource download resource
|
||||||
type Resource struct {
|
type Resource struct {
|
||||||
// if name is not empty, the resource is a folder and the name is the folder name
|
// if name is not empty, the resource is a folder and the name is the folder name
|
||||||
@@ -130,7 +94,12 @@ func (o *Options) InitSelectFiles(fileSize int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (o *Options) Clone() *Options {
|
func (o *Options) Clone() *Options {
|
||||||
return util.DeepClone(o)
|
return &Options{
|
||||||
|
Name: o.Name,
|
||||||
|
Path: o.Path,
|
||||||
|
SelectFiles: o.SelectFiles,
|
||||||
|
Extra: o.Extra,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseReqExtra[E any](req *Request) error {
|
func ParseReqExtra[E any](req *Request) error {
|
||||||
@@ -178,37 +147,12 @@ func (cfg *DownloaderStoreConfig) Init() *DownloaderStoreConfig {
|
|||||||
if cfg.MaxRunning == 0 {
|
if cfg.MaxRunning == 0 {
|
||||||
cfg.MaxRunning = 5
|
cfg.MaxRunning = 5
|
||||||
}
|
}
|
||||||
if cfg.ProtocolConfig == nil {
|
|
||||||
cfg.ProtocolConfig = make(map[string]any)
|
|
||||||
}
|
|
||||||
if cfg.Proxy == nil {
|
if cfg.Proxy == nil {
|
||||||
cfg.Proxy = &DownloaderProxyConfig{}
|
cfg.Proxy = &DownloaderProxyConfig{}
|
||||||
}
|
}
|
||||||
return cfg
|
return cfg
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *DownloaderStoreConfig) Merge(beforeCfg *DownloaderStoreConfig) *DownloaderStoreConfig {
|
|
||||||
if beforeCfg == nil {
|
|
||||||
return cfg
|
|
||||||
}
|
|
||||||
if cfg.DownloadDir == "" {
|
|
||||||
cfg.DownloadDir = beforeCfg.DownloadDir
|
|
||||||
}
|
|
||||||
if cfg.MaxRunning == 0 {
|
|
||||||
cfg.MaxRunning = beforeCfg.MaxRunning
|
|
||||||
}
|
|
||||||
if cfg.ProtocolConfig == nil {
|
|
||||||
cfg.ProtocolConfig = beforeCfg.ProtocolConfig
|
|
||||||
}
|
|
||||||
if cfg.Extra == nil {
|
|
||||||
cfg.Extra = beforeCfg.Extra
|
|
||||||
}
|
|
||||||
if cfg.Proxy == nil {
|
|
||||||
cfg.Proxy = beforeCfg.Proxy
|
|
||||||
}
|
|
||||||
return cfg
|
|
||||||
}
|
|
||||||
|
|
||||||
type DownloaderProxyConfig struct {
|
type DownloaderProxyConfig struct {
|
||||||
Enable bool `json:"enable"`
|
Enable bool `json:"enable"`
|
||||||
// System is the flag that use system proxy
|
// System is the flag that use system proxy
|
||||||
@@ -224,7 +168,7 @@ func (cfg *DownloaderProxyConfig) ToHandler() func(r *http.Request) (*url.URL, e
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if cfg.System {
|
if cfg.System {
|
||||||
safeProxyReloadConf()
|
ieproxy.ReloadConf()
|
||||||
return ieproxy.GetProxyFunc()
|
return ieproxy.GetProxyFunc()
|
||||||
}
|
}
|
||||||
if cfg.Scheme == "" || cfg.Host == "" {
|
if cfg.Scheme == "" || cfg.Host == "" {
|
||||||
@@ -239,7 +183,7 @@ func (cfg *DownloaderProxyConfig) ToUrl() *url.URL {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if cfg.System {
|
if cfg.System {
|
||||||
safeProxyReloadConf()
|
ieproxy.ReloadConf()
|
||||||
static := ieproxy.GetConf().Static
|
static := ieproxy.GetConf().Static
|
||||||
if static.Active && len(static.Protocols) > 0 {
|
if static.Active && len(static.Protocols) > 0 {
|
||||||
// If only one protocol, use it
|
// If only one protocol, use it
|
||||||
@@ -265,15 +209,6 @@ func (cfg *DownloaderProxyConfig) ToUrl() *url.URL {
|
|||||||
return util.BuildProxyUrl(cfg.Scheme, cfg.Host, cfg.Usr, cfg.Pwd)
|
return util.BuildProxyUrl(cfg.Scheme, cfg.Host, cfg.Usr, cfg.Pwd)
|
||||||
}
|
}
|
||||||
|
|
||||||
var prcLock sync.Mutex
|
|
||||||
|
|
||||||
func safeProxyReloadConf() {
|
|
||||||
prcLock.Lock()
|
|
||||||
defer prcLock.Unlock()
|
|
||||||
|
|
||||||
ieproxy.ReloadConf()
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseUrlSafe(rawUrl string) *url.URL {
|
func parseUrlSafe(rawUrl string) *url.URL {
|
||||||
u, err := url.Parse(rawUrl)
|
u, err := url.Parse(rawUrl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -1,264 +0,0 @@
|
|||||||
package base
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestDownloaderStoreConfig_Init(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
fields *DownloaderStoreConfig
|
|
||||||
want *DownloaderStoreConfig
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
"Init",
|
|
||||||
&DownloaderStoreConfig{},
|
|
||||||
&DownloaderStoreConfig{
|
|
||||||
MaxRunning: 5,
|
|
||||||
ProtocolConfig: map[string]any{},
|
|
||||||
Proxy: &DownloaderProxyConfig{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Init MaxRunning",
|
|
||||||
&DownloaderStoreConfig{
|
|
||||||
MaxRunning: 10,
|
|
||||||
},
|
|
||||||
&DownloaderStoreConfig{
|
|
||||||
MaxRunning: 10,
|
|
||||||
ProtocolConfig: map[string]any{},
|
|
||||||
Proxy: &DownloaderProxyConfig{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Init ProtocolConfig",
|
|
||||||
&DownloaderStoreConfig{
|
|
||||||
ProtocolConfig: map[string]any{
|
|
||||||
"key": "value",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
&DownloaderStoreConfig{
|
|
||||||
MaxRunning: 5,
|
|
||||||
ProtocolConfig: map[string]any{
|
|
||||||
"key": "value",
|
|
||||||
},
|
|
||||||
Proxy: &DownloaderProxyConfig{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Init Proxy",
|
|
||||||
&DownloaderStoreConfig{
|
|
||||||
Proxy: &DownloaderProxyConfig{
|
|
||||||
Enable: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
&DownloaderStoreConfig{
|
|
||||||
MaxRunning: 5,
|
|
||||||
ProtocolConfig: map[string]any{},
|
|
||||||
Proxy: &DownloaderProxyConfig{
|
|
||||||
Enable: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
cfg := &DownloaderStoreConfig{
|
|
||||||
FirstLoad: tt.fields.FirstLoad,
|
|
||||||
DownloadDir: tt.fields.DownloadDir,
|
|
||||||
MaxRunning: tt.fields.MaxRunning,
|
|
||||||
ProtocolConfig: tt.fields.ProtocolConfig,
|
|
||||||
Extra: tt.fields.Extra,
|
|
||||||
Proxy: tt.fields.Proxy,
|
|
||||||
}
|
|
||||||
if got := cfg.Init(); !reflect.DeepEqual(got, tt.want) {
|
|
||||||
t.Errorf("Init() = %v, want %v", got, tt.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDownloaderStoreConfig_Merge(t *testing.T) {
|
|
||||||
type args struct {
|
|
||||||
beforeCfg *DownloaderStoreConfig
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
fields *DownloaderStoreConfig
|
|
||||||
args args
|
|
||||||
want *DownloaderStoreConfig
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
"Merge Nil",
|
|
||||||
&DownloaderStoreConfig{},
|
|
||||||
args{
|
|
||||||
beforeCfg: nil,
|
|
||||||
},
|
|
||||||
&DownloaderStoreConfig{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Merge DownloadDir No Override",
|
|
||||||
&DownloaderStoreConfig{
|
|
||||||
DownloadDir: "before",
|
|
||||||
},
|
|
||||||
args{
|
|
||||||
beforeCfg: &DownloaderStoreConfig{
|
|
||||||
DownloadDir: "after",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
&DownloaderStoreConfig{
|
|
||||||
DownloadDir: "before",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Merge DownloadDir Override",
|
|
||||||
&DownloaderStoreConfig{},
|
|
||||||
args{
|
|
||||||
beforeCfg: &DownloaderStoreConfig{
|
|
||||||
DownloadDir: "after",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
&DownloaderStoreConfig{
|
|
||||||
DownloadDir: "after",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Merge MaxRunning No Override",
|
|
||||||
&DownloaderStoreConfig{
|
|
||||||
MaxRunning: 1,
|
|
||||||
},
|
|
||||||
args{
|
|
||||||
beforeCfg: &DownloaderStoreConfig{
|
|
||||||
MaxRunning: 10,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
&DownloaderStoreConfig{
|
|
||||||
MaxRunning: 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Merge MaxRunning Override",
|
|
||||||
&DownloaderStoreConfig{},
|
|
||||||
args{
|
|
||||||
beforeCfg: &DownloaderStoreConfig{
|
|
||||||
MaxRunning: 10,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
&DownloaderStoreConfig{
|
|
||||||
MaxRunning: 10,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Merge ProtocolConfig No Override",
|
|
||||||
&DownloaderStoreConfig{
|
|
||||||
ProtocolConfig: map[string]any{},
|
|
||||||
},
|
|
||||||
args{
|
|
||||||
beforeCfg: &DownloaderStoreConfig{
|
|
||||||
ProtocolConfig: map[string]any{
|
|
||||||
"key": "after",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
&DownloaderStoreConfig{
|
|
||||||
ProtocolConfig: map[string]any{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Merge ProtocolConfig Override",
|
|
||||||
&DownloaderStoreConfig{},
|
|
||||||
args{
|
|
||||||
beforeCfg: &DownloaderStoreConfig{
|
|
||||||
ProtocolConfig: map[string]any{
|
|
||||||
"key": "after",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
&DownloaderStoreConfig{
|
|
||||||
ProtocolConfig: map[string]any{
|
|
||||||
"key": "after",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Merge Extra No Override",
|
|
||||||
&DownloaderStoreConfig{
|
|
||||||
Extra: map[string]any{},
|
|
||||||
},
|
|
||||||
args{
|
|
||||||
beforeCfg: &DownloaderStoreConfig{
|
|
||||||
Extra: map[string]any{
|
|
||||||
"key": "after",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
&DownloaderStoreConfig{
|
|
||||||
Extra: map[string]any{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Merge Extra Override",
|
|
||||||
&DownloaderStoreConfig{},
|
|
||||||
args{
|
|
||||||
beforeCfg: &DownloaderStoreConfig{
|
|
||||||
Extra: map[string]any{
|
|
||||||
"key": "after",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
&DownloaderStoreConfig{
|
|
||||||
Extra: map[string]any{
|
|
||||||
"key": "after",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Merge Proxy No Override",
|
|
||||||
&DownloaderStoreConfig{
|
|
||||||
Proxy: &DownloaderProxyConfig{},
|
|
||||||
},
|
|
||||||
args{
|
|
||||||
beforeCfg: &DownloaderStoreConfig{
|
|
||||||
Proxy: &DownloaderProxyConfig{
|
|
||||||
Scheme: "http",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
&DownloaderStoreConfig{
|
|
||||||
Proxy: &DownloaderProxyConfig{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Merge Proxy Override",
|
|
||||||
&DownloaderStoreConfig{},
|
|
||||||
args{
|
|
||||||
beforeCfg: &DownloaderStoreConfig{
|
|
||||||
Proxy: &DownloaderProxyConfig{
|
|
||||||
Scheme: "http",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
&DownloaderStoreConfig{
|
|
||||||
Proxy: &DownloaderProxyConfig{
|
|
||||||
Scheme: "http",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
cfg := &DownloaderStoreConfig{
|
|
||||||
FirstLoad: tt.fields.FirstLoad,
|
|
||||||
DownloadDir: tt.fields.DownloadDir,
|
|
||||||
MaxRunning: tt.fields.MaxRunning,
|
|
||||||
ProtocolConfig: tt.fields.ProtocolConfig,
|
|
||||||
Extra: tt.fields.Extra,
|
|
||||||
Proxy: tt.fields.Proxy,
|
|
||||||
}
|
|
||||||
if got := cfg.Merge(tt.args.beforeCfg); !reflect.DeepEqual(got, tt.want) {
|
|
||||||
t.Errorf("Merge() = %v, want %v", got, tt.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+204
-423
@@ -11,12 +11,9 @@ import (
|
|||||||
gonanoid "github.com/matoous/go-nanoid/v2"
|
gonanoid "github.com/matoous/go-nanoid/v2"
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
"github.com/rs/zerolog/pkgerrors"
|
"github.com/rs/zerolog/pkgerrors"
|
||||||
"math"
|
"github.com/virtuald/go-paniclog"
|
||||||
gohttp "net/http"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime/debug"
|
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -45,29 +42,25 @@ var (
|
|||||||
type Listener func(event *Event)
|
type Listener func(event *Event)
|
||||||
|
|
||||||
type Progress struct {
|
type Progress struct {
|
||||||
// Total download time(ns)
|
// 下载耗时(纳秒)
|
||||||
Used int64 `json:"used"`
|
Used int64 `json:"used"`
|
||||||
// Download speed(bytes/s)
|
// 每秒下载字节数
|
||||||
Speed int64 `json:"speed"`
|
Speed int64 `json:"speed"`
|
||||||
// Downloaded size(bytes)
|
// 已下载的字节数
|
||||||
Downloaded int64 `json:"downloaded"`
|
Downloaded int64 `json:"downloaded"`
|
||||||
// Uploaded speed(bytes/s)
|
|
||||||
UploadSpeed int64 `json:"uploadSpeed"`
|
|
||||||
// Uploaded size(bytes)
|
|
||||||
Uploaded int64 `json:"uploaded"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Downloader struct {
|
type Downloader struct {
|
||||||
Logger *logger.Logger
|
Logger *logger.Logger
|
||||||
ExtensionLogger *logger.Logger
|
ExtensionLogger *logger.Logger
|
||||||
|
|
||||||
cfg *DownloaderConfig
|
cfg *DownloaderConfig
|
||||||
fetcherCache map[string]fetcher.Fetcher
|
fetcherBuilders map[string]fetcher.FetcherBuilder
|
||||||
storage Storage
|
fetcherCache map[string]fetcher.Fetcher
|
||||||
tasks []*Task
|
storage Storage
|
||||||
waitTasks []*Task
|
tasks []*Task
|
||||||
watchedTasks sync.Map
|
waitTasks []*Task
|
||||||
listener Listener
|
listener Listener
|
||||||
|
|
||||||
lock *sync.Mutex
|
lock *sync.Mutex
|
||||||
fetcherMapLock *sync.RWMutex
|
fetcherMapLock *sync.RWMutex
|
||||||
@@ -84,10 +77,11 @@ func NewDownloader(cfg *DownloaderConfig) *Downloader {
|
|||||||
cfg.Init()
|
cfg.Init()
|
||||||
|
|
||||||
d := &Downloader{
|
d := &Downloader{
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
fetcherCache: make(map[string]fetcher.Fetcher),
|
fetcherBuilders: make(map[string]fetcher.FetcherBuilder),
|
||||||
waitTasks: make([]*Task, 0),
|
fetcherCache: make(map[string]fetcher.Fetcher),
|
||||||
storage: cfg.Storage,
|
waitTasks: make([]*Task, 0),
|
||||||
|
storage: cfg.Storage,
|
||||||
|
|
||||||
lock: &sync.Mutex{},
|
lock: &sync.Mutex{},
|
||||||
fetcherMapLock: &sync.RWMutex{},
|
fetcherMapLock: &sync.RWMutex{},
|
||||||
@@ -95,6 +89,11 @@ func NewDownloader(cfg *DownloaderConfig) *Downloader {
|
|||||||
|
|
||||||
extensions: make([]*Extension, 0),
|
extensions: make([]*Extension, 0),
|
||||||
}
|
}
|
||||||
|
for _, f := range cfg.FetchBuilders {
|
||||||
|
for _, p := range f.Schemes() {
|
||||||
|
d.fetcherBuilders[strings.ToUpper(p)] = f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
zerolog.ErrorStackMarshaler = pkgerrors.MarshalStack
|
zerolog.ErrorStackMarshaler = pkgerrors.MarshalStack
|
||||||
d.Logger = logger.NewLogger(cfg.ProductionMode, filepath.Join(cfg.StorageDir, "logs", "core.log"))
|
d.Logger = logger.NewLogger(cfg.ProductionMode, filepath.Join(cfg.StorageDir, "logs", "core.log"))
|
||||||
@@ -125,14 +124,11 @@ func (d *Downloader) Setup() error {
|
|||||||
}
|
}
|
||||||
// init default config
|
// init default config
|
||||||
d.cfg.DownloaderStoreConfig.Init()
|
d.cfg.DownloaderStoreConfig.Init()
|
||||||
// init protocol config, if not exist, use default config
|
for _, fb := range d.fetcherBuilders {
|
||||||
for _, fm := range d.cfg.FetchManagers {
|
f := fb.Build()
|
||||||
protocol := fm.Name()
|
d.setupFetcher(f)
|
||||||
if _, ok := d.cfg.DownloaderStoreConfig.ProtocolConfig[protocol]; !ok {
|
|
||||||
d.cfg.DownloaderStoreConfig.ProtocolConfig[protocol] = fm.DefaultConfig()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
}
|
||||||
// load tasks from storage
|
// load tasks from storage
|
||||||
var tasks []*Task
|
var tasks []*Task
|
||||||
if err = d.storage.List(bucketTask, &tasks); err != nil {
|
if err = d.storage.List(bucketTask, &tasks); err != nil {
|
||||||
@@ -170,22 +166,6 @@ func (d *Downloader) Setup() error {
|
|||||||
}
|
}
|
||||||
d.extensions = extensions
|
d.extensions = extensions
|
||||||
|
|
||||||
// handle upload
|
|
||||||
go func() {
|
|
||||||
for _, task := range d.tasks {
|
|
||||||
if task.Status == base.DownloadStatusDone && task.Uploading {
|
|
||||||
if err := d.restoreTask(task); err != nil {
|
|
||||||
d.Logger.Error().Stack().Err(err).Msgf("task upload restore fetcher failed, task id: %s", task.ID)
|
|
||||||
}
|
|
||||||
if uploader, ok := task.fetcher.(fetcher.Uploader); ok {
|
|
||||||
if err := uploader.Upload(); err != nil {
|
|
||||||
d.Logger.Error().Stack().Err(err).Msgf("task upload failed, task id: %s", task.ID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// calculate download speed every tick
|
// calculate download speed every tick
|
||||||
go func() {
|
go func() {
|
||||||
for !d.closed.Load() {
|
for !d.closed.Load() {
|
||||||
@@ -194,39 +174,34 @@ func (d *Downloader) Setup() error {
|
|||||||
func() {
|
func() {
|
||||||
task.statusLock.Lock()
|
task.statusLock.Lock()
|
||||||
defer task.statusLock.Unlock()
|
defer task.statusLock.Unlock()
|
||||||
if task.Status != base.DownloadStatusRunning && !task.Uploading {
|
if task.Status != base.DownloadStatusRunning {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// check if task is deleted
|
// check if task is deleted
|
||||||
if d.GetTask(task.ID) == nil || task.fetcher == nil {
|
if d.GetTask(task.ID) == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
current := task.fetcher.Progress().TotalDownloaded()
|
current := task.fetcher.Progress().TotalDownloaded()
|
||||||
tick := float64(d.cfg.RefreshInterval) / 1000
|
task.Progress.Used = task.timer.Used()
|
||||||
downloadDataChanged := false
|
task.Progress.Speed = task.calcSpeed(current-task.Progress.Downloaded, float64(d.cfg.RefreshInterval)/1000)
|
||||||
if task.Status == base.DownloadStatusRunning {
|
task.Progress.Downloaded = current
|
||||||
downloadDataChanged = current != task.Progress.Downloaded
|
|
||||||
task.Progress.Used = task.timer.Used()
|
|
||||||
task.Progress.Speed = task.calcSpeed(task.speedArr, current-task.Progress.Downloaded, tick)
|
|
||||||
task.Progress.Downloaded = current
|
|
||||||
}
|
|
||||||
|
|
||||||
uploadDataChanged := false
|
|
||||||
if task.Uploading {
|
|
||||||
uploader := task.fetcher.(fetcher.Uploader)
|
|
||||||
currentUploaded := uploader.UploadedBytes()
|
|
||||||
uploadDataChanged = currentUploaded != task.Progress.Uploaded
|
|
||||||
task.Progress.UploadSpeed = task.calcSpeed(task.uploadSpeedArr, currentUploaded-task.Progress.Uploaded, tick)
|
|
||||||
task.Progress.Uploaded = currentUploaded
|
|
||||||
}
|
|
||||||
d.emit(EventKeyProgress, task)
|
d.emit(EventKeyProgress, task)
|
||||||
|
|
||||||
// store fetcher progress when download/upload data changed
|
// store fetcher progress
|
||||||
if !downloadDataChanged && !uploadDataChanged {
|
data, err := task.fetcherBuilder.Store(task.fetcher)
|
||||||
|
if err != nil {
|
||||||
|
d.Logger.Error().Stack().Err(err).Msgf("serialize fetcher failed: %s", task.ID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := d.storage.Put(bucketSave, task.ID, data); err != nil {
|
||||||
|
d.Logger.Error().Stack().Err(err).Msgf("persist fetcher failed: %s", task.ID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := d.storage.Put(bucketTask, task.ID, task); err != nil {
|
||||||
|
d.Logger.Error().Stack().Err(err).Msgf("persist task failed: %s", task.ID)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
d.saveTask(task)
|
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -236,56 +211,24 @@ func (d *Downloader) Setup() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Downloader) parseFm(url string) (fetcher.FetcherManager, error) {
|
func (d *Downloader) parseFb(url string) (fetcher.FetcherBuilder, error) {
|
||||||
for _, fm := range d.cfg.FetchManagers {
|
schema := util.ParseSchema(url)
|
||||||
for _, filter := range fm.Filters() {
|
fetchBuilder, ok := d.fetcherBuilders[schema]
|
||||||
if filter.Match(url) {
|
if ok {
|
||||||
return fm, nil
|
return fetchBuilder, nil
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return nil, ErrUnSupportedProtocol
|
return nil, ErrUnSupportedProtocol
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Downloader) setupFetcher(fm fetcher.FetcherManager, fetcher fetcher.Fetcher) {
|
func (d *Downloader) setupFetcher(fetcher fetcher.Fetcher) {
|
||||||
ctl := controller.NewController()
|
ctl := controller.NewController()
|
||||||
ctl.GetConfig = func(v any) {
|
ctl.GetConfig = func(v any) bool {
|
||||||
d.getProtocolConfig(fm.Name(), v)
|
return d.getProtocolConfig(fetcher.Name(), v)
|
||||||
}
|
|
||||||
// Get proxy config, task request proxy config has higher priority, then use global proxy config
|
|
||||||
ctl.GetProxy = func(requestProxy *base.RequestProxy) func(*gohttp.Request) (*url.URL, error) {
|
|
||||||
if requestProxy == nil {
|
|
||||||
return d.cfg.Proxy.ToHandler()
|
|
||||||
}
|
|
||||||
switch requestProxy.Mode {
|
|
||||||
case base.RequestProxyModeNone:
|
|
||||||
return nil
|
|
||||||
case base.RequestProxyModeCustom:
|
|
||||||
return requestProxy.ToHandler()
|
|
||||||
default:
|
|
||||||
return d.cfg.Proxy.ToHandler()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
ctl.ProxyConfig = d.cfg.Proxy
|
||||||
fetcher.Setup(ctl)
|
fetcher.Setup(ctl)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Downloader) saveTask(task *Task) error {
|
|
||||||
data, err := task.fetcherManager.Store(task.fetcher)
|
|
||||||
if err != nil {
|
|
||||||
d.Logger.Error().Stack().Err(err).Msgf("serialize fetcher failed: %s", task.ID)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := d.storage.Put(bucketSave, task.ID, data); err != nil {
|
|
||||||
d.Logger.Error().Stack().Err(err).Msgf("persist fetcher failed: %s", task.ID)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := d.storage.Put(bucketTask, task.ID, task); err != nil {
|
|
||||||
d.Logger.Error().Stack().Err(err).Msgf("persist task failed: %s", task.ID)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Downloader) Resolve(req *base.Request) (rr *ResolveResult, err error) {
|
func (d *Downloader) Resolve(req *base.Request) (rr *ResolveResult, err error) {
|
||||||
rrId, err := gonanoid.New()
|
rrId, err := gonanoid.New()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -358,18 +301,6 @@ 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.Clone())
|
|
||||||
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]
|
||||||
@@ -385,28 +316,56 @@ func (d *Downloader) Create(rrId string, opts *base.Options) (taskId string, err
|
|||||||
return d.doCreate(fetcher, opts)
|
return d.doCreate(fetcher, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Downloader) Pause(filter *TaskFilter) (err error) {
|
func (d *Downloader) Pause(id string) (err error) {
|
||||||
if filter == nil || filter.IsEmpty() {
|
task := d.GetTask(id)
|
||||||
return d.pauseAll()
|
if task == nil {
|
||||||
}
|
|
||||||
|
|
||||||
filter.NotStatuses = []base.Status{base.DownloadStatusPause, base.DownloadStatusError, base.DownloadStatusDone}
|
|
||||||
pauseTasks := d.GetTasksByFilter(filter)
|
|
||||||
if len(pauseTasks) == 0 {
|
|
||||||
return ErrTaskNotFound
|
return ErrTaskNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, task := range pauseTasks {
|
func() {
|
||||||
|
task.lock.Lock()
|
||||||
|
defer task.lock.Unlock()
|
||||||
|
|
||||||
|
if task.Status == base.DownloadStatusPause {
|
||||||
|
return
|
||||||
|
}
|
||||||
if err = d.doPause(task); err != nil {
|
if err = d.doPause(task); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
d.notifyRunning()
|
||||||
d.notifyRunning()
|
}()
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Downloader) pauseAll() (err error) {
|
func (d *Downloader) Continue(id string) (err error) {
|
||||||
|
task := d.GetTask(id)
|
||||||
|
if task == nil {
|
||||||
|
return ErrTaskNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
func() {
|
||||||
|
d.lock.Lock()
|
||||||
|
defer d.lock.Unlock()
|
||||||
|
|
||||||
|
remainRunningCount := d.remainRunningCount()
|
||||||
|
if remainRunningCount == 0 {
|
||||||
|
for _, t := range d.tasks {
|
||||||
|
if t.Status == base.DownloadStatusRunning {
|
||||||
|
if err = d.doPause(t); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.Status = base.DownloadStatusWait
|
||||||
|
d.waitTasks = append(d.waitTasks, t)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return d.doStart(task)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Downloader) PauseAll() (err error) {
|
||||||
func() {
|
func() {
|
||||||
d.lock.Lock()
|
d.lock.Lock()
|
||||||
defer d.lock.Unlock()
|
defer d.lock.Unlock()
|
||||||
@@ -424,65 +383,7 @@ func (d *Downloader) pauseAll() (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Continue specific tasks, if continue tasks will exceed maxRunning, it needs pause some running tasks before that
|
func (d *Downloader) ContinueAll() (err error) {
|
||||||
func (d *Downloader) Continue(filter *TaskFilter) (err error) {
|
|
||||||
if filter == nil || filter.IsEmpty() {
|
|
||||||
return d.continueAll()
|
|
||||||
}
|
|
||||||
|
|
||||||
filter.NotStatuses = []base.Status{base.DownloadStatusRunning, base.DownloadStatusDone}
|
|
||||||
continueTasks := d.GetTasksByFilter(filter)
|
|
||||||
if len(continueTasks) == 0 {
|
|
||||||
return ErrTaskNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
realContinueTasks := make([]*Task, 0)
|
|
||||||
func() {
|
|
||||||
d.lock.Lock()
|
|
||||||
defer d.lock.Unlock()
|
|
||||||
|
|
||||||
continueCount := len(continueTasks)
|
|
||||||
remainRunningCount := d.remainRunningCount()
|
|
||||||
needRunningCount := int(math.Min(float64(d.cfg.MaxRunning), float64(continueCount)))
|
|
||||||
needPauseCount := needRunningCount - remainRunningCount
|
|
||||||
if needPauseCount > 0 {
|
|
||||||
pausedCount := 0
|
|
||||||
for _, task := range d.tasks {
|
|
||||||
if task.Status == base.DownloadStatusRunning {
|
|
||||||
if err = d.doPause(task); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
task.Status = base.DownloadStatusWait
|
|
||||||
d.waitTasks = append(d.waitTasks, task)
|
|
||||||
pausedCount++
|
|
||||||
}
|
|
||||||
if pausedCount == needPauseCount {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, task := range continueTasks {
|
|
||||||
if len(realContinueTasks) < needRunningCount {
|
|
||||||
realContinueTasks = append(realContinueTasks, task)
|
|
||||||
} else {
|
|
||||||
task.Status = base.DownloadStatusWait
|
|
||||||
d.waitTasks = append(d.waitTasks, task)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
for _, task := range realContinueTasks {
|
|
||||||
if err = d.doStart(task); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// continueAll continue all tasks but does not affect tasks already running
|
|
||||||
func (d *Downloader) continueAll() (err error) {
|
|
||||||
continuedTasks := make([]*Task, 0)
|
continuedTasks := make([]*Task, 0)
|
||||||
|
|
||||||
func() {
|
func() {
|
||||||
@@ -503,7 +404,8 @@ func (d *Downloader) continueAll() (err error) {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
for _, task := range continuedTasks {
|
for _, task := range continuedTasks {
|
||||||
if err = d.doStart(task); err != nil {
|
tt := task
|
||||||
|
if err = d.doStart(tt); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -511,35 +413,47 @@ func (d *Downloader) continueAll() (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Downloader) ContinueBatch(filter *TaskFilter) (err error) {
|
func (d *Downloader) Delete(id string, force bool) (err error) {
|
||||||
if filter == nil || filter.IsEmpty() {
|
task := d.GetTask(id)
|
||||||
return d.continueAll()
|
if task == nil {
|
||||||
|
return ErrTaskNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
continueTasks := d.GetTasksByFilter(filter)
|
func() {
|
||||||
for _, task := range continueTasks {
|
d.lock.Lock()
|
||||||
if err = d.doStart(task); err != nil {
|
defer d.lock.Unlock()
|
||||||
return
|
|
||||||
|
for i, t := range d.tasks {
|
||||||
|
if t.ID == id {
|
||||||
|
d.tasks = append(d.tasks[:i], d.tasks[i+1:]...)
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
for i, t := range d.waitTasks {
|
||||||
|
if t.ID == id {
|
||||||
|
d.waitTasks = append(d.waitTasks[:i], d.waitTasks[i+1:]...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
err = d.doDelete(task, force)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
d.notifyRunning()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Downloader) Delete(filter *TaskFilter, force bool) (err error) {
|
func (d *Downloader) DeleteByStatues(statues []base.Status, force bool) (err error) {
|
||||||
if filter == nil || filter.IsEmpty() {
|
deleteTasks := d.GetTasksByStatues(statues)
|
||||||
return d.deleteAll()
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteTasks := d.GetTasksByFilter(filter)
|
|
||||||
if len(deleteTasks) == 0 {
|
if len(deleteTasks) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteIds := make([]string, 0)
|
deleteIds := make([]string, 0)
|
||||||
deleteTasksPtr := make([]*Task, 0)
|
|
||||||
for _, task := range deleteTasks {
|
for _, task := range deleteTasks {
|
||||||
deleteIds = append(deleteIds, task.ID)
|
deleteIds = append(deleteIds, task.ID)
|
||||||
deleteTasksPtr = append(deleteTasksPtr, task)
|
|
||||||
}
|
}
|
||||||
func() {
|
func() {
|
||||||
d.lock.Lock()
|
d.lock.Lock()
|
||||||
@@ -561,7 +475,7 @@ func (d *Downloader) Delete(filter *TaskFilter, force bool) (err error) {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
for _, task := range deleteTasksPtr {
|
for _, task := range deleteTasks {
|
||||||
err = d.doDelete(task, force)
|
err = d.doDelete(task, force)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@@ -572,45 +486,20 @@ func (d *Downloader) Delete(filter *TaskFilter, force bool) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Downloader) deleteAll() (err error) {
|
|
||||||
func() {
|
|
||||||
d.lock.Lock()
|
|
||||||
defer d.lock.Unlock()
|
|
||||||
|
|
||||||
d.tasks = make([]*Task, 0)
|
|
||||||
d.waitTasks = make([]*Task, 0)
|
|
||||||
}()
|
|
||||||
|
|
||||||
for _, task := range d.tasks {
|
|
||||||
if err = d.doDelete(task, true); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Downloader) Stats(id string) (sr any, err error) {
|
func (d *Downloader) Stats(id string) (sr any, err error) {
|
||||||
task := d.GetTask(id)
|
task := d.GetTask(id)
|
||||||
if task == nil {
|
if task == nil {
|
||||||
return sr, ErrTaskNotFound
|
return sr, ErrTaskNotFound
|
||||||
}
|
}
|
||||||
if task.fetcher == nil {
|
|
||||||
err = func() error {
|
|
||||||
task.statusLock.Lock()
|
|
||||||
defer task.statusLock.Unlock()
|
|
||||||
|
|
||||||
return d.restoreFetcher(task)
|
|
||||||
}()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sr = task.fetcher.Stats()
|
sr = task.fetcher.Stats()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Downloader) doDelete(task *Task, force bool) (err error) {
|
func (d *Downloader) doDelete(task *Task, force bool) (err error) {
|
||||||
err = func() error {
|
err = func() error {
|
||||||
|
d.lock.Lock()
|
||||||
|
defer d.lock.Unlock()
|
||||||
|
|
||||||
if err := d.storage.Delete(bucketTask, task.ID); err != nil {
|
if err := d.storage.Delete(bucketTask, task.ID); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -638,32 +527,19 @@ func (d *Downloader) doDelete(task *Task, force bool) (err error) {
|
|||||||
task = nil
|
task = nil
|
||||||
return nil
|
return nil
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
d.Logger.Error().Stack().Err(err).Msgf("delete task failed, task id: %s", task.ID)
|
d.Logger.Error().Stack().Err(err).Msgf("delete task failed, task id: %s", task.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Downloader) Close() error {
|
func (d *Downloader) Close() error {
|
||||||
d.closed.Store(true)
|
d.closed.Store(true)
|
||||||
|
if err := d.PauseAll(); err != nil {
|
||||||
closeArr := []func() error{
|
return err
|
||||||
d.pauseAll,
|
|
||||||
}
|
}
|
||||||
for _, fm := range d.cfg.FetchManagers {
|
return d.storage.Close()
|
||||||
closeArr = append(closeArr, fm.Close)
|
|
||||||
}
|
|
||||||
closeArr = append(closeArr, d.storage.Close)
|
|
||||||
// Make sure all resources are released, if had error, return the last error
|
|
||||||
var lastErr error
|
|
||||||
for i, close := range closeArr {
|
|
||||||
if err := close(); err != nil {
|
|
||||||
lastErr = err
|
|
||||||
d.Logger.Error().Stack().Err(err).Msgf("downloader close failed, index: %d", i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return lastErr
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Downloader) Clear() error {
|
func (d *Downloader) Clear() error {
|
||||||
@@ -711,51 +587,17 @@ func (d *Downloader) GetTasks() []*Task {
|
|||||||
return d.tasks
|
return d.tasks
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTasksByFilter get tasks by filter, if filter is nil, return all tasks
|
func (d *Downloader) GetTasksByStatues(statues []base.Status) []*Task {
|
||||||
// return tasks and if match all tasks
|
if len(statues) == 0 {
|
||||||
func (d *Downloader) GetTasksByFilter(filter *TaskFilter) []*Task {
|
|
||||||
if filter == nil || filter.IsEmpty() {
|
|
||||||
return d.tasks
|
return d.tasks
|
||||||
}
|
}
|
||||||
|
|
||||||
idMatch := func(task *Task) bool {
|
|
||||||
if len(filter.IDs) == 0 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
for _, id := range filter.IDs {
|
|
||||||
if task.ID == id {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
statusMatch := func(task *Task) bool {
|
|
||||||
if len(filter.Statuses) == 0 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
for _, status := range filter.Statuses {
|
|
||||||
if task.Status == status {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
notStatusMatch := func(task *Task) bool {
|
|
||||||
if len(filter.NotStatuses) == 0 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
for _, status := range filter.NotStatuses {
|
|
||||||
if task.Status == status {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
tasks := make([]*Task, 0)
|
tasks := make([]*Task, 0)
|
||||||
for _, task := range d.tasks {
|
for _, task := range d.tasks {
|
||||||
if idMatch(task) && statusMatch(task) && notStatusMatch(task) {
|
for _, status := range statues {
|
||||||
tasks = append(tasks, task)
|
if task.Status == status {
|
||||||
|
tasks = append(tasks, task)
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return tasks
|
return tasks
|
||||||
@@ -787,49 +629,11 @@ func (d *Downloader) getProtocolConfig(name string, v any) bool {
|
|||||||
|
|
||||||
// wait task done
|
// wait task done
|
||||||
func (d *Downloader) watch(task *Task) {
|
func (d *Downloader) watch(task *Task) {
|
||||||
if _, loaded := d.watchedTasks.LoadOrStore(task.ID, true); loaded {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
d.watchedTasks.Delete(task.ID)
|
|
||||||
}()
|
|
||||||
|
|
||||||
// wait task upload done
|
|
||||||
if task.Uploading {
|
|
||||||
if uploader, ok := task.fetcher.(fetcher.Uploader); ok {
|
|
||||||
go func() {
|
|
||||||
err := uploader.WaitUpload()
|
|
||||||
if err != nil {
|
|
||||||
d.Logger.Warn().Err(err).Msgf("task wait upload failed, task id: %s", task.ID)
|
|
||||||
}
|
|
||||||
d.lock.Lock()
|
|
||||||
defer d.lock.Unlock()
|
|
||||||
|
|
||||||
// Check if the task is deleted
|
|
||||||
if d.GetTask(task.ID) != nil {
|
|
||||||
task.Uploading = false
|
|
||||||
d.storage.Put(bucketTask, task.ID, task.clone())
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if task.Status == base.DownloadStatusDone {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err := task.fetcher.Wait()
|
err := task.fetcher.Wait()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
d.doOnError(task, err)
|
d.doOnError(task, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// When delete a not resolved task, need check if the task resource is nil
|
|
||||||
if task.Meta.Res == nil || d.GetTask(task.ID) == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
task.Progress.Used = task.timer.Used()
|
task.Progress.Used = task.timer.Used()
|
||||||
if task.Meta.Res.Size == 0 {
|
if task.Meta.Res.Size == 0 {
|
||||||
task.Meta.Res.Size = task.fetcher.Progress().TotalDownloaded()
|
task.Meta.Res.Size = task.fetcher.Progress().TotalDownloaded()
|
||||||
@@ -846,7 +650,6 @@ func (d *Downloader) watch(task *Task) {
|
|||||||
d.emit(EventKeyDone, task)
|
d.emit(EventKeyDone, task)
|
||||||
d.emit(EventKeyFinally, task, err)
|
d.emit(EventKeyFinally, task, err)
|
||||||
d.notifyRunning()
|
d.notifyRunning()
|
||||||
d.triggerOnDone(task)
|
|
||||||
|
|
||||||
if e, ok := task.Meta.Opts.Extra.(*http.OptsExtra); ok {
|
if e, ok := task.Meta.Opts.Extra.(*http.OptsExtra); ok {
|
||||||
downloadFilePath := task.Meta.SingleFilepath()
|
downloadFilePath := task.Meta.SingleFilepath()
|
||||||
@@ -880,51 +683,46 @@ func (d *Downloader) doOnError(task *Task, err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Downloader) restoreTask(task *Task) error {
|
func (d *Downloader) restoreFetcher(task *Task) error {
|
||||||
if task.fetcher == nil {
|
if task.fetcher == nil {
|
||||||
if err := d.restoreFetcher(task); err != nil {
|
fb, err := d.parseFb(task.Meta.Req.URL)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
task.fetcherBuilder = fb
|
||||||
|
err = func() error {
|
||||||
|
v, f := fb.Restore()
|
||||||
|
if v != nil {
|
||||||
|
err := d.storage.Pop(bucketSave, task.ID, v)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
task.fetcher = f(task.Meta, v)
|
||||||
|
return nil
|
||||||
|
}()
|
||||||
|
if err != nil {
|
||||||
|
d.Logger.Error().Stack().Err(err).Msgf("deserialize fetcher failed, task id: %s", task.ID)
|
||||||
|
}
|
||||||
|
if task.fetcher == nil {
|
||||||
|
task.fetcher = fb.Build()
|
||||||
|
}
|
||||||
|
d.setupFetcher(task.fetcher)
|
||||||
|
if task.fetcher.Meta().Req == nil {
|
||||||
|
task.fetcher.Meta().Req = task.Meta.Req
|
||||||
|
}
|
||||||
|
if task.fetcher.Meta().Res == nil {
|
||||||
|
task.fetcher.Meta().Res = task.Meta.Res
|
||||||
|
}
|
||||||
|
go d.watch(task)
|
||||||
|
} else if task.Status == base.DownloadStatusError {
|
||||||
|
go d.watch(task)
|
||||||
}
|
}
|
||||||
go d.watch(task)
|
|
||||||
task.fetcher.Create(task.Meta.Opts)
|
task.fetcher.Create(task.Meta.Opts)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Downloader) restoreFetcher(task *Task) error {
|
func (d *Downloader) doCreate(fetcher fetcher.Fetcher, opts *base.Options) (taskId string, err error) {
|
||||||
var fm fetcher.FetcherManager
|
|
||||||
for _, f := range d.cfg.FetchManagers {
|
|
||||||
if f.Name() == task.Protocol {
|
|
||||||
fm = f
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if fm == nil {
|
|
||||||
return ErrUnSupportedProtocol
|
|
||||||
}
|
|
||||||
task.fetcherManager = fm
|
|
||||||
v, f := fm.Restore()
|
|
||||||
if v != nil {
|
|
||||||
err := d.storage.Pop(bucketSave, task.ID, v)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
task.fetcher = f(task.Meta, v)
|
|
||||||
if task.fetcher == nil {
|
|
||||||
task.fetcher = task.fetcherManager.Build()
|
|
||||||
}
|
|
||||||
d.setupFetcher(task.fetcherManager, task.fetcher)
|
|
||||||
if task.fetcher.Meta().Req == nil {
|
|
||||||
task.fetcher.Meta().Req = task.Meta.Req
|
|
||||||
}
|
|
||||||
if task.fetcher.Meta().Res == nil {
|
|
||||||
task.fetcher.Meta().Res = task.Meta.Res
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Downloader) doCreate(f fetcher.Fetcher, opts *base.Options) (taskId string, err error) {
|
|
||||||
if opts == nil {
|
if opts == nil {
|
||||||
opts = &base.Options{}
|
opts = &base.Options{}
|
||||||
}
|
}
|
||||||
@@ -932,7 +730,7 @@ func (d *Downloader) doCreate(f fetcher.Fetcher, opts *base.Options) (taskId str
|
|||||||
opts.SelectFiles = make([]int, 0)
|
opts.SelectFiles = make([]int, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
meta := f.Meta()
|
meta := fetcher.Meta()
|
||||||
meta.Opts = opts
|
meta.Opts = opts
|
||||||
if opts.Path == "" {
|
if opts.Path == "" {
|
||||||
storeConfig, err := d.GetConfig()
|
storeConfig, err := d.GetConfig()
|
||||||
@@ -942,34 +740,19 @@ func (d *Downloader) doCreate(f fetcher.Fetcher, opts *base.Options) (taskId str
|
|||||||
opts.Path = storeConfig.DownloadDir
|
opts.Path = storeConfig.DownloadDir
|
||||||
}
|
}
|
||||||
|
|
||||||
// if enable white download directory, check if the download directory is in the white list
|
fb, err := d.parseFb(fetcher.Meta().Req.URL)
|
||||||
if len(d.cfg.DownloadDirWhiteList) > 0 {
|
|
||||||
inWhiteList := false
|
|
||||||
for _, dir := range d.cfg.DownloadDirWhiteList {
|
|
||||||
if match, err := filepath.Match(dir, opts.Path); match && err == nil {
|
|
||||||
inWhiteList = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !inWhiteList {
|
|
||||||
return "", errors.New("download directory is not in white list")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fm, err := d.parseFm(f.Meta().Req.URL)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
task := NewTask()
|
task := NewTask()
|
||||||
task.fetcherManager = fm
|
task.fetcherBuilder = fb
|
||||||
task.fetcher = f
|
task.fetcher = fetcher
|
||||||
task.Protocol = fm.Name()
|
task.Protocol = fetcher.Name()
|
||||||
task.Meta = f.Meta()
|
task.Meta = fetcher.Meta()
|
||||||
task.Progress = &Progress{}
|
task.Progress = &Progress{}
|
||||||
_, task.Uploading = f.(fetcher.Uploader)
|
|
||||||
initTask(task)
|
initTask(task)
|
||||||
if err = f.Create(opts); err != nil {
|
if err = fetcher.Create(opts); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err = d.storage.Put(bucketTask, task.ID, task.clone()); err != nil {
|
if err = d.storage.Put(bucketTask, task.ID, task.clone()); err != nil {
|
||||||
@@ -1012,12 +795,14 @@ func (d *Downloader) doStart(task *Task) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = d.restoreTask(task)
|
err = d.restoreFetcher(task)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
d.Logger.Error().Stack().Err(err).Msgf("restore fetcher failed, task id: %s", task.ID)
|
d.Logger.Error().Stack().Err(err).Msgf("restore fetcher failed, task id: %s", task.ID)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
isCreate = task.Status == base.DownloadStatusReady
|
isCreate = task.Status == base.DownloadStatusReady
|
||||||
|
|
||||||
|
d.triggerOnStart(task)
|
||||||
task.updateStatus(base.DownloadStatusRunning)
|
task.updateStatus(base.DownloadStatusRunning)
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -1034,7 +819,6 @@ func (d *Downloader) doStart(task *Task) (err error) {
|
|||||||
task.lock.Lock()
|
task.lock.Lock()
|
||||||
defer task.lock.Unlock()
|
defer task.lock.Unlock()
|
||||||
|
|
||||||
d.triggerOnStart(task)
|
|
||||||
if task.Meta.Res == nil {
|
if task.Meta.Res == nil {
|
||||||
err := task.fetcher.Resolve(task.Meta.Req)
|
err := task.fetcher.Resolve(task.Meta.Req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1044,28 +828,26 @@ func (d *Downloader) doStart(task *Task) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if isCreate {
|
if isCreate {
|
||||||
if task.fetcherManager.AutoRename() {
|
d.checkDuplicateLock.Lock()
|
||||||
d.checkDuplicateLock.Lock()
|
defer d.checkDuplicateLock.Unlock()
|
||||||
defer d.checkDuplicateLock.Unlock()
|
task.Meta.Opts.Name = util.ReplaceInvalidFilename(task.Meta.Opts.Name)
|
||||||
task.Meta.Opts.Name = util.ReplaceInvalidFilename(task.Meta.Opts.Name)
|
// check if the download file is duplicated and rename it automatically.
|
||||||
// check if the download file is duplicated and rename it automatically.
|
if task.Meta.Res.Name != "" {
|
||||||
if task.Meta.Res.Name != "" {
|
task.Meta.Res.Name = util.ReplaceInvalidFilename(task.Meta.Res.Name)
|
||||||
task.Meta.Res.Name = util.ReplaceInvalidFilename(task.Meta.Res.Name)
|
fullDirPath := task.Meta.FolderPath()
|
||||||
fullDirPath := task.Meta.FolderPath()
|
newName, err := util.CheckDuplicateAndRename(fullDirPath)
|
||||||
newName, err := util.CheckDuplicateAndRename(fullDirPath)
|
if err != nil {
|
||||||
if err != nil {
|
return err
|
||||||
return err
|
|
||||||
}
|
|
||||||
task.Meta.Opts.Name = newName
|
|
||||||
} else {
|
|
||||||
task.Meta.Res.Files[0].Name = util.ReplaceInvalidFilename(task.Meta.Res.Files[0].Name)
|
|
||||||
fullFilePath := task.Meta.SingleFilepath()
|
|
||||||
newName, err := util.CheckDuplicateAndRename(fullFilePath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
task.Meta.Opts.Name = newName
|
|
||||||
}
|
}
|
||||||
|
task.Meta.Opts.Name = newName
|
||||||
|
} else {
|
||||||
|
task.Meta.Res.Files[0].Name = util.ReplaceInvalidFilename(task.Meta.Res.Files[0].Name)
|
||||||
|
fullFilePath := task.Meta.SingleFilepath()
|
||||||
|
newName, err := util.CheckDuplicateAndRename(fullFilePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
task.Meta.Opts.Name = newName
|
||||||
}
|
}
|
||||||
|
|
||||||
task.Meta.Res.CalcSize(task.Meta.Opts.SelectFiles)
|
task.Meta.Res.CalcSize(task.Meta.Opts.SelectFiles)
|
||||||
@@ -1073,10 +855,10 @@ func (d *Downloader) doStart(task *Task) (err error) {
|
|||||||
|
|
||||||
task.Progress.Speed = 0
|
task.Progress.Speed = 0
|
||||||
task.timer.Start()
|
task.timer.Start()
|
||||||
if err := task.fetcher.Start(); err != nil {
|
if err := d.storage.Put(bucketTask, task.ID, task.clone()); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := d.saveTask(task); err != nil {
|
if err := task.fetcher.Start(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
d.emit(EventKeyStart, task)
|
d.emit(EventKeyStart, task)
|
||||||
@@ -1142,16 +924,16 @@ func logPanic(logDir string) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
debug.SetCrashOutput(f, debug.CrashOptions{})
|
paniclog.RedirectStderr(f)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Downloader) buildFetcher(url string) (fetcher.Fetcher, error) {
|
func (d *Downloader) buildFetcher(url string) (fetcher.Fetcher, error) {
|
||||||
fm, err := d.parseFm(url)
|
fb, err := d.parseFb(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
fetcher := fm.Build()
|
fetcher := fb.Build()
|
||||||
d.setupFetcher(fm, fetcher)
|
d.setupFetcher(fetcher)
|
||||||
return fetcher, nil
|
return fetcher, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1161,7 +943,6 @@ func initTask(task *Task) {
|
|||||||
task.statusLock = &sync.Mutex{}
|
task.statusLock = &sync.Mutex{}
|
||||||
task.lock = &sync.Mutex{}
|
task.lock = &sync.Mutex{}
|
||||||
task.speedArr = make([]int64, 0)
|
task.speedArr = make([]int64, 0)
|
||||||
task.uploadSpeedArr = make([]int64, 0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var defaultDownloader = NewDownloader(nil)
|
var defaultDownloader = NewDownloader(nil)
|
||||||
|
|||||||
+17
-305
@@ -5,8 +5,6 @@ import (
|
|||||||
"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"
|
"os"
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@@ -82,136 +80,27 @@ func TestDownloader_Create(t *testing.T) {
|
|||||||
want := test.FileMd5(test.BuildFile)
|
want := test.FileMd5(test.BuildFile)
|
||||||
got := test.FileMd5(test.DownloadFile)
|
got := test.FileMd5(test.DownloadFile)
|
||||||
if want != got {
|
if want != got {
|
||||||
t.Errorf("Downloader_Create() got = %v, want %v", got, want)
|
t.Errorf("Download() got = %v, want %v", got, want)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDownloader_CreateNotInWhite(t *testing.T) {
|
|
||||||
listener := test.StartTestFileServer()
|
|
||||||
defer listener.Close()
|
|
||||||
|
|
||||||
downloader := NewDownloader(&DownloaderConfig{
|
|
||||||
DownloadDirWhiteList: []string{"./downloads"},
|
|
||||||
})
|
|
||||||
if err := downloader.Setup(); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer downloader.Clear()
|
|
||||||
req := &base.Request{
|
|
||||||
URL: "http://" + listener.Addr().String() + "/" + test.BuildName,
|
|
||||||
}
|
|
||||||
rr, err := downloader.Resolve(req)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
wg.Add(1)
|
|
||||||
downloader.Listener(func(event *Event) {
|
|
||||||
if event.Key == EventKeyDone {
|
|
||||||
wg.Done()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
_, err = downloader.Create(rr.ID, &base.Options{
|
|
||||||
Path: test.Dir,
|
|
||||||
Name: test.DownloadName,
|
|
||||||
Extra: http.OptsExtra{
|
|
||||||
Connections: 4,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if !strings.Contains(err.Error(), "white") {
|
|
||||||
t.Errorf("TestDownloader_CreateNotInWhite() got = %v, want %v", err.Error(), "not in white list")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDownloader_CreateDirectBatch(t *testing.T) {
|
|
||||||
listener := test.StartTestFileServer()
|
|
||||||
defer listener.Close()
|
|
||||||
|
|
||||||
downloader := NewDownloader(nil)
|
|
||||||
if err := downloader.Setup(); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
downloader.Delete(nil, true)
|
|
||||||
downloader.Clear()
|
|
||||||
}()
|
|
||||||
|
|
||||||
reqs := make([]*base.Request, 0)
|
|
||||||
fileNames := make([]string, 0)
|
|
||||||
for i := 0; i < 5; i++ {
|
|
||||||
req := &base.Request{
|
|
||||||
URL: "http://" + listener.Addr().String() + "/" + test.BuildName,
|
|
||||||
}
|
|
||||||
reqs = append(reqs, req)
|
|
||||||
if i == 0 {
|
|
||||||
fileNames = append(fileNames, test.DownloadName)
|
|
||||||
} else {
|
|
||||||
arr := strings.Split(test.DownloadName, ".")
|
|
||||||
fileNames = append(fileNames, arr[0]+" ("+strconv.Itoa(i)+")."+arr[1])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
wg.Add(len(reqs))
|
|
||||||
downloader.Listener(func(event *Event) {
|
|
||||||
if event.Key == EventKeyDone {
|
|
||||||
wg.Done()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
_, err := downloader.CreateDirectBatch(reqs, &base.Options{
|
|
||||||
Path: test.Dir,
|
|
||||||
Name: test.DownloadName,
|
|
||||||
Extra: http.OptsExtra{
|
|
||||||
Connections: 4,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
wg.Wait()
|
|
||||||
|
|
||||||
tasks := downloader.GetTasks()
|
|
||||||
if len(tasks) != len(reqs) {
|
|
||||||
t.Errorf("CreateDirectBatch() task got = %v, want %v", len(tasks), len(reqs))
|
|
||||||
}
|
|
||||||
|
|
||||||
nameMatchMap := make(map[string]bool)
|
|
||||||
for _, name := range fileNames {
|
|
||||||
if _, err := os.Stat(test.Dir + "/" + name); os.IsNotExist(err) {
|
|
||||||
t.Errorf("CreateDirectBatch() file not exist: %v", name)
|
|
||||||
}
|
|
||||||
for _, task := range tasks {
|
|
||||||
if name == task.Meta.Opts.Name {
|
|
||||||
nameMatchMap[task.Meta.Opts.Name] = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(nameMatchMap) != len(reqs) {
|
|
||||||
t.Errorf("CreateDirectBatch() names got = %v, want %v", len(nameMatchMap), len(reqs))
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDownloader_CreateWithProxy(t *testing.T) {
|
func TestDownloader_CreateWithProxy(t *testing.T) {
|
||||||
// No proxy
|
// No proxy
|
||||||
doTestDownloaderCreateWithProxy(t, false, nil, func(proxyCfg *base.DownloaderProxyConfig) *base.DownloaderProxyConfig {
|
doTestDownloaderCreateWithProxy(t, false, func(proxyCfg *base.DownloaderProxyConfig) *base.DownloaderProxyConfig {
|
||||||
return nil
|
return nil
|
||||||
}, nil)
|
}, nil)
|
||||||
// Disable proxy
|
// Disable proxy
|
||||||
doTestDownloaderCreateWithProxy(t, false, nil, func(proxyCfg *base.DownloaderProxyConfig) *base.DownloaderProxyConfig {
|
doTestDownloaderCreateWithProxy(t, false, func(proxyCfg *base.DownloaderProxyConfig) *base.DownloaderProxyConfig {
|
||||||
proxyCfg.Enable = false
|
proxyCfg.Enable = false
|
||||||
return proxyCfg
|
return proxyCfg
|
||||||
}, nil)
|
}, nil)
|
||||||
// Enable system proxy but not set proxy environment variable
|
// Enable system proxy but not set proxy environment variable
|
||||||
doTestDownloaderCreateWithProxy(t, false, nil, func(proxyCfg *base.DownloaderProxyConfig) *base.DownloaderProxyConfig {
|
doTestDownloaderCreateWithProxy(t, false, func(proxyCfg *base.DownloaderProxyConfig) *base.DownloaderProxyConfig {
|
||||||
proxyCfg.System = true
|
proxyCfg.System = true
|
||||||
return proxyCfg
|
return proxyCfg
|
||||||
}, nil)
|
}, nil)
|
||||||
// Enable proxy but error proxy environment variable
|
// Enable proxy but error proxy environment variable
|
||||||
doTestDownloaderCreateWithProxy(t, false, nil, func(proxyCfg *base.DownloaderProxyConfig) *base.DownloaderProxyConfig {
|
doTestDownloaderCreateWithProxy(t, false, func(proxyCfg *base.DownloaderProxyConfig) *base.DownloaderProxyConfig {
|
||||||
os.Setenv("HTTP_PROXY", "http://127.0.0.1:1234")
|
os.Setenv("HTTP_PROXY", "http://127.0.0.1:1234")
|
||||||
os.Setenv("HTTPS_PROXY", "http://127.0.0.1:1234")
|
os.Setenv("HTTPS_PROXY", "http://127.0.0.1:1234")
|
||||||
proxyCfg.System = true
|
proxyCfg.System = true
|
||||||
@@ -222,50 +111,33 @@ func TestDownloader_CreateWithProxy(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
// Enable system proxy and set proxy environment variable
|
// Enable system proxy and set proxy environment variable
|
||||||
doTestDownloaderCreateWithProxy(t, false, nil, func(proxyCfg *base.DownloaderProxyConfig) *base.DownloaderProxyConfig {
|
doTestDownloaderCreateWithProxy(t, false, func(proxyCfg *base.DownloaderProxyConfig) *base.DownloaderProxyConfig {
|
||||||
os.Setenv("HTTP_PROXY", proxyCfg.ToUrl().String())
|
os.Setenv("HTTP_PROXY", proxyCfg.ToUrl().String())
|
||||||
os.Setenv("HTTPS_PROXY", proxyCfg.ToUrl().String())
|
os.Setenv("HTTPS_PROXY", proxyCfg.ToUrl().String())
|
||||||
proxyCfg.System = true
|
proxyCfg.System = true
|
||||||
return proxyCfg
|
return proxyCfg
|
||||||
}, nil)
|
}, nil)
|
||||||
// Invalid proxy scheme
|
// Invalid proxy scheme
|
||||||
doTestDownloaderCreateWithProxy(t, false, nil, func(proxyCfg *base.DownloaderProxyConfig) *base.DownloaderProxyConfig {
|
doTestDownloaderCreateWithProxy(t, false, func(proxyCfg *base.DownloaderProxyConfig) *base.DownloaderProxyConfig {
|
||||||
proxyCfg.Scheme = ""
|
proxyCfg.Scheme = ""
|
||||||
return proxyCfg
|
return proxyCfg
|
||||||
}, nil)
|
}, nil)
|
||||||
// Invalid proxy host
|
// Invalid proxy host
|
||||||
doTestDownloaderCreateWithProxy(t, false, nil, func(proxyCfg *base.DownloaderProxyConfig) *base.DownloaderProxyConfig {
|
doTestDownloaderCreateWithProxy(t, false, func(proxyCfg *base.DownloaderProxyConfig) *base.DownloaderProxyConfig {
|
||||||
proxyCfg.Host = ""
|
proxyCfg.Host = ""
|
||||||
return proxyCfg
|
return proxyCfg
|
||||||
}, nil)
|
}, nil)
|
||||||
// Use proxy without auth
|
// Use proxy without auth
|
||||||
doTestDownloaderCreateWithProxy(t, false, nil, func(proxyCfg *base.DownloaderProxyConfig) *base.DownloaderProxyConfig {
|
doTestDownloaderCreateWithProxy(t, false, func(proxyCfg *base.DownloaderProxyConfig) *base.DownloaderProxyConfig {
|
||||||
return proxyCfg
|
return proxyCfg
|
||||||
}, nil)
|
}, nil)
|
||||||
// Use proxy with auth
|
// Use proxy with auth
|
||||||
doTestDownloaderCreateWithProxy(t, true, nil, func(proxyCfg *base.DownloaderProxyConfig) *base.DownloaderProxyConfig {
|
doTestDownloaderCreateWithProxy(t, true, func(proxyCfg *base.DownloaderProxyConfig) *base.DownloaderProxyConfig {
|
||||||
return proxyCfg
|
return proxyCfg
|
||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
// Request proxy mode follow
|
|
||||||
doTestDownloaderCreateWithProxy(t, false, func(reqProxy *base.RequestProxy) *base.RequestProxy {
|
|
||||||
reqProxy.Mode = base.RequestProxyModeFollow
|
|
||||||
return reqProxy
|
|
||||||
}, nil, nil)
|
|
||||||
|
|
||||||
// Request proxy mode none
|
|
||||||
doTestDownloaderCreateWithProxy(t, false, func(reqProxy *base.RequestProxy) *base.RequestProxy {
|
|
||||||
reqProxy.Mode = base.RequestProxyModeNone
|
|
||||||
return reqProxy
|
|
||||||
}, nil, nil)
|
|
||||||
|
|
||||||
// Request proxy mode custom
|
|
||||||
doTestDownloaderCreateWithProxy(t, false, func(reqProxy *base.RequestProxy) *base.RequestProxy {
|
|
||||||
return reqProxy
|
|
||||||
}, nil, nil)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func doTestDownloaderCreateWithProxy(t *testing.T, auth bool, buildReqProxy func(reqProxy *base.RequestProxy) *base.RequestProxy, buildProxyConfig func(proxyCfg *base.DownloaderProxyConfig) *base.DownloaderProxyConfig, errHandler func(err error)) {
|
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"
|
||||||
@@ -278,29 +150,17 @@ func doTestDownloaderCreateWithProxy(t *testing.T, auth bool, buildReqProxy func
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
defer downloader.Clear()
|
defer downloader.Clear()
|
||||||
globalProxyCfg := &base.DownloaderProxyConfig{
|
downloader.cfg.DownloaderStoreConfig.Proxy = buildProxyConfig(&base.DownloaderProxyConfig{
|
||||||
Enable: true,
|
Enable: true,
|
||||||
Scheme: "socks5",
|
Scheme: "socks5",
|
||||||
Host: proxyListener.Addr().String(),
|
Host: proxyListener.Addr().String(),
|
||||||
Usr: usr,
|
Usr: usr,
|
||||||
Pwd: pwd,
|
Pwd: pwd,
|
||||||
}
|
})
|
||||||
if buildProxyConfig != nil {
|
|
||||||
globalProxyCfg = buildProxyConfig(globalProxyCfg)
|
|
||||||
}
|
|
||||||
downloader.cfg.DownloaderStoreConfig.Proxy = globalProxyCfg
|
|
||||||
|
|
||||||
req := &base.Request{
|
req := &base.Request{
|
||||||
URL: test.ExternalDownloadUrl,
|
URL: test.ExternalDownloadUrl,
|
||||||
}
|
}
|
||||||
if buildReqProxy != nil {
|
|
||||||
req.Proxy = buildReqProxy(&base.RequestProxy{
|
|
||||||
Scheme: "socks5",
|
|
||||||
Host: proxyListener.Addr().String(),
|
|
||||||
Usr: usr,
|
|
||||||
Pwd: pwd,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
rr, err := downloader.Resolve(req)
|
rr, err := downloader.Resolve(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errHandler == nil {
|
if errHandler == nil {
|
||||||
@@ -400,7 +260,7 @@ func TestDownloader_StoreAndRestore(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
time.Sleep(time.Millisecond * 1001)
|
time.Sleep(time.Millisecond * 1001)
|
||||||
err = downloader.Pause(&TaskFilter{IDs: []string{id}})
|
err = downloader.Pause(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -424,7 +284,7 @@ func TestDownloader_StoreAndRestore(t *testing.T) {
|
|||||||
wg.Done()
|
wg.Done()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
err = downloader.Continue(&TaskFilter{IDs: []string{id}})
|
err = downloader.Continue(id)
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -447,8 +307,8 @@ func TestDownloader_Protocol_Config(t *testing.T) {
|
|||||||
|
|
||||||
var httpCfg map[string]any
|
var httpCfg map[string]any
|
||||||
exits := downloader.getProtocolConfig("http", &httpCfg)
|
exits := downloader.getProtocolConfig("http", &httpCfg)
|
||||||
if !exits {
|
if exits {
|
||||||
t.Errorf("getProtocolConfig() got = %v, want %v", exits, true)
|
t.Errorf("getProtocolConfig() got = %v, want %v", exits, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
storeCfg := &base.DownloaderStoreConfig{
|
storeCfg := &base.DownloaderStoreConfig{
|
||||||
@@ -485,151 +345,3 @@ func TestDownloader_Protocol_Config(t *testing.T) {
|
|||||||
t.Errorf("GetConfig() got = %v, want %v", test.ToJson(storeCfg), test.ToJson(newStoreCfg))
|
t.Errorf("GetConfig() got = %v, want %v", test.ToJson(storeCfg), test.ToJson(newStoreCfg))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDownloader_GetTasksByFilter(t *testing.T) {
|
|
||||||
listener := test.StartTestFileServer()
|
|
||||||
defer listener.Close()
|
|
||||||
|
|
||||||
downloader := NewDownloader(nil)
|
|
||||||
if err := downloader.Setup(); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
downloader.Delete(nil, true)
|
|
||||||
downloader.Clear()
|
|
||||||
}()
|
|
||||||
|
|
||||||
reqs := make([]*base.Request, 0)
|
|
||||||
fileNames := make([]string, 0)
|
|
||||||
for i := 0; i < 10; i++ {
|
|
||||||
req := &base.Request{
|
|
||||||
URL: "http://" + listener.Addr().String() + "/" + test.BuildName,
|
|
||||||
}
|
|
||||||
reqs = append(reqs, req)
|
|
||||||
if i == 0 {
|
|
||||||
fileNames = append(fileNames, test.DownloadName)
|
|
||||||
} else {
|
|
||||||
arr := strings.Split(test.DownloadName, ".")
|
|
||||||
fileNames = append(fileNames, arr[0]+" ("+strconv.Itoa(i)+")."+arr[1])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
wg.Add(len(reqs))
|
|
||||||
downloader.Listener(func(event *Event) {
|
|
||||||
if event.Key == EventKeyDone {
|
|
||||||
wg.Done()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
taskIds, err := downloader.CreateDirectBatch(reqs, &base.Options{
|
|
||||||
Path: test.Dir,
|
|
||||||
Name: test.DownloadName,
|
|
||||||
Extra: http.OptsExtra{
|
|
||||||
Connections: 4,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
wg.Wait()
|
|
||||||
|
|
||||||
t.Run("GetTasksByFilter nil", func(t *testing.T) {
|
|
||||||
tasks := downloader.GetTasksByFilter(nil)
|
|
||||||
if len(tasks) != len(reqs) {
|
|
||||||
t.Errorf("GetTasksByFilter nil task got = %v, want %v", len(tasks), len(reqs))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("GetTasksByFilter empty", func(t *testing.T) {
|
|
||||||
tasks := downloader.GetTasksByFilter(&TaskFilter{})
|
|
||||||
if len(tasks) != len(reqs) {
|
|
||||||
t.Errorf("GetTasksByFilter empty task got = %v, want %v", len(tasks), len(reqs))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("GetTasksByFilter ids", func(t *testing.T) {
|
|
||||||
tasks := downloader.GetTasksByFilter(&TaskFilter{
|
|
||||||
IDs: taskIds,
|
|
||||||
})
|
|
||||||
if len(tasks) != len(reqs) {
|
|
||||||
t.Errorf("GetTasksByFilter ids task got = %v, want %v", len(tasks), len(reqs))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("GetTasksByFilter match ids", func(t *testing.T) {
|
|
||||||
tasks := downloader.GetTasksByFilter(&TaskFilter{
|
|
||||||
IDs: []string{taskIds[0]},
|
|
||||||
})
|
|
||||||
if len(tasks) != 1 {
|
|
||||||
t.Errorf("GetTasksByFilter ids task got = %v, want %v", len(tasks), 1)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("GetTasksByFilter not match ids", func(t *testing.T) {
|
|
||||||
tasks := downloader.GetTasksByFilter(&TaskFilter{
|
|
||||||
IDs: []string{"xxx"},
|
|
||||||
})
|
|
||||||
if len(tasks) != 0 {
|
|
||||||
t.Errorf("GetTasksByFilter ids task got = %v, want %v", len(tasks), 0)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("GetTasksByFilter status", func(t *testing.T) {
|
|
||||||
tasks := downloader.GetTasksByFilter(&TaskFilter{
|
|
||||||
Statuses: []base.Status{base.DownloadStatusDone},
|
|
||||||
})
|
|
||||||
if len(tasks) != len(reqs) {
|
|
||||||
t.Errorf("GetTasksByFilter status task got = %v, want %v", len(tasks), len(reqs))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("GetTasksByFilter not match status", func(t *testing.T) {
|
|
||||||
tasks := downloader.GetTasksByFilter(&TaskFilter{
|
|
||||||
Statuses: []base.Status{base.DownloadStatusError},
|
|
||||||
})
|
|
||||||
if len(tasks) != 0 {
|
|
||||||
t.Errorf("GetTasksByFilter status task got = %v, want %v", len(tasks), 0)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("GetTasksByFilter match notStatus", func(t *testing.T) {
|
|
||||||
tasks := downloader.GetTasksByFilter(&TaskFilter{
|
|
||||||
NotStatuses: []base.Status{base.DownloadStatusRunning, base.DownloadStatusPause},
|
|
||||||
})
|
|
||||||
if len(tasks) != len(reqs) {
|
|
||||||
t.Errorf("GetTasksByFilter match notStatus task got = %v, want %v", len(tasks), len(reqs))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("GetTasksByFilter not match notStatus", func(t *testing.T) {
|
|
||||||
tasks := downloader.GetTasksByFilter(&TaskFilter{
|
|
||||||
NotStatuses: []base.Status{base.DownloadStatusDone},
|
|
||||||
})
|
|
||||||
if len(tasks) != 0 {
|
|
||||||
t.Errorf("GetTasksByFilter not match notStatus task got = %v, want %v", len(tasks), 0)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("GetTasksByFilter match ids and status", func(t *testing.T) {
|
|
||||||
tasks := downloader.GetTasksByFilter(&TaskFilter{
|
|
||||||
IDs: []string{taskIds[0]},
|
|
||||||
Statuses: []base.Status{base.DownloadStatusDone},
|
|
||||||
})
|
|
||||||
if len(tasks) != 1 {
|
|
||||||
t.Errorf("GetTasksByFilter match ids and status task got = %v, want %v", len(tasks), 1)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("GetTasksByFilter not match ids and status", func(t *testing.T) {
|
|
||||||
tasks := downloader.GetTasksByFilter(&TaskFilter{
|
|
||||||
IDs: []string{taskIds[0]},
|
|
||||||
Statuses: []base.Status{base.DownloadStatusError},
|
|
||||||
})
|
|
||||||
if len(tasks) != 0 {
|
|
||||||
t.Errorf("GetTasksByFilter not match ids and status task got = %v, want %v", len(tasks), 0)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -106,7 +106,7 @@ func (e *Engine) await(value any) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (e *Engine) Close() {
|
func (e *Engine) Close() {
|
||||||
e.loop.StopNoWait()
|
e.loop.Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
@@ -145,10 +145,6 @@ func NewEngine(cfg *Config) *Engine {
|
|||||||
if err := runtime.Set("global", runtime.GlobalObject()); err != nil {
|
if err := runtime.Set("global", runtime.GlobalObject()); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// polyfill window
|
|
||||||
if err := runtime.Set("window", runtime.GlobalObject()); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// polyfill window.location
|
// polyfill window.location
|
||||||
if _, err := runtime.RunString("global.location = new URL('http://localhost');"); err != nil {
|
if _, err := runtime.RunString("global.location = new URL('http://localhost');"); err != nil {
|
||||||
return
|
return
|
||||||
@@ -178,15 +174,7 @@ func resolveResult(value goja.Value) (any, error) {
|
|||||||
if err, ok := p.Result().Export().(error); ok {
|
if err, ok := p.Result().Export().(error); ok {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
stack := p.Result().String()
|
return nil, errors.New(p.Result().String())
|
||||||
result := p.Result()
|
|
||||||
if ro, ok := result.(*goja.Object); ok {
|
|
||||||
stackVal := ro.Get("stack")
|
|
||||||
if stackVal != nil && stackVal.String() != "" {
|
|
||||||
stack = stackVal.String()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, errors.New(stack)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,12 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/GopeedLab/gopeed/internal/test"
|
||||||
|
"github.com/GopeedLab/gopeed/pkg/base"
|
||||||
|
gojaerror "github.com/GopeedLab/gopeed/pkg/download/engine/inject/error"
|
||||||
|
"github.com/GopeedLab/gopeed/pkg/download/engine/inject/file"
|
||||||
|
gojautil "github.com/GopeedLab/gopeed/pkg/download/engine/util"
|
||||||
|
"github.com/dop251/goja"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -13,13 +19,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/GopeedLab/gopeed/internal/test"
|
|
||||||
"github.com/GopeedLab/gopeed/pkg/base"
|
|
||||||
gojaerror "github.com/GopeedLab/gopeed/pkg/download/engine/inject/error"
|
|
||||||
"github.com/GopeedLab/gopeed/pkg/download/engine/inject/file"
|
|
||||||
gojautil "github.com/GopeedLab/gopeed/pkg/download/engine/util"
|
|
||||||
"github.com/dop251/goja"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestPolyfill(t *testing.T) {
|
func TestPolyfill(t *testing.T) {
|
||||||
@@ -27,8 +26,6 @@ func TestPolyfill(t *testing.T) {
|
|||||||
doTestPolyfill(t, "XMLHttpRequest")
|
doTestPolyfill(t, "XMLHttpRequest")
|
||||||
doTestPolyfill(t, "Blob")
|
doTestPolyfill(t, "Blob")
|
||||||
doTestPolyfill(t, "FormData")
|
doTestPolyfill(t, "FormData")
|
||||||
doTestPolyfill(t, "TextDecoder")
|
|
||||||
doTestPolyfill(t, "TextEncoder")
|
|
||||||
doTestPolyfill(t, "fetch")
|
doTestPolyfill(t, "fetch")
|
||||||
doTestPolyfill(t, "__gopeed_create_vm")
|
doTestPolyfill(t, "__gopeed_create_vm")
|
||||||
}
|
}
|
||||||
@@ -72,44 +69,6 @@ async function testOctetStream(file){
|
|||||||
return await resp.text();
|
return await resp.text();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function testRedirect() {
|
|
||||||
const url = host + '/redirect?num=3'
|
|
||||||
return await new Promise((resolve, reject) => {
|
|
||||||
fetch(url, {
|
|
||||||
method: 'HEAD',
|
|
||||||
redirect: 'error',
|
|
||||||
}).then(()=>reject())
|
|
||||||
|
|
||||||
fetch(url, {
|
|
||||||
method: 'HEAD',
|
|
||||||
redirect: 'follow',
|
|
||||||
}).then((res) =>res.headers.has('location') && reject()).catch(() => reject())
|
|
||||||
|
|
||||||
fetch(url, {
|
|
||||||
method: 'HEAD',
|
|
||||||
redirect: 'manual',
|
|
||||||
}).then((res) => {
|
|
||||||
const location = res.headers.get('location');
|
|
||||||
location ? resolve(location) : reject()
|
|
||||||
}).catch(() => reject())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async function testResponseUrl() {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const xhr = new XMLHttpRequest();
|
|
||||||
xhr.open('GET', host+'/redirect?num=3');
|
|
||||||
xhr.onload = function(){
|
|
||||||
if (xhr.responseURL.includes('/redirect?num=0')){
|
|
||||||
resolve();
|
|
||||||
}else{
|
|
||||||
reject();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
xhr.send();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function testFormData(file){
|
async function testFormData(file){
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('name', 'test');
|
formData.append('name', 'test');
|
||||||
@@ -121,30 +80,6 @@ async function testFormData(file){
|
|||||||
return await resp.json();
|
return await resp.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
function testHeader(){
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const xhr = new XMLHttpRequest();
|
|
||||||
xhr.open('GET', host+'/header');
|
|
||||||
xhr.setRequestHeader('X-Gopeed-Test', 'test1');
|
|
||||||
xhr.setRequestHeader('x-gopeed-test', 'test2');
|
|
||||||
xhr.setRequestHeader('x-Gopeed-test', 'test3');
|
|
||||||
xhr.onload = function(){
|
|
||||||
const testHeader1 = xhr.getResponseHeader("X-Gopeed-Test");
|
|
||||||
const testHeader2 = xhr.getResponseHeader("x-gopeed-test");
|
|
||||||
const testHeader3 = xhr.getResponseHeader("x-Gopeed-test");
|
|
||||||
const expect = 'test1, test2, test3';
|
|
||||||
const all = xhr.getAllResponseHeaders();
|
|
||||||
if(testHeader1 === expect && testHeader2 === expect && testHeader3 === expect
|
|
||||||
&& all.includes('X-Gopeed-Test: '+expect)){
|
|
||||||
resolve();
|
|
||||||
}else{
|
|
||||||
reject();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
xhr.send();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function testProgress(){
|
function testProgress(){
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const xhr = new XMLHttpRequest();
|
const xhr = new XMLHttpRequest();
|
||||||
@@ -237,20 +172,6 @@ function testTimeout(){
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
t.Run("testRedirect", func(t *testing.T) {
|
|
||||||
_, err := callTestFun(engine, "testRedirect")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("testResponseUrl", func(t *testing.T) {
|
|
||||||
_, err = callTestFun(engine, "testResponseUrl")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
func() {
|
func() {
|
||||||
jsFile, goFile, md5 := buildFile(t, engine.Runtime)
|
jsFile, goFile, md5 := buildFile(t, engine.Runtime)
|
||||||
result, err = callTestFun(engine, "testFormData", jsFile)
|
result, err = callTestFun(engine, "testFormData", jsFile)
|
||||||
@@ -269,19 +190,14 @@ function testTimeout(){
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
_, err = callTestFun(engine, "testHeader")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("header test failed", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = callTestFun(engine, "testProgress")
|
_, err = callTestFun(engine, "testProgress")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("progress test failed", err)
|
t.Fatal("progress test failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = callTestFun(engine, "testAbort")
|
_, err = callTestFun(engine, "testAbort")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("abort test failed", err)
|
t.Fatal("abort test failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = callTestFun(engine, "testTimeout")
|
_, err = callTestFun(engine, "testTimeout")
|
||||||
@@ -412,15 +328,6 @@ func startServer() net.Listener {
|
|||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
w.Write([]byte("ok"))
|
w.Write([]byte("ok"))
|
||||||
})
|
})
|
||||||
mux.HandleFunc("/header", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
for k, v := range r.Header {
|
|
||||||
if strings.HasPrefix(k, "X-Gopeed") {
|
|
||||||
w.Header().Set(k, strings.Join(v, ", "))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
w.Write([]byte("ok"))
|
|
||||||
})
|
|
||||||
mux.HandleFunc("/text", func(w http.ResponseWriter, r *http.Request) {
|
mux.HandleFunc("/text", func(w http.ResponseWriter, r *http.Request) {
|
||||||
buf, _ := io.ReadAll(r.Body)
|
buf, _ := io.ReadAll(r.Body)
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
@@ -460,16 +367,6 @@ func startServer() net.Listener {
|
|||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
w.Write([]byte("ok"))
|
w.Write([]byte("ok"))
|
||||||
})
|
})
|
||||||
mux.HandleFunc("/redirect", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
num := r.URL.Query().Get("num")
|
|
||||||
n, _ := strconv.Atoi(num)
|
|
||||||
if n > 0 {
|
|
||||||
http.Redirect(w, r, fmt.Sprintf("/redirect?num=%d", n-1), http.StatusFound)
|
|
||||||
} else {
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
w.Write([]byte("ok"))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
server.Handler = mux
|
server.Handler = mux
|
||||||
go server.Serve(listener)
|
go server.Serve(listener)
|
||||||
return listener
|
return listener
|
||||||
|
|||||||
@@ -3,18 +3,16 @@ package xhr
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
|
"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/util"
|
||||||
|
"github.com/dop251/goja"
|
||||||
"io"
|
"io"
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"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/util"
|
|
||||||
"github.com/dop251/goja"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -26,12 +24,6 @@ const (
|
|||||||
eventTimeout = "timeout"
|
eventTimeout = "timeout"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
redirectError = "error"
|
|
||||||
redirectFollow = "follow"
|
|
||||||
redirectManual = "manual"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ProgressEvent struct {
|
type ProgressEvent struct {
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
LengthComputable bool `json:"lengthComputable"`
|
LengthComputable bool `json:"lengthComputable"`
|
||||||
@@ -126,26 +118,18 @@ type XMLHttpRequestUpload struct {
|
|||||||
type XMLHttpRequest struct {
|
type XMLHttpRequest struct {
|
||||||
method string
|
method string
|
||||||
url string
|
url string
|
||||||
requestHeaders http.Header
|
requestHeaders map[string]string
|
||||||
responseHeaders http.Header
|
responseHeaders map[string]string
|
||||||
aborted bool
|
aborted bool
|
||||||
proxyHandler func(r *http.Request) (*url.URL, error)
|
proxyHandler func(r *http.Request) (*url.URL, error)
|
||||||
|
|
||||||
WithCredentials bool `json:"withCredentials"`
|
Upload *XMLHttpRequestUpload `json:"upload"`
|
||||||
Upload *XMLHttpRequestUpload `json:"upload"`
|
Timeout int `json:"timeout"`
|
||||||
Timeout int `json:"timeout"`
|
ReadyState int `json:"readyState"`
|
||||||
ReadyState int `json:"readyState"`
|
Status int `json:"status"`
|
||||||
Status int `json:"status"`
|
StatusText string `json:"statusText"`
|
||||||
StatusText string `json:"statusText"`
|
Response string `json:"response"`
|
||||||
Response string `json:"response"`
|
ResponseText string `json:"responseText"`
|
||||||
ResponseText string `json:"responseText"`
|
|
||||||
// https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest/responseURL
|
|
||||||
// https://xhr.spec.whatwg.org/#the-responseurl-attribute
|
|
||||||
ResponseUrl string `json:"responseURL"`
|
|
||||||
// extend fetch redirect
|
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/API/RequestInit#redirect
|
|
||||||
// https://fetch.spec.whatwg.org/#concept-request-redirect-mode
|
|
||||||
Redirect string `json:"redirect"`
|
|
||||||
*EventProp
|
*EventProp
|
||||||
Onreadystatechange func(event *ProgressEvent) `json:"onreadystatechange"`
|
Onreadystatechange func(event *ProgressEvent) `json:"onreadystatechange"`
|
||||||
}
|
}
|
||||||
@@ -153,13 +137,13 @@ type XMLHttpRequest struct {
|
|||||||
func (xhr *XMLHttpRequest) Open(method, url string) {
|
func (xhr *XMLHttpRequest) Open(method, url string) {
|
||||||
xhr.method = method
|
xhr.method = method
|
||||||
xhr.url = url
|
xhr.url = url
|
||||||
xhr.requestHeaders = make(http.Header)
|
xhr.requestHeaders = make(map[string]string)
|
||||||
xhr.responseHeaders = make(http.Header)
|
xhr.responseHeaders = make(map[string]string)
|
||||||
xhr.doReadystatechange(1)
|
xhr.doReadystatechange(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (xhr *XMLHttpRequest) SetRequestHeader(key, value string) {
|
func (xhr *XMLHttpRequest) SetRequestHeader(key, value string) {
|
||||||
xhr.requestHeaders.Add(key, value)
|
xhr.requestHeaders[key] = value
|
||||||
}
|
}
|
||||||
|
|
||||||
func (xhr *XMLHttpRequest) Send(data goja.Value) {
|
func (xhr *XMLHttpRequest) Send(data goja.Value) {
|
||||||
@@ -169,33 +153,31 @@ func (xhr *XMLHttpRequest) Send(data goja.Value) {
|
|||||||
var (
|
var (
|
||||||
contentType string
|
contentType string
|
||||||
contentLength int64
|
contentLength int64
|
||||||
isStringBody bool
|
|
||||||
)
|
)
|
||||||
if d == nil || xhr.method == "GET" || xhr.method == "HEAD" {
|
if d == nil || xhr.method == "GET" || xhr.method == "HEAD" {
|
||||||
req, err = http.NewRequest(xhr.method, xhr.url, nil)
|
req, err = http.NewRequest(xhr.method, xhr.url, nil)
|
||||||
} else {
|
} else {
|
||||||
switch v := d.(type) {
|
switch d.(type) {
|
||||||
case string:
|
case string:
|
||||||
req, err = http.NewRequest(xhr.method, xhr.url, bytes.NewBufferString(v))
|
req, err = http.NewRequest(xhr.method, xhr.url, bytes.NewBufferString(d.(string)))
|
||||||
contentType = "text/plain;charset=UTF-8"
|
contentType = "text/plain;charset=UTF-8"
|
||||||
contentLength = int64(len(v))
|
contentLength = int64(len(d.(string)))
|
||||||
isStringBody = true
|
|
||||||
case *file.File:
|
case *file.File:
|
||||||
req, err = http.NewRequest(xhr.method, xhr.url, v.Reader)
|
req, err = http.NewRequest(xhr.method, xhr.url, d.(*file.File).Reader)
|
||||||
contentType = "application/octet-stream"
|
contentType = "application/octet-stream"
|
||||||
contentLength = v.Size
|
contentLength = d.(*file.File).Size
|
||||||
case *formdata.FormData:
|
case *formdata.FormData:
|
||||||
pr, pw := io.Pipe()
|
pr, pw := io.Pipe()
|
||||||
mw := NewMultipart(pw)
|
mw := NewMultipart(pw)
|
||||||
for _, e := range v.Entries() {
|
for _, e := range d.(*formdata.FormData).Entries() {
|
||||||
arr := e.([]any)
|
arr := e.([]any)
|
||||||
k := arr[0].(string)
|
k := arr[0].(string)
|
||||||
v := arr[1]
|
v := arr[1]
|
||||||
switch v := v.(type) {
|
switch v.(type) {
|
||||||
case string:
|
case string:
|
||||||
mw.WriteField(k, v)
|
mw.WriteField(k, v.(string))
|
||||||
case *file.File:
|
case *file.File:
|
||||||
mw.WriteFile(k, v)
|
mw.WriteFile(k, v.(*file.File))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
go func() {
|
go func() {
|
||||||
@@ -212,32 +194,21 @@ func (xhr *XMLHttpRequest) Send(data goja.Value) {
|
|||||||
xhr.callOnerror()
|
xhr.callOnerror()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
req.Header = xhr.requestHeaders
|
if contentType != "" {
|
||||||
// Only string body can specify Content-Type header by user
|
|
||||||
if contentType != "" && (!isStringBody || req.Header.Get("Content-Type") == "") {
|
|
||||||
req.Header.Set("Content-Type", contentType)
|
req.Header.Set("Content-Type", contentType)
|
||||||
}
|
}
|
||||||
if contentLength > 0 {
|
if contentLength > 0 {
|
||||||
req.ContentLength = contentLength
|
req.ContentLength = contentLength
|
||||||
}
|
}
|
||||||
|
for k, v := range xhr.requestHeaders {
|
||||||
|
req.Header.Set(k, v)
|
||||||
|
}
|
||||||
transport := &http.Transport{
|
transport := &http.Transport{
|
||||||
Proxy: xhr.proxyHandler,
|
Proxy: xhr.proxyHandler,
|
||||||
}
|
}
|
||||||
client := &http.Client{
|
client := &http.Client{
|
||||||
Transport: transport,
|
Transport: transport,
|
||||||
Timeout: time.Duration(xhr.Timeout) * time.Millisecond,
|
Timeout: time.Duration(xhr.Timeout) * time.Millisecond,
|
||||||
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
|
||||||
if xhr.Redirect == redirectManual {
|
|
||||||
return http.ErrUseLastResponse
|
|
||||||
}
|
|
||||||
if xhr.Redirect == redirectError {
|
|
||||||
return errors.New("redirect failed")
|
|
||||||
}
|
|
||||||
if len(via) > 20 {
|
|
||||||
return errors.New("too many redirects")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
resp, err := client.Do(req)
|
resp, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -259,12 +230,9 @@ func (xhr *XMLHttpRequest) Send(data goja.Value) {
|
|||||||
if !xhr.aborted {
|
if !xhr.aborted {
|
||||||
xhr.Upload.callOnload()
|
xhr.Upload.callOnload()
|
||||||
}
|
}
|
||||||
|
for k, v := range resp.Header {
|
||||||
responseUrl := resp.Request.URL
|
xhr.responseHeaders[k] = v[0]
|
||||||
responseUrl.Fragment = ""
|
}
|
||||||
xhr.ResponseUrl = responseUrl.String()
|
|
||||||
|
|
||||||
xhr.responseHeaders = resp.Header
|
|
||||||
xhr.Status = resp.StatusCode
|
xhr.Status = resp.StatusCode
|
||||||
xhr.StatusText = resp.Status
|
xhr.StatusText = resp.Status
|
||||||
xhr.doReadystatechange(2)
|
xhr.doReadystatechange(2)
|
||||||
@@ -293,7 +261,7 @@ func (xhr *XMLHttpRequest) Abort() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (xhr *XMLHttpRequest) GetResponseHeader(key string) string {
|
func (xhr *XMLHttpRequest) GetResponseHeader(key string) string {
|
||||||
return strings.Join(xhr.responseHeaders.Values(key), ", ")
|
return xhr.responseHeaders[key]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (xhr *XMLHttpRequest) GetAllResponseHeaders() string {
|
func (xhr *XMLHttpRequest) GetAllResponseHeaders() string {
|
||||||
@@ -301,7 +269,7 @@ func (xhr *XMLHttpRequest) GetAllResponseHeaders() string {
|
|||||||
for k, v := range xhr.responseHeaders {
|
for k, v := range xhr.responseHeaders {
|
||||||
buf.WriteString(k)
|
buf.WriteString(k)
|
||||||
buf.WriteString(": ")
|
buf.WriteString(": ")
|
||||||
buf.WriteString(strings.Join(v, ", "))
|
buf.WriteString(v)
|
||||||
buf.WriteString("\r\n")
|
buf.WriteString("\r\n")
|
||||||
}
|
}
|
||||||
return buf.String()
|
return buf.String()
|
||||||
@@ -419,9 +387,10 @@ func (w *multipartWrapper) Size() int64 {
|
|||||||
w.statWriter.Close()
|
w.statWriter.Close()
|
||||||
size := int64(w.statBuffer.Len())
|
size := int64(w.statBuffer.Len())
|
||||||
for _, v := range w.fields {
|
for _, v := range w.fields {
|
||||||
switch v := v.(type) {
|
switch v.(type) {
|
||||||
case *file.File:
|
case *file.File:
|
||||||
size += v.Size
|
f := v.(*file.File)
|
||||||
|
size += f.Size
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return size
|
return size
|
||||||
@@ -429,17 +398,18 @@ func (w *multipartWrapper) Size() int64 {
|
|||||||
|
|
||||||
func (w *multipartWrapper) Send() error {
|
func (w *multipartWrapper) Send() error {
|
||||||
for k, v := range w.fields {
|
for k, v := range w.fields {
|
||||||
switch v := v.(type) {
|
switch v.(type) {
|
||||||
case string:
|
case string:
|
||||||
if err := w.writer.WriteField(k, v); err != nil {
|
if err := w.writer.WriteField(k, v.(string)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
case *file.File:
|
case *file.File:
|
||||||
fw, err := w.writer.CreateFormFile(k, v.Name)
|
f := v.(*file.File)
|
||||||
|
fw, err := w.writer.CreateFormFile(k, f.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if _, err = io.Copy(fw, v); err != nil {
|
if _, err = io.Copy(fw, f); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
(()=>{var e={725:function(e,t,r){var o,n,i;i="undefined"!=typeof self&&self||"undefined"!=typeof window&&window||void 0!==r.g&&r.g||this,o=function(e){"use strict";var t=i.BlobBuilder||i.WebKitBlobBuilder||i.MSBlobBuilder||i.MozBlobBuilder,r=i.URL||i.webkitURL||function(e,t){return(t=document.createElement("a")).href=e,t},o=i.Blob,n=r.createObjectURL,a=r.revokeObjectURL,s=i.Symbol&&i.Symbol.toStringTag,u=!1,f=!1,l=t&&t.prototype.append&&t.prototype.getBlob;try{u=2===new Blob(["ä"]).size,f=2===new Blob([new Uint8Array([1,2])]).size}catch(e){}function c(e){return e.map((function(e){if(e.buffer instanceof ArrayBuffer){var t=e.buffer;if(e.byteLength!==t.byteLength){var r=new Uint8Array(e.byteLength);r.set(new Uint8Array(t,e.byteOffset,e.byteLength)),t=r.buffer}return t}return e}))}function h(e,r){r=r||{};var o=new t;return c(e).forEach((function(e){o.append(e)})),r.type?o.getBlob(r.type):o.getBlob()}function d(e,t){return new o(c(e),t||{})}i.Blob&&(h.prototype=Blob.prototype,d.prototype=Blob.prototype);var p="function"==typeof TextEncoder?TextEncoder.prototype.encode.bind(new TextEncoder):function(e){for(var t=0,r=e.length,o=i.Uint8Array||Array,n=0,a=Math.max(32,r+(r>>1)+7),s=new o(a>>3<<3);t<r;){var u=e.charCodeAt(t++);if(u>=55296&&u<=56319){if(t<r){var f=e.charCodeAt(t);56320==(64512&f)&&(++t,u=((1023&u)<<10)+(1023&f)+65536)}if(u>=55296&&u<=56319)continue}if(n+4>s.length){a+=8,a=(a*=1+t/e.length*2)>>3<<3;var l=new Uint8Array(a);l.set(s),s=l}if(0!=(4294967168&u)){if(0==(4294965248&u))s[n++]=u>>6&31|192;else if(0==(4294901760&u))s[n++]=u>>12&15|224,s[n++]=u>>6&63|128;else{if(0!=(4292870144&u))continue;s[n++]=u>>18&7|240,s[n++]=u>>12&63|128,s[n++]=u>>6&63|128}s[n++]=63&u|128}else s[n++]=u}return s.slice(0,n)},y="function"==typeof TextDecoder?TextDecoder.prototype.decode.bind(new TextDecoder):function(e){for(var t=e.length,r=[],o=0;o<t;){var n,i,a,s,u=e[o],f=null,l=u>239?4:u>223?3:u>191?2:1;if(o+l<=t)switch(l){case 1:u<128&&(f=u);break;case 2:128==(192&(n=e[o+1]))&&(s=(31&u)<<6|63&n)>127&&(f=s);break;case 3:n=e[o+1],i=e[o+2],128==(192&n)&&128==(192&i)&&(s=(15&u)<<12|(63&n)<<6|63&i)>2047&&(s<55296||s>57343)&&(f=s);break;case 4:n=e[o+1],i=e[o+2],a=e[o+3],128==(192&n)&&128==(192&i)&&128==(192&a)&&(s=(15&u)<<18|(63&n)<<12|(63&i)<<6|63&a)>65535&&s<1114112&&(f=s)}null===f?(f=65533,l=1):f>65535&&(f-=65536,r.push(f>>>10&1023|55296),f=56320|1023&f),r.push(f),o+=l}for(var c=r.length,h="",d=0;d<c;)h+=String.fromCharCode.apply(String,r.slice(d,d+=4096));return h};function b(){var t=!!i.ActiveXObject||"-ms-scroll-limit"in document.documentElement.style&&"-ms-ime-align"in document.documentElement.style,r=i.XMLHttpRequest&&i.XMLHttpRequest.prototype.send;t&&r&&(XMLHttpRequest.prototype.send=function(e){e instanceof Blob?(this.setRequestHeader("Content-Type",e.type),r.call(this,e)):r.call(this,e)});try{new File([],""),e.File=i.File,e.FileReader=i.FileReader}catch(t){try{e.File=new Function('class File extends Blob {constructor(chunks, name, opts) {opts = opts || {};super(chunks, opts || {});this.name = name.replace(/\\//g, ":");this.lastModifiedDate = opts.lastModified ? new Date(opts.lastModified) : new Date();this.lastModified = +this.lastModifiedDate;}};return new File([], ""), File')()}catch(t){e.File=function(e,t,r){var o=new Blob(e,r),n=r&&void 0!==r.lastModified?new Date(r.lastModified):new Date;return o.name=t.replace(/\//g,":"),o.lastModifiedDate=n,o.lastModified=+n,o.toString=function(){return"[object File]"},s&&(o[s]="File"),o}}}}u?(b(),e.Blob=f?i.Blob:d):l?(b(),e.Blob=h):function(){function t(e){for(var t=new Array(e.byteLength),r=new Uint8Array(e),o=t.length;o--;)t[o]=r[o];return t}function o(e){for(var t="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",r=[],o=0;o<e.length;o+=3){var n=e[o],i=o+1<e.length,a=i?e[o+1]:0,s=o+2<e.length,u=s?e[o+2]:0,f=n>>2,l=(3&n)<<4|a>>4,c=(15&a)<<2|u>>6,h=63&u;s||(h=64,i||(c=64)),r.push(t[f],t[l],t[c],t[h])}return r.join("")}var s=Object.create||function(e){function t(){}return t.prototype=e,new t};function u(e){return Object.prototype.toString.call(e).slice(8,-1)}function f(e,t){return"object"==typeof e&&Object.prototype.isPrototypeOf.call(e.prototype,t)}var l=["Int8Array","Uint8Array","Uint8ClampedArray","Int16Array","Uint16Array","Int32Array","Uint32Array","Float32Array","Float64Array","ArrayBuffer"];function c(e){return t=l,r=u(e),-1!==t.indexOf(r)||f(i.ArrayBuffer,e);var t,r}function h(e,r){r=null==r?{}:r;for(var o=0,n=(e=e?e.slice():[]).length;o<n;o++){var a=e[o];a instanceof h?e[o]=a._buffer:"string"==typeof a?e[o]=p(a):"DataView"===u(s=a)||f(i.DataView,s)?e[o]=t(a.buffer):c(a)?e[o]=t(a):e[o]=p(String(a))}var s;this._buffer=i.Uint8Array?function(e){for(var t=0,r=e.length;r--;)t+=e[r].length;for(var o=new Uint8Array(t),n=0,i=0;i<e.length;i++){var a=e[i];o.set(a,n),n+=a.byteLength||a.length}return o}(e):[].concat.apply([],e),this.size=this._buffer.length,this.type=r.type||"",/[^\u0020-\u007E]/.test(this.type)?this.type="":this.type=this.type.toLowerCase()}funcLine truncated
|
(()=>{var t={725:function(t,e,r){var o,n,i;i="undefined"!=typeof self&&self||"undefined"!=typeof window&&window||void 0!==r.g&&r.g||this,o=function(t){"use strict";var e=i.BlobBuilder||i.WebKitBlobBuilder||i.MSBlobBuilder||i.MozBlobBuilder,r=i.URL||i.webkitURL||function(t,e){return(e=document.createElement("a")).href=t,e},o=i.Blob,n=r.createObjectURL,a=r.revokeObjectURL,s=i.Symbol&&i.Symbol.toStringTag,u=!1,f=!1,l=e&&e.prototype.append&&e.prototype.getBlob;try{u=2===new Blob(["ä"]).size,f=2===new Blob([new Uint8Array([1,2])]).size}catch(t){}function c(t){return t.map((function(t){if(t.buffer instanceof ArrayBuffer){var e=t.buffer;if(t.byteLength!==e.byteLength){var r=new Uint8Array(t.byteLength);r.set(new Uint8Array(e,t.byteOffset,t.byteLength)),e=r.buffer}return e}return t}))}function h(t,r){r=r||{};var o=new e;return c(t).forEach((function(t){o.append(t)})),r.type?o.getBlob(r.type):o.getBlob()}function p(t,e){return new o(c(t),e||{})}i.Blob&&(h.prototype=Blob.prototype,p.prototype=Blob.prototype);var y="function"==typeof TextEncoder?TextEncoder.prototype.encode.bind(new TextEncoder):function(t){for(var e=0,r=t.length,o=i.Uint8Array||Array,n=0,a=Math.max(32,r+(r>>1)+7),s=new o(a>>3<<3);e<r;){var u=t.charCodeAt(e++);if(u>=55296&&u<=56319){if(e<r){var f=t.charCodeAt(e);56320==(64512&f)&&(++e,u=((1023&u)<<10)+(1023&f)+65536)}if(u>=55296&&u<=56319)continue}if(n+4>s.length){a+=8,a=(a*=1+e/t.length*2)>>3<<3;var l=new Uint8Array(a);l.set(s),s=l}if(0!=(4294967168&u)){if(0==(4294965248&u))s[n++]=u>>6&31|192;else if(0==(4294901760&u))s[n++]=u>>12&15|224,s[n++]=u>>6&63|128;else{if(0!=(4292870144&u))continue;s[n++]=u>>18&7|240,s[n++]=u>>12&63|128,s[n++]=u>>6&63|128}s[n++]=63&u|128}else s[n++]=u}return s.slice(0,n)},d="function"==typeof TextDecoder?TextDecoder.prototype.decode.bind(new TextDecoder):function(t){for(var e=t.length,r=[],o=0;o<e;){var n,i,a,s,u=t[o],f=null,l=u>239?4:u>223?3:u>191?2:1;if(o+l<=e)switch(l){case 1:u<128&&(f=u);break;case 2:128==(192&(n=t[o+1]))&&(s=(31&u)<<6|63&n)>127&&(f=s);break;case 3:n=t[o+1],i=t[o+2],128==(192&n)&&128==(192&i)&&(s=(15&u)<<12|(63&n)<<6|63&i)>2047&&(s<55296||s>57343)&&(f=s);break;case 4:n=t[o+1],i=t[o+2],a=t[o+3],128==(192&n)&&128==(192&i)&&128==(192&a)&&(s=(15&u)<<18|(63&n)<<12|(63&i)<<6|63&a)>65535&&s<1114112&&(f=s)}null===f?(f=65533,l=1):f>65535&&(f-=65536,r.push(f>>>10&1023|55296),f=56320|1023&f),r.push(f),o+=l}for(var c=r.length,h="",p=0;p<c;)h+=String.fromCharCode.apply(String,r.slice(p,p+=4096));return h};function b(){var e=!!i.ActiveXObject||"-ms-scroll-limit"in document.documentElement.style&&"-ms-ime-align"in document.documentElement.style,r=i.XMLHttpRequest&&i.XMLHttpRequest.prototype.send;e&&r&&(XMLHttpRequest.prototype.send=function(t){t instanceof Blob?(this.setRequestHeader("Content-Type",t.type),r.call(this,t)):r.call(this,t)});try{new File([],""),t.File=i.File,t.FileReader=i.FileReader}catch(e){try{t.File=new Function('class File extends Blob {constructor(chunks, name, opts) {opts = opts || {};super(chunks, opts || {});this.name = name.replace(/\\//g, ":");this.lastModifiedDate = opts.lastModified ? new Date(opts.lastModified) : new Date();this.lastModified = +this.lastModifiedDate;}};return new File([], ""), File')()}catch(e){t.File=function(t,e,r){var o=new Blob(t,r),n=r&&void 0!==r.lastModified?new Date(r.lastModified):new Date;return o.name=e.replace(/\//g,":"),o.lastModifiedDate=n,o.lastModified=+n,o.toString=function(){return"[object File]"},s&&(o[s]="File"),o}}}}u?(b(),t.Blob=f?i.Blob:p):l?(b(),t.Blob=h):function(){function e(t){for(var e=new Array(t.byteLength),r=new Uint8Array(t),o=e.length;o--;)e[o]=r[o];return e}function o(t){for(var e="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",r=[],o=0;o<t.length;o+=3){var n=t[o],i=o+1<t.length,a=i?t[o+1]:0,s=o+2<t.length,u=s?t[o+2]:0,f=n>>2,l=(3&n)<<4|a>>4,c=(15&a)<<2|u>>6,h=63&u;s||(h=64,i||(c=64)),r.push(e[f],e[l],e[c],e[h])}return r.join("")}var s=Object.create||function(t){function e(){}return e.prototype=t,new e};function u(t){return Object.prototype.toString.call(t).slice(8,-1)}function f(t,e){return"object"==typeof t&&Object.prototype.isPrototypeOf.call(t.prototype,e)}var l=["Int8Array","Uint8Array","Uint8ClampedArray","Int16Array","Uint16Array","Int32Array","Uint32Array","Float32Array","Float64Array","ArrayBuffer"];function c(t){return e=l,r=u(t),-1!==e.indexOf(r)||f(i.ArrayBuffer,t);var e,r}function h(t,r){r=null==r?{}:r;for(var o=0,n=(t=t?t.slice():[]).length;o<n;o++){var a=t[o];a instanceof h?t[o]=a._buffer:"string"==typeof a?t[o]=y(a):"DataView"===u(s=a)||f(i.DataView,s)?t[o]=e(a.buffer):c(a)?t[o]=e(a):t[o]=y(String(a))}var s;this._buffer=i.Uint8Array?function(t){for(var e=0,r=t.length;r--;)e+=t[r].length;for(var o=new Uint8Array(e),n=0,i=0;i<t.length;i++){var a=t[i];o.set(a,n),n+=a.byteLength||a.length}return o}(t):[].concat.apply([],t),this.size=this._buffer.length,this.type=r.type||"",/[^\u0020-\u007E]/.test(this.type)?this.type="":this.type=this.type.toLowerCase()}funcLine truncated
|
||||||
+14
-1168
File diff suppressed because it is too large.
Load diff
@@ -7,19 +7,16 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1",
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
"dev": "webpack --mode production --watch",
|
"dev": "webpack --mode production --watch",
|
||||||
"build": "webpack --mode production",
|
"build": "webpack --mode production"
|
||||||
"postinstall": "patch-package"
|
|
||||||
},
|
},
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"patch-package": "^8.0.0",
|
|
||||||
"webpack": "^5.75.0",
|
"webpack": "^5.75.0",
|
||||||
"webpack-cli": "^5.0.1"
|
"webpack-cli": "^5.0.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"blob-polyfill": "^7.0.20220408",
|
"blob-polyfill": "^7.0.20220408",
|
||||||
"fastestsmallesttextencoderdecoder": "^1.0.22",
|
"whatwg-fetch": "^3.6.2"
|
||||||
"whatwg-fetch": "^3.6.20"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
diff --git a/node_modules/whatwg-fetch/dist/fetch.umd.js b/node_modules/whatwg-fetch/dist/fetch.umd.js
|
|
||||||
index 7a0d852..604691e 100644
|
|
||||||
--- a/node_modules/whatwg-fetch/dist/fetch.umd.js
|
|
||||||
+++ b/node_modules/whatwg-fetch/dist/fetch.umd.js
|
|
||||||
@@ -394,6 +394,7 @@
|
|
||||||
}
|
|
||||||
}());
|
|
||||||
this.referrer = null;
|
|
||||||
+ this.redirect = options.redirect || 'follow'
|
|
||||||
|
|
||||||
if ((this.method === 'GET' || this.method === 'HEAD') && body) {
|
|
||||||
throw new TypeError('Body not allowed for GET or HEAD requests')
|
|
||||||
@@ -606,6 +607,10 @@
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
+ if ('redirect' in xhr) {
|
|
||||||
+ xhr.redirect = request.redirect
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
if (init && typeof init.headers === 'object' && !(init.headers instanceof Headers || (g.Headers && init.headers instanceof g.Headers))) {
|
|
||||||
var names = [];
|
|
||||||
Object.getOwnPropertyNames(init.headers).forEach(function(name) {
|
|
||||||
diff --git a/node_modules/whatwg-fetch/fetch.js b/node_modules/whatwg-fetch/fetch.js
|
|
||||||
index f39a983..d1fc903 100644
|
|
||||||
--- a/node_modules/whatwg-fetch/fetch.js
|
|
||||||
+++ b/node_modules/whatwg-fetch/fetch.js
|
|
||||||
@@ -388,6 +388,7 @@ export function Request(input, options) {
|
|
||||||
}
|
|
||||||
}());
|
|
||||||
this.referrer = null
|
|
||||||
+ this.redirect = options.redirect || 'follow'
|
|
||||||
|
|
||||||
if ((this.method === 'GET' || this.method === 'HEAD') && body) {
|
|
||||||
throw new TypeError('Body not allowed for GET or HEAD requests')
|
|
||||||
@@ -600,6 +601,10 @@ export function fetch(input, init) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
+ if ('redirect' in xhr) {
|
|
||||||
+ xhr.redirect = request.redirect
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
if (init && typeof init.headers === 'object' && !(init.headers instanceof Headers || (g.Headers && init.headers instanceof g.Headers))) {
|
|
||||||
var names = [];
|
|
||||||
Object.getOwnPropertyNames(init.headers).forEach(function(name) {
|
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
import "./blob/index.js"
|
import "./blob/index.js"
|
||||||
import "./crypto/index.js"
|
import "./crypto/index.js"
|
||||||
// polyfill TextEncoder
|
|
||||||
import 'fastestsmallesttextencoderdecoder';
|
|
||||||
import "./fetch/index.js"
|
import "./fetch/index.js"
|
||||||
+10
-41
@@ -38,7 +38,7 @@ const (
|
|||||||
EventOnResolve ActivationEvent = "onResolve"
|
EventOnResolve ActivationEvent = "onResolve"
|
||||||
EventOnStart ActivationEvent = "onStart"
|
EventOnStart ActivationEvent = "onStart"
|
||||||
EventOnError ActivationEvent = "onError"
|
EventOnError ActivationEvent = "onError"
|
||||||
EventOnDone ActivationEvent = "onDone"
|
//EventOnDone ActivationEvent = "onDone"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (d *Downloader) InstallExtensionByGit(url string) (*Extension, error) {
|
func (d *Downloader) InstallExtensionByGit(url string) (*Extension, error) {
|
||||||
@@ -155,13 +155,7 @@ func (d *Downloader) DeleteExtension(identity string) error {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err = d.storage.Delete(bucketExtension, identity); err != nil {
|
return d.storage.Delete(bucketExtension, identity)
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err = d.storage.Delete(bucketExtensionStorage, identity); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Downloader) GetExtensions() []*Extension {
|
func (d *Downloader) GetExtensions() []*Extension {
|
||||||
@@ -304,17 +298,6 @@ func (d *Downloader) triggerOnError(task *Task, err error) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Downloader) triggerOnDone(task *Task) {
|
|
||||||
doTrigger(d,
|
|
||||||
EventOnDone,
|
|
||||||
task.Meta.Req,
|
|
||||||
&OnErrorContext{
|
|
||||||
Task: NewExtensionTask(d, task),
|
|
||||||
},
|
|
||||||
nil,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func doTrigger[T any](d *Downloader, event ActivationEvent, req *base.Request, ctx T, handler func(ext *Extension, gopeed *Instance, ctx T)) error {
|
func doTrigger[T any](d *Downloader, event ActivationEvent, req *base.Request, ctx T, handler func(ext *Extension, gopeed *Instance, ctx T)) error {
|
||||||
// init extension global object
|
// init extension global object
|
||||||
gopeed := &Instance{
|
gopeed := &Instance{
|
||||||
@@ -622,9 +605,9 @@ func (h InstanceEvents) OnError(fn goja.Callable) {
|
|||||||
h.register(EventOnError, fn)
|
h.register(EventOnError, fn)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h InstanceEvents) OnDone(fn goja.Callable) {
|
//func (h InstanceEvents) OnDone(fn goja.Callable) {
|
||||||
h.register(EventOnDone, fn)
|
// h.register(HookEventOnDone, fn)
|
||||||
}
|
//}
|
||||||
|
|
||||||
type ExtensionInfo struct {
|
type ExtensionInfo struct {
|
||||||
Identity string `json:"identity"`
|
Identity string `json:"identity"`
|
||||||
@@ -698,39 +681,25 @@ type OnErrorContext struct {
|
|||||||
Error error `json:"error"`
|
Error error `json:"error"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type OnDoneContext struct {
|
|
||||||
Task *Task `json:"task"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExtensionTask is a wrapper of Task, it's used to interact with extension scripts.
|
|
||||||
// Avoid extension scripts modifying task directly, use ExtensionTask to encapsulate task,
|
|
||||||
// only some fields can be modified, such as request info.
|
|
||||||
type ExtensionTask struct {
|
type ExtensionTask struct {
|
||||||
download *Downloader
|
|
||||||
|
|
||||||
*Task
|
*Task
|
||||||
|
|
||||||
|
download *Downloader
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewExtensionTask(download *Downloader, task *Task) *ExtensionTask {
|
func NewExtensionTask(download *Downloader, task *Task) *ExtensionTask {
|
||||||
// restricts extension scripts to only modify request info
|
|
||||||
newTask := task.clone()
|
|
||||||
newTask.Meta.Req = task.Meta.Req
|
|
||||||
return &ExtensionTask{
|
return &ExtensionTask{
|
||||||
|
Task: task.clone(),
|
||||||
download: download,
|
download: download,
|
||||||
Task: newTask,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *ExtensionTask) Continue() error {
|
func (t *ExtensionTask) Continue() error {
|
||||||
return t.download.Continue(&TaskFilter{
|
return t.download.Continue(t.ID)
|
||||||
IDs: []string{t.ID},
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *ExtensionTask) Pause() error {
|
func (t *ExtensionTask) Pause() error {
|
||||||
return t.download.Pause(&TaskFilter{
|
return t.download.Pause(t.ID)
|
||||||
IDs: []string{t.ID},
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseSettings(settings []*Setting) map[string]any {
|
func parseSettings(settings []*Setting) map[string]any {
|
||||||
|
|||||||
@@ -247,45 +247,8 @@ func TestDownloader_Extension_OnError(t *testing.T) {
|
|||||||
}
|
}
|
||||||
// extension on error modify url and continue download
|
// extension on error modify url and continue download
|
||||||
task := downloader.GetTask(id)
|
task := downloader.GetTask(id)
|
||||||
if task.Status != base.DownloadStatusDone {
|
if task.Meta.Req.URL != "https://github.com" {
|
||||||
t.Fatalf("except status is done, actual: %s", task.Status)
|
t.Fatalf("except url: https://github.com, actual: %s", task.Meta.Req.URL)
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDownloader_Extension_OnDone(t *testing.T) {
|
|
||||||
setupDownloader(func(downloader *Downloader) {
|
|
||||||
if _, err := downloader.InstallExtensionByFolder("./testdata/extensions/on_done", false); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
errCh := make(chan error, 1)
|
|
||||||
downloader.Listener(func(event *Event) {
|
|
||||||
if event.Key == EventKeyFinally {
|
|
||||||
errCh <- event.Err
|
|
||||||
}
|
|
||||||
})
|
|
||||||
id, err := downloader.CreateDirect(&base.Request{
|
|
||||||
URL: "https://github.com",
|
|
||||||
}, nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case err = <-errCh:
|
|
||||||
break
|
|
||||||
case <-time.After(time.Second * 10):
|
|
||||||
err = errors.New("timeout")
|
|
||||||
}
|
|
||||||
// wait for script execution
|
|
||||||
time.Sleep(time.Millisecond * 3000)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
panic("extension on done download error: " + err.Error())
|
|
||||||
}
|
|
||||||
// extension on error modify url and continue download
|
|
||||||
task := downloader.GetTask(id)
|
|
||||||
if task.Meta.Req.Labels["modified"] != "true" {
|
|
||||||
t.Fatalf("except label: modified=true, actual: %s", task.Meta.Req.Labels["modified"])
|
|
||||||
}
|
}
|
||||||
if task.Status != base.DownloadStatusDone {
|
if task.Status != base.DownloadStatusDone {
|
||||||
t.Fatalf("except status is done, actual: %s", task.Status)
|
t.Fatalf("except status is done, actual: %s", task.Status)
|
||||||
|
|||||||
+24
-72
@@ -1,7 +1,6 @@
|
|||||||
package download
|
package download
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"github.com/GopeedLab/gopeed/internal/controller"
|
"github.com/GopeedLab/gopeed/internal/controller"
|
||||||
"github.com/GopeedLab/gopeed/internal/fetcher"
|
"github.com/GopeedLab/gopeed/internal/fetcher"
|
||||||
"github.com/GopeedLab/gopeed/internal/protocol/bt"
|
"github.com/GopeedLab/gopeed/internal/protocol/bt"
|
||||||
@@ -20,21 +19,19 @@ type ResolveResult struct {
|
|||||||
|
|
||||||
type Task struct {
|
type Task struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Protocol string `json:"protocol"`
|
|
||||||
Meta *fetcher.FetcherMeta `json:"meta"`
|
Meta *fetcher.FetcherMeta `json:"meta"`
|
||||||
Status base.Status `json:"status"`
|
Status base.Status `json:"status"`
|
||||||
Uploading bool `json:"uploading"`
|
Protocol string `json:"protocol"`
|
||||||
Progress *Progress `json:"progress"`
|
Progress *Progress `json:"progress"`
|
||||||
CreatedAt time.Time `json:"createdAt"`
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
UpdatedAt time.Time `json:"updatedAt"`
|
UpdatedAt time.Time `json:"updatedAt"`
|
||||||
|
|
||||||
fetcherManager fetcher.FetcherManager
|
fetcherBuilder fetcher.FetcherBuilder
|
||||||
fetcher fetcher.Fetcher
|
fetcher fetcher.Fetcher
|
||||||
timer *util.Timer
|
timer *util.Timer
|
||||||
statusLock *sync.Mutex
|
statusLock *sync.Mutex
|
||||||
lock *sync.Mutex
|
lock *sync.Mutex
|
||||||
speedArr []int64
|
speedArr []int64
|
||||||
uploadSpeedArr []int64
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTask() *Task {
|
func NewTask() *Task {
|
||||||
@@ -50,86 +47,41 @@ func NewTask() *Task {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Name returns the display name of the task.
|
|
||||||
func (t *Task) Name() string {
|
|
||||||
// Custom name first
|
|
||||||
if t.Meta.Opts.Name != "" {
|
|
||||||
return t.Meta.Opts.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
// Task is not resolved, parse the name from the URL
|
|
||||||
if t.Meta.Res == nil {
|
|
||||||
fallbackName := "unknown"
|
|
||||||
if t.fetcherManager == nil {
|
|
||||||
return fallbackName
|
|
||||||
}
|
|
||||||
parseName := t.fetcherManager.ParseName(t.Meta.Req.URL)
|
|
||||||
if parseName == "" {
|
|
||||||
return fallbackName
|
|
||||||
}
|
|
||||||
return parseName
|
|
||||||
}
|
|
||||||
|
|
||||||
// Task is a folder
|
|
||||||
if t.Meta.Res.Name != "" {
|
|
||||||
return t.Meta.Res.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the name of the first file
|
|
||||||
return t.Meta.Res.Files[0].Name
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Task) MarshalJSON() ([]byte, error) {
|
|
||||||
type rawTaskType Task
|
|
||||||
jsonTask := struct {
|
|
||||||
rawTaskType
|
|
||||||
Name string `json:"name"`
|
|
||||||
}{
|
|
||||||
rawTaskType(*t),
|
|
||||||
t.Name(),
|
|
||||||
}
|
|
||||||
return json.Marshal(jsonTask)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Task) updateStatus(status base.Status) {
|
func (t *Task) updateStatus(status base.Status) {
|
||||||
t.UpdatedAt = time.Now()
|
t.UpdatedAt = time.Now()
|
||||||
t.Status = status
|
t.Status = status
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Task) clone() *Task {
|
func (t *Task) clone() *Task {
|
||||||
return util.DeepClone(t)
|
return &Task{
|
||||||
|
ID: t.ID,
|
||||||
|
Meta: t.Meta,
|
||||||
|
Status: t.Status,
|
||||||
|
Progress: t.Progress,
|
||||||
|
CreatedAt: t.CreatedAt,
|
||||||
|
UpdatedAt: t.UpdatedAt,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Task) calcSpeed(speedArr []int64, downloaded int64, usedTime float64) int64 {
|
func (t *Task) calcSpeed(downloaded int64, usedTime float64) int64 {
|
||||||
speedArr = append(speedArr, downloaded)
|
t.speedArr = append(t.speedArr, downloaded)
|
||||||
if len(speedArr) > 6 {
|
if len(t.speedArr) > 6 {
|
||||||
speedArr = speedArr[1:]
|
t.speedArr = t.speedArr[1:]
|
||||||
}
|
}
|
||||||
var total int64
|
var total int64
|
||||||
for _, v := range speedArr {
|
for _, v := range t.speedArr {
|
||||||
total += v
|
total += v
|
||||||
}
|
}
|
||||||
return int64(float64(total) / float64(len(speedArr)) / usedTime)
|
return int64(float64(total) / float64(len(t.speedArr)) / usedTime)
|
||||||
}
|
|
||||||
|
|
||||||
type TaskFilter struct {
|
|
||||||
IDs []string
|
|
||||||
Statuses []base.Status
|
|
||||||
NotStatuses []base.Status
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *TaskFilter) IsEmpty() bool {
|
|
||||||
return len(f.IDs) == 0 && len(f.Statuses) == 0 && len(f.NotStatuses) == 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type DownloaderConfig struct {
|
type DownloaderConfig struct {
|
||||||
Controller *controller.Controller
|
Controller *controller.Controller
|
||||||
FetchManagers []fetcher.FetcherManager
|
FetchBuilders []fetcher.FetcherBuilder
|
||||||
|
|
||||||
RefreshInterval int `json:"refreshInterval"` // RefreshInterval time duration to refresh task progress(ms)
|
RefreshInterval int `json:"refreshInterval"` // RefreshInterval time duration to refresh task progress(ms)
|
||||||
Storage Storage
|
Storage Storage
|
||||||
StorageDir string
|
StorageDir string
|
||||||
DownloadDirWhiteList []string `json:"downloadDirWhiteList"`
|
|
||||||
|
|
||||||
ProductionMode bool
|
ProductionMode bool
|
||||||
|
|
||||||
@@ -140,10 +92,10 @@ func (cfg *DownloaderConfig) Init() *DownloaderConfig {
|
|||||||
if cfg.Controller == nil {
|
if cfg.Controller == nil {
|
||||||
cfg.Controller = controller.NewController()
|
cfg.Controller = controller.NewController()
|
||||||
}
|
}
|
||||||
if len(cfg.FetchManagers) == 0 {
|
if len(cfg.FetchBuilders) == 0 {
|
||||||
cfg.FetchManagers = []fetcher.FetcherManager{
|
cfg.FetchBuilders = []fetcher.FetcherBuilder{
|
||||||
new(http.FetcherManager),
|
new(http.FetcherBuilder),
|
||||||
new(bt.FetcherManager),
|
new(bt.FetcherBuilder),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if cfg.RefreshInterval == 0 {
|
if cfg.RefreshInterval == 0 {
|
||||||
|
|||||||
@@ -69,9 +69,7 @@ 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()
|
||||||
if bucketData, ok := memData[bucket]; ok {
|
memData[bucket][key] = v
|
||||||
bucketData[key] = v
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
gopeed.events.onDone(async function (ctx) {
|
|
||||||
gopeed.logger.info("url", ctx.task.meta.req.url);
|
|
||||||
ctx.task.meta.req.labels['modified'] = 'true';
|
|
||||||
});
|
|
||||||
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "on-done",
|
|
||||||
"title": "gopeed extension on done event test",
|
|
||||||
"version": "0.0.1",
|
|
||||||
"scripts": [
|
|
||||||
{
|
|
||||||
"event": "onDone",
|
|
||||||
"match": {
|
|
||||||
"urls": [
|
|
||||||
"*://github.com/*"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"entry": "index.js"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -2,6 +2,7 @@ gopeed.events.onError(async function (ctx) {
|
|||||||
gopeed.logger.info("url", ctx.task.meta.req.url);
|
gopeed.logger.info("url", ctx.task.meta.req.url);
|
||||||
gopeed.logger.info("error", ctx.error);
|
gopeed.logger.info("error", ctx.error);
|
||||||
ctx.task.meta.req.url = "https://github.com";
|
ctx.task.meta.req.url = "https://github.com";
|
||||||
|
ctx.task.pause();
|
||||||
ctx.task.continue();
|
ctx.task.continue();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -4,16 +4,11 @@ type ReqExtra struct {
|
|||||||
Trackers []string `json:"trackers"`
|
Trackers []string `json:"trackers"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stats for torrent
|
// Stats for download
|
||||||
type Stats struct {
|
type Stats struct {
|
||||||
|
// bt stats
|
||||||
// health indicators of torrents, from large to small, ConnectedSeeders are also the key to the health of seed resources
|
// health indicators of torrents, from large to small, ConnectedSeeders are also the key to the health of seed resources
|
||||||
TotalPeers int `json:"totalPeers"`
|
TotalPeers int `json:"totalPeers"`
|
||||||
ActivePeers int `json:"activePeers"`
|
ActivePeers int `json:"activePeers"`
|
||||||
ConnectedSeeders int `json:"connectedSeeders"`
|
ConnectedSeeders int `json:"connectedSeeders"`
|
||||||
// Total seed bytes
|
|
||||||
SeedBytes int64 `json:"seedBytes"`
|
|
||||||
// Seed ratio
|
|
||||||
SeedRatio float64 `json:"seedRatio"`
|
|
||||||
// Total seed time
|
|
||||||
SeedTime int64 `json:"seedTime"`
|
|
||||||
}
|
}
|
||||||
+29
-103
@@ -8,20 +8,8 @@ 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,
|
|
||||||
"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) {
|
||||||
@@ -57,44 +45,14 @@ 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) {
|
||||||
filter, errResult := parseIdFilter(r)
|
vars := mux.Vars(r)
|
||||||
if errResult != nil {
|
taskId := vars["id"]
|
||||||
WriteJson(w, errResult)
|
if taskId == "" {
|
||||||
|
WriteJson(w, model.NewErrorResult("param invalid: id", model.CodeInvalidParam))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if err := Downloader.Pause(taskId); err != nil {
|
||||||
if err := Downloader.Pause(filter); err != nil {
|
|
||||||
WriteJson(w, model.NewErrorResult(err.Error()))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
WriteJson(w, model.NewNilResult())
|
|
||||||
}
|
|
||||||
|
|
||||||
func PauseTasks(w http.ResponseWriter, r *http.Request) {
|
|
||||||
filter, errResult := parseFilter(r)
|
|
||||||
if errResult != nil {
|
|
||||||
WriteJson(w, errResult)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := Downloader.Pause(filter); err != nil {
|
|
||||||
WriteJson(w, model.NewErrorResult(err.Error()))
|
WriteJson(w, model.NewErrorResult(err.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -102,27 +60,29 @@ func PauseTasks(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ContinueTask(w http.ResponseWriter, r *http.Request) {
|
func ContinueTask(w http.ResponseWriter, r *http.Request) {
|
||||||
filter, errResult := parseIdFilter(r)
|
vars := mux.Vars(r)
|
||||||
if errResult != nil {
|
taskId := vars["id"]
|
||||||
WriteJson(w, errResult)
|
if taskId == "" {
|
||||||
|
WriteJson(w, model.NewErrorResult("param invalid: id", model.CodeInvalidParam))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if err := Downloader.Continue(taskId); err != nil {
|
||||||
if err := Downloader.Continue(filter); err != nil {
|
|
||||||
WriteJson(w, model.NewErrorResult(err.Error()))
|
WriteJson(w, model.NewErrorResult(err.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
WriteJson(w, model.NewNilResult())
|
WriteJson(w, model.NewNilResult())
|
||||||
}
|
}
|
||||||
|
|
||||||
func ContinueTasks(w http.ResponseWriter, r *http.Request) {
|
func PauseAllTask(w http.ResponseWriter, r *http.Request) {
|
||||||
filter, errResult := parseFilter(r)
|
if err := Downloader.PauseAll(); err != nil {
|
||||||
if errResult != nil {
|
WriteJson(w, model.NewErrorResult(err.Error()))
|
||||||
WriteJson(w, errResult)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
WriteJson(w, model.NewNilResult())
|
||||||
|
}
|
||||||
|
|
||||||
if err := Downloader.Continue(filter); err != nil {
|
func ContinueAllTask(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if err := Downloader.ContinueAll(); err != nil {
|
||||||
WriteJson(w, model.NewErrorResult(err.Error()))
|
WriteJson(w, model.NewErrorResult(err.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -130,14 +90,14 @@ func ContinueTasks(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func DeleteTask(w http.ResponseWriter, r *http.Request) {
|
func DeleteTask(w http.ResponseWriter, r *http.Request) {
|
||||||
filter, errResult := parseIdFilter(r)
|
vars := mux.Vars(r)
|
||||||
if errResult != nil {
|
taskId := vars["id"]
|
||||||
WriteJson(w, errResult)
|
force := r.FormValue("force")
|
||||||
|
if taskId == "" {
|
||||||
|
WriteJson(w, model.NewErrorResult("param invalid: id", model.CodeInvalidParam))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
force := r.FormValue("force")
|
if err := Downloader.Delete(taskId, force == "true"); err != nil {
|
||||||
|
|
||||||
if err := Downloader.Delete(filter, force == "true"); err != nil {
|
|
||||||
WriteJson(w, model.NewErrorResult(err.Error()))
|
WriteJson(w, model.NewErrorResult(err.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -145,14 +105,10 @@ func DeleteTask(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func DeleteTasks(w http.ResponseWriter, r *http.Request) {
|
func DeleteTasks(w http.ResponseWriter, r *http.Request) {
|
||||||
filter, errResult := parseFilter(r)
|
r.ParseForm()
|
||||||
if errResult != nil {
|
status := r.Form["status"]
|
||||||
WriteJson(w, errResult)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
force := r.FormValue("force")
|
force := r.FormValue("force")
|
||||||
|
if err := Downloader.DeleteByStatues(convertStatues(status), force == "true"); err != nil {
|
||||||
if err := Downloader.Delete(filter, force == "true"); err != nil {
|
|
||||||
WriteJson(w, model.NewErrorResult(err.Error()))
|
WriteJson(w, model.NewErrorResult(err.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -175,13 +131,9 @@ func GetTask(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetTasks(w http.ResponseWriter, r *http.Request) {
|
func GetTasks(w http.ResponseWriter, r *http.Request) {
|
||||||
filter, errResult := parseFilter(r)
|
r.ParseForm()
|
||||||
if errResult != nil {
|
status := r.Form["status"]
|
||||||
WriteJson(w, errResult)
|
tasks := Downloader.GetTasksByStatues(convertStatues(status))
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
tasks := Downloader.GetTasksByFilter(filter)
|
|
||||||
WriteJson(w, model.NewOkResult(tasks))
|
WriteJson(w, model.NewOkResult(tasks))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -344,32 +296,6 @@ func GetStats(w http.ResponseWriter, r *http.Request) {
|
|||||||
WriteJson(w, model.NewOkResult(statsResult))
|
WriteJson(w, model.NewOkResult(statsResult))
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseIdFilter(r *http.Request) (*download.TaskFilter, any) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
taskId := vars["id"]
|
|
||||||
if taskId == "" {
|
|
||||||
return nil, model.NewErrorResult("param invalid: id", model.CodeInvalidParam)
|
|
||||||
}
|
|
||||||
|
|
||||||
filter := &download.TaskFilter{
|
|
||||||
IDs: []string{taskId},
|
|
||||||
}
|
|
||||||
return filter, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseFilter(r *http.Request) (*download.TaskFilter, any) {
|
|
||||||
if err := r.ParseForm(); err != nil {
|
|
||||||
return nil, model.NewErrorResult(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
filter := &download.TaskFilter{
|
|
||||||
IDs: r.Form["id"],
|
|
||||||
Statuses: convertStatues(r.Form["status"]),
|
|
||||||
NotStatuses: convertStatues(r.Form["notStatus"]),
|
|
||||||
}
|
|
||||||
return filter, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func convertStatues(statues []string) []base.Status {
|
func convertStatues(statues []string) []base.Status {
|
||||||
result := make([]base.Status, 0)
|
result := make([]base.Status, 0)
|
||||||
for _, status := range statues {
|
for _, status := range statues {
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
type Command struct {
|
||||||
|
Protocol string `json:"protocol"`
|
||||||
|
Action string `json:"action"`
|
||||||
|
Params any `json:"params"`
|
||||||
|
}
|
||||||
@@ -2,7 +2,6 @@ package model
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"github.com/GopeedLab/gopeed/pkg/base"
|
|
||||||
"io/fs"
|
"io/fs"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -14,14 +13,12 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type StartConfig struct {
|
type StartConfig struct {
|
||||||
Network string `json:"network"`
|
Network string `json:"network"`
|
||||||
Address string `json:"address"`
|
Address string `json:"address"`
|
||||||
RefreshInterval int `json:"refreshInterval"`
|
RefreshInterval int `json:"refreshInterval"`
|
||||||
Storage Storage `json:"storage"`
|
Storage Storage `json:"storage"`
|
||||||
StorageDir string `json:"storageDir"`
|
StorageDir string `json:"storageDir"`
|
||||||
WhiteDownloadDirs []string `json:"whiteDownloadDirs"`
|
ApiToken string `json:"apiToken"`
|
||||||
ApiToken string `json:"apiToken"`
|
|
||||||
DownloadConfig *base.DownloaderStoreConfig `json:"downloadConfig"`
|
|
||||||
|
|
||||||
ProductionMode bool
|
ProductionMode bool
|
||||||
|
|
||||||
|
|||||||
@@ -7,8 +7,3 @@ 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"`
|
|
||||||
}
|
|
||||||
+10
-23
@@ -19,42 +19,31 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
srv *http.Server
|
srv *http.Server
|
||||||
runningPort int
|
|
||||||
|
|
||||||
Downloader *download.Downloader
|
Downloader *download.Downloader
|
||||||
)
|
)
|
||||||
|
|
||||||
func Start(startCfg *model.StartConfig) (port int, err error) {
|
func Start(startCfg *model.StartConfig) (int, error) {
|
||||||
// avoid repeat start
|
srv, listener, err := BuildServer(startCfg)
|
||||||
if srv != nil {
|
|
||||||
return runningPort, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var listener net.Listener
|
|
||||||
srv, listener, err = BuildServer(startCfg)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
if err := srv.Serve(listener); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
if err := srv.Serve(listener); err != nil && err != http.ErrServerClosed {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
port := 0
|
||||||
if addr, ok := listener.Addr().(*net.TCPAddr); ok {
|
if addr, ok := listener.Addr().(*net.TCPAddr); ok {
|
||||||
port = addr.Port
|
port = addr.Port
|
||||||
runningPort = port
|
|
||||||
}
|
}
|
||||||
return
|
return port, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func Stop() {
|
func Stop() {
|
||||||
defer func() {
|
|
||||||
srv = nil
|
|
||||||
}()
|
|
||||||
|
|
||||||
if srv != nil {
|
if srv != nil {
|
||||||
if err := srv.Shutdown(context.TODO()); err != nil {
|
if err := srv.Shutdown(context.TODO()); err != nil {
|
||||||
Downloader.Logger.Warn().Err(err).Msg("shutdown server failed")
|
Downloader.Logger.Warn().Err(err).Msg("shutdown server failed")
|
||||||
@@ -99,14 +88,12 @@ 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/pause").HandlerFunc(PauseTasks)
|
|
||||||
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/continue").HandlerFunc(ContinueTasks)
|
r.Methods(http.MethodPut).Path("/api/v1/tasks/pause").HandlerFunc(PauseAllTask)
|
||||||
|
r.Methods(http.MethodPut).Path("/api/v1/tasks/continue").HandlerFunc(ContinueAllTask)
|
||||||
r.Methods(http.MethodDelete).Path("/api/v1/tasks/{id}").HandlerFunc(DeleteTask)
|
r.Methods(http.MethodDelete).Path("/api/v1/tasks/{id}").HandlerFunc(DeleteTask)
|
||||||
r.Methods(http.MethodDelete).Path("/api/v1/tasks").HandlerFunc(DeleteTasks)
|
r.Methods(http.MethodDelete).Path("/api/v1/tasks").HandlerFunc(DeleteTasks)
|
||||||
r.Methods(http.MethodGet).Path("/api/v1/tasks/{id}").HandlerFunc(GetTask)
|
r.Methods(http.MethodGet).Path("/api/v1/tasks/{id}").HandlerFunc(GetTask)
|
||||||
@@ -154,7 +141,7 @@ func BuildServer(startCfg *model.StartConfig) (*http.Server, net.Listener, error
|
|||||||
defer func() {
|
defer func() {
|
||||||
if v := recover(); v != nil {
|
if v := recover(); v != nil {
|
||||||
err := errors.WithStack(fmt.Errorf("%v", v))
|
err := errors.WithStack(fmt.Errorf("%v", v))
|
||||||
Downloader.Logger.Error().Stack().Err(err).Msgf("http server panic: %s %s", r.Method, r.RequestURI)
|
Downloader.Logger.Error().Stack().Err(err).Msg("http server panic")
|
||||||
WriteJson(w, model.NewErrorResult(err.Error(), model.CodeError))
|
WriteJson(w, model.NewErrorResult(err.Error(), model.CodeError))
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|||||||
+10
-29
@@ -63,18 +63,6 @@ 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)
|
||||||
@@ -137,21 +125,6 @@ func TestCreateDirectTask(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCreateDirectTaskBatch(t *testing.T) {
|
|
||||||
doTest(func() {
|
|
||||||
reqs := make([]*base.Request, 0)
|
|
||||||
for i := 0; i < 5; 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
|
||||||
@@ -582,8 +555,16 @@ func doTest(handler func()) {
|
|||||||
Downloader.Clear()
|
Downloader.Clear()
|
||||||
}()
|
}()
|
||||||
defer func() {
|
defer func() {
|
||||||
Downloader.Pause(nil)
|
Downloader.PauseAll()
|
||||||
Downloader.Delete(nil, true)
|
|
||||||
|
tasks := Downloader.GetTasks()
|
||||||
|
taskIds := make([]string, len(tasks))
|
||||||
|
for i, task := range tasks {
|
||||||
|
taskIds[i] = task.ID
|
||||||
|
}
|
||||||
|
for _, id := range taskIds {
|
||||||
|
Downloader.Delete(id, true)
|
||||||
|
}
|
||||||
os.RemoveAll(cfg.StorageDir)
|
os.RemoveAll(cfg.StorageDir)
|
||||||
}()
|
}()
|
||||||
taskReq.URL = "http://" + fileListener.Addr().String() + "/" + test.BuildName
|
taskReq.URL = "http://" + fileListener.Addr().String() + "/" + test.BuildName
|
||||||
|
|||||||
@@ -12,17 +12,3 @@ func MapToStruct(s any, v any) error {
|
|||||||
}
|
}
|
||||||
return json.Unmarshal(b, v)
|
return json.Unmarshal(b, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
func DeepClone[T any](v *T) *T {
|
|
||||||
if v == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var t T
|
|
||||||
b, err := json.Marshal(v)
|
|
||||||
if err != nil {
|
|
||||||
return &t
|
|
||||||
}
|
|
||||||
json.Unmarshal(b, &t)
|
|
||||||
return &t
|
|
||||||
}
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
package util
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestDeepClone(t *testing.T) {
|
|
||||||
type user struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Age int `json:"age"`
|
|
||||||
|
|
||||||
v int
|
|
||||||
}
|
|
||||||
|
|
||||||
type args[T any] struct {
|
|
||||||
v *T
|
|
||||||
}
|
|
||||||
type testCase[T any] struct {
|
|
||||||
name string
|
|
||||||
args args[T]
|
|
||||||
want *T
|
|
||||||
}
|
|
||||||
tests := []testCase[user]{
|
|
||||||
{
|
|
||||||
name: "case 1",
|
|
||||||
args: args[user]{
|
|
||||||
v: &user{
|
|
||||||
Name: "test",
|
|
||||||
Age: 10,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
want: &user{
|
|
||||||
Name: "test",
|
|
||||||
Age: 10,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "case 2",
|
|
||||||
args: args[user]{
|
|
||||||
v: &user{
|
|
||||||
Name: "test",
|
|
||||||
Age: 10,
|
|
||||||
v: 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
want: &user{
|
|
||||||
Name: "test",
|
|
||||||
Age: 10,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
if got := DeepClone(tt.args.v); !reflect.DeepEqual(got, tt.want) {
|
|
||||||
t.Errorf("DeepClone() = %v, want %v", got, tt.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+8
-1
@@ -8,12 +8,18 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const FileSchema = "FILE"
|
||||||
|
|
||||||
func ParseSchema(url string) string {
|
func ParseSchema(url string) string {
|
||||||
index := strings.Index(url, ":")
|
index := strings.Index(url, ":")
|
||||||
|
// if no schema or windows path like C:\a.txt, return FILE
|
||||||
if index == -1 || index == 1 {
|
if index == -1 || index == 1 {
|
||||||
return ""
|
return FileSchema
|
||||||
}
|
}
|
||||||
schema := url[:index]
|
schema := url[:index]
|
||||||
|
if schema == "data" {
|
||||||
|
schema, _ = ParseDataUri(url)
|
||||||
|
}
|
||||||
return strings.ToUpper(schema)
|
return strings.ToUpper(schema)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,6 +32,7 @@ func ParseDataUri(uri string) (string, []byte) {
|
|||||||
}
|
}
|
||||||
mime := matches[1]
|
mime := matches[1]
|
||||||
base64Data := matches[2]
|
base64Data := matches[2]
|
||||||
|
// 解码Base64数据
|
||||||
data, err := base64.StdEncoding.DecodeString(base64Data)
|
data, err := base64.StdEncoding.DecodeString(base64Data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil
|
return "", nil
|
||||||
|
|||||||
@@ -41,21 +41,21 @@ func TestParseSchema(t *testing.T) {
|
|||||||
args: args{
|
args: args{
|
||||||
url: "./url.go",
|
url: "./url.go",
|
||||||
},
|
},
|
||||||
want: "",
|
want: "FILE",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "data-uri",
|
name: "data-uri",
|
||||||
args: args{
|
args: args{
|
||||||
url: "data:application/x-bittorrent;base64,test",
|
url: "data:application/x-bittorrent;base64,test",
|
||||||
},
|
},
|
||||||
want: "DATA",
|
want: "APPLICATION/X-BITTORRENT",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "windows-path",
|
name: "windows-path",
|
||||||
args: args{
|
args: args{
|
||||||
url: "D:\\bt.torrent",
|
url: "D:\\bt.torrent",
|
||||||
},
|
},
|
||||||
want: "",
|
want: "FILE",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ apply plugin: 'kotlin-android'
|
|||||||
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
|
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 34
|
compileSdkVersion 33
|
||||||
ndkVersion flutter.ndkVersion
|
ndkVersion flutter.ndkVersion
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
|
|||||||
@@ -2,19 +2,12 @@
|
|||||||
package="com.gopeed">
|
package="com.gopeed">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
|
||||||
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
|
|
||||||
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
|
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
|
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
|
|
||||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:requestLegacyExternalStorage="true"
|
|
||||||
android:name="${applicationName}"
|
android:name="${applicationName}"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="Gopeed"
|
android:label="Gopeed">
|
||||||
android:extractNativeLibs="true">
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 181 KiB |
@@ -13,7 +13,6 @@
|
|||||||
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, ); }; };
|
||||||
@@ -35,7 +34,6 @@
|
|||||||
/* 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>"; };
|
||||||
@@ -65,7 +63,6 @@
|
|||||||
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;
|
||||||
};
|
};
|
||||||
@@ -133,7 +130,6 @@
|
|||||||
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>";
|
||||||
|
|||||||
@@ -129,20 +129,12 @@ Future<void> continueTask(String id) async {
|
|||||||
return _parse(() => _client.dio.put("/api/v1/tasks/$id/continue"), null);
|
return _parse(() => _client.dio.put("/api/v1/tasks/$id/continue"), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> pauseAllTasks(List<String>? ids) async {
|
Future<void> pauseAllTasks() async {
|
||||||
return _parse(
|
return _parse(() => _client.dio.put("/api/v1/tasks/pause"), null);
|
||||||
() => _client.dio.put("/api/v1/tasks/pause", queryParameters: {
|
|
||||||
"id": ids,
|
|
||||||
}),
|
|
||||||
null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> continueAllTasks(List<String>? ids) async {
|
Future<void> continueAllTasks() async {
|
||||||
return _parse(
|
return _parse(() => _client.dio.put("/api/v1/tasks/continue"), null);
|
||||||
() => _client.dio.put("/api/v1/tasks/continue", queryParameters: {
|
|
||||||
"id": ids,
|
|
||||||
}),
|
|
||||||
null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> deleteTask(String id, bool force) async {
|
Future<void> deleteTask(String id, bool force) async {
|
||||||
@@ -150,15 +142,6 @@ Future<void> deleteTask(String id, bool force) async {
|
|||||||
() => _client.dio.delete("/api/v1/tasks/$id?force=$force"), null);
|
() => _client.dio.delete("/api/v1/tasks/$id?force=$force"), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> deleteTasks(List<String>? ids, bool force) async {
|
|
||||||
return _parse(
|
|
||||||
() => _client.dio.delete("/api/v1/tasks", queryParameters: {
|
|
||||||
"id": ids,
|
|
||||||
"force": force,
|
|
||||||
}),
|
|
||||||
null);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<DownloaderConfig> getConfig() async {
|
Future<DownloaderConfig> getConfig() async {
|
||||||
return _parse(() => _client.dio.get("/api/v1/config"),
|
return _parse(() => _client.dio.get("/api/v1/config"),
|
||||||
(data) => DownloaderConfig.fromJson(data));
|
(data) => DownloaderConfig.fromJson(data));
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,7 +29,6 @@ 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,60 +39,39 @@ class HttpConfig {
|
|||||||
bool useServerCtime;
|
bool useServerCtime;
|
||||||
|
|
||||||
HttpConfig({
|
HttpConfig({
|
||||||
this.userAgent = '',
|
this.userAgent =
|
||||||
this.connections = 0,
|
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36',
|
||||||
|
this.connections = 16,
|
||||||
this.useServerCtime = false,
|
this.useServerCtime = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
class BtConfig {
|
class BtConfig {
|
||||||
int listenPort;
|
int listenPort = 0;
|
||||||
List<String> trackers;
|
List<String> trackers = [];
|
||||||
bool seedKeep;
|
|
||||||
double seedRatio;
|
|
||||||
int seedTime;
|
|
||||||
|
|
||||||
BtConfig({
|
BtConfig();
|
||||||
this.listenPort = 0,
|
|
||||||
this.trackers = const [],
|
|
||||||
this.seedKeep = false,
|
|
||||||
this.seedRatio = 0,
|
|
||||||
this.seedTime = 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
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;
|
|
||||||
bool defaultDirectDownload;
|
|
||||||
bool defaultBtClient;
|
|
||||||
|
|
||||||
ExtraConfigBt bt = ExtraConfigBt();
|
ExtraConfigBt bt = ExtraConfigBt();
|
||||||
|
|
||||||
ExtraConfig({
|
ExtraConfig();
|
||||||
this.themeMode = '',
|
|
||||||
this.locale = '',
|
|
||||||
this.lastDeleteTaskKeep = false,
|
|
||||||
this.defaultDirectDownload = false,
|
|
||||||
this.defaultBtClient = true,
|
|
||||||
});
|
|
||||||
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,6 +112,5 @@ 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);
|
||||||
}
|
}
|
||||||
@@ -37,8 +37,9 @@ Map<String, dynamic> _$ProtocolConfigToJson(ProtocolConfig instance) =>
|
|||||||
};
|
};
|
||||||
|
|
||||||
HttpConfig _$HttpConfigFromJson(Map<String, dynamic> json) => HttpConfig(
|
HttpConfig _$HttpConfigFromJson(Map<String, dynamic> json) => HttpConfig(
|
||||||
userAgent: json['userAgent'] as String? ?? '',
|
userAgent: json['userAgent'] as String? ??
|
||||||
connections: (json['connections'] as num?)?.toInt() ?? 0,
|
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36',
|
||||||
|
connections: (json['connections'] as num?)?.toInt() ?? 16,
|
||||||
useServerCtime: json['useServerCtime'] as bool? ?? false,
|
useServerCtime: json['useServerCtime'] as bool? ?? false,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -49,40 +50,25 @@ Map<String, dynamic> _$HttpConfigToJson(HttpConfig instance) =>
|
|||||||
'useServerCtime': instance.useServerCtime,
|
'useServerCtime': instance.useServerCtime,
|
||||||
};
|
};
|
||||||
|
|
||||||
BtConfig _$BtConfigFromJson(Map<String, dynamic> json) => BtConfig(
|
BtConfig _$BtConfigFromJson(Map<String, dynamic> json) => BtConfig()
|
||||||
listenPort: (json['listenPort'] as num?)?.toInt() ?? 0,
|
..listenPort = (json['listenPort'] as num).toInt()
|
||||||
trackers: (json['trackers'] as List<dynamic>?)
|
..trackers =
|
||||||
?.map((e) => e as String)
|
(json['trackers'] as List<dynamic>).map((e) => e as String).toList();
|
||||||
.toList() ??
|
|
||||||
const [],
|
|
||||||
seedKeep: json['seedKeep'] as bool? ?? false,
|
|
||||||
seedRatio: (json['seedRatio'] as num?)?.toDouble() ?? 0,
|
|
||||||
seedTime: (json['seedTime'] as num?)?.toInt() ?? 0,
|
|
||||||
);
|
|
||||||
|
|
||||||
Map<String, dynamic> _$BtConfigToJson(BtConfig instance) => <String, dynamic>{
|
Map<String, dynamic> _$BtConfigToJson(BtConfig instance) => <String, dynamic>{
|
||||||
'listenPort': instance.listenPort,
|
'listenPort': instance.listenPort,
|
||||||
'trackers': instance.trackers,
|
'trackers': instance.trackers,
|
||||||
'seedKeep': instance.seedKeep,
|
|
||||||
'seedRatio': instance.seedRatio,
|
|
||||||
'seedTime': instance.seedTime,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
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
|
||||||
lastDeleteTaskKeep: json['lastDeleteTaskKeep'] as bool? ?? false,
|
..bt = ExtraConfigBt.fromJson(json['bt'] as Map<String, dynamic>);
|
||||||
defaultDirectDownload: json['defaultDirectDownload'] as bool? ?? false,
|
|
||||||
defaultBtClient: json['defaultBtClient'] as bool? ?? true,
|
|
||||||
)..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,
|
|
||||||
'defaultDirectDownload': instance.defaultDirectDownload,
|
|
||||||
'defaultBtClient': instance.defaultBtClient,
|
|
||||||
'bt': instance.bt.toJson(),
|
'bt': instance.bt.toJson(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ class Options {
|
|||||||
Object? extra;
|
Object? extra;
|
||||||
|
|
||||||
Options({
|
Options({
|
||||||
this.name = '',
|
required this.name,
|
||||||
this.path = '',
|
required this.path,
|
||||||
this.selectFiles = const [],
|
this.selectFiles = const [],
|
||||||
this.extra,
|
this.extra,
|
||||||
});
|
});
|
||||||
@@ -24,16 +24,12 @@ class Options {
|
|||||||
|
|
||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
class OptsExtraHttp {
|
class OptsExtraHttp {
|
||||||
int connections;
|
int connections = 0;
|
||||||
bool autoTorrent;
|
bool autoTorrent = false;
|
||||||
|
|
||||||
OptsExtraHttp({
|
OptsExtraHttp();
|
||||||
this.connections = 0,
|
|
||||||
this.autoTorrent = false,
|
|
||||||
});
|
|
||||||
|
|
||||||
factory OptsExtraHttp.fromJson(Map<String, dynamic> json) =>
|
factory OptsExtraHttp.fromJson(Map<String, dynamic> json) =>
|
||||||
_$OptsExtraHttpFromJson(json);
|
_$OptsExtraHttpFromJson(json);
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => _$OptsExtraHttpToJson(this);
|
Map<String, dynamic> toJson() => _$OptsExtraHttpToJson(this);
|
||||||
}
|
}
|
||||||
@@ -7,8 +7,8 @@ part of 'options.dart';
|
|||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
Options _$OptionsFromJson(Map<String, dynamic> json) => Options(
|
Options _$OptionsFromJson(Map<String, dynamic> json) => Options(
|
||||||
name: json['name'] as String? ?? '',
|
name: json['name'] as String,
|
||||||
path: json['path'] as String? ?? '',
|
path: json['path'] as String,
|
||||||
selectFiles: (json['selectFiles'] as List<dynamic>?)
|
selectFiles: (json['selectFiles'] as List<dynamic>?)
|
||||||
?.map((e) => (e as num).toInt())
|
?.map((e) => (e as num).toInt())
|
||||||
.toList() ??
|
.toList() ??
|
||||||
@@ -34,10 +34,9 @@ Map<String, dynamic> _$OptionsToJson(Options instance) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
OptsExtraHttp _$OptsExtraHttpFromJson(Map<String, dynamic> json) =>
|
OptsExtraHttp _$OptsExtraHttpFromJson(Map<String, dynamic> json) =>
|
||||||
OptsExtraHttp(
|
OptsExtraHttp()
|
||||||
connections: (json['connections'] as num?)?.toInt() ?? 0,
|
..connections = (json['connections'] as num).toInt()
|
||||||
autoTorrent: json['autoTorrent'] as bool? ?? false,
|
..autoTorrent = json['autoTorrent'] as bool;
|
||||||
);
|
|
||||||
|
|
||||||
Map<String, dynamic> _$OptsExtraHttpToJson(OptsExtraHttp instance) =>
|
Map<String, dynamic> _$OptsExtraHttpToJson(OptsExtraHttp instance) =>
|
||||||
<String, dynamic>{
|
<String, dynamic>{
|
||||||
|
|||||||
@@ -6,16 +6,12 @@ part 'request.g.dart';
|
|||||||
class Request {
|
class Request {
|
||||||
String url;
|
String url;
|
||||||
Object? extra;
|
Object? extra;
|
||||||
Map<String, String>? labels;
|
Map<String, String>? labels = {};
|
||||||
RequestProxy? proxy;
|
|
||||||
bool skipVerifyCert;
|
|
||||||
|
|
||||||
Request({
|
Request({
|
||||||
required this.url,
|
required this.url,
|
||||||
this.extra,
|
this.extra,
|
||||||
this.labels,
|
this.labels,
|
||||||
this.proxy,
|
|
||||||
this.skipVerifyCert = false,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
factory Request.fromJson(Map<String, dynamic> json) =>
|
factory Request.fromJson(Map<String, dynamic> json) =>
|
||||||
@@ -26,15 +22,11 @@ class Request {
|
|||||||
|
|
||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
class ReqExtraHttp {
|
class ReqExtraHttp {
|
||||||
String method;
|
String method = 'GET';
|
||||||
Map<String, String> header;
|
Map<String, String> header = {};
|
||||||
String body;
|
String body = '';
|
||||||
|
|
||||||
ReqExtraHttp({
|
ReqExtraHttp();
|
||||||
this.method = 'GET',
|
|
||||||
this.header = const {},
|
|
||||||
this.body = '',
|
|
||||||
});
|
|
||||||
|
|
||||||
factory ReqExtraHttp.fromJson(Map<String, dynamic> json) =>
|
factory ReqExtraHttp.fromJson(Map<String, dynamic> json) =>
|
||||||
_$ReqExtraHttpFromJson(json);
|
_$ReqExtraHttpFromJson(json);
|
||||||
@@ -44,42 +36,12 @@ class ReqExtraHttp {
|
|||||||
|
|
||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
class ReqExtraBt {
|
class ReqExtraBt {
|
||||||
List<String> trackers;
|
List<String> trackers = [];
|
||||||
|
|
||||||
ReqExtraBt({
|
ReqExtraBt();
|
||||||
this.trackers = const [],
|
|
||||||
});
|
|
||||||
|
|
||||||
factory ReqExtraBt.fromJson(Map<String, dynamic> json) =>
|
factory ReqExtraBt.fromJson(Map<String, dynamic> json) =>
|
||||||
_$ReqExtraBtFromJson(json);
|
_$ReqExtraBtFromJson(json);
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => _$ReqExtraBtToJson(this);
|
Map<String, dynamic> toJson() => _$ReqExtraBtToJson(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
enum RequestProxyMode {
|
|
||||||
follow,
|
|
||||||
none,
|
|
||||||
custom,
|
|
||||||
}
|
|
||||||
|
|
||||||
@JsonSerializable()
|
|
||||||
class RequestProxy {
|
|
||||||
RequestProxyMode mode;
|
|
||||||
String scheme;
|
|
||||||
String host;
|
|
||||||
String usr;
|
|
||||||
String pwd;
|
|
||||||
|
|
||||||
RequestProxy({
|
|
||||||
this.mode = RequestProxyMode.follow,
|
|
||||||
this.scheme = 'http',
|
|
||||||
this.host = '',
|
|
||||||
this.usr = '',
|
|
||||||
this.pwd = '',
|
|
||||||
});
|
|
||||||
|
|
||||||
factory RequestProxy.fromJson(Map<String, dynamic> json) =>
|
|
||||||
_$RequestProxyFromJson(json);
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => _$RequestProxyToJson(this);
|
|
||||||
}
|
|
||||||
@@ -12,10 +12,6 @@ Request _$RequestFromJson(Map<String, dynamic> json) => Request(
|
|||||||
labels: (json['labels'] as Map<String, dynamic>?)?.map(
|
labels: (json['labels'] as Map<String, dynamic>?)?.map(
|
||||||
(k, e) => MapEntry(k, e as String),
|
(k, e) => MapEntry(k, e as String),
|
||||||
),
|
),
|
||||||
proxy: json['proxy'] == null
|
|
||||||
? null
|
|
||||||
: RequestProxy.fromJson(json['proxy'] as Map<String, dynamic>),
|
|
||||||
skipVerifyCert: json['skipVerifyCert'] as bool? ?? false,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$RequestToJson(Request instance) {
|
Map<String, dynamic> _$RequestToJson(Request instance) {
|
||||||
@@ -31,19 +27,13 @@ Map<String, dynamic> _$RequestToJson(Request instance) {
|
|||||||
|
|
||||||
writeNotNull('extra', instance.extra);
|
writeNotNull('extra', instance.extra);
|
||||||
writeNotNull('labels', instance.labels);
|
writeNotNull('labels', instance.labels);
|
||||||
writeNotNull('proxy', instance.proxy?.toJson());
|
|
||||||
val['skipVerifyCert'] = instance.skipVerifyCert;
|
|
||||||
return val;
|
return val;
|
||||||
}
|
}
|
||||||
|
|
||||||
ReqExtraHttp _$ReqExtraHttpFromJson(Map<String, dynamic> json) => ReqExtraHttp(
|
ReqExtraHttp _$ReqExtraHttpFromJson(Map<String, dynamic> json) => ReqExtraHttp()
|
||||||
method: json['method'] as String? ?? 'GET',
|
..method = json['method'] as String
|
||||||
header: (json['header'] as Map<String, dynamic>?)?.map(
|
..header = Map<String, String>.from(json['header'] as Map)
|
||||||
(k, e) => MapEntry(k, e as String),
|
..body = json['body'] as String;
|
||||||
) ??
|
|
||||||
const {},
|
|
||||||
body: json['body'] as String? ?? '',
|
|
||||||
);
|
|
||||||
|
|
||||||
Map<String, dynamic> _$ReqExtraHttpToJson(ReqExtraHttp instance) =>
|
Map<String, dynamic> _$ReqExtraHttpToJson(ReqExtraHttp instance) =>
|
||||||
<String, dynamic>{
|
<String, dynamic>{
|
||||||
@@ -52,38 +42,11 @@ Map<String, dynamic> _$ReqExtraHttpToJson(ReqExtraHttp instance) =>
|
|||||||
'body': instance.body,
|
'body': instance.body,
|
||||||
};
|
};
|
||||||
|
|
||||||
ReqExtraBt _$ReqExtraBtFromJson(Map<String, dynamic> json) => ReqExtraBt(
|
ReqExtraBt _$ReqExtraBtFromJson(Map<String, dynamic> json) => ReqExtraBt()
|
||||||
trackers: (json['trackers'] as List<dynamic>?)
|
..trackers =
|
||||||
?.map((e) => e as String)
|
(json['trackers'] as List<dynamic>).map((e) => e as String).toList();
|
||||||
.toList() ??
|
|
||||||
const [],
|
|
||||||
);
|
|
||||||
|
|
||||||
Map<String, dynamic> _$ReqExtraBtToJson(ReqExtraBt instance) =>
|
Map<String, dynamic> _$ReqExtraBtToJson(ReqExtraBt instance) =>
|
||||||
<String, dynamic>{
|
<String, dynamic>{
|
||||||
'trackers': instance.trackers,
|
'trackers': instance.trackers,
|
||||||
};
|
};
|
||||||
|
|
||||||
RequestProxy _$RequestProxyFromJson(Map<String, dynamic> json) => RequestProxy(
|
|
||||||
mode: $enumDecodeNullable(_$RequestProxyModeEnumMap, json['mode']) ??
|
|
||||||
RequestProxyMode.follow,
|
|
||||||
scheme: json['scheme'] as String? ?? 'http',
|
|
||||||
host: json['host'] as String? ?? '',
|
|
||||||
usr: json['usr'] as String? ?? '',
|
|
||||||
pwd: json['pwd'] as String? ?? '',
|
|
||||||
);
|
|
||||||
|
|
||||||
Map<String, dynamic> _$RequestProxyToJson(RequestProxy instance) =>
|
|
||||||
<String, dynamic>{
|
|
||||||
'mode': _$RequestProxyModeEnumMap[instance.mode]!,
|
|
||||||
'scheme': instance.scheme,
|
|
||||||
'host': instance.host,
|
|
||||||
'usr': instance.usr,
|
|
||||||
'pwd': instance.pwd,
|
|
||||||
};
|
|
||||||
|
|
||||||
const _$RequestProxyModeEnumMap = {
|
|
||||||
RequestProxyMode.follow: 'follow',
|
|
||||||
RequestProxyMode.none: 'none',
|
|
||||||
RequestProxyMode.custom: 'custom',
|
|
||||||
};
|
|
||||||
@@ -6,26 +6,19 @@ part 'task.g.dart';
|
|||||||
|
|
||||||
enum Status { ready, running, pause, wait, error, done }
|
enum Status { ready, running, pause, wait, error, done }
|
||||||
|
|
||||||
enum Protocol { http, bt }
|
|
||||||
|
|
||||||
@JsonSerializable(explicitToJson: true)
|
@JsonSerializable(explicitToJson: true)
|
||||||
class Task {
|
class Task {
|
||||||
String id;
|
String id;
|
||||||
String name;
|
|
||||||
Protocol? protocol;
|
|
||||||
Meta meta;
|
Meta meta;
|
||||||
Status status;
|
Status status;
|
||||||
bool uploading;
|
|
||||||
Progress progress;
|
Progress progress;
|
||||||
DateTime createdAt;
|
DateTime createdAt;
|
||||||
DateTime updatedAt;
|
DateTime updatedAt;
|
||||||
|
|
||||||
Task({
|
Task({
|
||||||
required this.id,
|
required this.id,
|
||||||
required this.name,
|
|
||||||
required this.meta,
|
required this.meta,
|
||||||
required this.status,
|
required this.status,
|
||||||
required this.uploading,
|
|
||||||
required this.progress,
|
required this.progress,
|
||||||
required this.createdAt,
|
required this.createdAt,
|
||||||
required this.updatedAt,
|
required this.updatedAt,
|
||||||
@@ -41,15 +34,11 @@ class Progress {
|
|||||||
int used;
|
int used;
|
||||||
int speed;
|
int speed;
|
||||||
int downloaded;
|
int downloaded;
|
||||||
int uploadSpeed;
|
|
||||||
int uploaded;
|
|
||||||
|
|
||||||
Progress({
|
Progress({
|
||||||
required this.used,
|
required this.used,
|
||||||
required this.speed,
|
required this.speed,
|
||||||
required this.downloaded,
|
required this.downloaded,
|
||||||
required this.uploadSpeed,
|
|
||||||
required this.uploaded,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
factory Progress.fromJson(Map<String, dynamic> json) =>
|
factory Progress.fromJson(Map<String, dynamic> json) =>
|
||||||
|
|||||||
@@ -8,36 +8,21 @@ part of 'task.dart';
|
|||||||
|
|
||||||
Task _$TaskFromJson(Map<String, dynamic> json) => Task(
|
Task _$TaskFromJson(Map<String, dynamic> json) => Task(
|
||||||
id: json['id'] as String,
|
id: json['id'] as String,
|
||||||
name: json['name'] as String,
|
|
||||||
meta: Meta.fromJson(json['meta'] as Map<String, dynamic>),
|
meta: Meta.fromJson(json['meta'] as Map<String, dynamic>),
|
||||||
status: $enumDecode(_$StatusEnumMap, json['status']),
|
status: $enumDecode(_$StatusEnumMap, json['status']),
|
||||||
uploading: json['uploading'] as bool,
|
|
||||||
progress: Progress.fromJson(json['progress'] as Map<String, dynamic>),
|
progress: Progress.fromJson(json['progress'] as Map<String, dynamic>),
|
||||||
createdAt: DateTime.parse(json['createdAt'] as String),
|
createdAt: DateTime.parse(json['createdAt'] as String),
|
||||||
updatedAt: DateTime.parse(json['updatedAt'] as String),
|
updatedAt: DateTime.parse(json['updatedAt'] as String),
|
||||||
)..protocol = $enumDecodeNullable(_$ProtocolEnumMap, json['protocol']);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$TaskToJson(Task instance) {
|
Map<String, dynamic> _$TaskToJson(Task instance) => <String, dynamic>{
|
||||||
final val = <String, dynamic>{
|
'id': instance.id,
|
||||||
'id': instance.id,
|
'meta': instance.meta.toJson(),
|
||||||
'name': instance.name,
|
'status': _$StatusEnumMap[instance.status]!,
|
||||||
};
|
'progress': instance.progress.toJson(),
|
||||||
|
'createdAt': instance.createdAt.toIso8601String(),
|
||||||
void writeNotNull(String key, dynamic value) {
|
'updatedAt': instance.updatedAt.toIso8601String(),
|
||||||
if (value != null) {
|
};
|
||||||
val[key] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
writeNotNull('protocol', _$ProtocolEnumMap[instance.protocol]);
|
|
||||||
val['meta'] = instance.meta.toJson();
|
|
||||||
val['status'] = _$StatusEnumMap[instance.status]!;
|
|
||||||
val['uploading'] = instance.uploading;
|
|
||||||
val['progress'] = instance.progress.toJson();
|
|
||||||
val['createdAt'] = instance.createdAt.toIso8601String();
|
|
||||||
val['updatedAt'] = instance.updatedAt.toIso8601String();
|
|
||||||
return val;
|
|
||||||
}
|
|
||||||
|
|
||||||
const _$StatusEnumMap = {
|
const _$StatusEnumMap = {
|
||||||
Status.ready: 'ready',
|
Status.ready: 'ready',
|
||||||
@@ -48,23 +33,14 @@ const _$StatusEnumMap = {
|
|||||||
Status.done: 'done',
|
Status.done: 'done',
|
||||||
};
|
};
|
||||||
|
|
||||||
const _$ProtocolEnumMap = {
|
|
||||||
Protocol.http: 'http',
|
|
||||||
Protocol.bt: 'bt',
|
|
||||||
};
|
|
||||||
|
|
||||||
Progress _$ProgressFromJson(Map<String, dynamic> json) => Progress(
|
Progress _$ProgressFromJson(Map<String, dynamic> json) => Progress(
|
||||||
used: (json['used'] as num).toInt(),
|
used: (json['used'] as num).toInt(),
|
||||||
speed: (json['speed'] as num).toInt(),
|
speed: (json['speed'] as num).toInt(),
|
||||||
downloaded: (json['downloaded'] as num).toInt(),
|
downloaded: (json['downloaded'] as num).toInt(),
|
||||||
uploadSpeed: (json['uploadSpeed'] as num).toInt(),
|
|
||||||
uploaded: (json['uploaded'] as num).toInt(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$ProgressToJson(Progress instance) => <String, dynamic>{
|
Map<String, dynamic> _$ProgressToJson(Progress instance) => <String, dynamic>{
|
||||||
'used': instance.used,
|
'used': instance.used,
|
||||||
'speed': instance.speed,
|
'speed': instance.speed,
|
||||||
'downloaded': instance.downloaded,
|
'downloaded': instance.downloaded,
|
||||||
'uploadSpeed': instance.uploadSpeed,
|
|
||||||
'uploaded': instance.uploaded,
|
|
||||||
};
|
};
|
||||||
@@ -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:launch_at_startup/launch_at_startup.dart';
|
import 'package:gopeed/database/entity.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';
|
||||||
@@ -16,21 +16,14 @@ import 'package:window_manager/window_manager.dart';
|
|||||||
|
|
||||||
import '../../../../api/api.dart';
|
import '../../../../api/api.dart';
|
||||||
import '../../../../api/model/downloader_config.dart';
|
import '../../../../api/model/downloader_config.dart';
|
||||||
import '../../../../api/model/request.dart';
|
|
||||||
import '../../../../core/common/start_config.dart';
|
import '../../../../core/common/start_config.dart';
|
||||||
import '../../../../core/libgopeed_boot.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';
|
||||||
import '../../../../util/util.dart';
|
import '../../../../util/util.dart';
|
||||||
import '../../../routes/app_pages.dart';
|
import '../../../routes/app_pages.dart';
|
||||||
import '../../create/controllers/create_controller.dart';
|
|
||||||
import '../../create/dto/create_router_params.dart';
|
|
||||||
import '../../redirect/views/redirect_view.dart';
|
|
||||||
|
|
||||||
const unixSocketPath = 'gopeed.sock';
|
const unixSocketPath = 'gopeed.sock';
|
||||||
|
|
||||||
@@ -80,7 +73,6 @@ 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;
|
||||||
@@ -106,16 +98,12 @@ 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
|
||||||
void onClose() {
|
void onClose() {
|
||||||
_linkSubscription?.cancel();
|
_linkSubscription?.cancel();
|
||||||
trayManager.removeListener(this);
|
trayManager.removeListener(this);
|
||||||
LibgopeedBoot.instance.stop();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -126,12 +114,6 @@ 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();
|
||||||
@@ -164,7 +146,8 @@ class AppController extends GetxController with WindowListener, TrayListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _initDeepLinks() async {
|
Future<void> _initDeepLinks() async {
|
||||||
if (Util.isWeb()) {
|
// currently only support android
|
||||||
|
if (!Util.isAndroid()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,13 +155,13 @@ class AppController extends GetxController with WindowListener, TrayListener {
|
|||||||
|
|
||||||
// Handle link when app is in warm state (front or background)
|
// Handle link when app is in warm state (front or background)
|
||||||
_linkSubscription = _appLinks.uriLinkStream.listen((uri) async {
|
_linkSubscription = _appLinks.uriLinkStream.listen((uri) async {
|
||||||
await _handleDeepLink(uri);
|
await _toCreate(uri);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Check initial link if app was in cold state (terminated)
|
// Check initial link if app was in cold state (terminated)
|
||||||
final uri = await _appLinks.getInitialLink();
|
final uri = await _appLinks.getInitialAppLink();
|
||||||
if (uri != null) {
|
if (uri != null) {
|
||||||
await _handleDeepLink(uri);
|
await _toCreate(uri);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -198,20 +181,10 @@ class AppController extends GetxController with WindowListener, TrayListener {
|
|||||||
} else if (Util.isMacos()) {
|
} else if (Util.isMacos()) {
|
||||||
await trayManager.setIcon('assets/tray_icon/icon_mac.png',
|
await trayManager.setIcon('assets/tray_icon/icon_mac.png',
|
||||||
isTemplate: true);
|
isTemplate: true);
|
||||||
} else if (Platform.environment.containsKey('FLATPAK_ID') ||
|
|
||||||
Platform.environment.containsKey('SNAP')) {
|
|
||||||
await trayManager.setIcon('com.gopeed.Gopeed');
|
|
||||||
} else {
|
} else {
|
||||||
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 => {
|
||||||
@@ -219,13 +192,14 @@ 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(null)},
|
onClick: (menuItem) async => {continueAllTasks()},
|
||||||
),
|
),
|
||||||
MenuItem(
|
MenuItem(
|
||||||
label: "pauseAll".tr,
|
label: "pauseAll".tr,
|
||||||
onClick: (menuItem) async => {pauseAllTasks(null)},
|
onClick: (menuItem) async => {pauseAllTasks()},
|
||||||
),
|
),
|
||||||
MenuItem(
|
MenuItem(
|
||||||
label: 'setting'.tr,
|
label: 'setting'.tr,
|
||||||
@@ -248,14 +222,7 @@ class AppController extends GetxController with WindowListener, TrayListener {
|
|||||||
MenuItem.separator(),
|
MenuItem.separator(),
|
||||||
MenuItem(
|
MenuItem(
|
||||||
label: 'exit'.tr,
|
label: 'exit'.tr,
|
||||||
onClick: (menuItem) async {
|
onClick: (menuItem) => {windowManager.destroy()},
|
||||||
try {
|
|
||||||
await LibgopeedBoot.instance.stop();
|
|
||||||
} catch (e) {
|
|
||||||
logger.w("libgopeed stop fail", e);
|
|
||||||
}
|
|
||||||
windowManager.destroy();
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
if (!Util.isLinux()) {
|
if (!Util.isLinux()) {
|
||||||
@@ -273,12 +240,16 @@ class AppController extends GetxController with WindowListener, TrayListener {
|
|||||||
|
|
||||||
FlutterForegroundTask.init(
|
FlutterForegroundTask.init(
|
||||||
androidNotificationOptions: AndroidNotificationOptions(
|
androidNotificationOptions: AndroidNotificationOptions(
|
||||||
channelId: 'gopeed_service',
|
channelId: 'gopeed_service',
|
||||||
channelName: 'Gopeed Background Service',
|
channelName: 'Gopeed Background Service',
|
||||||
channelImportance: NotificationChannelImportance.LOW,
|
channelImportance: NotificationChannelImportance.LOW,
|
||||||
showWhen: true,
|
showWhen: true,
|
||||||
priority: NotificationPriority.LOW,
|
priority: NotificationPriority.LOW,
|
||||||
),
|
iconData: const NotificationIconData(
|
||||||
|
resType: ResourceType.mipmap,
|
||||||
|
resPrefix: ResourcePrefix.ic,
|
||||||
|
name: 'launcher',
|
||||||
|
)),
|
||||||
iosNotificationOptions: const IOSNotificationOptions(
|
iosNotificationOptions: const IOSNotificationOptions(
|
||||||
showNotification: true,
|
showNotification: true,
|
||||||
playSound: false,
|
playSound: false,
|
||||||
@@ -298,47 +269,15 @@ class AppController extends GetxController with WindowListener, TrayListener {
|
|||||||
FlutterForegroundTask.startService(
|
FlutterForegroundTask.startService(
|
||||||
notificationTitle: "serviceTitle".tr,
|
notificationTitle: "serviceTitle".tr,
|
||||||
notificationText: "serviceText".tr,
|
notificationText: "serviceText".tr,
|
||||||
notificationIcon: const NotificationIconData(
|
|
||||||
resType: ResourceType.mipmap,
|
|
||||||
resPrefix: ResourcePrefix.ic,
|
|
||||||
name: 'launcher',
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _handleDeepLink(Uri uri) async {
|
Future<void> _toCreate(Uri uri) async {
|
||||||
if (uri.scheme == "gopeed") {
|
final path = (uri.scheme == "magnet" || uri.scheme == "http" || uri.scheme == "https")
|
||||||
if (uri.path == "/create") {
|
? uri.toString()
|
||||||
final params = uri.queryParameters["params"];
|
: (await toFile(uri.toString())).path;
|
||||||
if (params?.isNotEmpty == true) {
|
await Get.rootDelegate.offAndToNamed(Routes.CREATE, arguments: path);
|
||||||
final paramsJson = String.fromCharCodes(base64Decode(params!));
|
|
||||||
Get.rootDelegate.offAndToNamed(Routes.REDIRECT,
|
|
||||||
arguments: RedirectArgs(Routes.CREATE,
|
|
||||||
arguments:
|
|
||||||
CreateRouterParams.fromJson(jsonDecode(paramsJson))));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Get.rootDelegate.offAndToNamed(Routes.CREATE);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Get.rootDelegate.offAndToNamed(Routes.HOME);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
String path;
|
|
||||||
if (uri.scheme == "magnet" ||
|
|
||||||
uri.scheme == "http" ||
|
|
||||||
uri.scheme == "https") {
|
|
||||||
path = uri.toString();
|
|
||||||
} else if (uri.scheme == "file") {
|
|
||||||
path = Util.isWindows() ? uri.path.substring(1) : uri.path;
|
|
||||||
} else {
|
|
||||||
path = (await toFile(uri.toString())).path;
|
|
||||||
}
|
|
||||||
Get.rootDelegate.offAndToNamed(Routes.REDIRECT,
|
|
||||||
arguments: RedirectArgs(Routes.CREATE,
|
|
||||||
arguments: CreateRouterParams(req: Request(url: path))));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String runningAddress() {
|
String runningAddress() {
|
||||||
@@ -366,24 +305,22 @@ class AppController extends GetxController with WindowListener, TrayListener {
|
|||||||
return _defaultStartConfig!;
|
return _defaultStartConfig!;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<StartConfig> loadStartConfig() async {
|
Future<void> 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<DownloaderConfig> loadDownloaderConfig() async {
|
Future<void> loadDownloaderConfig() async {
|
||||||
try {
|
try {
|
||||||
downloaderConfig.value = await getConfig();
|
downloaderConfig.value = await getConfig();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.w("load downloader config fail", e, StackTrace.current);
|
logger.w("load downloader config fail", e);
|
||||||
downloaderConfig.value = DownloaderConfig();
|
downloaderConfig.value = DownloaderConfig();
|
||||||
}
|
}
|
||||||
await _initDownloaderConfig();
|
await _initDownloaderConfig();
|
||||||
return downloaderConfig.value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> trackerUpdate() async {
|
Future<void> trackerUpdate() async {
|
||||||
@@ -476,8 +413,12 @@ 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()) {
|
||||||
config.downloadDir = (await getExternalStorageDirectory())?.path ??
|
final downloadDir = (await DownloadsPath.downloadsDirectory())?.path;
|
||||||
(await getApplicationDocumentsDirectory()).path;
|
if (downloadDir != null) {
|
||||||
|
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 {
|
||||||
@@ -486,17 +427,6 @@ 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,
|
||||||
|
|||||||
@@ -3,19 +3,16 @@ import 'dart:typed_data';
|
|||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:gopeed/api/model/request.dart';
|
|
||||||
|
|
||||||
import '../../app/controllers/app_controller.dart';
|
|
||||||
|
|
||||||
class CreateController extends GetxController
|
class CreateController extends GetxController
|
||||||
with GetSingleTickerProviderStateMixin {
|
with GetSingleTickerProviderStateMixin {
|
||||||
|
// final files = [].obs;
|
||||||
final RxList fileInfos = [].obs;
|
final RxList fileInfos = [].obs;
|
||||||
final RxList openedFolders = [].obs;
|
final RxList openedFolders = [].obs;
|
||||||
final selectedIndexes = <int>[].obs;
|
final selectedIndexes = <int>[].obs;
|
||||||
final isConfirming = false.obs;
|
final isConfirming = false.obs;
|
||||||
final showAdvanced = false.obs;
|
final showAdvanced = false.obs;
|
||||||
final directDownload = false.obs;
|
final directDownload = false.obs;
|
||||||
final proxyConfig = Rx<RequestProxy?>(null);
|
|
||||||
late TabController advancedTabController;
|
late TabController advancedTabController;
|
||||||
final oldUrl = "".obs;
|
final oldUrl = "".obs;
|
||||||
final fileDataUri = "".obs;
|
final fileDataUri = "".obs;
|
||||||
@@ -24,11 +21,6 @@ class CreateController extends GetxController
|
|||||||
void onInit() {
|
void onInit() {
|
||||||
super.onInit();
|
super.onInit();
|
||||||
advancedTabController = TabController(length: 2, vsync: this);
|
advancedTabController = TabController(length: 2, vsync: this);
|
||||||
directDownload.value = Get.find<AppController>()
|
|
||||||
.downloaderConfig
|
|
||||||
.value
|
|
||||||
.extra
|
|
||||||
.defaultDirectDownload;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
import 'package:json_annotation/json_annotation.dart';
|
|
||||||
|
|
||||||
import '../../../../api/model/options.dart';
|
|
||||||
import '../../../../api/model/request.dart';
|
|
||||||
|
|
||||||
part 'create_router_params.g.dart';
|
|
||||||
|
|
||||||
@JsonSerializable(explicitToJson: true)
|
|
||||||
class CreateRouterParams {
|
|
||||||
Request? req;
|
|
||||||
Options? opt;
|
|
||||||
|
|
||||||
CreateRouterParams({
|
|
||||||
this.req,
|
|
||||||
this.opt,
|
|
||||||
});
|
|
||||||
|
|
||||||
factory CreateRouterParams.fromJson(
|
|
||||||
Map<String, dynamic> json,
|
|
||||||
) =>
|
|
||||||
_$CreateRouterParamsFromJson(json);
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => _$CreateRouterParamsToJson(this);
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
|
|
||||||
part of 'create_router_params.dart';
|
|
||||||
|
|
||||||
// **************************************************************************
|
|
||||||
// JsonSerializableGenerator
|
|
||||||
// **************************************************************************
|
|
||||||
|
|
||||||
CreateRouterParams _$CreateRouterParamsFromJson(Map<String, dynamic> json) =>
|
|
||||||
CreateRouterParams(
|
|
||||||
req: json['req'] == null
|
|
||||||
? null
|
|
||||||
: Request.fromJson(json['req'] as Map<String, dynamic>),
|
|
||||||
opt: json['opt'] == null
|
|
||||||
? null
|
|
||||||
: Options.fromJson(json['opt'] as Map<String, dynamic>),
|
|
||||||
);
|
|
||||||
|
|
||||||
Map<String, dynamic> _$CreateRouterParamsToJson(CreateRouterParams instance) {
|
|
||||||
final val = <String, dynamic>{};
|
|
||||||
|
|
||||||
void writeNotNull(String key, dynamic value) {
|
|
||||||
if (value != null) {
|
|
||||||
val[key] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
writeNotNull('req', instance.req?.toJson());
|
|
||||||
writeNotNull('opt', instance.opt?.toJson());
|
|
||||||
return val;
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,4 @@
|
|||||||
import 'dart:convert';
|
import 'package:autoscale_tabbarview/autoscale_tabbarview.dart';
|
||||||
|
|
||||||
import 'package:contentsize_tabbarview/contentsize_tabbarview.dart';
|
|
||||||
import 'package:desktop_drop/desktop_drop.dart';
|
import 'package:desktop_drop/desktop_drop.dart';
|
||||||
import 'package:file_picker/file_picker.dart';
|
import 'package:file_picker/file_picker.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@@ -14,19 +12,16 @@ import '../../../../api/model/create_task.dart';
|
|||||||
import '../../../../api/model/options.dart';
|
import '../../../../api/model/options.dart';
|
||||||
import '../../../../api/model/request.dart';
|
import '../../../../api/model/request.dart';
|
||||||
import '../../../../api/model/resolve_result.dart';
|
import '../../../../api/model/resolve_result.dart';
|
||||||
import '../../../../api/model/task.dart';
|
|
||||||
import '../../../../database/database.dart';
|
import '../../../../database/database.dart';
|
||||||
import '../../../../util/input_formatter.dart';
|
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_tree_view.dart';
|
import '../../../views/file_list_view.dart';
|
||||||
import '../../app/controllers/app_controller.dart';
|
import '../../app/controllers/app_controller.dart';
|
||||||
import '../../history/views/history_view.dart';
|
import '../../history/views/history_view.dart';
|
||||||
import '../controllers/create_controller.dart';
|
import '../controllers/create_controller.dart';
|
||||||
import '../dto/create_router_params.dart';
|
|
||||||
|
|
||||||
class CreateView extends GetView<CreateController> {
|
class CreateView extends GetView<CreateController> {
|
||||||
final _confirmFormKey = GlobalKey<FormState>();
|
final _confirmFormKey = GlobalKey<FormState>();
|
||||||
@@ -36,30 +31,13 @@ class CreateView extends GetView<CreateController> {
|
|||||||
final _connectionsController = TextEditingController();
|
final _connectionsController = TextEditingController();
|
||||||
final _pathController = TextEditingController();
|
final _pathController = TextEditingController();
|
||||||
final _confirmController = RoundedLoadingButtonController();
|
final _confirmController = RoundedLoadingButtonController();
|
||||||
final _proxyIpController = TextEditingController();
|
final _httpUaController = TextEditingController();
|
||||||
final _proxyPortController = TextEditingController();
|
final _httpCookieController = TextEditingController();
|
||||||
final _proxyUsrController = TextEditingController();
|
final _httpRefererController = TextEditingController();
|
||||||
final _proxyPwdController = TextEditingController();
|
|
||||||
final _httpHeaderControllers = [
|
|
||||||
(
|
|
||||||
name: TextEditingController(text: "User-Agent"),
|
|
||||||
value: TextEditingController()
|
|
||||||
),
|
|
||||||
(
|
|
||||||
name: TextEditingController(text: "Cookie"),
|
|
||||||
value: TextEditingController()
|
|
||||||
),
|
|
||||||
(
|
|
||||||
name: TextEditingController(text: "Referer"),
|
|
||||||
value: TextEditingController()
|
|
||||||
),
|
|
||||||
];
|
|
||||||
final _btTrackerController = TextEditingController();
|
final _btTrackerController = TextEditingController();
|
||||||
|
|
||||||
final _availableSchemes = ["http:", "https:", "magnet:"];
|
final _availableSchemes = ["http:", "https:", "magnet:"];
|
||||||
|
|
||||||
final _skipVerifyCertController = false.obs;
|
|
||||||
|
|
||||||
CreateView({Key? key}) : super(key: key);
|
CreateView({Key? key}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -75,91 +53,32 @@ class CreateView extends GetView<CreateController> {
|
|||||||
_pathController.text = appController.downloaderConfig.value.downloadDir;
|
_pathController.text = appController.downloaderConfig.value.downloadDir;
|
||||||
}
|
}
|
||||||
|
|
||||||
final CreateRouterParams? routerParams = Get.rootDelegate.arguments();
|
final String? filePath = Get.rootDelegate.arguments();
|
||||||
if (routerParams?.req?.url.isNotEmpty ?? false) {
|
if (_urlController.text.isEmpty) {
|
||||||
// get url from route arguments
|
if (filePath?.isNotEmpty ?? false) {
|
||||||
final url = routerParams!.req!.url;
|
// get file path from route arguments
|
||||||
_urlController.text = url;
|
_urlController.text = filePath!;
|
||||||
_urlController.selection = TextSelection.fromPosition(
|
_urlController.selection = TextSelection.fromPosition(
|
||||||
TextPosition(offset: _urlController.text.length));
|
TextPosition(offset: _urlController.text.length));
|
||||||
final uppercaseUrl = url.toUpperCase();
|
} else {
|
||||||
Protocol? protocol;
|
// read clipboard
|
||||||
if (uppercaseUrl.startsWith("HTTP:") ||
|
Clipboard.getData('text/plain').then((value) {
|
||||||
uppercaseUrl.startsWith("HTTPS:")) {
|
if (value?.text?.isNotEmpty ?? false) {
|
||||||
protocol = Protocol.http;
|
if (_availableSchemes
|
||||||
}
|
.where((e) =>
|
||||||
if (uppercaseUrl.startsWith("MAGNET:") ||
|
value!.text!.startsWith(e) ||
|
||||||
uppercaseUrl.endsWith(".TORRENT")) {
|
value.text!.startsWith(e.toUpperCase()))
|
||||||
protocol = Protocol.bt;
|
.isNotEmpty) {
|
||||||
}
|
_urlController.text = value!.text!;
|
||||||
if (protocol != null) {
|
_urlController.selection = TextSelection.fromPosition(
|
||||||
final extraHandlers = {
|
TextPosition(offset: _urlController.text.length));
|
||||||
Protocol.http: () {
|
return;
|
||||||
final reqExtra = ReqExtraHttp.fromJson(
|
}
|
||||||
jsonDecode(jsonEncode(routerParams.req!.extra)));
|
|
||||||
_httpHeaderControllers.clear();
|
|
||||||
reqExtra.header.forEach((key, value) {
|
|
||||||
_httpHeaderControllers.add(
|
|
||||||
(
|
|
||||||
name: TextEditingController(text: key),
|
|
||||||
value: TextEditingController(text: value),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
_skipVerifyCertController.value = routerParams.req!.skipVerifyCert;
|
|
||||||
},
|
|
||||||
Protocol.bt: () {
|
|
||||||
final reqExtra = ReqExtraBt.fromJson(
|
|
||||||
jsonDecode(jsonEncode(routerParams.req!.extra)));
|
|
||||||
_btTrackerController.text = reqExtra.trackers.join("\n");
|
|
||||||
},
|
|
||||||
};
|
|
||||||
if (routerParams.req?.extra != null) {
|
|
||||||
extraHandlers[protocol]?.call();
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle options
|
recognizeMagnetUri(value!.text!);
|
||||||
if (routerParams.opt != null) {
|
|
||||||
_renameController.text = routerParams.opt!.name;
|
|
||||||
_pathController.text = routerParams.opt!.path;
|
|
||||||
|
|
||||||
final optionsHandlers = {
|
|
||||||
Protocol.http: () {
|
|
||||||
final opt = routerParams.opt!;
|
|
||||||
_renameController.text = opt.name;
|
|
||||||
_pathController.text = opt.path;
|
|
||||||
if (opt.extra != null) {
|
|
||||||
final optsExtraHttp =
|
|
||||||
OptsExtraHttp.fromJson(jsonDecode(jsonEncode(opt.extra)));
|
|
||||||
_connectionsController.text =
|
|
||||||
optsExtraHttp.connections.toString();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Protocol.bt: null,
|
|
||||||
};
|
|
||||||
if (routerParams.opt?.extra != null) {
|
|
||||||
optionsHandlers[protocol]?.call();
|
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
} else if (_urlController.text.isEmpty) {
|
|
||||||
// read clipboard
|
|
||||||
Clipboard.getData('text/plain').then((value) {
|
|
||||||
if (value?.text?.isNotEmpty ?? false) {
|
|
||||||
if (_availableSchemes
|
|
||||||
.where((e) =>
|
|
||||||
value!.text!.startsWith(e) ||
|
|
||||||
value.text!.startsWith(e.toUpperCase()))
|
|
||||||
.isNotEmpty) {
|
|
||||||
_urlController.text = value!.text!;
|
|
||||||
_urlController.selection = TextSelection.fromPosition(
|
|
||||||
TextPosition(offset: _urlController.text.length));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
recognizeMagnetUri(value!.text!);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
@@ -286,7 +205,7 @@ class CreateView extends GetView<CreateController> {
|
|||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Theme.of(context)
|
color: Theme.of(context)
|
||||||
.colorScheme
|
.colorScheme
|
||||||
.surface,
|
.background,
|
||||||
borderRadius:
|
borderRadius:
|
||||||
BorderRadius.circular(
|
BorderRadius.circular(
|
||||||
10.0),
|
10.0),
|
||||||
@@ -341,164 +260,7 @@ class CreateView extends GetView<CreateController> {
|
|||||||
() => Visibility(
|
() => Visibility(
|
||||||
visible: controller.showAdvanced.value,
|
visible: controller.showAdvanced.value,
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
children: [
|
||||||
Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Transform.translate(
|
|
||||||
offset: const Offset(-40, 0),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
const Icon(
|
|
||||||
Icons.wifi_2_bar,
|
|
||||||
color: Colors.grey,
|
|
||||||
),
|
|
||||||
const SizedBox(
|
|
||||||
width: 15,
|
|
||||||
),
|
|
||||||
SizedBox(
|
|
||||||
width: 150,
|
|
||||||
child: DropdownButton<
|
|
||||||
RequestProxyMode>(
|
|
||||||
hint: Text('proxy'.tr),
|
|
||||||
isExpanded: true,
|
|
||||||
value: controller
|
|
||||||
.proxyConfig.value?.mode,
|
|
||||||
onChanged: (value) async {
|
|
||||||
if (value != null) {
|
|
||||||
controller.proxyConfig
|
|
||||||
.value = RequestProxy()
|
|
||||||
..mode = value;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
items: [
|
|
||||||
DropdownMenuItem<
|
|
||||||
RequestProxyMode>(
|
|
||||||
value:
|
|
||||||
RequestProxyMode.follow,
|
|
||||||
child: Text(
|
|
||||||
'followSettings'.tr),
|
|
||||||
),
|
|
||||||
DropdownMenuItem<
|
|
||||||
RequestProxyMode>(
|
|
||||||
value:
|
|
||||||
RequestProxyMode.none,
|
|
||||||
child: Text('noProxy'.tr),
|
|
||||||
),
|
|
||||||
DropdownMenuItem<
|
|
||||||
RequestProxyMode>(
|
|
||||||
value:
|
|
||||||
RequestProxyMode.custom,
|
|
||||||
child:
|
|
||||||
Text('customProxy'.tr),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
))
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
...(controller.proxyConfig.value?.mode ==
|
|
||||||
RequestProxyMode.custom
|
|
||||||
? [
|
|
||||||
SizedBox(
|
|
||||||
width: 150,
|
|
||||||
child: DropdownButtonFormField<
|
|
||||||
String>(
|
|
||||||
value: controller
|
|
||||||
.proxyConfig.value?.scheme,
|
|
||||||
onChanged: (value) async {
|
|
||||||
if (value != null) {}
|
|
||||||
},
|
|
||||||
items: const [
|
|
||||||
DropdownMenuItem<String>(
|
|
||||||
value: 'http',
|
|
||||||
child: Text('HTTP'),
|
|
||||||
),
|
|
||||||
DropdownMenuItem<String>(
|
|
||||||
value: 'https',
|
|
||||||
child: Text('HTTPS'),
|
|
||||||
),
|
|
||||||
DropdownMenuItem<String>(
|
|
||||||
value: 'socks5',
|
|
||||||
child: Text('SOCKS5'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Row(children: [
|
|
||||||
Flexible(
|
|
||||||
child: TextFormField(
|
|
||||||
controller:
|
|
||||||
_proxyIpController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'server'.tr,
|
|
||||||
contentPadding:
|
|
||||||
const EdgeInsets.all(
|
|
||||||
0.0),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Padding(
|
|
||||||
padding: EdgeInsets.only(
|
|
||||||
left: 10)),
|
|
||||||
Flexible(
|
|
||||||
child: TextFormField(
|
|
||||||
controller:
|
|
||||||
_proxyPortController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'port'.tr,
|
|
||||||
contentPadding:
|
|
||||||
const EdgeInsets.all(
|
|
||||||
0.0),
|
|
||||||
),
|
|
||||||
keyboardType:
|
|
||||||
TextInputType.number,
|
|
||||||
inputFormatters: [
|
|
||||||
FilteringTextInputFormatter
|
|
||||||
.digitsOnly,
|
|
||||||
NumericalRangeFormatter(
|
|
||||||
min: 0, max: 65535),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
]),
|
|
||||||
Row(children: [
|
|
||||||
Flexible(
|
|
||||||
child: TextFormField(
|
|
||||||
controller:
|
|
||||||
_proxyUsrController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'username'.tr,
|
|
||||||
contentPadding:
|
|
||||||
const EdgeInsets.all(
|
|
||||||
0.0),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Padding(
|
|
||||||
padding: EdgeInsets.only(
|
|
||||||
left: 10)),
|
|
||||||
Flexible(
|
|
||||||
child: TextFormField(
|
|
||||||
controller:
|
|
||||||
_proxyPwdController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'password'.tr,
|
|
||||||
contentPadding:
|
|
||||||
const EdgeInsets.all(
|
|
||||||
0.0),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
])
|
|
||||||
]
|
|
||||||
: const []),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const Divider(),
|
|
||||||
TabBar(
|
TabBar(
|
||||||
controller: controller.advancedTabController,
|
controller: controller.advancedTabController,
|
||||||
tabs: const [
|
tabs: const [
|
||||||
@@ -510,105 +272,40 @@ class CreateView extends GetView<CreateController> {
|
|||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
DefaultTabController(
|
AutoScaleTabBarView(
|
||||||
length: 2,
|
controller: controller.advancedTabController,
|
||||||
child: ContentSizeTabBarView(
|
children: [
|
||||||
controller:
|
Column(
|
||||||
controller.advancedTabController,
|
children: [
|
||||||
children: [
|
TextFormField(
|
||||||
Column(
|
controller: _httpUaController,
|
||||||
children: [
|
decoration: const InputDecoration(
|
||||||
..._httpHeaderControllers.map((e) {
|
labelText: 'User-Agent',
|
||||||
return Row(
|
)),
|
||||||
children: [
|
TextFormField(
|
||||||
Flexible(
|
controller: _httpCookieController,
|
||||||
child: TextFormField(
|
decoration: const InputDecoration(
|
||||||
controller: e.name,
|
labelText: 'Cookie',
|
||||||
decoration: InputDecoration(
|
)),
|
||||||
hintText:
|
TextFormField(
|
||||||
'httpHeaderName'.tr,
|
controller: _httpRefererController,
|
||||||
),
|
decoration: const InputDecoration(
|
||||||
),
|
labelText: 'Referer',
|
||||||
),
|
)),
|
||||||
const Padding(
|
],
|
||||||
padding: EdgeInsets.only(
|
),
|
||||||
left: 10)),
|
Column(
|
||||||
Flexible(
|
children: [
|
||||||
child: TextFormField(
|
TextFormField(
|
||||||
controller: e.value,
|
controller: _btTrackerController,
|
||||||
decoration: InputDecoration(
|
maxLines: 5,
|
||||||
hintText:
|
decoration: InputDecoration(
|
||||||
'httpHeaderValue'.tr,
|
labelText: 'Trakers',
|
||||||
),
|
hintText: 'addTrackerHit'.tr,
|
||||||
),
|
)),
|
||||||
),
|
],
|
||||||
const Padding(
|
)
|
||||||
padding: EdgeInsets.only(
|
],
|
||||||
left: 10)),
|
|
||||||
IconButton(
|
|
||||||
icon: const Icon(Icons.add),
|
|
||||||
onPressed: () {
|
|
||||||
_httpHeaderControllers.add(
|
|
||||||
(
|
|
||||||
name:
|
|
||||||
TextEditingController(),
|
|
||||||
value:
|
|
||||||
TextEditingController(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
controller.showAdvanced
|
|
||||||
.update((val) => val);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
icon:
|
|
||||||
const Icon(Icons.remove),
|
|
||||||
onPressed: () {
|
|
||||||
if (_httpHeaderControllers
|
|
||||||
.length <=
|
|
||||||
1) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_httpHeaderControllers
|
|
||||||
.remove(e);
|
|
||||||
controller.showAdvanced
|
|
||||||
.update((val) => val);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
Padding(
|
|
||||||
padding:
|
|
||||||
const EdgeInsets.only(top: 10),
|
|
||||||
child: CompactCheckbox(
|
|
||||||
label: 'skipVerifyCert'.tr,
|
|
||||||
value: _skipVerifyCertController
|
|
||||||
.value,
|
|
||||||
onChanged: (bool? value) {
|
|
||||||
_skipVerifyCertController
|
|
||||||
.value = value ?? false;
|
|
||||||
},
|
|
||||||
textStyle: const TextStyle(
|
|
||||||
color: Colors.grey,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Column(
|
|
||||||
children: [
|
|
||||||
TextFormField(
|
|
||||||
controller: _btTrackerController,
|
|
||||||
maxLines: 5,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'Trackers',
|
|
||||||
hintText: 'addTrackerHit'.tr,
|
|
||||||
)),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
).paddingOnly(top: 16),
|
).paddingOnly(top: 16),
|
||||||
@@ -624,13 +321,23 @@ class CreateView extends GetView<CreateController> {
|
|||||||
Row(
|
Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
CompactCheckbox(
|
TextButton(
|
||||||
label: 'directDownload'.tr,
|
onPressed: () {
|
||||||
value: controller.directDownload.value,
|
controller.directDownload.value =
|
||||||
onChanged: (bool? value) {
|
!controller.directDownload.value;
|
||||||
controller.directDownload.value =
|
},
|
||||||
value ?? false;
|
child: Row(children: [
|
||||||
}),
|
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 =
|
||||||
@@ -698,7 +405,7 @@ class CreateView extends GetView<CreateController> {
|
|||||||
Util.isWeb() && controller.fileDataUri.isNotEmpty;
|
Util.isWeb() && controller.fileDataUri.isNotEmpty;
|
||||||
final submitUrl = isWebFileChosen
|
final submitUrl = isWebFileChosen
|
||||||
? controller.fileDataUri.value
|
? controller.fileDataUri.value
|
||||||
: _urlController.text.trim();
|
: _urlController.text;
|
||||||
|
|
||||||
final urls = Util.textToLines(submitUrl);
|
final urls = Util.textToLines(submitUrl);
|
||||||
// Add url to the history
|
// Add url to the history
|
||||||
@@ -708,7 +415,7 @@ class CreateView extends GetView<CreateController> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Check if is direct download, there has two ways to direct download
|
Check if is direct download, there has two ways to direct download
|
||||||
1. Direct download option is checked
|
1. Direct download option is checked
|
||||||
2. Muli line urls
|
2. Muli line urls
|
||||||
@@ -718,26 +425,18 @@ class CreateView extends GetView<CreateController> {
|
|||||||
if (isDirect) {
|
if (isDirect) {
|
||||||
await Future.wait(urls.map((url) {
|
await Future.wait(urls.map((url) {
|
||||||
return createTask(CreateTask(
|
return createTask(CreateTask(
|
||||||
req: Request(
|
req: Request(url: url, extra: parseReqExtra(url)),
|
||||||
url: url,
|
|
||||||
extra: parseReqExtra(url),
|
|
||||||
proxy: parseProxy(),
|
|
||||||
skipVerifyCert: _skipVerifyCertController.value,
|
|
||||||
),
|
|
||||||
opt: Options(
|
opt: Options(
|
||||||
name: isMultiLine ? "" : _renameController.text,
|
name: isMultiLine ? "" : _renameController.text,
|
||||||
path: _pathController.text,
|
path: _pathController.text,
|
||||||
selectFiles: [],
|
selectFiles: [],
|
||||||
extra: parseReqOptsExtra(),
|
extra: parseReqOptsExtra())));
|
||||||
)));
|
|
||||||
}));
|
}));
|
||||||
Get.rootDelegate.offNamed(Routes.TASK);
|
Get.rootDelegate.offNamed(Routes.TASK);
|
||||||
} else {
|
} else {
|
||||||
final rr = await resolve(Request(
|
final rr = await resolve(Request(
|
||||||
url: submitUrl,
|
url: submitUrl,
|
||||||
extra: parseReqExtra(_urlController.text),
|
extra: parseReqExtra(_urlController.text),
|
||||||
proxy: parseProxy(),
|
|
||||||
skipVerifyCert: _skipVerifyCertController.value,
|
|
||||||
));
|
));
|
||||||
await _showResolveDialog(rr);
|
await _showResolveDialog(rr);
|
||||||
}
|
}
|
||||||
@@ -750,37 +449,20 @@ class CreateView extends GetView<CreateController> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RequestProxy? parseProxy() {
|
|
||||||
if (controller.proxyConfig.value?.mode == RequestProxyMode.custom) {
|
|
||||||
return RequestProxy()
|
|
||||||
..mode = RequestProxyMode.custom
|
|
||||||
..scheme = _proxyIpController.text
|
|
||||||
..host = "${_proxyIpController.text}:${_proxyPortController.text}"
|
|
||||||
..usr = _proxyUsrController.text
|
|
||||||
..pwd = _proxyPwdController.text;
|
|
||||||
}
|
|
||||||
return controller.proxyConfig.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
Object? parseReqExtra(String url) {
|
Object? parseReqExtra(String url) {
|
||||||
Object? reqExtra;
|
Object? reqExtra;
|
||||||
if (controller.showAdvanced.value) {
|
if (controller.showAdvanced.value) {
|
||||||
switch (controller.advancedTabController.index) {
|
final u = Uri.parse(_urlController.text);
|
||||||
case 0:
|
if (u.scheme.startsWith("http")) {
|
||||||
final header = Map<String, String>.fromEntries(_httpHeaderControllers
|
reqExtra = ReqExtraHttp()
|
||||||
.map((e) => MapEntry(e.name.text, e.value.text)));
|
..header = {
|
||||||
header.removeWhere(
|
"User-Agent": _httpUaController.text,
|
||||||
(key, value) => key.trim().isEmpty || value.trim().isEmpty);
|
"Cookie": _httpCookieController.text,
|
||||||
if (header.isNotEmpty) {
|
"Referer": _httpRefererController.text,
|
||||||
reqExtra = ReqExtraHttp()..header = header;
|
};
|
||||||
}
|
} else {
|
||||||
break;
|
reqExtra = ReqExtraBt()
|
||||||
case 1:
|
..trackers = Util.textToLines(_btTrackerController.text);
|
||||||
if (_btTrackerController.text.trim().isNotEmpty) {
|
|
||||||
reqExtra = ReqExtraBt()
|
|
||||||
..trackers = Util.textToLines(_btTrackerController.text);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return reqExtra;
|
return reqExtra;
|
||||||
@@ -819,13 +501,7 @@ class CreateView extends GetView<CreateController> {
|
|||||||
child: Form(
|
child: Form(
|
||||||
key: createFormKey,
|
key: createFormKey,
|
||||||
autovalidateMode: AutovalidateMode.always,
|
autovalidateMode: AutovalidateMode.always,
|
||||||
child: FileTreeView(
|
child: FileListView(files: rr.res.files)),
|
||||||
files: rr.res.files,
|
|
||||||
initialValues: rr.res.files.asMap().keys.toList(),
|
|
||||||
onSelectionChanged: (List<int> values) {
|
|
||||||
controller.selectedIndexes.value = values;
|
|
||||||
},
|
|
||||||
)),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -869,7 +545,7 @@ class CreateView extends GetView<CreateController> {
|
|||||||
controller.selectedIndexes.map((index) {
|
controller.selectedIndexes.map((index) {
|
||||||
final file = rr.res.files[index];
|
final file = rr.res.files[index];
|
||||||
return createTask(CreateTask(
|
return createTask(CreateTask(
|
||||||
req: file.req!..proxy = parseProxy(),
|
req: file.req!,
|
||||||
opt: Options(
|
opt: Options(
|
||||||
name: file.name,
|
name: file.name,
|
||||||
path: path.join(_pathController.text,
|
path: path.join(_pathController.text,
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
import 'package:get/get.dart';
|
|
||||||
|
|
||||||
import '../controllers/redirect_controller.dart';
|
|
||||||
|
|
||||||
class RedirectBinding extends Bindings {
|
|
||||||
@override
|
|
||||||
void dependencies() {
|
|
||||||
Get.lazyPut<RedirectController>(
|
|
||||||
() => RedirectController(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
import 'package:get/get.dart';
|
|
||||||
|
|
||||||
class RedirectController extends GetxController {}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
|
|
||||||
import '../controllers/redirect_controller.dart';
|
|
||||||
|
|
||||||
class RedirectArgs {
|
|
||||||
final String page;
|
|
||||||
final dynamic arguments;
|
|
||||||
|
|
||||||
RedirectArgs(this.page, {this.arguments});
|
|
||||||
}
|
|
||||||
|
|
||||||
class RedirectView extends GetView<RedirectController> {
|
|
||||||
const RedirectView({Key? key}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final redirectArgs = Get.rootDelegate.arguments() as RedirectArgs;
|
|
||||||
// Waiting for previous page controller to delete, avoid deleting controller that route page after redirect
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
|
||||||
await Future.delayed(const Duration(milliseconds: 350));
|
|
||||||
Get.rootDelegate
|
|
||||||
.offAndToNamed(redirectArgs.page, arguments: redirectArgs.arguments);
|
|
||||||
});
|
|
||||||
return const SizedBox();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,23 +1,18 @@
|
|||||||
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:gopeed/app/views/copy_button.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 '../../../../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/scheme_register/scheme_register.dart';
|
|
||||||
import '../../../../util/util.dart';
|
import '../../../../util/util.dart';
|
||||||
import '../../../views/check_list_view.dart';
|
import '../../../views/check_list_view.dart';
|
||||||
import '../../../views/directory_selector.dart';
|
import '../../../views/directory_selector.dart';
|
||||||
@@ -39,22 +34,13 @@ class SettingView extends GetView<SettingController> {
|
|||||||
final startCfg = appController.startConfig;
|
final startCfg = appController.startConfig;
|
||||||
|
|
||||||
Timer? timer;
|
Timer? timer;
|
||||||
Future<bool> debounceSave(
|
debounceSave({bool needRestart = false}) {
|
||||||
{Future<String> Function()? check, bool needRestart = false}) {
|
var completer = Completer<void>();
|
||||||
var completer = Completer<bool>();
|
|
||||||
timer?.cancel();
|
timer?.cancel();
|
||||||
timer = Timer(const Duration(milliseconds: 1000), () async {
|
timer = Timer(const Duration(milliseconds: 1000), () {
|
||||||
if (check != null) {
|
|
||||||
final checkResult = await check();
|
|
||||||
if (checkResult.isNotEmpty) {
|
|
||||||
showErrorMessage(checkResult);
|
|
||||||
completer.complete(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
appController
|
appController
|
||||||
.saveConfig()
|
.saveConfig()
|
||||||
.then((_) => completer.complete(true))
|
.then(completer.complete)
|
||||||
.onError(completer.completeError);
|
.onError(completer.completeError);
|
||||||
if (needRestart) {
|
if (needRestart) {
|
||||||
showMessage('tip'.tr, 'effectAfterRestart'.tr);
|
showMessage('tip'.tr, 'effectAfterRestart'.tr);
|
||||||
@@ -81,7 +67,6 @@ 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(
|
||||||
@@ -110,82 +95,16 @@ class SettingView extends GetView<SettingController> {
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
final buildDefaultDirectDownload =
|
|
||||||
_buildConfigItem('defaultDirectDownload', () {
|
|
||||||
return appController.downloaderConfig.value.extra.defaultDirectDownload
|
|
||||||
? 'on'.tr
|
|
||||||
: 'off'.tr;
|
|
||||||
}, (Key key) {
|
|
||||||
return Container(
|
|
||||||
alignment: Alignment.centerLeft,
|
|
||||||
child: Switch(
|
|
||||||
value:
|
|
||||||
appController.downloaderConfig.value.extra.defaultDirectDownload,
|
|
||||||
onChanged: (bool value) async {
|
|
||||||
appController.downloaderConfig.update((val) {
|
|
||||||
val!.extra.defaultDirectDownload = value;
|
|
||||||
});
|
|
||||||
await debounceSave();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
buildBrowserExtension() {
|
buildBrowserExtension() {
|
||||||
return ListTile(
|
return ListTile(
|
||||||
title: Text('browserExtension'.tr),
|
title: Text('browserExtension'.tr),
|
||||||
subtitle: const Row(
|
subtitle: const OpenInNew(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
text: "Chrome",
|
||||||
children: [
|
url:
|
||||||
OpenInNew(
|
"https://chromewebstore.google.com/detail/gopeed/mijpgljlfcapndmchhjffkpckknofcnd",
|
||||||
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', () {
|
|
||||||
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 =
|
||||||
@@ -230,9 +149,8 @@ class SettingView extends GetView<SettingController> {
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
final buildHttpUseServerCtime = _buildConfigItem(
|
final buildHttpUseServerCtime = _buildConfigItem('useServerCtime'.tr,
|
||||||
'useServerCtime', () => httpConfig.useServerCtime ? 'on'.tr : 'off'.tr,
|
() => httpConfig.useServerCtime ? 'on'.tr : 'off'.tr, (Key key) {
|
||||||
(Key key) {
|
|
||||||
return Container(
|
return Container(
|
||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
child: Switch(
|
child: Switch(
|
||||||
@@ -359,100 +277,6 @@ class SettingView extends GetView<SettingController> {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
final buildBtSeedConfig = _buildConfigItem('seedConfig',
|
|
||||||
() => '${'seedKeep'.tr}(${btConfig.seedKeep ? 'on'.tr : 'off'.tr})',
|
|
||||||
(Key key) {
|
|
||||||
final seedRatioController =
|
|
||||||
TextEditingController(text: btConfig.seedRatio.toString());
|
|
||||||
seedRatioController.addListener(() {
|
|
||||||
if (seedRatioController.text.isNotEmpty) {
|
|
||||||
btConfig.seedRatio = double.parse(seedRatioController.text);
|
|
||||||
debounceSave();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
final seedTimeController =
|
|
||||||
TextEditingController(text: (btConfig.seedTime ~/ 60).toString());
|
|
||||||
seedTimeController.addListener(() {
|
|
||||||
if (seedTimeController.text.isNotEmpty) {
|
|
||||||
btConfig.seedTime = int.parse(seedTimeController.text) * 60;
|
|
||||||
debounceSave();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
SwitchListTile(
|
|
||||||
controlAffinity: ListTileControlAffinity.leading,
|
|
||||||
contentPadding: EdgeInsets.zero,
|
|
||||||
value: btConfig.seedKeep,
|
|
||||||
onChanged: (bool value) {
|
|
||||||
downloaderCfg.update((val) {
|
|
||||||
val!.protocolConfig.bt.seedKeep = value;
|
|
||||||
});
|
|
||||||
debounceSave();
|
|
||||||
},
|
|
||||||
title: Text('seedKeep'.tr)),
|
|
||||||
btConfig.seedKeep
|
|
||||||
? null
|
|
||||||
: TextField(
|
|
||||||
controller: seedRatioController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'seedRatio'.tr,
|
|
||||||
),
|
|
||||||
keyboardType:
|
|
||||||
const TextInputType.numberWithOptions(decimal: true),
|
|
||||||
inputFormatters: [
|
|
||||||
FilteringTextInputFormatter.allow(
|
|
||||||
RegExp(r'^\d+\.?\d{0,2}')),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
btConfig.seedKeep
|
|
||||||
? null
|
|
||||||
: TextField(
|
|
||||||
controller: seedTimeController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'seedTime'.tr,
|
|
||||||
),
|
|
||||||
keyboardType: TextInputType.number,
|
|
||||||
inputFormatters: [
|
|
||||||
FilteringTextInputFormatter.digitsOnly,
|
|
||||||
NumericalRangeFormatter(min: 0, max: 100000000),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
].where((e) => e != null).map((e) => e!).toList(),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
final buildBtDefaultClientConfig = !Util.isWindows()
|
|
||||||
? () => null
|
|
||||||
: _buildConfigItem('setAsDefaultBtClient', () {
|
|
||||||
return appController.downloaderConfig.value.extra.defaultBtClient
|
|
||||||
? 'on'.tr
|
|
||||||
: 'off'.tr;
|
|
||||||
}, (Key key) {
|
|
||||||
return Container(
|
|
||||||
alignment: Alignment.centerLeft,
|
|
||||||
child: Switch(
|
|
||||||
value:
|
|
||||||
appController.downloaderConfig.value.extra.defaultBtClient,
|
|
||||||
onChanged: (bool value) async {
|
|
||||||
try {
|
|
||||||
if (value) {
|
|
||||||
registerDefaultTorrentClient();
|
|
||||||
} else {
|
|
||||||
unregisterDefaultTorrentClient();
|
|
||||||
}
|
|
||||||
appController.downloaderConfig.update((val) {
|
|
||||||
val!.extra.defaultBtClient = value;
|
|
||||||
});
|
|
||||||
await debounceSave();
|
|
||||||
} catch (e) {
|
|
||||||
showErrorMessage(e);
|
|
||||||
logger.e('register default torrent client fail', e);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
// ui config items start
|
// ui config items start
|
||||||
final buildTheme = _buildConfigItem(
|
final buildTheme = _buildConfigItem(
|
||||||
@@ -798,41 +622,11 @@ 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;
|
||||||
|
|
||||||
final saved = await debounceSave(
|
await debounceSave(needRestart: true);
|
||||||
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;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -898,32 +692,6 @@ class SettingView extends GetView<SettingController> {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// advanced config log items start
|
|
||||||
buildLogsDir() {
|
|
||||||
return ListTile(
|
|
||||||
title: Text("logDirectory".tr),
|
|
||||||
subtitle: Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: TextField(
|
|
||||||
controller: TextEditingController(text: logsDir()),
|
|
||||||
enabled: false,
|
|
||||||
readOnly: true,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Util.isDesktop()
|
|
||||||
? IconButton(
|
|
||||||
icon: const Icon(Icons.folder_open),
|
|
||||||
onPressed: () {
|
|
||||||
launchUrl(Uri.file(logsDir()));
|
|
||||||
},
|
|
||||||
)
|
|
||||||
: CopyButton(logsDir()),
|
|
||||||
],
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
return Obx(() {
|
return Obx(() {
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
@@ -958,9 +726,7 @@ class SettingView extends GetView<SettingController> {
|
|||||||
children: _addDivider([
|
children: _addDivider([
|
||||||
buildDownloadDir(),
|
buildDownloadDir(),
|
||||||
buildMaxRunning(),
|
buildMaxRunning(),
|
||||||
buildDefaultDirectDownload(),
|
buildBrowserExtension()
|
||||||
buildBrowserExtension(),
|
|
||||||
buildAutoStartup(),
|
|
||||||
]),
|
]),
|
||||||
)),
|
)),
|
||||||
const Text('HTTP'),
|
const Text('HTTP'),
|
||||||
@@ -979,8 +745,6 @@ class SettingView extends GetView<SettingController> {
|
|||||||
buildBtListenPort(),
|
buildBtListenPort(),
|
||||||
buildBtTrackerSubscribeUrls(),
|
buildBtTrackerSubscribeUrls(),
|
||||||
buildBtTrackers(),
|
buildBtTrackers(),
|
||||||
buildBtSeedConfig(),
|
|
||||||
buildBtDefaultClientConfig(),
|
|
||||||
]),
|
]),
|
||||||
)),
|
)),
|
||||||
Text('ui'.tr),
|
Text('ui'.tr),
|
||||||
@@ -1037,13 +801,6 @@ class SettingView extends GetView<SettingController> {
|
|||||||
: null,
|
: null,
|
||||||
]),
|
]),
|
||||||
)),
|
)),
|
||||||
Text('developer'.tr),
|
|
||||||
Card(
|
|
||||||
child: Column(
|
|
||||||
children: _addDivider([
|
|
||||||
buildLogsDir(),
|
|
||||||
]),
|
|
||||||
)),
|
|
||||||
]),
|
]),
|
||||||
))
|
))
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,27 +1,5 @@
|
|||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:gopeed/api/model/task.dart';
|
|
||||||
|
|
||||||
class TaskController extends GetxController {
|
class TaskController extends GetxController {
|
||||||
final tabIndex = 0.obs;
|
final tabIndex = 0.obs;
|
||||||
final scaffoldKey = GlobalKey<ScaffoldState>();
|
|
||||||
final selectTask = Rx<Task?>(null);
|
|
||||||
|
|
||||||
@override
|
|
||||||
void onInit() {
|
|
||||||
super.onInit();
|
|
||||||
if (kIsWeb) {
|
|
||||||
BrowserContextMenu.disableContextMenu();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void onClose() {
|
|
||||||
super.onClose();
|
|
||||||
if (kIsWeb) {
|
|
||||||
BrowserContextMenu.enableContextMenu();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -9,13 +9,5 @@ class TaskDownloadingController extends TaskListController {
|
|||||||
Status.pause,
|
Status.pause,
|
||||||
Status.wait,
|
Status.wait,
|
||||||
Status.error
|
Status.error
|
||||||
], (a, b) {
|
], (a, b) => b.createdAt.compareTo(a.createdAt));
|
||||||
if (a.status == Status.running && b.status != Status.running) {
|
|
||||||
return -1;
|
|
||||||
} else if (a.status != Status.running && b.status == Status.running) {
|
|
||||||
return 1;
|
|
||||||
} else {
|
|
||||||
return b.updatedAt.compareTo(a.updatedAt);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
@@ -12,7 +12,6 @@ abstract class TaskListController extends GetxController {
|
|||||||
TaskListController(this.statuses, this.compare);
|
TaskListController(this.statuses, this.compare);
|
||||||
|
|
||||||
final tasks = <Task>[].obs;
|
final tasks = <Task>[].obs;
|
||||||
final selectedTaskIds = <String>[].obs;
|
|
||||||
final isRunning = false.obs;
|
final isRunning = false.obs;
|
||||||
|
|
||||||
late final Timer _timer;
|
late final Timer _timer;
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ class TaskDownloadedView extends GetView<TaskDownloadedController> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BuildTaskListView(
|
return BuildTaskListView(tasks: controller.tasks);
|
||||||
tasks: controller.tasks, selectedTaskIds: controller.selectedTaskIds);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -9,7 +9,6 @@ class TaskDownloadingView extends GetView<TaskDownloadingController> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BuildTaskListView(
|
return BuildTaskListView(tasks: controller.tasks);
|
||||||
tasks: controller.tasks, selectedTaskIds: controller.selectedTaskIds);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7,9 +7,10 @@ import 'package:url_launcher/url_launcher.dart';
|
|||||||
|
|
||||||
import '../../../../api/api.dart' as api;
|
import '../../../../api/api.dart' as api;
|
||||||
import '../../../../util/browser_download/browser_download.dart';
|
import '../../../../util/browser_download/browser_download.dart';
|
||||||
|
import '../../../../util/file_icon.dart';
|
||||||
|
import '../../../../util/icons.dart';
|
||||||
import '../../../../util/util.dart';
|
import '../../../../util/util.dart';
|
||||||
import '../../../views/breadcrumb_view.dart';
|
import '../../../views/breadcrumb_view.dart';
|
||||||
import '../../../views/file_icon.dart';
|
|
||||||
import '../controllers/task_files_controller.dart';
|
import '../controllers/task_files_controller.dart';
|
||||||
|
|
||||||
class TaskFilesView extends GetView<TaskFilesController> {
|
class TaskFilesView extends GetView<TaskFilesController> {
|
||||||
@@ -64,8 +65,9 @@ class TaskFilesView extends GetView<TaskFilesController> {
|
|||||||
[meta.opts.path, meta.res!.name, fileRelativePath]);
|
[meta.opts.path, meta.res!.name, fileRelativePath]);
|
||||||
final fileName = basename(filePath);
|
final fileName = basename(filePath);
|
||||||
return ListTile(
|
return ListTile(
|
||||||
leading:
|
leading: file.isDirectory
|
||||||
Icon(fileIcon(fileName, isFolder: file.isDirectory)),
|
? const Icon(Icons.folder)
|
||||||
|
: Icon(FaIcons.allIcons[findIcon(fileName)]),
|
||||||
title: Text(fileName),
|
title: Text(fileName),
|
||||||
subtitle: file.isDirectory
|
subtitle: file.isDirectory
|
||||||
? Text('items'.trParams({
|
? Text('items'.trParams({
|
||||||
@@ -87,7 +89,7 @@ class TaskFilesView extends GetView<TaskFilesController> {
|
|||||||
return [
|
return [
|
||||||
IconButton(
|
IconButton(
|
||||||
icon:
|
icon:
|
||||||
const Icon(Icons.open_in_new),
|
const Icon(Icons.play_circle),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
launchUrl(Uri.parse(accessUrl),
|
launchUrl(Uri.parse(accessUrl),
|
||||||
webOnlyWindowName:
|
webOnlyWindowName:
|
||||||
@@ -102,9 +104,14 @@ class TaskFilesView extends GetView<TaskFilesController> {
|
|||||||
}()
|
}()
|
||||||
: [
|
: [
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.open_in_new),
|
icon: const Icon(Icons.play_circle),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await OpenFilex.open(filePath);
|
print(filePath);
|
||||||
|
try {
|
||||||
|
await OpenFilex.open(filePath);
|
||||||
|
} catch (e) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.share),
|
icon: const Icon(Icons.share),
|
||||||
|
|||||||
@@ -1,13 +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 '../../../../api/model/task.dart';
|
|
||||||
import '../../../../util/file_explorer.dart';
|
|
||||||
import '../../../../util/util.dart';
|
|
||||||
import '../../../routes/app_pages.dart';
|
|
||||||
import '../../../views/copy_button.dart';
|
|
||||||
import '../controllers/task_controller.dart';
|
import '../controllers/task_controller.dart';
|
||||||
import '../controllers/task_downloaded_controller.dart';
|
import '../controllers/task_downloaded_controller.dart';
|
||||||
import '../controllers/task_downloading_controller.dart';
|
import '../controllers/task_downloading_controller.dart';
|
||||||
@@ -19,12 +12,9 @@ class TaskView extends GetView<TaskController> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final selectTask = controller.selectTask;
|
|
||||||
|
|
||||||
return DefaultTabController(
|
return DefaultTabController(
|
||||||
length: 2,
|
length: 2,
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
key: controller.scaffoldKey,
|
|
||||||
appBar: PreferredSize(
|
appBar: PreferredSize(
|
||||||
preferredSize: const Size.fromHeight(56),
|
preferredSize: const Size.fromHeight(56),
|
||||||
child: AppBar(
|
child: AppBar(
|
||||||
@@ -64,87 +54,7 @@ class TaskView extends GetView<TaskController> {
|
|||||||
TaskDownloadedView(),
|
TaskDownloadedView(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
endDrawer: Drawer(
|
|
||||||
// Add a ListView to the drawer. This ensures the user can scroll
|
|
||||||
// through the options in the drawer if there isn't enough vertical
|
|
||||||
// space to fit everything.
|
|
||||||
child: Obx(() => ListView(
|
|
||||||
// Important: Remove any padding from the ListView.
|
|
||||||
padding: EdgeInsets.zero,
|
|
||||||
children: [
|
|
||||||
SizedBox(
|
|
||||||
height: MediaQuery.of(context).padding.top + 65,
|
|
||||||
child: DrawerHeader(
|
|
||||||
child: Text(
|
|
||||||
'taskDetail'.tr,
|
|
||||||
style: Theme.of(context).textTheme.titleLarge,
|
|
||||||
)),
|
|
||||||
),
|
|
||||||
ListTile(
|
|
||||||
title: Text('taskName'.tr),
|
|
||||||
subtitle: buildTooltipSubtitle(selectTask.value?.name)),
|
|
||||||
ListTile(
|
|
||||||
title: Text('taskUrl'.tr),
|
|
||||||
subtitle:
|
|
||||||
buildTooltipSubtitle(selectTask.value?.meta.req.url),
|
|
||||||
trailing: CopyButton(selectTask.value?.meta.req.url),
|
|
||||||
),
|
|
||||||
ListTile(
|
|
||||||
title: Text('downloadPath'.tr),
|
|
||||||
subtitle:
|
|
||||||
buildTooltipSubtitle(selectTask.value?.explorerUrl),
|
|
||||||
trailing: IconButton(
|
|
||||||
icon: const Icon(Icons.folder_open),
|
|
||||||
onPressed: () {
|
|
||||||
selectTask.value?.explorer();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget buildTooltipSubtitle(String? text) {
|
|
||||||
final showText = text ?? "";
|
|
||||||
return Tooltip(
|
|
||||||
message: showText,
|
|
||||||
child: Text(
|
|
||||||
showText,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension TaskEnhance on Task {
|
|
||||||
bool get isFolder {
|
|
||||||
return meta.res?.name.isNotEmpty ?? false;
|
|
||||||
}
|
|
||||||
|
|
||||||
String get explorerUrl {
|
|
||||||
return path.join(Util.safeDir(meta.opts.path), Util.safeDir(name));
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> explorer() async {
|
|
||||||
if (Util.isDesktop()) {
|
|
||||||
await FileExplorer.openAndSelectFile(explorerUrl);
|
|
||||||
} else {
|
|
||||||
Get.rootDelegate.toNamed(Routes.TASK_FILES, parameters: {'id': id});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> open() async {
|
|
||||||
if (status != Status.done) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isFolder) {
|
|
||||||
await explorer();
|
|
||||||
} else {
|
|
||||||
await OpenFilex.open(explorerUrl);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
import 'package:gopeed/app/modules/task/views/task_files_view.dart';
|
||||||
|
import 'package:gopeed/app/modules/task/views/task_view.dart';
|
||||||
|
|
||||||
import '../modules/create/bindings/create_binding.dart';
|
import '../modules/create/bindings/create_binding.dart';
|
||||||
import '../modules/create/views/create_view.dart';
|
import '../modules/create/views/create_view.dart';
|
||||||
@@ -6,16 +8,12 @@ import '../modules/extension/bindings/extension_binding.dart';
|
|||||||
import '../modules/extension/views/extension_view.dart';
|
import '../modules/extension/views/extension_view.dart';
|
||||||
import '../modules/home/bindings/home_binding.dart';
|
import '../modules/home/bindings/home_binding.dart';
|
||||||
import '../modules/home/views/home_view.dart';
|
import '../modules/home/views/home_view.dart';
|
||||||
import '../modules/redirect/bindings/redirect_binding.dart';
|
|
||||||
import '../modules/redirect/views/redirect_view.dart';
|
|
||||||
import '../modules/root/bindings/root_binding.dart';
|
import '../modules/root/bindings/root_binding.dart';
|
||||||
import '../modules/root/views/root_view.dart';
|
import '../modules/root/views/root_view.dart';
|
||||||
import '../modules/setting/bindings/setting_binding.dart';
|
import '../modules/setting/bindings/setting_binding.dart';
|
||||||
import '../modules/setting/views/setting_view.dart';
|
import '../modules/setting/views/setting_view.dart';
|
||||||
import '../modules/task/bindings/task_binding.dart';
|
import '../modules/task/bindings/task_binding.dart';
|
||||||
import '../modules/task/bindings/task_files_binding.dart';
|
import '../modules/task/bindings/task_files_binding.dart';
|
||||||
import '../modules/task/views/task_files_view.dart';
|
|
||||||
import '../modules/task/views/task_view.dart';
|
|
||||||
|
|
||||||
part 'app_routes.dart';
|
part 'app_routes.dart';
|
||||||
|
|
||||||
@@ -70,11 +68,6 @@ class AppPages {
|
|||||||
page: () => CreateView(),
|
page: () => CreateView(),
|
||||||
binding: CreateBinding(),
|
binding: CreateBinding(),
|
||||||
),
|
),
|
||||||
GetPage(
|
|
||||||
name: _Paths.REDIRECT,
|
|
||||||
page: () => const RedirectView(),
|
|
||||||
binding: RedirectBinding(),
|
|
||||||
),
|
|
||||||
]),
|
]),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
Loaded 100 of 161 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user