mirror of
https://github.com/GopeedLab/gopeed.git
synced 2026-11-12 19:33:41 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
63210dce63 |
No files matched your search
@@ -1,8 +0,0 @@
|
|||||||
<!--
|
|
||||||
请根据以下模版提issue
|
|
||||||
-->
|
|
||||||
### 问题描述(必要)
|
|
||||||
### 版本号(必要)
|
|
||||||
### 操作系统(必要)
|
|
||||||
### 相关截图
|
|
||||||
### 相关日志
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
name-template: 'v1.2.0 🌈'
|
|
||||||
tag-template: 'v1.2.0'
|
|
||||||
categories:
|
|
||||||
- title: '🆕 Features'
|
|
||||||
labels:
|
|
||||||
- 'feat'
|
|
||||||
- 'feature'
|
|
||||||
- 'enhancement'
|
|
||||||
- title: '🐛 Bug Fixes'
|
|
||||||
labels:
|
|
||||||
- 'fix'
|
|
||||||
- 'bugfix'
|
|
||||||
- 'bug'
|
|
||||||
- title: '🧰 Maintenance'
|
|
||||||
label: 'chore'
|
|
||||||
- title: '📖 Document'
|
|
||||||
label: 'doc'
|
|
||||||
- title: '🚀 CI/CD'
|
|
||||||
label: 'ci'
|
|
||||||
change-template: '- $TITLE @$AUTHOR (#$NUMBER)'
|
|
||||||
version-resolver:
|
|
||||||
major:
|
|
||||||
labels:
|
|
||||||
- 'major'
|
|
||||||
minor:
|
|
||||||
labels:
|
|
||||||
- 'minor'
|
|
||||||
patch:
|
|
||||||
labels:
|
|
||||||
- 'patch'
|
|
||||||
default: patch
|
|
||||||
template: |
|
|
||||||
## Changes
|
|
||||||
|
|
||||||
$CHANGES
|
|
||||||
@@ -1,336 +0,0 @@
|
|||||||
name: build
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
release:
|
|
||||||
types: [published]
|
|
||||||
jobs:
|
|
||||||
get-release:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
outputs:
|
|
||||||
tag_name: ${{ steps.get-release.outputs.tag_name }}
|
|
||||||
upload_url: ${{ steps.get-release.outputs.upload_url }}
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- uses: thebritican/[email protected]
|
|
||||||
id: get-release
|
|
||||||
build-windows:
|
|
||||||
runs-on: windows-latest
|
|
||||||
needs: [get-release]
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- uses: actions/setup-go@v3
|
|
||||||
with:
|
|
||||||
go-version: "^1.19"
|
|
||||||
cache: true
|
|
||||||
- uses: subosito/flutter-action@v2
|
|
||||||
- name: Build
|
|
||||||
env:
|
|
||||||
VERSION: ${{ needs.get-release.outputs.tag_name }}
|
|
||||||
run: |
|
|
||||||
go build -tags nosqlite -ldflags="-w -s" -buildmode=c-shared -o ui/flutter/windows/libgopeed.dll github.com/monkeyWie/gopeed/bind/desktop
|
|
||||||
cd ui/flutter
|
|
||||||
flutter build windows
|
|
||||||
$mingw = "C:\ProgramData\Chocolatey\lib\mingw\tools\install\mingw64\bin"
|
|
||||||
$system = "C:\Windows\System32"
|
|
||||||
$release = "build\windows\runner\Release\"
|
|
||||||
cp $mingw\libstdc++-6.dll $release
|
|
||||||
cp $mingw\libgcc_s_seh-1.dll $release
|
|
||||||
cp $mingw\libwinpthread-1.dll $release
|
|
||||||
cp $system\msvcp140.dll $release
|
|
||||||
cp $system\vcruntime140.dll $release
|
|
||||||
cp $system\vcruntime140_1.dll $release
|
|
||||||
|
|
||||||
New-Item -Path build\windows\Output -ItemType Directory
|
|
||||||
Compress-Archive -Path "$release*" -DestinationPath build\windows\Output\Gopeed-$env:VERSION-windows-amd64.zip
|
|
||||||
|
|
||||||
cd build/windows
|
|
||||||
echo @"
|
|
||||||
; Script generated by the Inno Setup Script Wizard.
|
|
||||||
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
|
|
||||||
|
|
||||||
#define MyAppName "Gopeed"
|
|
||||||
#define MyAppVersion "$($env:VERSION.substring(1))"
|
|
||||||
#define MyAppPublisher "monkeyWie"
|
|
||||||
#define MyAppURL "https://gopeed.com"
|
|
||||||
#define MyAppExeName "gopeed.exe"
|
|
||||||
|
|
||||||
[Setup]
|
|
||||||
; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications.
|
|
||||||
; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
|
|
||||||
AppId={{5960F34D-1E42-402C-8C85-DE2FF24CBAE4}
|
|
||||||
AppName={#MyAppName}
|
|
||||||
AppVersion={#MyAppVersion}
|
|
||||||
;AppVerName={#MyAppName} {#MyAppVersion}
|
|
||||||
AppPublisher={#MyAppPublisher}
|
|
||||||
AppPublisherURL={#MyAppURL}
|
|
||||||
AppSupportURL={#MyAppURL}
|
|
||||||
AppUpdatesURL={#MyAppURL}
|
|
||||||
DefaultDirName={autopf}\gopeed
|
|
||||||
DisableProgramGroupPage=yes
|
|
||||||
LicenseFile=..\..\..\..\LICENSE
|
|
||||||
; Remove the following line to run in administrative install mode (install for all users.)
|
|
||||||
PrivilegesRequired=lowest
|
|
||||||
OutputBaseFilename=gopeed
|
|
||||||
SetupIconFile=..\..\assets\icon\icon.ico
|
|
||||||
Compression=lzma
|
|
||||||
SolidCompression=yes
|
|
||||||
WizardStyle=modern
|
|
||||||
|
|
||||||
[Languages]
|
|
||||||
Name: "english"; MessagesFile: "compiler:Default.isl"
|
|
||||||
|
|
||||||
[Tasks]
|
|
||||||
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
|
|
||||||
|
|
||||||
[Files]
|
|
||||||
Source: ".\runner\Release\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs
|
|
||||||
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
|
|
||||||
|
|
||||||
[Icons]
|
|
||||||
Name: "{autoprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
|
|
||||||
Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
|
|
||||||
|
|
||||||
[Run]
|
|
||||||
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent
|
|
||||||
"@ > setup.iss
|
|
||||||
iscc.exe setup.iss
|
|
||||||
mv "Output\gopeed.exe" "Output\Gopeed-$env:VERSION-windows-amd64-install.exe"
|
|
||||||
- name: Upload
|
|
||||||
uses: shogo82148/actions-upload-release-asset@v1
|
|
||||||
with:
|
|
||||||
upload_url: ${{ needs.get-release.outputs.upload_url }}
|
|
||||||
asset_path: ui/flutter/build/windows/Output/*
|
|
||||||
overwrite: true
|
|
||||||
build-macos-arm64-lib:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: [get-release]
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- uses: actions/setup-go@v3
|
|
||||||
with:
|
|
||||||
go-version: "^1.19"
|
|
||||||
cache: true
|
|
||||||
- run: mkdir -p ~/image-cache
|
|
||||||
- id: image-cache
|
|
||||||
uses: actions/cache@v3
|
|
||||||
with:
|
|
||||||
path: ~/image-cache
|
|
||||||
key: image-cache-xgo-v1.19
|
|
||||||
- if: steps.image-cache.outputs.cache-hit != 'true'
|
|
||||||
run: |
|
|
||||||
docker pull techknowlogick/xgo:go-1.19.x
|
|
||||||
docker save -o ~/image-cache/xgo.tar techknowlogick/xgo:go-1.19.x
|
|
||||||
- if: steps.image-cache.outputs.cache-hit == 'true'
|
|
||||||
run: docker load -i ~/image-cache/xgo.tar
|
|
||||||
- name: Build
|
|
||||||
run: |
|
|
||||||
go install src.techknowlogick.com/xgo@latest
|
|
||||||
xgo --targets=darwin/arm64 -tags="nosqlite" -ldflags="-w -s" -buildmode=c-shared -pkg=bind/desktop -out=libgopeed .
|
|
||||||
mv libgopeed-*.dylib libgopeed.dylib
|
|
||||||
- uses: actions/upload-artifact@v3
|
|
||||||
with:
|
|
||||||
name: macos-arm64-lib
|
|
||||||
path: libgopeed.dylib
|
|
||||||
build-macos:
|
|
||||||
runs-on: macos-latest
|
|
||||||
needs: [get-release, build-macos-arm64-lib]
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- uses: actions/setup-go@v3
|
|
||||||
with:
|
|
||||||
go-version: "^1.19"
|
|
||||||
cache: true
|
|
||||||
- uses: actions/setup-node@v3
|
|
||||||
with:
|
|
||||||
node-version: 16
|
|
||||||
- uses: subosito/flutter-action@v2
|
|
||||||
- name: Install appdmg
|
|
||||||
run: npm install -g appdmg
|
|
||||||
- uses: actions/download-artifact@v3
|
|
||||||
with:
|
|
||||||
name: macos-arm64-lib
|
|
||||||
path: ui/flutter/macos/Frameworks
|
|
||||||
- name: Build
|
|
||||||
env:
|
|
||||||
VERSION: ${{ needs.get-release.outputs.tag_name }}
|
|
||||||
run: |
|
|
||||||
PROJECT_DIR=$(pwd)
|
|
||||||
|
|
||||||
cd ui/flutter
|
|
||||||
flutter build macos
|
|
||||||
cd build/macos/Build/Products/Release
|
|
||||||
cat>appdmg.json<<EOF
|
|
||||||
{
|
|
||||||
"title": "Gopeed",
|
|
||||||
"icon": "Gopeed.app/Contents/Resources/AppIcon.icns",
|
|
||||||
"contents": [
|
|
||||||
{ "x": 448, "y": 344, "type": "link", "path": "/Applications" },
|
|
||||||
{ "x": 192, "y": 344, "type": "file", "path": "Gopeed.app" }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
EOF
|
|
||||||
mkdir dist
|
|
||||||
appdmg appdmg.json dist/Gopeed-$VERSION-macos-arm64.dmg
|
|
||||||
|
|
||||||
# amd64
|
|
||||||
cd $PROJECT_DIR
|
|
||||||
go build -tags nosqlite -ldflags="-w -s" -buildmode=c-shared -o bin/libgopeed.dylib github.com/monkeyWie/gopeed/bind/desktop
|
|
||||||
cp bin/libgopeed.dylib ui/flutter/build/macos/Build/Products/Release/Gopeed.app/Contents/Frameworks/
|
|
||||||
cd ui/flutter/build/macos/Build/Products/Release
|
|
||||||
appdmg appdmg.json dist/Gopeed-$VERSION-macos-amd64.dmg
|
|
||||||
- name: Upload
|
|
||||||
uses: shogo82148/actions-upload-release-asset@v1
|
|
||||||
with:
|
|
||||||
upload_url: ${{ needs.get-release.outputs.upload_url }}
|
|
||||||
asset_path: ui/flutter/build/macos/Build/Products/Release/dist/*
|
|
||||||
overwrite: true
|
|
||||||
build-linux:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: [get-release]
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- uses: actions/setup-go@v3
|
|
||||||
with:
|
|
||||||
go-version: "^1.19"
|
|
||||||
cache: true
|
|
||||||
- uses: subosito/flutter-action@v2
|
|
||||||
- run: |
|
|
||||||
sudo apt-get update -y
|
|
||||||
sudo apt-get install -y ninja-build libgtk-3-dev
|
|
||||||
- name: Build
|
|
||||||
env:
|
|
||||||
VERSION: ${{ needs.get-release.outputs.tag_name }}
|
|
||||||
run: |
|
|
||||||
go build -tags nosqlite -ldflags="-w -s" -buildmode=c-shared -o ui/flutter/linux/bundle/lib/libgopeed.so github.com/monkeyWie/gopeed/bind/desktop
|
|
||||||
cd ui/flutter
|
|
||||||
flutter build linux
|
|
||||||
mkdir -p debian/gui
|
|
||||||
cp assets/icon/icon_1024.png debian/gui/gopeed.png
|
|
||||||
cat>debian/debian.yaml<<EOF
|
|
||||||
flutter_app:
|
|
||||||
command: gopeed
|
|
||||||
arch: x64
|
|
||||||
parent: /usr/local/lib
|
|
||||||
control:
|
|
||||||
Package: gopeed
|
|
||||||
Version: ${VERSION:1}
|
|
||||||
Architecture: amd64
|
|
||||||
Essential: no
|
|
||||||
Priority: optional
|
|
||||||
Depends:
|
|
||||||
Maintainer: monkeyWie
|
|
||||||
Description: High speed downloader that supports all platforms.
|
|
||||||
EOF
|
|
||||||
|
|
||||||
cat>debian/gui/gopeed.desktop<<EOF
|
|
||||||
[Desktop Entry]
|
|
||||||
Version=${VERSION:1}
|
|
||||||
Name=Gopeed
|
|
||||||
GenericName=Gopeed
|
|
||||||
Comment=High speed downloader that supports all platforms.
|
|
||||||
Terminal=false
|
|
||||||
Type=Application
|
|
||||||
Categories=Utility
|
|
||||||
Keywords=Flutter;Downloader;
|
|
||||||
EOF
|
|
||||||
|
|
||||||
dart pub global activate flutter_to_debian
|
|
||||||
flutter_to_debian
|
|
||||||
mv debian/packages/gopeed*.deb debian/packages/Gopeed-$VERSION-linux-amd64.deb
|
|
||||||
- name: Upload
|
|
||||||
uses: shogo82148/actions-upload-release-asset@v1
|
|
||||||
with:
|
|
||||||
upload_url: ${{ needs.get-release.outputs.upload_url }}
|
|
||||||
asset_path: ui/flutter/debian/packages/*
|
|
||||||
overwrite: true
|
|
||||||
build-android:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: [get-release]
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- uses: actions/setup-go@v3
|
|
||||||
with:
|
|
||||||
go-version: "^1.19"
|
|
||||||
cache: true
|
|
||||||
- uses: actions/setup-java@v3
|
|
||||||
with:
|
|
||||||
distribution: "zulu"
|
|
||||||
java-version: "11"
|
|
||||||
- uses: subosito/flutter-action@v2
|
|
||||||
- name: Build
|
|
||||||
env:
|
|
||||||
VERSION: ${{ needs.get-release.outputs.tag_name }}
|
|
||||||
run: |
|
|
||||||
mkdir ui/flutter/android/app/libs
|
|
||||||
go install golang.org/x/mobile/cmd/gomobile@latest
|
|
||||||
gomobile init
|
|
||||||
gomobile bind -tags nosqlite -ldflags="-w -s" -o ui/flutter/android/app/libs/libgopeed.aar -target=android -androidapi 19 -javapkg=com.gopeed github.com/monkeyWie/gopeed/bind/mobile
|
|
||||||
cd ui/flutter
|
|
||||||
flutter build apk
|
|
||||||
mkdir dist
|
|
||||||
cp build/app/outputs/flutter-apk/app-release.apk dist/Gopeed-$VERSION-android.apk
|
|
||||||
- name: Upload
|
|
||||||
uses: shogo82148/actions-upload-release-asset@v1
|
|
||||||
with:
|
|
||||||
upload_url: ${{ needs.get-release.outputs.upload_url }}
|
|
||||||
asset_path: ui/flutter/dist/*
|
|
||||||
overwrite: true
|
|
||||||
build-web:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: [get-release]
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- uses: actions/setup-go@v3
|
|
||||||
with:
|
|
||||||
go-version: "^1.19"
|
|
||||||
cache: true
|
|
||||||
- uses: subosito/flutter-action@v2
|
|
||||||
- name: Build
|
|
||||||
env:
|
|
||||||
VERSION: ${{ needs.get-release.outputs.tag_name }}
|
|
||||||
run: |
|
|
||||||
cd ui/flutter
|
|
||||||
flutter build web
|
|
||||||
cd ../../
|
|
||||||
cp -r ui/flutter/build/web cmd/web/dist
|
|
||||||
mkdir -p dist/zip
|
|
||||||
goos_arr=(windows darwin linux)
|
|
||||||
goarch_arr=(386 amd64 arm64)
|
|
||||||
export CGO_ENABLED=0
|
|
||||||
for goos in "${goos_arr[@]}"; do
|
|
||||||
for goarch in "${goarch_arr[@]}"; do
|
|
||||||
name=gopeed-web-$VERSION-$goos-$goarch
|
|
||||||
dir="dist/$name/"
|
|
||||||
(GOOS=$goos GOARCH=$goarch go build -tags nosqlite,web -ldflags="-s -w" -o $dir github.com/monkeyWie/gopeed/cmd/web \
|
|
||||||
&& cd $dir \
|
|
||||||
&& file=$(ls -AU | head -1) \
|
|
||||||
&& mkdir $name \
|
|
||||||
&& mv $file $name/$(echo $file | sed -e "s/web/gopeed/g") \
|
|
||||||
&& zip -r ../zip/$name.zip * \
|
|
||||||
&& cd ../..) \
|
|
||||||
|| true
|
|
||||||
done
|
|
||||||
done
|
|
||||||
- name: Upload
|
|
||||||
uses: shogo82148/actions-upload-release-asset@v1
|
|
||||||
with:
|
|
||||||
upload_url: ${{ needs.get-release.outputs.upload_url }}
|
|
||||||
asset_path: dist/zip/*
|
|
||||||
overwrite: true
|
|
||||||
build-docker:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: [get-release]
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- name: Build
|
|
||||||
env:
|
|
||||||
VERSION: ${{ needs.get-release.outputs.tag_name }}
|
|
||||||
run: |
|
|
||||||
tag=liwei2633/gopeed:${VERSION:1}
|
|
||||||
docker build -t $tag .
|
|
||||||
docker login -u liwei2633 -p ${{ secrets.DOCKER_PASSWORD }}
|
|
||||||
docker push $tag
|
|
||||||
docker tag $tag liwei2633/gopeed:latest
|
|
||||||
docker push liwei2633/gopeed:latest
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
name: release
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
paths:
|
|
||||||
- "bind/**"
|
|
||||||
- "cmd/**"
|
|
||||||
- "internal/**"
|
|
||||||
- "pkg/**"
|
|
||||||
- "ui/**"
|
|
||||||
- ".github/workflows/release.yml"
|
|
||||||
- ".github/release-drafter.yml"
|
|
||||||
- "go.mod"
|
|
||||||
- "go.sum"
|
|
||||||
- "Dockerfile"
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
release:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
outputs:
|
|
||||||
tag_name: ${{ steps.create_release.outputs.tag_name }}
|
|
||||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- uses: actions/setup-go@v3
|
|
||||||
with:
|
|
||||||
go-version: "^1.19"
|
|
||||||
- uses: release-drafter/release-drafter@v5
|
|
||||||
id: create_release
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
name: test
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
paths:
|
|
||||||
- "bind/**"
|
|
||||||
- "cmd/**"
|
|
||||||
- "internal/**"
|
|
||||||
- "pkg/**"
|
|
||||||
- "ui/**"
|
|
||||||
- ".github/workflows/test.yml"
|
|
||||||
- "go.mod"
|
|
||||||
- "go.sum"
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
paths:
|
|
||||||
- "bind/**"
|
|
||||||
- "cmd/**"
|
|
||||||
- "internal/**"
|
|
||||||
- "pkg/**"
|
|
||||||
- "ui/**"
|
|
||||||
- ".github/workflows/test.yml"
|
|
||||||
- "go.mod"
|
|
||||||
- "go.sum"
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
# lint:
|
|
||||||
# name: Lint
|
|
||||||
# runs-on: ubuntu-latest
|
|
||||||
# steps:
|
|
||||||
# - uses: actions/setup-go@v2
|
|
||||||
# with:
|
|
||||||
# go-version: '^1.19'
|
|
||||||
# - uses: actions/checkout@v2
|
|
||||||
# - name: Lint Go Code
|
|
||||||
# run: |
|
|
||||||
# export PATH=$PATH:$(go env GOPATH)/bin # temporary fix. See https://github.com/actions/setup-go/issues/14
|
|
||||||
# go get -u golang.org/x/lint/golint
|
|
||||||
# golint -set_exit_status ./...
|
|
||||||
test:
|
|
||||||
name: Test
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- uses: actions/setup-go@v3
|
|
||||||
with:
|
|
||||||
go-version: "^1.19"
|
|
||||||
cache: true
|
|
||||||
- name: Run Unit tests.
|
|
||||||
run: go test $(go list ./... | grep -v /cmd/ | grep -v /bind/ ) -v -covermode=count -coverprofile=coverage.txt
|
|
||||||
- uses: codecov/codecov-action@v3
|
|
||||||
with:
|
|
||||||
files: ./coverage.txt
|
|
||||||
fail_ci_if_error: true
|
|
||||||
build:
|
|
||||||
name: Build
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
# needs: [lint, test]
|
|
||||||
needs: [test]
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- uses: actions/setup-go@v3
|
|
||||||
with:
|
|
||||||
go-version: "^1.19"
|
|
||||||
cache: true
|
|
||||||
- name: Build
|
|
||||||
run: go build github.com/monkeyWie/gopeed/cmd/gopeed
|
|
||||||
+2
-13
@@ -11,17 +11,6 @@
|
|||||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||||
*.out
|
*.out
|
||||||
|
|
||||||
.idea/
|
debug
|
||||||
|
|
||||||
bin/
|
.idea/
|
||||||
|
|
||||||
.torrent.db
|
|
||||||
.torrent.db-shm
|
|
||||||
.torrent.db-wal
|
|
||||||
|
|
||||||
*.data
|
|
||||||
*.db
|
|
||||||
|
|
||||||
.DS_Store
|
|
||||||
|
|
||||||
dist/
|
|
||||||
Vendored
+17
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
// Use IntelliSense to learn about possible attributes.
|
||||||
|
// Hover to view descriptions of existing attributes.
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Launch",
|
||||||
|
"type": "go",
|
||||||
|
"request": "launch",
|
||||||
|
"mode": "auto",
|
||||||
|
"program": "${fileDirname}",
|
||||||
|
"env": {},
|
||||||
|
"args": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Vendored
+9
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"go.useLanguageServer": true,
|
||||||
|
"[go]": {
|
||||||
|
"editor.formatOnSave": true,
|
||||||
|
"editor.codeActionsOnSave": {
|
||||||
|
"source.organizeImports": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
-21
@@ -1,21 +0,0 @@
|
|||||||
FROM cirrusci/flutter:3.3.7 AS flutter
|
|
||||||
WORKDIR /app
|
|
||||||
COPY ./ui/flutter/pubspec.yaml ./ui/flutter/pubspec.lock ./
|
|
||||||
RUN flutter pub get
|
|
||||||
COPY ./ui/flutter ./
|
|
||||||
RUN flutter build web
|
|
||||||
|
|
||||||
FROM golang:1.19.3 AS go
|
|
||||||
WORKDIR /app
|
|
||||||
COPY ./go.mod ./go.sum ./
|
|
||||||
RUN go mod download
|
|
||||||
COPY . .
|
|
||||||
COPY --from=flutter /app/build/web ./cmd/web/dist
|
|
||||||
RUN CGO_ENABLED=0 go build -tags nosqlite,web -ldflags="-s -w" -o dist/gopeed github.com/monkeyWie/gopeed/cmd/web
|
|
||||||
|
|
||||||
FROM alpine:3.14.2
|
|
||||||
LABEL maintainer="monkeyWie"
|
|
||||||
WORKDIR /app
|
|
||||||
COPY --from=go /app/dist/gopeed ./
|
|
||||||
EXPOSE 9999
|
|
||||||
ENTRYPOINT ["./gopeed"]
|
|
||||||
@@ -1,162 +1,20 @@
|
|||||||
[](https://github.com/monkeyWie/gopeed/actions?query=workflow%3Atest)
|
## gopeed
|
||||||
[](https://codecov.io/gh/monkeyWie/gopeed)
|
支持多协议(HTTP、BitTorrent)下载的客户端,提供命令行、RESTful API、WebSocket、Go类库方式来使用。
|
||||||
[](https://github.com/monkeyWie/gopeed/releases)
|
|
||||||
[](https://discord.gg/ZUJqJrwCGB)
|
|
||||||
|
|
||||||

|
## TODO
|
||||||
|
- [x] HTTP下载实现
|
||||||
|
- [ ] BitTorrent下载实现
|
||||||
|
- [x] .torrent文件解析
|
||||||
|
- [x] tracker协议实现
|
||||||
|
- [x] peer wire protocol协议实现
|
||||||
|
- [ ] DHT协议实现
|
||||||
|
- [ ] 磁力链接支持
|
||||||
|
- [ ] uTP协议实现
|
||||||
|
- [ ] 下载接口抽象(不关心具体协议)
|
||||||
|
- [ ] 支持自定义配置
|
||||||
|
- [ ] 限速功能实现
|
||||||
|
- [ ] 命令行工具提供
|
||||||
|
- [ ] RESTful服务提供
|
||||||
|
|
||||||
[English](/README.md) | [中文](/README_zh-CN.md)
|
## 参与
|
||||||
|
由于项目目前还未定型,代码可能随时有大的调整,所以暂不接受PR,当然如果有什么好的想法可以在issue区提出来。
|
||||||
## Introduction
|
|
||||||
|
|
||||||
Gopeed is a high-speed downloader developed by `Golang`+`Flutter`, which supports (HTTP, BitTorrent, Magnet) protocol downloads and supports all platforms.
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
**Supported platforms**
|
|
||||||
|
|
||||||
- [x] windows
|
|
||||||
- [x] macos
|
|
||||||
- [x] linux
|
|
||||||
- [x] android
|
|
||||||
- [ ] ios
|
|
||||||
- [x] web
|
|
||||||
- [x] docker
|
|
||||||
|
|
||||||
[To Release](https://github.com/monkeyWie/gopeed/releases/latest)
|
|
||||||
|
|
||||||
> Tips: If the macos open failed, please execute the `xattr -d com.apple.quarantine /Applications/Gopeed.app` command in the terminal
|
|
||||||
|
|
||||||
### Command tool
|
|
||||||
|
|
||||||
use `go install`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
go install github.com/monkeyWie/gopeed/cmd/gopeed
|
|
||||||
```
|
|
||||||
|
|
||||||
### Docker
|
|
||||||
|
|
||||||
#### Start directly
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker run -d -p 9999:9999 -v /path/to/download:/download liwei2633/gopeed
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Using Docker Compose
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
version: '3'
|
|
||||||
|
|
||||||
services:
|
|
||||||
gopeed:
|
|
||||||
container_name: gopeed
|
|
||||||
ports:
|
|
||||||
- 9999:9999
|
|
||||||
image: liwei2633/gopeed
|
|
||||||
volumes:
|
|
||||||
- ~/Downloads:/download
|
|
||||||
restart: unless-stopped
|
|
||||||
```
|
|
||||||
|
|
||||||
Use the command below to start container:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker compose up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Access Gopeed
|
|
||||||
|
|
||||||
When the docker container is running, you can access the web page through `http://localhost:9999`.
|
|
||||||
> Tip: Modify the download path to `/download` on the setting page to access the downloaded files on the host.
|
|
||||||
|
|
||||||
## Showcase
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## Development
|
|
||||||
|
|
||||||
This project is divided into two parts, the front end uses `flutter`, the back end uses `Golang`, and the two sides communicate through the `http` protocol. On the unix system, `unix socket` is used, and on the windows system, `tcp` protocol is used.
|
|
||||||
|
|
||||||
> The front code is located in the `ui/flutter` directory.
|
|
||||||
|
|
||||||
### Environment
|
|
||||||
|
|
||||||
1. Golang 1.19+
|
|
||||||
2. Flutter 3.0+
|
|
||||||
|
|
||||||
### Clone
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git clone [email protected]:monkeyWie/gopeed.git
|
|
||||||
```
|
|
||||||
|
|
||||||
### Build
|
|
||||||
|
|
||||||
#### Desktop
|
|
||||||
|
|
||||||
First, you need to configure the environment according to the [flutter desktop official website document](https://docs.flutter.dev/development/platform-integration/desktop), and then you need to prepare the `cgo` environment, which can be searched for yourself.
|
|
||||||
|
|
||||||
command:
|
|
||||||
|
|
||||||
- windows
|
|
||||||
|
|
||||||
```bash
|
|
||||||
go build -tags nosqlite -ldflags="-w -s" -buildmode=c-shared -o ui/flutter/windows/libgopeed.dll github.com/monkeyWie/gopeed/bind/desktop
|
|
||||||
cd ui/flutter
|
|
||||||
flutter build windows
|
|
||||||
```
|
|
||||||
|
|
||||||
- macos
|
|
||||||
|
|
||||||
```bash
|
|
||||||
go build -tags nosqlite -ldflags="-w -s" -buildmode=c-shared -o ui/flutter/macos/Frameworks/libgopeed.dylib github.com/monkeyWie/gopeed/bind/desktop
|
|
||||||
cd ui/flutter
|
|
||||||
flutter build macos
|
|
||||||
```
|
|
||||||
|
|
||||||
- linux
|
|
||||||
|
|
||||||
```bash
|
|
||||||
go build -tags nosqlite -ldflags="-w -s" -buildmode=c-shared -o ui/flutter/linux/bundle/lib/libgopeed.so github.com/monkeyWie/gopeed/bind/desktop
|
|
||||||
cd ui/flutter
|
|
||||||
flutter build linux
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Mobile
|
|
||||||
|
|
||||||
Same as before, you also need to prepare the `cgo` environment, and then install `gomobile`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
go install golang.org/x/mobile/cmd/gomobile@latest
|
|
||||||
gomobile init
|
|
||||||
```
|
|
||||||
|
|
||||||
command:
|
|
||||||
|
|
||||||
- android
|
|
||||||
|
|
||||||
```bash
|
|
||||||
gomobile bind -tags nosqlite -ldflags="-w -s" -o ui/flutter/android/app/libs/libgopeed.aar -target=android -androidapi 19 -javapkg=com.gopeed github.com/monkeyWie/gopeed/bind/mobile
|
|
||||||
cd ui/flutter
|
|
||||||
flutter build apk
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Web
|
|
||||||
|
|
||||||
Web platform communicates directly with the backend http server, no additional environment is required.
|
|
||||||
|
|
||||||
command:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd ui/flutter
|
|
||||||
flutter build web
|
|
||||||
cd ../../
|
|
||||||
rm -rf cmd/web/dist
|
|
||||||
cp -r ui/flutter/build/web cmd/web/dist
|
|
||||||
go build -tags nosqlite,web -ldflags="-s -w" -o bin/ github.com/monkeyWie/gopeed/cmd/web
|
|
||||||
````
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
[GPLv3](LICENSE)
|
|
||||||
-162
@@ -1,162 +0,0 @@
|
|||||||
[](https://github.com/monkeyWie/gopeed/actions?query=workflow%3Atest)
|
|
||||||
[](https://codecov.io/gh/monkeyWie/gopeed)
|
|
||||||
[](https://github.com/monkeyWie/gopeed/releases)
|
|
||||||
[](https://discord.gg/ZUJqJrwCGB)
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
[English](/README.md) | [中文](/README_zh-CN.md)
|
|
||||||
|
|
||||||
## 介绍
|
|
||||||
|
|
||||||
Gopeed 是一款由`Golang`+`Flutter`开发的高速下载器,支持(HTTP、BitTorrent、Magnet)协议下载,并且支持全平台使用。
|
|
||||||
|
|
||||||
## 下载
|
|
||||||
|
|
||||||
**已支持平台**
|
|
||||||
|
|
||||||
- [x] windows
|
|
||||||
- [x] macos
|
|
||||||
- [x] linux
|
|
||||||
- [x] android
|
|
||||||
- [ ] ios
|
|
||||||
- [x] web
|
|
||||||
- [x] docker
|
|
||||||
|
|
||||||
[点击前往](https://github.com/monkeyWie/gopeed/releases/latest)
|
|
||||||
|
|
||||||
> 注:macos 版本运行如果提示损坏,请在终端执行 `xattr -d com.apple.quarantine /Applications/Gopeed.app` 命令
|
|
||||||
|
|
||||||
### 命令行工具
|
|
||||||
|
|
||||||
使用`go install`安装:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
go install github.com/monkeyWie/gopeed/cmd/gopeed
|
|
||||||
```
|
|
||||||
|
|
||||||
### Docker
|
|
||||||
|
|
||||||
#### 直接运行
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker run -d -p 9999:9999 -v /path/to/download:/download liwei2633/gopeed
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 使用 Docker Compose
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
version: '3'
|
|
||||||
|
|
||||||
services:
|
|
||||||
gopeed:
|
|
||||||
container_name: gopeed
|
|
||||||
ports:
|
|
||||||
- 9999:9999
|
|
||||||
image: liwei2633/gopeed
|
|
||||||
volumes:
|
|
||||||
- ~/Downloads:/download
|
|
||||||
restart: unless-stopped
|
|
||||||
```
|
|
||||||
|
|
||||||
输入以下指令启动容器:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker compose up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 访问服务
|
|
||||||
|
|
||||||
当 docker 容器运行时,可以通过 `http://localhost:9999` 访问 web 页面。
|
|
||||||
> 提示:在设置页面把下载路径修改为 `/download` 以便在宿主机访问下载完的文件。
|
|
||||||
|
|
||||||
## 界面展示
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## 开发
|
|
||||||
|
|
||||||
本项目分为前端和后端两个部分,前端使用`flutter`,后端使用`Golang`,两边通过`http`协议进行通讯,在 unix 系统下,使用的是`unix socket`,在 windows 系统下,使用的是`tcp`协议。
|
|
||||||
|
|
||||||
> 前端代码位于`ui/flutter`目录下。
|
|
||||||
|
|
||||||
### 环境要求
|
|
||||||
|
|
||||||
1. Golang 1.19+
|
|
||||||
2. Flutter 3.0+
|
|
||||||
|
|
||||||
### 克隆项目
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git clone [email protected]:monkeyWie/gopeed.git
|
|
||||||
```
|
|
||||||
|
|
||||||
### 编译
|
|
||||||
|
|
||||||
#### 桌面端
|
|
||||||
|
|
||||||
首先需要按照[flutter desktop 官网文档](https://docs.flutter.dev/development/platform-integration/desktop)进行环境配置,然后需要准备好`cgo`环境,具体可以自行搜索。
|
|
||||||
|
|
||||||
构建命令:
|
|
||||||
|
|
||||||
- windows
|
|
||||||
|
|
||||||
```bash
|
|
||||||
go build -tags nosqlite -ldflags="-w -s" -buildmode=c-shared -o ui/flutter/windows/libgopeed.dll github.com/monkeyWie/gopeed/bind/desktop
|
|
||||||
cd ui/flutter
|
|
||||||
flutter build windows
|
|
||||||
```
|
|
||||||
|
|
||||||
- macos
|
|
||||||
|
|
||||||
```bash
|
|
||||||
go build -tags nosqlite -ldflags="-w -s" -buildmode=c-shared -o ui/flutter/macos/Frameworks/libgopeed.dylib github.com/monkeyWie/gopeed/bind/desktop
|
|
||||||
cd ui/flutter
|
|
||||||
flutter build macos
|
|
||||||
```
|
|
||||||
|
|
||||||
- linux
|
|
||||||
|
|
||||||
```bash
|
|
||||||
go build -tags nosqlite -ldflags="-w -s" -buildmode=c-shared -o ui/flutter/linux/bundle/lib/libgopeed.so github.com/monkeyWie/gopeed/bind/desktop
|
|
||||||
cd ui/flutter
|
|
||||||
flutter build linux
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 移动端
|
|
||||||
|
|
||||||
同样的也是需要准备好`cgo`环境,接着安装`gomobile`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
go install golang.org/x/mobile/cmd/gomobile@latest
|
|
||||||
gomobile init
|
|
||||||
```
|
|
||||||
|
|
||||||
构建命令:
|
|
||||||
|
|
||||||
- android
|
|
||||||
|
|
||||||
```bash
|
|
||||||
gomobile bind -tags nosqlite -ldflags="-w -s" -o ui/flutter/android/app/libs/libgopeed.aar -target=android -androidapi 19 -javapkg=com.gopeed github.com/monkeyWie/gopeed/bind/mobile
|
|
||||||
cd ui/flutter
|
|
||||||
flutter build apk
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Web 端(推荐本地调试使用)
|
|
||||||
|
|
||||||
Web 端直接与后端 http 服务通讯,不需要额外准备环境。
|
|
||||||
|
|
||||||
构建命令:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd ui/flutter
|
|
||||||
flutter build web
|
|
||||||
cd ../../
|
|
||||||
rm -rf cmd/web/dist
|
|
||||||
cp -r ui/flutter/build/web cmd/web/dist
|
|
||||||
go build -tags nosqlite,web -ldflags="-s -w" -o bin/ github.com/monkeyWie/gopeed/cmd/web
|
|
||||||
```
|
|
||||||
|
|
||||||
## 开源许可
|
|
||||||
|
|
||||||
基于 [GPLv3](LICENSE) 协议开源。
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 26 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 12 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 29 KiB |
@@ -1,30 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/monkeyWie/gopeed/pkg/base"
|
|
||||||
"github.com/monkeyWie/gopeed/pkg/download"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
finallyCh := make(chan error)
|
|
||||||
_, err := download.Boot().
|
|
||||||
URL("https://www.baidu.com/index.html").
|
|
||||||
Listener(func(event *download.Event) {
|
|
||||||
if event.Key == download.EventKeyFinally {
|
|
||||||
finallyCh <- event.Err
|
|
||||||
}
|
|
||||||
}).
|
|
||||||
Create(&base.Options{
|
|
||||||
Connections: 8,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
err = <-finallyCh
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("download fail:%v\n", err)
|
|
||||||
} else {
|
|
||||||
fmt.Println("download success")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import "C"
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"github.com/monkeyWie/gopeed/pkg/rest"
|
|
||||||
"github.com/monkeyWie/gopeed/pkg/rest/model"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {}
|
|
||||||
|
|
||||||
//export Start
|
|
||||||
func Start(cfg *C.char) (int, *C.char) {
|
|
||||||
var config model.StartConfig
|
|
||||||
if err := json.Unmarshal([]byte(C.GoString(cfg)), &config); err != nil {
|
|
||||||
return 0, C.CString(err.Error())
|
|
||||||
}
|
|
||||||
realPort, err := rest.Start(&config)
|
|
||||||
if err != nil {
|
|
||||||
return 0, C.CString(err.Error())
|
|
||||||
}
|
|
||||||
return realPort, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//export Stop
|
|
||||||
func Stop() {
|
|
||||||
rest.Stop()
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
package libgopeed
|
|
||||||
|
|
||||||
// #cgo LDFLAGS: -static-libstdc++
|
|
||||||
import "C"
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"github.com/monkeyWie/gopeed/pkg/rest"
|
|
||||||
"github.com/monkeyWie/gopeed/pkg/rest/model"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Start(cfg string) (int, error) {
|
|
||||||
var config model.StartConfig
|
|
||||||
if err := json.Unmarshal([]byte(cfg), &config); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return rest.Start(&config)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Stop() {
|
|
||||||
rest.Stop()
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
|
|
||||||
_______ ______ .______ _______ _______ _______
|
|
||||||
/ _____| / __ \ | _ \ | ____|| ____|| \
|
|
||||||
| | __ | | | | | |_) | | |__ | |__ | .--. |
|
|
||||||
| | |_ | | | | | | ___/ | __| | __| | | | |
|
|
||||||
| |__| | | `--' | | | | |____ | |____ | '--' |
|
|
||||||
\______| \______/ | _| |_______||_______||_______/
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
_ "embed"
|
|
||||||
"fmt"
|
|
||||||
"github.com/monkeyWie/gopeed/pkg/rest"
|
|
||||||
"github.com/monkeyWie/gopeed/pkg/rest/model"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:embed banner.txt
|
|
||||||
var banner string
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
cfg := &model.StartConfig{
|
|
||||||
Network: "tcp",
|
|
||||||
Address: "127.0.0.1:9999",
|
|
||||||
Storage: model.StorageBolt,
|
|
||||||
}
|
|
||||||
fmt.Println(banner)
|
|
||||||
srv, listener, err := rest.BuildServer(cfg)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
fmt.Printf("Server start success on http://%s\n", listener.Addr().String())
|
|
||||||
if err := srv.Serve(listener); err != nil && err != http.ErrServerClosed {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
type args struct {
|
|
||||||
url string
|
|
||||||
connections *int
|
|
||||||
dir *string
|
|
||||||
}
|
|
||||||
|
|
||||||
func parse() *args {
|
|
||||||
dir, err := os.Getwd()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
var args args
|
|
||||||
args.connections = flag.Int("C", 16, "Concurrent connections.")
|
|
||||||
args.dir = flag.String("D", dir, "Store directory.")
|
|
||||||
flag.Parse()
|
|
||||||
t := flag.Args()
|
|
||||||
if len(t) > 0 {
|
|
||||||
args.url = t[0]
|
|
||||||
} else {
|
|
||||||
gPrintln("missing url parameter, for example: gopeed https://www.google.com or gopeed bt.torrent or gopeed magnet:?xt=urn:btih:...")
|
|
||||||
gPrintln("try 'gopeed -h' for more information")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
return &args
|
|
||||||
}
|
|
||||||
|
|
||||||
func gPrint(msg string) {
|
|
||||||
fmt.Print("gopeed: " + msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
func gPrintln(msg string) {
|
|
||||||
gPrint(msg + "\n")
|
|
||||||
}
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/monkeyWie/gopeed/pkg/base"
|
|
||||||
"github.com/monkeyWie/gopeed/pkg/download"
|
|
||||||
"github.com/monkeyWie/gopeed/pkg/protocol/http"
|
|
||||||
"github.com/monkeyWie/gopeed/pkg/util"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
const progressWidth = 20
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
args := parse()
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
_, err := download.Boot().
|
|
||||||
URL(args.url).
|
|
||||||
Listener(func(event *download.Event) {
|
|
||||||
if event.Key == download.EventKeyProgress {
|
|
||||||
printProgress(event.Task, "downloading...")
|
|
||||||
}
|
|
||||||
if event.Key == download.EventKeyFinally {
|
|
||||||
var title string
|
|
||||||
if event.Err != nil {
|
|
||||||
title = "fail"
|
|
||||||
} else {
|
|
||||||
title = "complete"
|
|
||||||
}
|
|
||||||
printProgress(event.Task, title)
|
|
||||||
fmt.Println()
|
|
||||||
if event.Err != nil {
|
|
||||||
fmt.Printf("reason: %s", event.Err.Error())
|
|
||||||
} else {
|
|
||||||
fmt.Printf("saving path: %s", *args.dir)
|
|
||||||
}
|
|
||||||
wg.Done()
|
|
||||||
}
|
|
||||||
}).
|
|
||||||
Create(&base.Options{
|
|
||||||
Path: *args.dir,
|
|
||||||
Extra: http.OptsExtra{Connections: *args.connections},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
wg.Add(1)
|
|
||||||
wg.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
lastLineLen = 0
|
|
||||||
sb = new(strings.Builder)
|
|
||||||
)
|
|
||||||
|
|
||||||
func printProgress(task *download.Task, title string) {
|
|
||||||
rate := float64(task.Progress.Downloaded) / float64(task.Res.Size)
|
|
||||||
completeWidth := int(progressWidth * rate)
|
|
||||||
speed := util.ByteFmt(task.Progress.Speed)
|
|
||||||
totalSize := util.ByteFmt(task.Res.Size)
|
|
||||||
sb.WriteString(fmt.Sprintf("\r%s [", title))
|
|
||||||
for i := 0; i < progressWidth; i++ {
|
|
||||||
if i < completeWidth {
|
|
||||||
sb.WriteString("■")
|
|
||||||
} else {
|
|
||||||
sb.WriteString("□")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sb.WriteString(fmt.Sprintf("] %.1f%% %s/s %s", rate*100, speed, totalSize))
|
|
||||||
if lastLineLen != 0 {
|
|
||||||
paddingLen := lastLineLen - sb.Len()
|
|
||||||
if paddingLen > 0 {
|
|
||||||
sb.WriteString(strings.Repeat(" ", paddingLen))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lastLineLen = sb.Len()
|
|
||||||
fmt.Print(sb.String())
|
|
||||||
sb.Reset()
|
|
||||||
}
|
|
||||||
+28
@@ -0,0 +1,28 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/monkeyWie/gopeed-core/download"
|
||||||
|
"github.com/monkeyWie/gopeed-core/download/common"
|
||||||
|
"github.com/monkeyWie/gopeed-core/download/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
downloader := buildDownloader()
|
||||||
|
res, err := downloader.Resolve(&common.Request{
|
||||||
|
URL: "http://192.168.200.163:8088/docker-compose",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
err = downloader.Create(res, &common.Options{
|
||||||
|
Path: "E:\\test\\gopeed\\http",
|
||||||
|
Connections: 4,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildDownloader() *download.Downloader {
|
||||||
|
return download.NewDownloader(&http.Fetcher{BaseFetcher: &common.BaseFetcher{}})
|
||||||
|
}
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
|
|
||||||
_______ ______ .______ _______ _______ _______
|
|
||||||
/ _____| / __ \ | _ \ | ____|| ____|| \
|
|
||||||
| | __ | | | | | |_) | | |__ | |__ | .--. |
|
|
||||||
| | |_ | | | | | | ___/ | __| | __| | | | |
|
|
||||||
| |__| | | `--' | | | | |____ | |____ | '--' |
|
|
||||||
\______| \______/ | _| |_______||_______||_______/
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
//go:build web
|
|
||||||
// +build web
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"embed"
|
|
||||||
"fmt"
|
|
||||||
"github.com/monkeyWie/gopeed/pkg/rest"
|
|
||||||
"github.com/monkeyWie/gopeed/pkg/rest/model"
|
|
||||||
"io/fs"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:embed banner.txt
|
|
||||||
var banner string
|
|
||||||
|
|
||||||
//go:embed dist/*
|
|
||||||
var dist embed.FS
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
sub, err := fs.Sub(dist, "dist")
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
cfg := &model.StartConfig{
|
|
||||||
Network: "tcp",
|
|
||||||
Address: "0.0.0.0:9999",
|
|
||||||
Storage: model.StorageBolt,
|
|
||||||
WebEnable: true,
|
|
||||||
WebFS: sub,
|
|
||||||
}
|
|
||||||
fmt.Println(banner)
|
|
||||||
srv, listener, err := rest.BuildServer(cfg)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
fmt.Printf("Server start success on http://%s\n", listener.Addr().String())
|
|
||||||
if err := srv.Serve(listener); err != nil && err != http.ErrServerClosed {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
version: '3'
|
|
||||||
|
|
||||||
services:
|
|
||||||
gopeed:
|
|
||||||
container_name: gopeed
|
|
||||||
ports:
|
|
||||||
- 9999:9999 # HTTP port (host:container)
|
|
||||||
image: liwei2633/gopeed
|
|
||||||
volumes:
|
|
||||||
- ~/Downloads:/download # replace with your prefer path
|
|
||||||
restart: unless-stopped
|
|
||||||
@@ -1,13 +1,15 @@
|
|||||||
package base
|
package common
|
||||||
|
|
||||||
type Status string
|
import "errors"
|
||||||
|
|
||||||
|
type Status int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
DownloadStatusReady Status = "ready"
|
DownloadStatusReady Status = iota
|
||||||
DownloadStatusRunning Status = "running"
|
DownloadStatusStart
|
||||||
DownloadStatusPause Status = "pause"
|
DownloadStatusPause
|
||||||
DownloadStatusError Status = "error"
|
DownloadStatusError
|
||||||
DownloadStatusDone Status = "done"
|
DownloadStatusDone
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -21,3 +23,8 @@ const (
|
|||||||
|
|
||||||
HttpHeaderRangeFormat = "bytes=%d-%d"
|
HttpHeaderRangeFormat = "bytes=%d-%d"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
PauseErr = errors.New("pause")
|
||||||
|
DeleteErr = errors.New("delete")
|
||||||
|
)
|
||||||
@@ -0,0 +1,149 @@
|
|||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golang.org/x/net/proxy"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 下载请求
|
||||||
|
type Request struct {
|
||||||
|
// 下载链接
|
||||||
|
URL string
|
||||||
|
// 附加信息
|
||||||
|
Extra interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 资源信息
|
||||||
|
type Resource struct {
|
||||||
|
Req *Request
|
||||||
|
// 资源总大小
|
||||||
|
Size int64
|
||||||
|
// 是否支持断点下载
|
||||||
|
Range bool
|
||||||
|
// 资源所包含的文件列表
|
||||||
|
Files []*FileInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
type Options struct {
|
||||||
|
// 保存文件名
|
||||||
|
Name string
|
||||||
|
// 保存目录
|
||||||
|
Path string
|
||||||
|
// 并发连接数
|
||||||
|
Connections int
|
||||||
|
}
|
||||||
|
|
||||||
|
type FileInfo struct {
|
||||||
|
Name string
|
||||||
|
Path string
|
||||||
|
Size int64
|
||||||
|
}
|
||||||
|
|
||||||
|
type Process interface {
|
||||||
|
Start() error
|
||||||
|
Pause() error
|
||||||
|
Continue() error
|
||||||
|
Delete() error
|
||||||
|
}
|
||||||
|
|
||||||
|
type FileCreator interface {
|
||||||
|
Create(name string, size int64) (file *os.File, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Controller struct {
|
||||||
|
Files map[string]*os.File
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewController() *Controller {
|
||||||
|
return &Controller{Files: make(map[string]*os.File)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) Touch(name string, size int64) (file *os.File, err error) {
|
||||||
|
if size > 0 {
|
||||||
|
err := os.Truncate(name, size)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
file, err = os.OpenFile(name, os.O_RDWR, 0666)
|
||||||
|
} else {
|
||||||
|
file, err = os.Create(name)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
c.Files[name] = file
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) Open(name string) (file *os.File, err error) {
|
||||||
|
file, err = os.OpenFile(name, os.O_RDWR, 0666)
|
||||||
|
if err == nil {
|
||||||
|
c.Files[name] = file
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) Write(name string, offset int64, buf []byte) (int, error) {
|
||||||
|
return c.Files[name].WriteAt(buf, offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) Close(name string) error {
|
||||||
|
err := c.Files[name].Close()
|
||||||
|
delete(c.Files, name)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) ContextDialer() (proxy.Dialer, error) {
|
||||||
|
// return proxy.SOCKS5("tpc", "127.0.0.1:9999", nil, nil)
|
||||||
|
var dialer proxy.Dialer
|
||||||
|
return &DialerWarp{dialer: dialer}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type DialerWarp struct {
|
||||||
|
dialer proxy.Dialer
|
||||||
|
}
|
||||||
|
|
||||||
|
type ConnWarp struct {
|
||||||
|
conn net.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ConnWarp) Read(b []byte) (n int, err error) {
|
||||||
|
return c.conn.Read(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ConnWarp) Write(b []byte) (n int, err error) {
|
||||||
|
return c.conn.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ConnWarp) Close() error {
|
||||||
|
return c.conn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ConnWarp) LocalAddr() net.Addr {
|
||||||
|
return c.conn.LocalAddr()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ConnWarp) RemoteAddr() net.Addr {
|
||||||
|
return c.conn.RemoteAddr()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ConnWarp) SetDeadline(t time.Time) error {
|
||||||
|
return c.conn.SetDeadline(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ConnWarp) SetReadDeadline(t time.Time) error {
|
||||||
|
return c.conn.SetReadDeadline(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ConnWarp) SetWriteDeadline(t time.Time) error {
|
||||||
|
return c.conn.SetWriteDeadline(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DialerWarp) Dial(network, addr string) (c net.Conn, err error) {
|
||||||
|
conn, err := d.dialer.Dial(network, addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &ConnWarp{conn: conn}, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package common
|
||||||
|
|
||||||
|
// 对应协议的下载支持
|
||||||
|
type Fetcher interface {
|
||||||
|
BaseFetch
|
||||||
|
// 支持的协议列表
|
||||||
|
Protocols() []string
|
||||||
|
// 解析请求
|
||||||
|
Resolve(req *Request) (*Resource, error)
|
||||||
|
// 创建任务
|
||||||
|
Create(res *Resource, opts *Options) (Process, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type BaseFetch interface {
|
||||||
|
InitCtl(ctl *Controller)
|
||||||
|
GetCtl() *Controller
|
||||||
|
}
|
||||||
|
|
||||||
|
type BaseFetcher struct {
|
||||||
|
ctl *Controller
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BaseFetcher) InitCtl(ctl *Controller) {
|
||||||
|
b.ctl = ctl
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BaseFetcher) GetCtl() *Controller {
|
||||||
|
return b.ctl
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
package download
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/monkeyWie/gopeed-core/download/common"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TaskInfo struct {
|
||||||
|
ID string
|
||||||
|
Res *common.Resource
|
||||||
|
Opts *common.Options
|
||||||
|
Status common.Status
|
||||||
|
Files map[string]*common.FileInfo
|
||||||
|
|
||||||
|
process common.Process
|
||||||
|
}
|
||||||
|
|
||||||
|
type Downloader struct {
|
||||||
|
fetches map[string]common.Fetcher
|
||||||
|
tasks map[string]*TaskInfo
|
||||||
|
*common.Controller
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDownloader(fetchers ...common.Fetcher) *Downloader {
|
||||||
|
d := &Downloader{Controller: common.NewController()}
|
||||||
|
d.fetches = make(map[string]common.Fetcher)
|
||||||
|
for _, f := range fetchers {
|
||||||
|
for _, p := range f.Protocols() {
|
||||||
|
d.fetches[strings.ToUpper(p)] = f
|
||||||
|
}
|
||||||
|
f.InitCtl(d.Controller)
|
||||||
|
}
|
||||||
|
d.tasks = make(map[string]*TaskInfo)
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Downloader) getFetcher(URL string) (common.Fetcher, error) {
|
||||||
|
url, err := url.Parse(URL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if fetch, ok := d.fetches[strings.ToUpper(url.Scheme)]; ok {
|
||||||
|
return fetch, nil
|
||||||
|
}
|
||||||
|
return nil, errors.New("unsupported protocol")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Downloader) Resolve(req *common.Request) (*common.Resource, error) {
|
||||||
|
fetcher, err := d.getFetcher(req.URL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return fetcher.Resolve(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Downloader) Create(res *common.Resource, opts *common.Options) error {
|
||||||
|
fetcher, err := d.getFetcher(res.Req.URL)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
process, err := fetcher.Create(res, opts)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
id := uuid.New().String()
|
||||||
|
d.tasks[id] = &TaskInfo{
|
||||||
|
ID: id,
|
||||||
|
Res: res,
|
||||||
|
Opts: opts,
|
||||||
|
Status: common.DownloadStatusReady,
|
||||||
|
process: process,
|
||||||
|
}
|
||||||
|
return process.Start()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Downloader) Pause(id string) {
|
||||||
|
d.tasks[id].process.Pause()
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package download
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/monkeyWie/gopeed-core/download/common"
|
||||||
|
"github.com/monkeyWie/gopeed-core/download/http"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDownloader_Resolve(t *testing.T) {
|
||||||
|
downloader := buildDownloader()
|
||||||
|
res, err := downloader.Resolve(&common.Request{
|
||||||
|
URL: "http://192.168.200.163:8088/docker-compose",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
fmt.Println(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDownloader_Create(t *testing.T) {
|
||||||
|
downloader := buildDownloader()
|
||||||
|
res, err := downloader.Resolve(&common.Request{
|
||||||
|
URL: "http://192.168.200.163:8088/docker-compose",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = downloader.Create(res, &common.Options{
|
||||||
|
Path: "E:\\test\\gopeed\\http",
|
||||||
|
Connections: 4,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildDownloader() *Downloader {
|
||||||
|
return NewDownloader(&http.Fetcher{BaseFetcher: &common.BaseFetcher{}})
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package file_allocate
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FileAllocate interface {
|
||||||
|
Allocate(name string, size int64) (file *os.File, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type DefaultFileAllocator struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *DefaultFileAllocator) Allocate(name string, size int64) (file *os.File, err error) {
|
||||||
|
err = os.Truncate(name, size)
|
||||||
|
file, err = os.OpenFile(name, os.O_RDWR, 0644)
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package file_allocate
|
||||||
|
|
||||||
|
import "os"
|
||||||
|
|
||||||
|
type PreFileAllocator struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PreFileAllocator) Allocate(name string, size int64) (file *os.File, err error) {
|
||||||
|
err = os.Truncate(name, size)
|
||||||
|
file, err = os.OpenFile(name, os.O_RDWR, 0644)
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -0,0 +1,167 @@
|
|||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"github.com/monkeyWie/gopeed-core/download/common"
|
||||||
|
"github.com/monkeyWie/gopeed-core/download/http/model"
|
||||||
|
"io/ioutil"
|
||||||
|
"mime"
|
||||||
|
"net/http"
|
||||||
|
"net/http/cookiejar"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RequestError struct {
|
||||||
|
Code int
|
||||||
|
Msg string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRequestError(code int, msg string) *RequestError {
|
||||||
|
return &RequestError{Code: code, Msg: msg}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (re *RequestError) Error() string {
|
||||||
|
return fmt.Sprintf("http request fail,code:%d", re.Code)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Fetcher struct {
|
||||||
|
*common.BaseFetcher
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFetcher() *Fetcher {
|
||||||
|
return &Fetcher{BaseFetcher: &common.BaseFetcher{}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Fetcher) Protocols() []string {
|
||||||
|
return []string{"HTTP", "HTTPS"}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Fetcher) Resolve(req *common.Request) (*common.Resource, error) {
|
||||||
|
httpReq, err := BuildRequest(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
client := BuildClient()
|
||||||
|
// 只访问一个字节,测试资源是否支持Range请求
|
||||||
|
httpReq.Header.Set(common.HttpHeaderRange, fmt.Sprintf(common.HttpHeaderRangeFormat, 0, 0))
|
||||||
|
httpResp, err := client.Do(httpReq)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// 拿到响应头就关闭,不用加defer
|
||||||
|
httpResp.Body.Close()
|
||||||
|
res := &common.Resource{
|
||||||
|
Req: req,
|
||||||
|
Range: false,
|
||||||
|
Files: []*common.FileInfo{},
|
||||||
|
}
|
||||||
|
if common.HttpCodePartialContent == httpResp.StatusCode {
|
||||||
|
// 返回206响应码表示支持断点下载
|
||||||
|
res.Range = true
|
||||||
|
// 解析资源大小: bytes 0-1000/1001 => 1001
|
||||||
|
contentTotal := subLastSlash(httpResp.Header.Get(common.HttpHeaderContentRange))
|
||||||
|
if contentTotal != "" {
|
||||||
|
parse, err := strconv.ParseInt(contentTotal, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
res.Size = parse
|
||||||
|
}
|
||||||
|
} else if common.HttpCodeOK == httpResp.StatusCode {
|
||||||
|
// 返回200响应码,不支持断点下载,通过Content-Length头获取文件大小,获取不到的话可能是chunked编码
|
||||||
|
contentLength := httpResp.Header.Get(common.HttpHeaderContentLength)
|
||||||
|
if contentLength != "" {
|
||||||
|
parse, err := strconv.ParseInt(contentLength, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
res.Size = parse
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return nil, NewRequestError(httpResp.StatusCode, httpResp.Status)
|
||||||
|
}
|
||||||
|
file := &common.FileInfo{
|
||||||
|
Size: res.Size,
|
||||||
|
}
|
||||||
|
contentDisposition := httpResp.Header.Get(common.HttpHeaderContentDisposition)
|
||||||
|
if contentDisposition != "" {
|
||||||
|
_, params, _ := mime.ParseMediaType(contentDisposition)
|
||||||
|
filename := params["filename"]
|
||||||
|
if filename != "" {
|
||||||
|
file.Name = filename
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Get file name by URL
|
||||||
|
if file.Name == "" {
|
||||||
|
parse, err := url.Parse(req.URL)
|
||||||
|
if err == nil {
|
||||||
|
// e.g. /files/test.txt => test.txt
|
||||||
|
file.Name = subLastSlash(parse.Path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// unknown file name
|
||||||
|
if file.Name == "" {
|
||||||
|
file.Name = "unknown"
|
||||||
|
}
|
||||||
|
res.Files = append(res.Files, file)
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Fetcher) Create(res *common.Resource, opts *common.Options) (common.Process, error) {
|
||||||
|
if opts.Connections != 1 && !res.Range {
|
||||||
|
opts.Connections = 1
|
||||||
|
}
|
||||||
|
return NewProcess(h, res, opts), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func subLastSlash(str string) string {
|
||||||
|
if str == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
index := strings.LastIndex(str, "/")
|
||||||
|
if index != -1 {
|
||||||
|
return str[index+1:]
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func BuildClient() *http.Client {
|
||||||
|
// Cookie handle
|
||||||
|
jar, _ := cookiejar.New(nil)
|
||||||
|
return &http.Client{
|
||||||
|
Jar: jar,
|
||||||
|
Timeout: time.Second * 10,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BuildRequest(req *common.Request) (*http.Request, error) {
|
||||||
|
url, err := url.Parse(req.URL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
httpReq := &http.Request{
|
||||||
|
URL: url,
|
||||||
|
Header: map[string][]string{},
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Extra != nil {
|
||||||
|
if extra, ok := req.Extra.(model.Extra); ok {
|
||||||
|
if extra.Method != "" {
|
||||||
|
httpReq.Method = extra.Method
|
||||||
|
} else {
|
||||||
|
httpReq.Method = http.MethodGet
|
||||||
|
}
|
||||||
|
if len(extra.Header) > 0 {
|
||||||
|
for k, v := range extra.Header {
|
||||||
|
httpReq.Header[k] = []string{v}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
httpReq.Body = ioutil.NopCloser(bytes.NewBufferString(extra.Body))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return httpReq, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,307 @@
|
|||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/md5"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"github.com/monkeyWie/gopeed-core/download/common"
|
||||||
|
"io"
|
||||||
|
"math/rand"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const testDir = "./"
|
||||||
|
const buildSize = 200 * 1024 * 1024
|
||||||
|
const buildName = "build.data"
|
||||||
|
const buildFile = testDir + buildName
|
||||||
|
const downloadName = "download.data"
|
||||||
|
const downloadFile = testDir + downloadName
|
||||||
|
|
||||||
|
func TestFetcher_Resolve(t *testing.T) {
|
||||||
|
testResolve(startTestFileServer, &common.Resource{
|
||||||
|
Size: buildSize,
|
||||||
|
Range: true,
|
||||||
|
Files: []*common.FileInfo{
|
||||||
|
{
|
||||||
|
Name: buildName,
|
||||||
|
Size: buildSize,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, t)
|
||||||
|
testResolve(startTestChunkedServer, &common.Resource{
|
||||||
|
Size: 0,
|
||||||
|
Range: false,
|
||||||
|
Files: []*common.FileInfo{
|
||||||
|
{
|
||||||
|
Name: buildName,
|
||||||
|
Size: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testResolve(startTestServer func() net.Listener, want *common.Resource, t *testing.T) {
|
||||||
|
listener := startTestServer()
|
||||||
|
defer listener.Close()
|
||||||
|
fetcher := NewFetcher()
|
||||||
|
res, err := fetcher.Resolve(&common.Request{
|
||||||
|
URL: "http://" + listener.Addr().String() + "/" + buildName,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
res.Req = nil
|
||||||
|
if !reflect.DeepEqual(want, res) {
|
||||||
|
t.Errorf("Resolve error = %v, want %v", res, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFetcher_DownloadNormal(t *testing.T) {
|
||||||
|
listener := startTestFileServer()
|
||||||
|
defer listener.Close()
|
||||||
|
// 正常下载
|
||||||
|
downloadNormal(listener, 1, t)
|
||||||
|
downloadNormal(listener, 5, t)
|
||||||
|
downloadNormal(listener, 8, t)
|
||||||
|
downloadNormal(listener, 16, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFetcher_DownloadContinue(t *testing.T) {
|
||||||
|
listener := startTestFileServer()
|
||||||
|
defer listener.Close()
|
||||||
|
// 暂停继续
|
||||||
|
downloadContinue(listener, 1, t)
|
||||||
|
fmt.Println("d1")
|
||||||
|
downloadContinue(listener, 5, t)
|
||||||
|
fmt.Println("d5")
|
||||||
|
downloadContinue(listener, 8, t)
|
||||||
|
fmt.Println("d8")
|
||||||
|
downloadContinue(listener, 16, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFetcher_DownloadChunked(t *testing.T) {
|
||||||
|
listener := startTestChunkedServer()
|
||||||
|
defer listener.Close()
|
||||||
|
// chunked编码下载
|
||||||
|
//downloadNormal(listener, 1, t)
|
||||||
|
downloadContinue(listener, 1, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFetcher_DownloadRetry(t *testing.T) {
|
||||||
|
listener := startTestRetryServer()
|
||||||
|
defer listener.Close()
|
||||||
|
// chunked编码下载
|
||||||
|
downloadNormal(listener, 1, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFetcher_DownloadError(t *testing.T) {
|
||||||
|
listener := startTestErrorServer()
|
||||||
|
defer listener.Close()
|
||||||
|
// chunked编码下载
|
||||||
|
downloadError(listener, 1, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func startTestFileServer() net.Listener {
|
||||||
|
return startTestServer(func() http.Handler {
|
||||||
|
return http.FileServer(http.Dir(testDir))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func startTestChunkedServer() net.Listener {
|
||||||
|
return startTestServer(func() http.Handler {
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
mux.HandleFunc("/"+buildName, func(writer http.ResponseWriter, request *http.Request) {
|
||||||
|
file, err := os.Open(buildFile)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
io.Copy(writer, file)
|
||||||
|
})
|
||||||
|
return mux
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func startTestRetryServer() net.Listener {
|
||||||
|
counter := 0
|
||||||
|
return startTestServer(func() http.Handler {
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
mux.HandleFunc("/"+buildName, func(writer http.ResponseWriter, request *http.Request) {
|
||||||
|
counter++
|
||||||
|
if counter != 1 && counter < 5 {
|
||||||
|
writer.WriteHeader(500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
file, err := os.Open(buildFile)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
io.Copy(writer, file)
|
||||||
|
})
|
||||||
|
return mux
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func startTestErrorServer() net.Listener {
|
||||||
|
counter := 0
|
||||||
|
return startTestServer(func() http.Handler {
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
mux.HandleFunc("/"+buildName, func(writer http.ResponseWriter, request *http.Request) {
|
||||||
|
counter++
|
||||||
|
if counter != 1 {
|
||||||
|
writer.WriteHeader(500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return mux
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func startTestServer(serverHandle func() http.Handler) net.Listener {
|
||||||
|
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
file, err := os.Create(buildFile)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
// 随机生成一个文件
|
||||||
|
l := int64(8192)
|
||||||
|
buf := make([]byte, l)
|
||||||
|
size := int64(0)
|
||||||
|
for {
|
||||||
|
_, err := rand.Read(buf)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if size+l >= buildSize {
|
||||||
|
file.WriteAt(buf[0:buildSize-size], size)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
file.WriteAt(buf, size)
|
||||||
|
size += l
|
||||||
|
}
|
||||||
|
server := &http.Server{}
|
||||||
|
server.Handler = serverHandle()
|
||||||
|
go server.Serve(listener)
|
||||||
|
|
||||||
|
return &delFileListener{
|
||||||
|
File: file,
|
||||||
|
Listener: listener,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type delFileListener struct {
|
||||||
|
*os.File
|
||||||
|
net.Listener
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *delFileListener) Close() error {
|
||||||
|
defer func() {
|
||||||
|
c.File.Close()
|
||||||
|
if err := ifExistAndRemove(c.File.Name()); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
if err := ifExistAndRemove(downloadFile); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return c.Listener.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func ifExistAndRemove(name string) error {
|
||||||
|
if _, err := os.Stat(name); !os.IsNotExist(err) {
|
||||||
|
return os.Remove(name)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func downloadReady(listener net.Listener, connections int, t *testing.T) common.Process {
|
||||||
|
fetcher := NewFetcher()
|
||||||
|
fetcher.InitCtl(common.NewController())
|
||||||
|
res, err := fetcher.Resolve(&common.Request{
|
||||||
|
URL: "http://" + listener.Addr().String() + "/" + buildName,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
process, err := fetcher.Create(res, &common.Options{
|
||||||
|
Name: downloadName,
|
||||||
|
Path: testDir,
|
||||||
|
Connections: connections,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return process
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func downloadNormal(listener net.Listener, connections int, t *testing.T) {
|
||||||
|
process := downloadReady(listener, connections, t)
|
||||||
|
err := process.Start()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
want := fileMd5(buildFile)
|
||||||
|
got := fileMd5(downloadFile)
|
||||||
|
if want != got {
|
||||||
|
t.Errorf("Download error = %v, want %v", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func downloadContinue(listener net.Listener, connections int, t *testing.T) {
|
||||||
|
process := downloadReady(listener, connections, t)
|
||||||
|
go func() {
|
||||||
|
err := process.Start()
|
||||||
|
if err != nil && err != common.PauseErr {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
time.Sleep(time.Millisecond * 200)
|
||||||
|
if err := process.Pause(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
time.Sleep(time.Millisecond * 200)
|
||||||
|
if err := process.Continue(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
want := fileMd5(buildFile)
|
||||||
|
got := fileMd5(downloadFile)
|
||||||
|
if want != got {
|
||||||
|
t.Errorf("Download error = %v, want %v", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func downloadError(listener net.Listener, connections int, t *testing.T) {
|
||||||
|
process := downloadReady(listener, connections, t)
|
||||||
|
err := process.Start()
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("Download error = %v, want %v", err, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fileMd5(filePath string) string {
|
||||||
|
file, err := os.Open(filePath)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
// Tell the program to call the following function when the current function returns
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
// Open a new hash interface to write to
|
||||||
|
hash := md5.New()
|
||||||
|
|
||||||
|
// Copy the file in the hash interface and check for any error
|
||||||
|
if _, err := io.Copy(hash, file); err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return hex.EncodeToString(hash.Sum(nil))
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/monkeyWie/gopeed-core/download/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Chunk struct {
|
||||||
|
Status common.Status
|
||||||
|
Begin int64
|
||||||
|
End int64
|
||||||
|
Downloaded int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewChunk(begin int64, end int64) *Chunk {
|
||||||
|
return &Chunk{
|
||||||
|
Status: common.DownloadStatusReady,
|
||||||
|
Begin: begin,
|
||||||
|
End: end,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
type Extra struct {
|
||||||
|
Method string
|
||||||
|
Header map[string]string
|
||||||
|
Body string
|
||||||
|
}
|
||||||
@@ -0,0 +1,245 @@
|
|||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/monkeyWie/gopeed-core/download/common"
|
||||||
|
"github.com/monkeyWie/gopeed-core/download/http/model"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"path/filepath"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Process struct {
|
||||||
|
fetcher *Fetcher
|
||||||
|
|
||||||
|
res *common.Resource
|
||||||
|
opts *common.Options
|
||||||
|
status common.Status
|
||||||
|
clients []*http.Response
|
||||||
|
chunks []*model.Chunk
|
||||||
|
|
||||||
|
pauseCond *sync.Cond
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewProcess(fetcher *Fetcher, res *common.Resource, opts *common.Options) *Process {
|
||||||
|
return &Process{
|
||||||
|
fetcher: fetcher,
|
||||||
|
res: res,
|
||||||
|
opts: opts,
|
||||||
|
status: common.DownloadStatusReady,
|
||||||
|
|
||||||
|
pauseCond: sync.NewCond(&sync.Mutex{}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Process) Start() error {
|
||||||
|
ctl := p.fetcher.GetCtl()
|
||||||
|
// 创建文件
|
||||||
|
name := p.name()
|
||||||
|
_, err := ctl.Touch(name, p.res.Size)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer p.close()
|
||||||
|
p.status = common.DownloadStatusStart
|
||||||
|
if p.res.Range {
|
||||||
|
// 每个连接平均需要下载的分块大小
|
||||||
|
chunkSize := p.res.Size / int64(p.opts.Connections)
|
||||||
|
p.chunks = make([]*model.Chunk, p.opts.Connections)
|
||||||
|
p.clients = make([]*http.Response, p.opts.Connections)
|
||||||
|
for i := 0; i < p.opts.Connections; i++ {
|
||||||
|
var (
|
||||||
|
begin = chunkSize * int64(i)
|
||||||
|
end int64
|
||||||
|
)
|
||||||
|
if i == p.opts.Connections-1 {
|
||||||
|
// 最后一个分块需要保证把文件下载完
|
||||||
|
end = p.res.Size
|
||||||
|
} else {
|
||||||
|
end = begin + chunkSize
|
||||||
|
}
|
||||||
|
chunk := model.NewChunk(begin, end)
|
||||||
|
p.chunks[i] = chunk
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 只支持单连接下载
|
||||||
|
p.chunks = make([]*model.Chunk, 1)
|
||||||
|
p.clients = make([]*http.Response, 1)
|
||||||
|
p.chunks[0] = model.NewChunk(0, 0)
|
||||||
|
}
|
||||||
|
return p.fetch()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Process) Pause() error {
|
||||||
|
p.pauseCond.L.Lock()
|
||||||
|
defer p.pauseCond.L.Unlock()
|
||||||
|
if common.DownloadStatusStart != p.status {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
p.status = common.DownloadStatusPause
|
||||||
|
p.stop()
|
||||||
|
// 释放锁,并等待下载结束
|
||||||
|
p.pauseCond.Wait()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Process) Continue() error {
|
||||||
|
if func() bool {
|
||||||
|
p.pauseCond.L.Lock()
|
||||||
|
defer p.pauseCond.L.Unlock()
|
||||||
|
if common.DownloadStatusPause != p.status && common.DownloadStatusError != p.status {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
p.status = common.DownloadStatusStart
|
||||||
|
return false
|
||||||
|
}() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
ctl = p.fetcher.GetCtl()
|
||||||
|
name = p.name()
|
||||||
|
)
|
||||||
|
_, err := ctl.Open(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer ctl.Close(name)
|
||||||
|
return p.fetch()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Process) Delete() error {
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Process) close() error {
|
||||||
|
return p.fetcher.GetCtl().Close(p.name())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Process) name() string {
|
||||||
|
// 创建文件
|
||||||
|
var filename = p.opts.Name
|
||||||
|
if filename == "" {
|
||||||
|
filename = p.res.Files[0].Name
|
||||||
|
}
|
||||||
|
return filepath.Join(p.opts.Path, filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Process) fetch() error {
|
||||||
|
errCh := make(chan error, p.opts.Connections)
|
||||||
|
defer close(errCh)
|
||||||
|
|
||||||
|
for i := 0; i < p.opts.Connections; i++ {
|
||||||
|
go func(i int) {
|
||||||
|
errCh <- p.fetchChunk(i, p.name(), p.chunks[i])
|
||||||
|
}(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
var retErr error
|
||||||
|
for i := 0; i < p.opts.Connections; i++ {
|
||||||
|
err := <-errCh
|
||||||
|
if err != nil && retErr == nil {
|
||||||
|
retErr = err
|
||||||
|
// 有一个连接失败就立即终止下载
|
||||||
|
if retErr != common.PauseErr {
|
||||||
|
func() {
|
||||||
|
p.pauseCond.L.Lock()
|
||||||
|
defer p.pauseCond.L.Unlock()
|
||||||
|
p.stop()
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.pauseCond.Signal()
|
||||||
|
return retErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Process) stop() {
|
||||||
|
if len(p.clients) > 0 {
|
||||||
|
for _, client := range p.clients {
|
||||||
|
fmt.Println("client close")
|
||||||
|
if client != nil {
|
||||||
|
client.Body.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Process) fetchChunk(index int, name string, chunk *model.Chunk) (err error) {
|
||||||
|
httpReq, err := BuildRequest(p.res.Req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
client = BuildClient()
|
||||||
|
buf = make([]byte, 8192)
|
||||||
|
)
|
||||||
|
// 重试5次
|
||||||
|
for i := 0; i < 5; i++ {
|
||||||
|
var (
|
||||||
|
resp *http.Response
|
||||||
|
retry bool
|
||||||
|
)
|
||||||
|
if p.res.Range {
|
||||||
|
httpReq.Header.Set(common.HttpHeaderRange,
|
||||||
|
fmt.Sprintf(common.HttpHeaderRangeFormat, chunk.Begin+chunk.Downloaded, chunk.End))
|
||||||
|
} else {
|
||||||
|
chunk.Downloaded = 0
|
||||||
|
}
|
||||||
|
err = func() error {
|
||||||
|
p.pauseCond.L.Lock()
|
||||||
|
defer p.pauseCond.L.Unlock()
|
||||||
|
if p.status == common.DownloadStatusPause {
|
||||||
|
return common.PauseErr
|
||||||
|
}
|
||||||
|
resp, err = client.Do(httpReq)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.clients[index] = resp
|
||||||
|
if resp.StatusCode != common.HttpCodeOK && resp.StatusCode != common.HttpCodePartialContent {
|
||||||
|
err = NewRequestError(resp.StatusCode, resp.Status)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}()
|
||||||
|
if err != nil {
|
||||||
|
if err == common.PauseErr {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// 请求失败重试
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
retry, err = func() (bool, error) {
|
||||||
|
defer resp.Body.Close()
|
||||||
|
for {
|
||||||
|
n, err := resp.Body.Read(buf)
|
||||||
|
if n > 0 {
|
||||||
|
_, err := p.fetcher.GetCtl().Write(name, chunk.Begin+chunk.Downloaded, buf[:n])
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
chunk.Downloaded += int64(n)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
if err != io.EOF {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}()
|
||||||
|
if err == nil || !retry {
|
||||||
|
// 下载成功,跳出重试
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
p.chunks[index].Status = common.DownloadStatusError
|
||||||
|
} else {
|
||||||
|
p.chunks[index].Status = common.DownloadStatusDone
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -1,76 +1,12 @@
|
|||||||
module github.com/monkeyWie/gopeed
|
module github.com/monkeyWie/gopeed-core
|
||||||
|
|
||||||
go 1.19
|
go 1.13
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/anacrolix/torrent v1.46.0
|
github.com/RoaringBitmap/roaring v0.4.21
|
||||||
github.com/gorilla/mux v1.8.0
|
github.com/cenkalti/mse v0.0.0-20140930130441-6ef65f170972
|
||||||
github.com/matoous/go-nanoid/v2 v2.0.0
|
github.com/google/uuid v1.1.1
|
||||||
go.etcd.io/bbolt v1.3.6
|
github.com/marksamman/bencode v0.0.0-20150821143521-dc84f26e086e
|
||||||
golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde
|
github.com/sirupsen/logrus v1.4.2
|
||||||
)
|
golang.org/x/net v0.0.0-20200707034311-ab3426394381
|
||||||
|
|
||||||
require (
|
|
||||||
crawshaw.io/sqlite v0.3.3-0.20210127221821-98b1f83c5508 // indirect
|
|
||||||
github.com/RoaringBitmap/roaring v1.0.1-0.20220510143707-3f418c4f42a4 // indirect
|
|
||||||
github.com/ajwerner/btree v0.0.0-20211221152037-f427b3e689c0 // indirect
|
|
||||||
github.com/anacrolix/chansync v0.3.0 // indirect
|
|
||||||
github.com/anacrolix/dht/v2 v2.16.2-0.20220311024416-dd658f18fd51 // indirect
|
|
||||||
github.com/anacrolix/envpprof v1.2.1 // indirect
|
|
||||||
github.com/anacrolix/generics v0.0.0-20220618083756-f99e35403a60 // indirect
|
|
||||||
github.com/anacrolix/go-libutp v1.2.0 // indirect
|
|
||||||
github.com/anacrolix/log v0.13.1 // indirect
|
|
||||||
github.com/anacrolix/missinggo v1.3.0 // indirect
|
|
||||||
github.com/anacrolix/missinggo/perf v1.0.0 // indirect
|
|
||||||
github.com/anacrolix/missinggo/v2 v2.7.0 // indirect
|
|
||||||
github.com/anacrolix/mmsg v1.0.0 // indirect
|
|
||||||
github.com/anacrolix/multiless v0.2.1-0.20211218050420-533661eef5dc // indirect
|
|
||||||
github.com/anacrolix/stm v0.3.0 // indirect
|
|
||||||
github.com/anacrolix/sync v0.4.0 // indirect
|
|
||||||
github.com/anacrolix/upnp v0.1.3-0.20220123035249-922794e51c96 // indirect
|
|
||||||
github.com/anacrolix/utp v0.1.0 // indirect
|
|
||||||
github.com/bahlo/generic-list-go v0.2.0 // indirect
|
|
||||||
github.com/benbjohnson/immutable v0.3.0 // indirect
|
|
||||||
github.com/bits-and-blooms/bitset v1.2.0 // indirect
|
|
||||||
github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 // indirect
|
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
|
||||||
github.com/dustin/go-humanize v1.0.0 // indirect
|
|
||||||
github.com/edsrzf/mmap-go v1.1.0 // indirect
|
|
||||||
github.com/felixge/httpsnoop v1.0.1 // indirect
|
|
||||||
github.com/google/btree v1.0.1 // indirect
|
|
||||||
github.com/google/uuid v1.3.0 // indirect
|
|
||||||
github.com/gorilla/handlers v1.5.1 // indirect
|
|
||||||
github.com/gorilla/websocket v1.4.2 // indirect
|
|
||||||
github.com/huandu/xstrings v1.3.2 // indirect
|
|
||||||
github.com/lispad/go-generics-tools v1.0.0 // indirect
|
|
||||||
github.com/mschoch/smat v0.2.0 // indirect
|
|
||||||
github.com/pion/datachannel v1.5.2 // indirect
|
|
||||||
github.com/pion/dtls/v2 v2.1.2 // indirect
|
|
||||||
github.com/pion/ice/v2 v2.1.20 // indirect
|
|
||||||
github.com/pion/interceptor v0.1.7 // indirect
|
|
||||||
github.com/pion/logging v0.2.2 // indirect
|
|
||||||
github.com/pion/mdns v0.0.5 // indirect
|
|
||||||
github.com/pion/randutil v0.1.0 // indirect
|
|
||||||
github.com/pion/rtcp v1.2.9 // indirect
|
|
||||||
github.com/pion/rtp v1.7.4 // indirect
|
|
||||||
github.com/pion/sctp v1.8.2 // indirect
|
|
||||||
github.com/pion/sdp/v3 v3.0.4 // indirect
|
|
||||||
github.com/pion/srtp/v2 v2.0.5 // indirect
|
|
||||||
github.com/pion/stun v0.3.5 // indirect
|
|
||||||
github.com/pion/transport v0.13.0 // indirect
|
|
||||||
github.com/pion/turn/v2 v2.0.6 // indirect
|
|
||||||
github.com/pion/udp v0.1.1 // indirect
|
|
||||||
github.com/pion/webrtc/v3 v3.1.24-0.20220208053747-94262c1b2b38 // indirect
|
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
|
||||||
github.com/rs/dnscache v0.0.0-20210201191234-295bba877686 // indirect
|
|
||||||
github.com/tidwall/btree v1.3.1 // indirect
|
|
||||||
golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838 // indirect
|
|
||||||
golang.org/x/exp v0.0.0-20220428152302-39d4317da171 // indirect
|
|
||||||
golang.org/x/mobile v0.0.0-20220928052126-fa6bcb076835 // indirect
|
|
||||||
golang.org/x/mod v0.6.0-dev.0.20211013180041-c96bc1413d57 // indirect
|
|
||||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect
|
|
||||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect
|
|
||||||
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect
|
|
||||||
golang.org/x/tools v0.1.8-0.20211029000441-d6a9af8af023 // indirect
|
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
|
||||||
)
|
)
|
||||||
@@ -1,470 +1,57 @@
|
|||||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
github.com/RoaringBitmap/roaring v0.4.21 h1:WJ/zIlNX4wQZ9x8Ey33O1UaD9TCTakYsdLFSBcTwH+8=
|
||||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
github.com/RoaringBitmap/roaring v0.4.21/go.mod h1:D0gp8kJQgE1A4LQ5wFLggQEyvDi06Mq5mKs52e1TwOo=
|
||||||
crawshaw.io/iox v0.0.0-20181124134642-c51c3df30797 h1:yDf7ARQc637HoxDho7xjqdvO5ZA2Yb+xzv/fOnnvZzw=
|
github.com/cenkalti/mse v0.0.0-20140930130441-6ef65f170972 h1:FvAasVrPm/VUO8M0odLDqA9Y7VbALYq3PRAwmWGPw78=
|
||||||
crawshaw.io/iox v0.0.0-20181124134642-c51c3df30797/go.mod h1:sXBiorCo8c46JlQV3oXPKINnZ8mcqnye1EkVkqsectk=
|
github.com/cenkalti/mse v0.0.0-20140930130441-6ef65f170972/go.mod h1:wGf99xKTJ8f/a8cC1inMqFz9cGEzISLLid5F6i5fI/g=
|
||||||
crawshaw.io/sqlite v0.3.2/go.mod h1:igAO5JulrQ1DbdZdtVq48mnZUBAPOeFzer7VhDWNtW4=
|
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||||
crawshaw.io/sqlite v0.3.3-0.20210127221821-98b1f83c5508 h1:fILCBBFnjnrQ0whVJlGhfv1E/QiaFDNtGFBObEVRnYg=
|
|
||||||
crawshaw.io/sqlite v0.3.3-0.20210127221821-98b1f83c5508/go.mod h1:igAO5JulrQ1DbdZdtVq48mnZUBAPOeFzer7VhDWNtW4=
|
|
||||||
filippo.io/edwards25519 v1.0.0-rc.1 h1:m0VOOB23frXZvAOK44usCgLWvtsxIoMCTBGJZlpmGfU=
|
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
|
||||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
|
||||||
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.23/go.mod h1:D0gp8kJQgE1A4LQ5wFLggQEyvDi06Mq5mKs52e1TwOo=
|
|
||||||
github.com/RoaringBitmap/roaring v1.0.1-0.20220510143707-3f418c4f42a4 h1:LxR70Si8X8IlYTeKiPReOBt9pla6wEJDlkawfwDdrB0=
|
|
||||||
github.com/RoaringBitmap/roaring v1.0.1-0.20220510143707-3f418c4f42a4/go.mod h1:icnadbWcNyfEHlYdr+tDlOTih1Bf/h+rzPpv4sbomAA=
|
|
||||||
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/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/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/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/anacrolix/chansync v0.3.0 h1:lRu9tbeuw3wl+PhMu/r+JJCRu5ArFXIluOgdF0ao6/U=
|
|
||||||
github.com/anacrolix/chansync v0.3.0/go.mod h1:DZsatdsdXxD0WiwcGl0nJVwyjCKMDv+knl1q2iBjA2k=
|
|
||||||
github.com/anacrolix/dht/v2 v2.16.2-0.20220311024416-dd658f18fd51 h1:issCwqC43gQ7n0gg9rn0EeVYXnQMI7vlnWub4oidtlU=
|
|
||||||
github.com/anacrolix/dht/v2 v2.16.2-0.20220311024416-dd658f18fd51/go.mod h1:osiyaNrMLG9dw7wUtVMaII/NdCjlXeHjUcYzXnmop68=
|
|
||||||
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.1.0/go.mod h1:My7T5oSqVfEn4MD4Meczkw/f5lSIndGAKu/0SM/rkf4=
|
|
||||||
github.com/anacrolix/envpprof v1.2.1 h1:25TJe6t/i0AfzzldiGFKCpD+s+dk8lONBcacJZB2rdE=
|
|
||||||
github.com/anacrolix/envpprof v1.2.1/go.mod h1:My7T5oSqVfEn4MD4Meczkw/f5lSIndGAKu/0SM/rkf4=
|
|
||||||
github.com/anacrolix/generics v0.0.0-20220618083756-f99e35403a60 h1:k4/h2B1gGF+PJGyGHxs8nmHHt1pzWXZWBj6jn4OBlRc=
|
|
||||||
github.com/anacrolix/generics v0.0.0-20220618083756-f99e35403a60/go.mod h1:ff2rHB/joTV03aMSSn/AZNnaIpUw0h3njetGsaXcMy8=
|
|
||||||
github.com/anacrolix/go-libutp v1.2.0 h1:sjxoB+/ARiKUR7IK/6wLWyADIBqGmu1fm0xo+8Yy7u0=
|
|
||||||
github.com/anacrolix/go-libutp v1.2.0/go.mod h1:RrJ3KcaDcf9Jqp33YL5V/5CBEc6xMc7aJL8wXfuWL50=
|
|
||||||
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.10.0/go.mod h1:s5yBP/j046fm9odtUTbHOfDUq/zh1W8OkPpJtnX0oQI=
|
|
||||||
github.com/anacrolix/log v0.10.1-0.20220123034749-3920702c17f8/go.mod h1:GmnE2c0nvz8pOIPUSC9Rawgefy1sDXqposC2wgtBZE4=
|
|
||||||
github.com/anacrolix/log v0.13.1 h1:BmVwTdxHd5VcNrLylgKwph4P4wf+5VvPgOK4yi91fTY=
|
|
||||||
github.com/anacrolix/log v0.13.1/go.mod h1:D4+CvN8SnruK6zIFS/xPoRJmtvtnxs+CSfDQ+BFxZ68=
|
|
||||||
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/missinggo v0.0.0-20180725070939-60ef2fbf63df/go.mod h1:kwGiTUTZ0+p4vAz3VbAI5a30t2YbvemcmspjKwrAz5s=
|
|
||||||
github.com/anacrolix/missinggo v1.1.0/go.mod h1:MBJu3Sk/k3ZfGYcS7z18gwfu72Ey/xopPFJJbTi5yIo=
|
|
||||||
github.com/anacrolix/missinggo v1.1.2-0.20190815015349-b888af804467/go.mod h1:MBJu3Sk/k3ZfGYcS7z18gwfu72Ey/xopPFJJbTi5yIo=
|
|
||||||
github.com/anacrolix/missinggo v1.2.1/go.mod h1:J5cMhif8jPmFoC3+Uvob3OXXNIhOUikzMt+uUjeM21Y=
|
|
||||||
github.com/anacrolix/missinggo v1.3.0 h1:06HlMsudotL7BAELRZs0yDZ4yVXsHXGi323QBjAVASw=
|
|
||||||
github.com/anacrolix/missinggo v1.3.0/go.mod h1:bqHm8cE8xr+15uVfMG3BFui/TxyB6//H5fwlq/TeqMc=
|
|
||||||
github.com/anacrolix/missinggo/perf v1.0.0 h1:7ZOGYziGEBytW49+KmYGTaNfnwUqP1HBsy6BqESAJVw=
|
|
||||||
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.5.1/go.mod h1:WEjqh2rmKECd0t1VhQkLGTdIWXO6f6NLjp5GlMZ+6FA=
|
|
||||||
github.com/anacrolix/missinggo/v2 v2.5.2/go.mod h1:yNvsLrtZYRYCOI+KRH/JM8TodHjtIE/bjOGhQaLOWIE=
|
|
||||||
github.com/anacrolix/missinggo/v2 v2.7.0 h1:4fzOAAn/VCvfWGviLmh64MPMttrlYew81JdPO7nSHvI=
|
|
||||||
github.com/anacrolix/missinggo/v2 v2.7.0/go.mod h1:2IZIvmRTizALNYFYXsPR7ofXPzJgyBpKZ4kMqMEICkI=
|
|
||||||
github.com/anacrolix/mmsg v0.0.0-20180515031531-a4a3ba1fc8bb/go.mod h1:x2/ErsYUmT77kezS63+wzZp8E3byYB0gzirM/WMBLfw=
|
|
||||||
github.com/anacrolix/mmsg v1.0.0 h1:btC7YLjOn29aTUAExJiVUhQOuf/8rhm+/nWCMAnL3Hg=
|
|
||||||
github.com/anacrolix/mmsg v1.0.0/go.mod h1:x8kRaJY/dCrY9Al0PEcj1mb/uFHwP6GCJ9fLl4thEPc=
|
|
||||||
github.com/anacrolix/multiless v0.2.1-0.20211218050420-533661eef5dc h1:K047jUtd0Xv4SEpv/5DoBgDvj4ZNpT1SOVtMlFpRrh0=
|
|
||||||
github.com/anacrolix/multiless v0.2.1-0.20211218050420-533661eef5dc/go.mod h1:TrCLEZfIDbMVfLoQt5tOoiBS/uq4y8+ojuEVVvTNPX4=
|
|
||||||
github.com/anacrolix/stm v0.2.0/go.mod h1:zoVQRvSiGjGoTmbM0vSLIiaKjWtNPeTvXUSdJQA4hsg=
|
|
||||||
github.com/anacrolix/stm v0.3.0 h1:peQncJSNJtk1YBrFbW0DLKYqll+sa0kOk8EvXRcO+wA=
|
|
||||||
github.com/anacrolix/stm v0.3.0/go.mod h1:spImf/rXwiAUoYYJK1YCZeWkpaHZ3kzjGFjwK5OStfU=
|
|
||||||
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.4.0 h1:T+MdO/u87ir/ijWsTFsPYw5jVm0SMm4kVpg8t4KF38o=
|
|
||||||
github.com/anacrolix/sync v0.4.0/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 v1.0.0/go.mod h1:1m2U/K6ZT+JZG0+bdMK6qauP49QT4wE5pmhJXOKKCHw=
|
|
||||||
github.com/anacrolix/tagflag v1.1.0/go.mod h1:Scxs9CV10NQatSmbyjqmqmeQNwGzlNe0CMUMIxqHIG8=
|
|
||||||
github.com/anacrolix/torrent v1.46.0 h1:m5SGlW4p0dJkqrIh4bCqzcKbKFFmfJorf9LSpSM5dEc=
|
|
||||||
github.com/anacrolix/torrent v1.46.0/go.mod h1:3DE+VA4AgyfKDPjZcIo70D3VFZRo3bfdEBn70CGjca4=
|
|
||||||
github.com/anacrolix/upnp v0.1.3-0.20220123035249-922794e51c96 h1:QAVZ3pN/J4/UziniAhJR2OZ9Ox5kOY2053tBbbqUPYA=
|
|
||||||
github.com/anacrolix/upnp v0.1.3-0.20220123035249-922794e51c96/go.mod h1:Wa6n8cYIdaG35x15aH3Zy6d03f7P728QfdcDeD/IEOs=
|
|
||||||
github.com/anacrolix/utp v0.1.0 h1:FOpQOmIwYsnENnz7tAGohA+r6iXpRjrq8ssKSre2Cp4=
|
|
||||||
github.com/anacrolix/utp v0.1.0/go.mod h1:MDwc+vsGEq7RMw6lr2GKOEqjWny5hO5OZXRVNaBJ2Dk=
|
|
||||||
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
|
||||||
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
|
|
||||||
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
|
|
||||||
github.com/benbjohnson/immutable v0.2.0/go.mod h1:uc6OHo6PN2++n98KHLxW8ef4W42ylHiQSENghE1ezxI=
|
|
||||||
github.com/benbjohnson/immutable v0.3.0 h1:TVRhuZx2wG9SZ0LRdqlbs9S5BZ6Y24hJEHTCgWHZEIw=
|
|
||||||
github.com/benbjohnson/immutable v0.3.0/go.mod h1:uc6OHo6PN2++n98KHLxW8ef4W42ylHiQSENghE1ezxI=
|
|
||||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
|
||||||
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/bits-and-blooms/bitset v1.2.0 h1:Kn4yilvwNtMACtf1eYDlG8H77R07mZSPbMjLyS07ChA=
|
|
||||||
github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
|
|
||||||
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-20191230175014-e8f45d346db8 h1:GKTyiRCL6zVf5wWaqKnf+7Qs6GbEPfd4iMOitWzXJx8=
|
|
||||||
github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8/go.mod h1:spo1JLcs67NmW1aVLEgtA8Yy1elc+X8y5SRW1sFW4Og=
|
|
||||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
|
||||||
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/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
|
github.com/glycerine/go-unsnap-stream v0.0.0-20181221182339-f9677308dec2 h1:Ujru1hufTHVb++eG6OuNDKMxZnGIvF6o/u8q/8h2+I4=
|
||||||
github.com/dustin/go-humanize v0.0.0-20180421182945-02af3965c54e/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
|
||||||
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
|
|
||||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
|
||||||
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/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
|
|
||||||
github.com/edsrzf/mmap-go v1.1.0 h1:6EUwBLQ/Mcr1EYLE4Tn1VdW1A4ckqCQWZBw8Hr0kjpQ=
|
|
||||||
github.com/edsrzf/mmap-go v1.1.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q=
|
|
||||||
github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ=
|
|
||||||
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
|
||||||
github.com/frankban/quicktest v1.9.0/go.mod h1:ui7WezCLWMWxVWr1GETZY3smRy0G4KWq9vcPtJmFl7Y=
|
|
||||||
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
|
|
||||||
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/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/goconvey v0.0.0-20190410193231-58a59202ab31 h1:gclg6gY70GLy3PbkQ1AERPfmLMMagS60DKF78eWwLn8=
|
||||||
github.com/glycerine/goconvey v0.0.0-20180728074245-46e3a41ad493/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24=
|
|
||||||
github.com/glycerine/goconvey v0.0.0-20190315024820-982ee783a72e/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24=
|
|
||||||
github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24=
|
github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24=
|
||||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
|
||||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
|
||||||
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-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/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/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
|
||||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
|
||||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
|
||||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
|
||||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
|
||||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
|
||||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
|
||||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
|
||||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
|
||||||
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.2/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.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/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
|
||||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
|
github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99 h1:twflg0XRTjwKpxb/jFExr4HGq6on2dEOmnL6FV+fgPw=
|
||||||
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
|
|
||||||
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.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.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
|
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
|
||||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
|
||||||
github.com/google/uuid v1.3.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-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-20190910122728-9d188e94fb99/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||||
github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4=
|
|
||||||
github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q=
|
|
||||||
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/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
|
||||||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
|
||||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
|
||||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
|
||||||
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.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.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
|
||||||
github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw=
|
|
||||||
github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
|
||||||
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/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/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
|
||||||
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/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
github.com/marksamman/bencode v0.0.0-20150821143521-dc84f26e086e h1:KMs6SK8iDSR1+ZzOK10L5wGPpWDByyvOe5nrqk51g2U=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/marksamman/bencode v0.0.0-20150821143521-dc84f26e086e/go.mod h1:+AHfJo5+69p+fjvMJTmYajNP9rFBHQcaTDFmuXRRATI=
|
||||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae h1:VeRdUYdCw49yizlSbMEn2SZ+gT+3IUKx8BqxyQdz+BY=
|
||||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
|
||||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
|
||||||
github.com/lispad/go-generics-tools v1.0.0 h1:pPtt9QERE5sGQfwvLakKe2sjhcNKKsDSnr3939ofj1E=
|
|
||||||
github.com/lispad/go-generics-tools v1.0.0/go.mod h1:stn7X24ZIyFvaSyttafq3VlJzGJJJkUtLYdbgi/gopM=
|
|
||||||
github.com/matoous/go-nanoid v1.5.0/go.mod h1:zyD2a71IubI24efhpvkJz+ZwfwagzgSO6UNiFsZKN7U=
|
|
||||||
github.com/matoous/go-nanoid/v2 v2.0.0 h1:d19kur2QuLeHmJBkvYkFdhFBzLoo1XVm2GgTpL+9Tj0=
|
|
||||||
github.com/matoous/go-nanoid/v2 v2.0.0/go.mod h1:FtS4aGPVfEkxKxhdWPAspZpZSh1cOjtM7Ej/So3hR0g=
|
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
|
||||||
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/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
|
||||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
|
||||||
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/philhofer/fwd v1.0.0 h1:UbZqGr5Y38ApvM/V/jEljVxwocdweyH+vmYvRPBnbqQ=
|
||||||
github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw=
|
|
||||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
|
||||||
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.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.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/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/pion/datachannel v1.5.2 h1:piB93s8LGmbECrpO84DnkIVWasRMk3IimbcXkTQLE6E=
|
|
||||||
github.com/pion/datachannel v1.5.2/go.mod h1:FTGQWaHrdCwIJ1rw6xBIfZVkslikjShim5yr05XFuCQ=
|
|
||||||
github.com/pion/dtls/v2 v2.1.1/go.mod h1:qG3gA7ZPZemBqpEFqRKyURYdKEwFZQCGb7gv9T3ON3Y=
|
|
||||||
github.com/pion/dtls/v2 v2.1.2 h1:22Q1Jk9L++Yo7BIf9130MonNPfPVb+YgdYLeyQotuAA=
|
|
||||||
github.com/pion/dtls/v2 v2.1.2/go.mod h1:o6+WvyLDAlXF7YiPB/RlskRoeK+/JtuaZa5emwQcWus=
|
|
||||||
github.com/pion/ice/v2 v2.1.20 h1:xpxXyX5b4WjCh/D905gzBeW/hbJxMEPx2ptVfrhVE6M=
|
|
||||||
github.com/pion/ice/v2 v2.1.20/go.mod h1:hEAldRzBhTtAfvlU1V/2/nLCMvveQWFKPNCop+63/Iw=
|
|
||||||
github.com/pion/interceptor v0.1.7 h1:HThW0tIIKT9RRoDWGURe8rlZVOx0fJHxBHpA0ej0+bo=
|
|
||||||
github.com/pion/interceptor v0.1.7/go.mod h1:Lh3JSl/cbJ2wP8I3ccrjh1K/deRGRn3UlSPuOTiHb6U=
|
|
||||||
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/mdns v0.0.5 h1:Q2oj/JB3NqfzY9xGZ1fPzZzK7sDSD8rZPOvcIQ10BCw=
|
|
||||||
github.com/pion/mdns v0.0.5/go.mod h1:UgssrvdD3mxpi8tMxAXbsppL3vJ4Jipw1mTCW+al01g=
|
|
||||||
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/rtcp v1.2.6/go.mod h1:52rMNPWFsjr39z9B9MhnkqhPLoeHTv1aN63o/42bWE0=
|
|
||||||
github.com/pion/rtcp v1.2.9 h1:1ujStwg++IOLIEoOiIQ2s+qBuJ1VN81KW+9pMPsif+U=
|
|
||||||
github.com/pion/rtcp v1.2.9/go.mod h1:qVPhiCzAm4D/rxb6XzKeyZiQK69yJpbUDJSF7TgrqNo=
|
|
||||||
github.com/pion/rtp v1.7.0/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
|
|
||||||
github.com/pion/rtp v1.7.4 h1:4dMbjb1SuynU5OpA3kz1zHK+u+eOCQjW3MAeVHf1ODA=
|
|
||||||
github.com/pion/rtp v1.7.4/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
|
|
||||||
github.com/pion/sctp v1.8.0/go.mod h1:xFe9cLMZ5Vj6eOzpyiKjT9SwGM4KpK/8Jbw5//jc+0s=
|
|
||||||
github.com/pion/sctp v1.8.2 h1:yBBCIrUMJ4yFICL3RIvR4eh/H2BTTvlligmSTy+3kiA=
|
|
||||||
github.com/pion/sctp v1.8.2/go.mod h1:xFe9cLMZ5Vj6eOzpyiKjT9SwGM4KpK/8Jbw5//jc+0s=
|
|
||||||
github.com/pion/sdp/v3 v3.0.4 h1:2Kf+dgrzJflNCSw3TV5v2VLeI0s/qkzy2r5jlR0wzf8=
|
|
||||||
github.com/pion/sdp/v3 v3.0.4/go.mod h1:bNiSknmJE0HYBprTHXKPQ3+JjacTv5uap92ueJZKsRk=
|
|
||||||
github.com/pion/srtp/v2 v2.0.5 h1:ks3wcTvIUE/GHndO3FAvROQ9opy0uLELpwHJaQ1yqhQ=
|
|
||||||
github.com/pion/srtp/v2 v2.0.5/go.mod h1:8k6AJlal740mrZ6WYxc4Dg6qDqqhxoRG2GSjlUhDF0A=
|
|
||||||
github.com/pion/stun v0.3.5 h1:uLUCBCkQby4S1cf6CGuR9QrVOKcvUwFeemaC865QHDg=
|
|
||||||
github.com/pion/stun v0.3.5/go.mod h1:gDMim+47EeEtfWogA37n6qXZS88L5V6LqFcf+DZA2UA=
|
|
||||||
github.com/pion/transport v0.12.2/go.mod h1:N3+vZQD9HlDP5GWkZ85LohxNsDcNgofQmyL6ojX5d8Q=
|
|
||||||
github.com/pion/transport v0.12.3/go.mod h1:OViWW9SP2peE/HbwBvARicmAVnesphkNkCVZIWJ6q9A=
|
|
||||||
github.com/pion/transport v0.13.0 h1:KWTA5ZrQogizzYwPEciGtHPLwpAjE91FgXnyu+Hv2uY=
|
|
||||||
github.com/pion/transport v0.13.0/go.mod h1:yxm9uXpK9bpBBWkITk13cLo1y5/ur5VQpG22ny6EP7g=
|
|
||||||
github.com/pion/turn/v2 v2.0.6 h1:AsXjSPR6Im15DMTB39NlfdTY9BQfieANPBjdg/aVNwY=
|
|
||||||
github.com/pion/turn/v2 v2.0.6/go.mod h1:+y7xl719J8bAEVpSXBXvTxStjJv3hbz9YFflvkpcGPw=
|
|
||||||
github.com/pion/udp v0.1.1 h1:8UAPvyqmsxK8oOjloDk4wUt63TzFe9WEJkg5lChlj7o=
|
|
||||||
github.com/pion/udp v0.1.1/go.mod h1:6AFo+CMdKQm7UiA0eUPA8/eVCTx8jBIITLZHc9DWX5M=
|
|
||||||
github.com/pion/webrtc/v3 v3.1.24-0.20220208053747-94262c1b2b38 h1:+IEql+S+YAj3S5e7Ftl/u4xPcZGG0WwLFsyFj6NRTz4=
|
|
||||||
github.com/pion/webrtc/v3 v3.1.24-0.20220208053747-94262c1b2b38/go.mod h1:L5S/oAhL0Fzt/rnftVQRrP80/j5jygY7XRZzWwFx6P4=
|
|
||||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
|
||||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
|
||||||
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.5.1/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
|
|
||||||
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-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
|
||||||
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
|
||||||
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.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
|
|
||||||
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.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
|
||||||
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/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
|
||||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
|
||||||
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
|
|
||||||
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
|
||||||
github.com/rs/dnscache v0.0.0-20210201191234-295bba877686 h1:IJ6Df0uxPDtNoByV0KkzVKNseWvZFCNM/S9UoyOMCSI=
|
|
||||||
github.com/rs/dnscache v0.0.0-20210201191234-295bba877686/go.mod h1:qe5TWALJ8/a1Lqznoc5BDHpYX/8HU60Hm2AwRmqzxqA=
|
|
||||||
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/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
|
|
||||||
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/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/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-20190306220146-200a235640ff/go.mod h1:KSQcGKpxUMHk3nbYzs/tIBAM2iDooCn0BmttHOJEbLs=
|
|
||||||
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/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.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||||
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/tinylib/msgp v1.1.0 h1:9fQd+ICuRIu/ue4vxJZu6/LzxN0HwMds2nq/0cFvxHU=
|
||||||
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.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
|
|
||||||
github.com/tidwall/btree v1.3.1 h1:636+tdVDs8Hjcf35Di260W2xCW4KuoXOKyk9QWOvCpA=
|
|
||||||
github.com/tidwall/btree v1.3.1/go.mod h1:LGm8L/DZjPLmeWGjv5kFrY8dL4uVhMmzmmLYmsObdKE=
|
|
||||||
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/willf/bitset v1.1.10 h1:NotGKqX0KwQ72NUzqrjZq5ipPNDQex9lo3WpaS8L2sc=
|
||||||
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/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
|
||||||
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
|
||||||
go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU=
|
|
||||||
go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
|
|
||||||
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.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
|
||||||
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-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
|
||||||
golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838 h1:71vQrMauZZhcTVK6KdYM+rklehEEwb3E+ZhaE5jrPrE=
|
|
||||||
golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
|
||||||
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4=
|
|
||||||
golang.org/x/exp v0.0.0-20220428152302-39d4317da171 h1:TfdoLivD44QwvssI9Sv1xwa5DcL5XQr4au4sZ2F2NV4=
|
|
||||||
golang.org/x/exp v0.0.0-20220428152302-39d4317da171/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE=
|
|
||||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
|
||||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
|
||||||
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-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
|
||||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
|
||||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
|
||||||
golang.org/x/mobile v0.0.0-20220928052126-fa6bcb076835 h1:v6yt1xSu+4xV0fPEmB8s2BHdOaoU0SSZrVQzj6pRvqk=
|
|
||||||
golang.org/x/mobile v0.0.0-20220928052126-fa6bcb076835/go.mod h1:M32cGdzp91A8Ex9qQtyZinr19EYxzkFqDjW2oyHzTDQ=
|
|
||||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|
||||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|
||||||
golang.org/x/mod v0.6.0-dev.0.20211013180041-c96bc1413d57 h1:LQmS1nU0twXLA96Kt7U9qtHJEbBk3z6Q0V4UXjZkpr4=
|
|
||||||
golang.org/x/mod v0.6.0-dev.0.20211013180041-c96bc1413d57/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
|
|
||||||
golang.org/x/net v0.0.0-20180524181706-dfa909b99c79/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-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-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
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-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20200707034311-ab3426394381/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-20201201195509-5d6afe98e0b7/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
|
||||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/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-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
|
||||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
|
||||||
golang.org/x/net v0.0.0-20211201190559-0a0e4e1bb54c/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
|
||||||
golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
|
||||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk=
|
|
||||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
|
||||||
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/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/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-20201020160332-67f06af15bc9/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-20220819030929-7fc1605a5dde h1:ejfdSekXMDxDLbRrJMwUk6KnSLZ2McaUCVcIKM+N6jc=
|
|
||||||
golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
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-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
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 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc=
|
||||||
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-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
|
||||||
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-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-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-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-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-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM=
|
|
||||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
|
||||||
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=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
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.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
|
||||||
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac h1:7zkz7BUtwNFFqcowJ+RIgu2MaV/MapERkDIy+mwPyjs=
|
|
||||||
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
|
||||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.0.0-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-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
|
||||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
|
||||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/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-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
|
||||||
golang.org/x/tools v0.1.8-0.20211022200916-316ba0b74098/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
|
|
||||||
golang.org/x/tools v0.1.8-0.20211029000441-d6a9af8af023 h1:0c3L82FDQ5rt1bjTBlchS8t6RQ6299/+5bWMnRLh+uI=
|
|
||||||
golang.org/x/tools v0.1.8-0.20211029000441-d6a9af8af023/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
|
|
||||||
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-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
|
|
||||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
|
||||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
|
||||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
|
||||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
|
||||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
|
||||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
|
||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
|
||||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
|
||||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
|
||||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
|
||||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
|
||||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
|
||||||
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.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/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/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
|
||||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
|
||||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/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-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
package controller
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Controller struct {
|
|
||||||
GetConfig func(v any) (bool, error)
|
|
||||||
FileController
|
|
||||||
//ContextDialer() (proxy.Dialer, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type FileController interface {
|
|
||||||
Touch(name string, size int64) (file *os.File, err error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type DefaultFileController struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewController() *Controller {
|
|
||||||
return &Controller{
|
|
||||||
FileController: &DefaultFileController{},
|
|
||||||
GetConfig: func(v any) (bool, error) {
|
|
||||||
return false, nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *DefaultFileController) Touch(name string, size int64) (file *os.File, err error) {
|
|
||||||
dir := filepath.Dir(name)
|
|
||||||
if err = os.MkdirAll(dir, os.ModePerm); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
file, err = os.Create(name)
|
|
||||||
if err != nil && !errors.Is(err, os.ErrExist) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if size > 0 {
|
|
||||||
err = os.Truncate(name, size)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
/*func (c *DefaultController) ContextDialer() (proxy.Dialer, error) {
|
|
||||||
// return proxy.SOCKS5("tpc", "127.0.0.1:9999", nil, nil)
|
|
||||||
var dialer proxy.Dialer
|
|
||||||
return &DialerWarp{dialer: dialer}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type DialerWarp struct {
|
|
||||||
dialer proxy.Dialer
|
|
||||||
}
|
|
||||||
|
|
||||||
type ConnWarp struct {
|
|
||||||
conn net.Conn
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ConnWarp) Read(b []byte) (n int, err error) {
|
|
||||||
return c.conn.Read(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ConnWarp) Write(b []byte) (n int, err error) {
|
|
||||||
return c.conn.Write(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ConnWarp) Close() error {
|
|
||||||
return c.conn.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ConnWarp) LocalAddr() net.Addr {
|
|
||||||
return c.conn.LocalAddr()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ConnWarp) RemoteAddr() net.Addr {
|
|
||||||
return c.conn.RemoteAddr()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ConnWarp) SetDeadline(t time.Time) error {
|
|
||||||
return c.conn.SetDeadline(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ConnWarp) SetReadDeadline(t time.Time) error {
|
|
||||||
return c.conn.SetReadDeadline(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ConnWarp) SetWriteDeadline(t time.Time) error {
|
|
||||||
return c.conn.SetWriteDeadline(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DialerWarp) Dial(network, addr string) (c net.Conn, err error) {
|
|
||||||
conn, err := d.dialer.Dial(network, addr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &ConnWarp{conn: conn}, nil
|
|
||||||
}*/
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
package fetcher
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/monkeyWie/gopeed/internal/controller"
|
|
||||||
"github.com/monkeyWie/gopeed/pkg/base"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Fetcher defines the interface for a download protocol.
|
|
||||||
type Fetcher interface {
|
|
||||||
// Name return the name of the protocol.
|
|
||||||
Name() string
|
|
||||||
|
|
||||||
Setup(ctl *controller.Controller) error
|
|
||||||
// Resolve resource info from request
|
|
||||||
Resolve(req *base.Request) (res *base.Resource, err error)
|
|
||||||
// Create ready to download, but not started
|
|
||||||
Create(res *base.Resource, opts *base.Options) (err error)
|
|
||||||
Start() (err error)
|
|
||||||
Pause() (err error)
|
|
||||||
Continue() (err error)
|
|
||||||
Close() (err error)
|
|
||||||
|
|
||||||
// Progress returns the progress of the download.
|
|
||||||
Progress() Progress
|
|
||||||
// Wait for the download to complete, this method will block until the download is done.
|
|
||||||
Wait() (err error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FetcherBuilder defines the interface for a fetcher builder.
|
|
||||||
type FetcherBuilder interface {
|
|
||||||
// Schemes returns the schemes supported by the fetcher.
|
|
||||||
Schemes() []string
|
|
||||||
// Build returns a new fetcher.
|
|
||||||
Build() Fetcher
|
|
||||||
|
|
||||||
// Store fetcher
|
|
||||||
Store(fetcher Fetcher) (any, error)
|
|
||||||
// Restore fetcher
|
|
||||||
Restore() (v any, f func(res *base.Resource, opts *base.Options, v any) Fetcher)
|
|
||||||
}
|
|
||||||
|
|
||||||
type DefaultFetcher struct {
|
|
||||||
Ctl *controller.Controller
|
|
||||||
DoneCh chan error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *DefaultFetcher) Setup(ctl *controller.Controller) (err error) {
|
|
||||||
f.Ctl = ctl
|
|
||||||
f.DoneCh = make(chan error, 1)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *DefaultFetcher) Wait() (err error) {
|
|
||||||
return <-f.DoneCh
|
|
||||||
}
|
|
||||||
|
|
||||||
// Progress is a map of the progress of each file in the torrent.
|
|
||||||
type Progress []int64
|
|
||||||
|
|
||||||
// TotalDownloaded returns the total downloaded bytes.
|
|
||||||
func (p Progress) TotalDownloaded() int64 {
|
|
||||||
total := int64(0)
|
|
||||||
for _, d := range p {
|
|
||||||
total += d
|
|
||||||
}
|
|
||||||
return total
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
package bt
|
|
||||||
|
|
||||||
type config struct {
|
|
||||||
Trackers []string `json:"trackers"`
|
|
||||||
}
|
|
||||||
@@ -1,235 +0,0 @@
|
|||||||
package bt
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/anacrolix/torrent"
|
|
||||||
"github.com/anacrolix/torrent/metainfo"
|
|
||||||
"github.com/anacrolix/torrent/storage"
|
|
||||||
"github.com/monkeyWie/gopeed/internal/controller"
|
|
||||||
"github.com/monkeyWie/gopeed/internal/fetcher"
|
|
||||||
"github.com/monkeyWie/gopeed/pkg/base"
|
|
||||||
"github.com/monkeyWie/gopeed/pkg/util"
|
|
||||||
"path/filepath"
|
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
var client *torrent.Client
|
|
||||||
|
|
||||||
type Fetcher struct {
|
|
||||||
Ctl *controller.Controller
|
|
||||||
|
|
||||||
torrent *torrent.Torrent
|
|
||||||
res *base.Resource
|
|
||||||
opts *base.Options
|
|
||||||
|
|
||||||
ready atomic.Bool
|
|
||||||
progress fetcher.Progress
|
|
||||||
|
|
||||||
torrentPaths map[string]string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *Fetcher) Name() string {
|
|
||||||
return "bt"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *Fetcher) Setup(ctl *controller.Controller) (err error) {
|
|
||||||
f.Ctl = ctl
|
|
||||||
var once sync.Once
|
|
||||||
once.Do(func() {
|
|
||||||
cfg := torrent.NewDefaultClientConfig()
|
|
||||||
cfg.ListenPort = 0
|
|
||||||
|
|
||||||
// Support custom download path for each torrent
|
|
||||||
pieceCompletion, err := storage.NewDefaultPieceCompletionForDir(cfg.DataDir)
|
|
||||||
if err != nil {
|
|
||||||
pieceCompletion = storage.NewMapPieceCompletion()
|
|
||||||
}
|
|
||||||
clientImplCloser := storage.NewFileOpts(storage.NewFileClientOpts{
|
|
||||||
ClientBaseDir: cfg.DataDir,
|
|
||||||
TorrentDirMaker: func(baseDir string, info *metainfo.Info, infoHash metainfo.Hash) string {
|
|
||||||
if dir, ok := f.torrentPaths[infoHash.String()]; ok {
|
|
||||||
return dir
|
|
||||||
}
|
|
||||||
return baseDir
|
|
||||||
},
|
|
||||||
PieceCompletion: pieceCompletion,
|
|
||||||
})
|
|
||||||
cfg.DefaultStorage = clientImplCloser
|
|
||||||
client, err = torrent.NewClient(cfg)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
f.torrentPaths = make(map[string]string)
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *Fetcher) Wait() (err error) {
|
|
||||||
for {
|
|
||||||
if f.ready.Load() {
|
|
||||||
done := true
|
|
||||||
for _, selectIndex := range f.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.opts.SelectFiles {
|
|
||||||
if i == selectIndex {
|
|
||||||
selected = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !selected {
|
|
||||||
util.SafeRemove(filepath.Join(f.opts.Path, file.Path()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
time.Sleep(time.Millisecond * 500)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *Fetcher) Resolve(req *base.Request) (res *base.Resource, err error) {
|
|
||||||
if err = f.addTorrent(req.URL, true); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer f.torrent.Drop()
|
|
||||||
res = &base.Resource{
|
|
||||||
Req: req,
|
|
||||||
Name: f.torrent.Name(),
|
|
||||||
Range: true,
|
|
||||||
Files: make([]*base.FileInfo, len(f.torrent.Files())),
|
|
||||||
Hash: f.torrent.InfoHash().String(),
|
|
||||||
}
|
|
||||||
for i, file := range f.torrent.Files() {
|
|
||||||
res.Files[i] = &base.FileInfo{
|
|
||||||
Name: filepath.Base(file.DisplayPath()),
|
|
||||||
Path: util.Dir(file.Path()),
|
|
||||||
Size: file.Length(),
|
|
||||||
}
|
|
||||||
res.Size += file.Length()
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *Fetcher) Create(res *base.Resource, opts *base.Options) (err error) {
|
|
||||||
f.res = res
|
|
||||||
f.opts = opts
|
|
||||||
if len(f.opts.SelectFiles) == 0 {
|
|
||||||
f.opts.SelectFiles = make([]int, 0)
|
|
||||||
for i := range f.torrent.Files() {
|
|
||||||
f.opts.SelectFiles = append(f.opts.SelectFiles, i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if f.opts.Path != "" {
|
|
||||||
f.torrentPaths[res.Hash] = f.opts.Path
|
|
||||||
}
|
|
||||||
|
|
||||||
f.progress = make(fetcher.Progress, len(f.opts.SelectFiles))
|
|
||||||
f.ready.Store(false)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *Fetcher) Start() (err error) {
|
|
||||||
if err = f.addTorrent(f.res.Req.URL, false); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
files := f.torrent.Files()
|
|
||||||
if len(f.opts.SelectFiles) == len(files) {
|
|
||||||
f.torrent.DownloadAll()
|
|
||||||
} else {
|
|
||||||
for _, selectIndex := range f.opts.SelectFiles {
|
|
||||||
file := files[selectIndex]
|
|
||||||
file.Download()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *Fetcher) Pause() (err error) {
|
|
||||||
f.ready.Store(false)
|
|
||||||
f.torrent.Drop()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *Fetcher) Continue() (err error) {
|
|
||||||
return f.Start()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *Fetcher) Close() (err error) {
|
|
||||||
f.torrent.Drop()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *Fetcher) Progress() fetcher.Progress {
|
|
||||||
if !f.ready.Load() {
|
|
||||||
return f.progress
|
|
||||||
}
|
|
||||||
for i := range f.progress {
|
|
||||||
selectIndex := f.opts.SelectFiles[i]
|
|
||||||
file := f.torrent.Files()[selectIndex]
|
|
||||||
f.progress[i] = file.BytesCompleted()
|
|
||||||
}
|
|
||||||
return f.progress
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *Fetcher) addTorrent(url string, resolve bool) (err error) {
|
|
||||||
schema := util.ParseSchema(url)
|
|
||||||
if schema == "MAGNET" {
|
|
||||||
f.torrent, err = client.AddMagnet(url)
|
|
||||||
} else {
|
|
||||||
f.torrent, err = client.AddTorrentFromFile(url)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var cfg config
|
|
||||||
exist, err := f.Ctl.GetConfig(&cfg)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if exist && len(cfg.Trackers) > 0 {
|
|
||||||
announceList := make([][]string, 0)
|
|
||||||
for _, tracker := range cfg.Trackers {
|
|
||||||
announceList = append(announceList, []string{tracker})
|
|
||||||
}
|
|
||||||
f.torrent.AddTrackers(announceList)
|
|
||||||
}
|
|
||||||
<-f.torrent.GotInfo()
|
|
||||||
if resolve {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
f.ready.Store(true)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
type FetcherBuilder struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
var schemes = []string{"FILE", "MAGNET"}
|
|
||||||
|
|
||||||
func (fb *FetcherBuilder) Schemes() []string {
|
|
||||||
return schemes
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fb *FetcherBuilder) Build() fetcher.Fetcher {
|
|
||||||
return &Fetcher{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fb *FetcherBuilder) Store(f fetcher.Fetcher) (data any, err error) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fb *FetcherBuilder) Restore() (v any, f func(res *base.Resource, opts *base.Options, v any) fetcher.Fetcher) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
package bt
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"github.com/monkeyWie/gopeed/internal/controller"
|
|
||||||
"github.com/monkeyWie/gopeed/internal/fetcher"
|
|
||||||
"github.com/monkeyWie/gopeed/internal/test"
|
|
||||||
"github.com/monkeyWie/gopeed/pkg/base"
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestFetcher_Resolve_Torrent(t *testing.T) {
|
|
||||||
doResolve(t, buildFetcher())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFetcher_Config(t *testing.T) {
|
|
||||||
doResolve(t, buildConfigFetcher())
|
|
||||||
}
|
|
||||||
|
|
||||||
func doResolve(t *testing.T, fetcher fetcher.Fetcher) {
|
|
||||||
res, err := fetcher.Resolve(&base.Request{
|
|
||||||
URL: "./testdata/ubuntu-22.04-live-server-amd64.iso.torrent",
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
want := &base.Resource{
|
|
||||||
Req: res.Req,
|
|
||||||
Name: "ubuntu-22.04-live-server-amd64.iso",
|
|
||||||
Size: 1466714112,
|
|
||||||
Range: true,
|
|
||||||
Files: []*base.FileInfo{
|
|
||||||
{
|
|
||||||
Name: "ubuntu-22.04-live-server-amd64.iso",
|
|
||||||
Size: 1466714112,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Hash: "8a55cfbd5ca5d11507364765936c4f9e55b253ed",
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(want, res) {
|
|
||||||
t.Errorf("Resolve() got = %v, want %v", res, want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildFetcher() fetcher.Fetcher {
|
|
||||||
fetcher := new(FetcherBuilder).Build()
|
|
||||||
fetcher.Setup(controller.NewController())
|
|
||||||
return fetcher
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildConfigFetcher() fetcher.Fetcher {
|
|
||||||
fetcher := new(FetcherBuilder).Build()
|
|
||||||
newController := controller.NewController()
|
|
||||||
mockCfg := config{
|
|
||||||
Trackers: []string{
|
|
||||||
"udp://tracker.birkenwald.de:6969/announce",
|
|
||||||
"udp://tracker.bitsearch.to:1337/announce",
|
|
||||||
}}
|
|
||||||
newController.GetConfig = func(v any) (bool, error) {
|
|
||||||
if err := json.Unmarshal([]byte(test.ToJson(mockCfg)), v); err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
fetcher.Setup(newController)
|
|
||||||
return fetcher
|
|
||||||
}
|
|
||||||
Binary file not shown.
@@ -1,5 +0,0 @@
|
|||||||
package http
|
|
||||||
|
|
||||||
type config struct {
|
|
||||||
Connections int `json:"connections"`
|
|
||||||
}
|
|
||||||
@@ -1,473 +0,0 @@
|
|||||||
package http
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"github.com/monkeyWie/gopeed/internal/fetcher"
|
|
||||||
"github.com/monkeyWie/gopeed/pkg/base"
|
|
||||||
fhttp "github.com/monkeyWie/gopeed/pkg/protocol/http"
|
|
||||||
"github.com/monkeyWie/gopeed/pkg/util"
|
|
||||||
"golang.org/x/sync/errgroup"
|
|
||||||
"io"
|
|
||||||
"mime"
|
|
||||||
"net/http"
|
|
||||||
"net/http/cookiejar"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type RequestError struct {
|
|
||||||
Code int
|
|
||||||
Msg string
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRequestError(code int, msg string) *RequestError {
|
|
||||||
return &RequestError{Code: code, Msg: msg}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (re *RequestError) Error() string {
|
|
||||||
return fmt.Sprintf("http request fail,code:%d", re.Code)
|
|
||||||
}
|
|
||||||
|
|
||||||
type chunk struct {
|
|
||||||
Status base.Status
|
|
||||||
Begin int64
|
|
||||||
End int64
|
|
||||||
Downloaded int64
|
|
||||||
}
|
|
||||||
|
|
||||||
func newChunk(begin int64, end int64) *chunk {
|
|
||||||
return &chunk{
|
|
||||||
Status: base.DownloadStatusReady,
|
|
||||||
Begin: begin,
|
|
||||||
End: end,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type Fetcher struct {
|
|
||||||
*fetcher.DefaultFetcher
|
|
||||||
|
|
||||||
res *base.Resource
|
|
||||||
opts *base.Options
|
|
||||||
status base.Status
|
|
||||||
chunks []*chunk
|
|
||||||
|
|
||||||
file *os.File
|
|
||||||
ctx context.Context
|
|
||||||
cancel context.CancelFunc
|
|
||||||
pauseCh chan interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *Fetcher) Name() string {
|
|
||||||
return "http"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *Fetcher) Resolve(req *base.Request) (*base.Resource, error) {
|
|
||||||
if err := base.ParseReqExtra[fhttp.ReqExtra](req); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
httpReq, err := buildRequest(nil, req)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
client := buildClient()
|
|
||||||
// 只访问一个字节,测试资源是否支持Range请求
|
|
||||||
httpReq.Header.Set(base.HttpHeaderRange, fmt.Sprintf(base.HttpHeaderRangeFormat, 0, 0))
|
|
||||||
httpResp, err := client.Do(httpReq)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// 拿到响应头就关闭,不用加defer
|
|
||||||
httpResp.Body.Close()
|
|
||||||
res := &base.Resource{
|
|
||||||
Req: req,
|
|
||||||
Range: false,
|
|
||||||
Files: []*base.FileInfo{},
|
|
||||||
}
|
|
||||||
if base.HttpCodePartialContent == httpResp.StatusCode {
|
|
||||||
// 返回206响应码表示支持断点下载
|
|
||||||
res.Range = true
|
|
||||||
// 解析资源大小: bytes 0-1000/1001 => 1001
|
|
||||||
contentTotal := path.Base(httpResp.Header.Get(base.HttpHeaderContentRange))
|
|
||||||
if contentTotal != "" {
|
|
||||||
parse, err := strconv.ParseInt(contentTotal, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
res.Size = parse
|
|
||||||
}
|
|
||||||
} else if base.HttpCodeOK == httpResp.StatusCode {
|
|
||||||
// 返回200响应码,不支持断点下载,通过Content-Length头获取文件大小,获取不到的话可能是chunked编码
|
|
||||||
contentLength := httpResp.Header.Get(base.HttpHeaderContentLength)
|
|
||||||
if contentLength != "" {
|
|
||||||
parse, err := strconv.ParseInt(contentLength, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
res.Size = parse
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return nil, NewRequestError(httpResp.StatusCode, httpResp.Status)
|
|
||||||
}
|
|
||||||
file := &base.FileInfo{
|
|
||||||
Size: res.Size,
|
|
||||||
}
|
|
||||||
contentDisposition := httpResp.Header.Get(base.HttpHeaderContentDisposition)
|
|
||||||
if contentDisposition != "" {
|
|
||||||
_, params, _ := mime.ParseMediaType(contentDisposition)
|
|
||||||
filename := params["filePath"]
|
|
||||||
if filename != "" {
|
|
||||||
file.Name = filename
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Get file filePath by URL
|
|
||||||
if file.Name == "" && strings.Count(req.URL, "/") > 2 {
|
|
||||||
file.Name = filepath.Base(req.URL)
|
|
||||||
}
|
|
||||||
// unknown file filePath
|
|
||||||
if file.Name == "" {
|
|
||||||
file.Name = "unknown"
|
|
||||||
}
|
|
||||||
res.Files = append(res.Files, file)
|
|
||||||
res.Name = file.Name
|
|
||||||
return res, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *Fetcher) Create(res *base.Resource, opts *base.Options) error {
|
|
||||||
f.res = res
|
|
||||||
f.opts = opts
|
|
||||||
f.status = base.DownloadStatusReady
|
|
||||||
|
|
||||||
if err := base.ParseReqExtra[fhttp.ReqExtra](res.Req); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := base.ParseOptsExtra[fhttp.OptsExtra](opts); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if opts.Extra == nil {
|
|
||||||
opts.Extra = &fhttp.ReqExtra{}
|
|
||||||
}
|
|
||||||
extra := opts.Extra.(*fhttp.OptsExtra)
|
|
||||||
if extra.Connections == 0 {
|
|
||||||
var cfg config
|
|
||||||
exist, err := f.Ctl.GetConfig(&cfg)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if exist {
|
|
||||||
extra.Connections = cfg.Connections
|
|
||||||
} else {
|
|
||||||
extra.Connections = 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *Fetcher) Start() (err error) {
|
|
||||||
// 创建文件
|
|
||||||
name := f.filepath()
|
|
||||||
f.file, err = f.Ctl.Touch(name, f.res.Size)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
f.status = base.DownloadStatusRunning
|
|
||||||
f.chunks = f.splitChunk()
|
|
||||||
f.fetch()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *Fetcher) Pause() (err error) {
|
|
||||||
if base.DownloadStatusRunning != f.status {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
f.status = base.DownloadStatusPause
|
|
||||||
|
|
||||||
if f.cancel != nil {
|
|
||||||
f.cancel()
|
|
||||||
<-f.pauseCh
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *Fetcher) Continue() (err error) {
|
|
||||||
if base.DownloadStatusRunning == f.status || base.DownloadStatusDone == f.status {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
f.status = base.DownloadStatusRunning
|
|
||||||
f.file, err = os.OpenFile(f.filepath(), os.O_RDWR, os.ModeAppend)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
f.fetch()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *Fetcher) Close() (err error) {
|
|
||||||
if err = f.Pause(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *Fetcher) Progress() fetcher.Progress {
|
|
||||||
p := make(fetcher.Progress, 0)
|
|
||||||
if len(f.chunks) > 0 {
|
|
||||||
total := int64(0)
|
|
||||||
for _, chunk := range f.chunks {
|
|
||||||
total += chunk.Downloaded
|
|
||||||
}
|
|
||||||
p = append(p, total)
|
|
||||||
}
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *Fetcher) filepath() string {
|
|
||||||
return util.Filepath(f.opts.Path, f.res.Files[0].Name, f.opts.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *Fetcher) fetch() {
|
|
||||||
f.ctx, f.cancel = context.WithCancel(context.Background())
|
|
||||||
eg, _ := errgroup.WithContext(f.ctx)
|
|
||||||
for i := 0; i < f.opts.Extra.(*fhttp.OptsExtra).Connections; i++ {
|
|
||||||
i := i
|
|
||||||
eg.Go(func() error {
|
|
||||||
return f.fetchChunk(i)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
err := eg.Wait()
|
|
||||||
// 下载停止,关闭文件句柄
|
|
||||||
f.file.Close()
|
|
||||||
if f.status == base.DownloadStatusPause {
|
|
||||||
f.pauseCh <- nil
|
|
||||||
} else {
|
|
||||||
if err != nil {
|
|
||||||
f.status = base.DownloadStatusError
|
|
||||||
} else {
|
|
||||||
f.status = base.DownloadStatusDone
|
|
||||||
}
|
|
||||||
f.DoneCh <- err
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *Fetcher) fetchChunk(index int) (err error) {
|
|
||||||
chunk := f.chunks[index]
|
|
||||||
|
|
||||||
httpReq, err := buildRequest(f.ctx, f.res.Req)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var (
|
|
||||||
client = buildClient()
|
|
||||||
buf = make([]byte, 8192)
|
|
||||||
)
|
|
||||||
// 重试5次
|
|
||||||
for i := 0; i < 5; i++ {
|
|
||||||
// 如果下载完成直接返回
|
|
||||||
if chunk.Status == base.DownloadStatusDone {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// 如果已暂停直接跳出
|
|
||||||
if f.status == base.DownloadStatusPause {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
var (
|
|
||||||
resp *http.Response
|
|
||||||
retry bool
|
|
||||||
)
|
|
||||||
if f.res.Range {
|
|
||||||
httpReq.Header.Set(base.HttpHeaderRange,
|
|
||||||
fmt.Sprintf(base.HttpHeaderRangeFormat, chunk.Begin+chunk.Downloaded, chunk.End))
|
|
||||||
} else {
|
|
||||||
chunk.Downloaded = 0
|
|
||||||
}
|
|
||||||
err = func() error {
|
|
||||||
resp, err = client.Do(httpReq)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if resp.StatusCode != base.HttpCodeOK && resp.StatusCode != base.HttpCodePartialContent {
|
|
||||||
err = NewRequestError(resp.StatusCode, resp.Status)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}()
|
|
||||||
if err != nil {
|
|
||||||
// 请求失败重试
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// 请求成功就重置错误次数,连续失败5次才终止
|
|
||||||
i = 0
|
|
||||||
retry, err = func() (bool, error) {
|
|
||||||
defer resp.Body.Close()
|
|
||||||
for {
|
|
||||||
n, err := resp.Body.Read(buf)
|
|
||||||
if n > 0 {
|
|
||||||
_, err := f.file.WriteAt(buf[:n], chunk.Begin+chunk.Downloaded)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
chunk.Downloaded += int64(n)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
if err != io.EOF {
|
|
||||||
return true, err
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false, nil
|
|
||||||
}()
|
|
||||||
if !retry {
|
|
||||||
// 下载成功,跳出重试
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
chunk.Status = base.DownloadStatusError
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
isComplete := f.res.Range && chunk.Downloaded >= chunk.End-chunk.Begin+1
|
|
||||||
if f.status == base.DownloadStatusPause && !isComplete {
|
|
||||||
chunk.Status = base.DownloadStatusPause
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
chunk.Status = base.DownloadStatusDone
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *Fetcher) splitChunk() (chunks []*chunk) {
|
|
||||||
if f.res.Range {
|
|
||||||
connections := f.opts.Extra.(*fhttp.OptsExtra).Connections
|
|
||||||
// 每个连接平均需要下载的分块大小
|
|
||||||
chunkSize := f.res.Size / int64(connections)
|
|
||||||
chunks = make([]*chunk, connections)
|
|
||||||
for i := 0; i < connections; i++ {
|
|
||||||
var (
|
|
||||||
begin = chunkSize * int64(i)
|
|
||||||
end int64
|
|
||||||
)
|
|
||||||
if i == connections-1 {
|
|
||||||
// 最后一个分块需要保证把文件下载完
|
|
||||||
end = f.res.Size - 1
|
|
||||||
} else {
|
|
||||||
end = begin + chunkSize - 1
|
|
||||||
}
|
|
||||||
chunk := newChunk(begin, end)
|
|
||||||
chunks[i] = chunk
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 只支持单连接下载
|
|
||||||
chunks = make([]*chunk, 1)
|
|
||||||
chunks[0] = newChunk(0, 0)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildClient() *http.Client {
|
|
||||||
// Cookie handle
|
|
||||||
jar, _ := cookiejar.New(nil)
|
|
||||||
return &http.Client{
|
|
||||||
Jar: jar,
|
|
||||||
Timeout: time.Second * 60,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildRequest(ctx context.Context, req *base.Request) (httpReq *http.Request, err error) {
|
|
||||||
url, err := url.Parse(req.URL)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
method string
|
|
||||||
body io.Reader
|
|
||||||
)
|
|
||||||
headers := make(map[string][]string)
|
|
||||||
if req.Extra == nil {
|
|
||||||
method = http.MethodGet
|
|
||||||
} else {
|
|
||||||
extra := req.Extra.(*fhttp.ReqExtra)
|
|
||||||
if extra.Method != "" {
|
|
||||||
method = extra.Method
|
|
||||||
} else {
|
|
||||||
method = http.MethodGet
|
|
||||||
}
|
|
||||||
if len(extra.Header) > 0 {
|
|
||||||
for k, v := range extra.Header {
|
|
||||||
headers[k] = []string{v}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if extra.Body != "" {
|
|
||||||
body = bytes.NewBufferString(extra.Body)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ctx != nil {
|
|
||||||
httpReq, err = http.NewRequestWithContext(ctx, method, url.String(), body)
|
|
||||||
} else {
|
|
||||||
httpReq, err = http.NewRequest(method, url.String(), body)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
httpReq.Header = headers
|
|
||||||
return httpReq, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type fetcherData struct {
|
|
||||||
Chunks []*chunk
|
|
||||||
}
|
|
||||||
|
|
||||||
type FetcherBuilder struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
var schemes = []string{"HTTP", "HTTPS"}
|
|
||||||
|
|
||||||
func (fb *FetcherBuilder) Schemes() []string {
|
|
||||||
return schemes
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fb *FetcherBuilder) Build() fetcher.Fetcher {
|
|
||||||
return &Fetcher{
|
|
||||||
DefaultFetcher: new(fetcher.DefaultFetcher),
|
|
||||||
pauseCh: make(chan interface{}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fb *FetcherBuilder) Store(f fetcher.Fetcher) (data any, err error) {
|
|
||||||
_f := f.(*Fetcher)
|
|
||||||
return &fetcherData{
|
|
||||||
Chunks: _f.chunks,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fb *FetcherBuilder) Restore() (v any, f func(res *base.Resource, opts *base.Options, v any) fetcher.Fetcher) {
|
|
||||||
return &fetcherData{}, func(res *base.Resource, opts *base.Options, v any) fetcher.Fetcher {
|
|
||||||
fd := v.(*fetcherData)
|
|
||||||
fb := &FetcherBuilder{}
|
|
||||||
fetcher := fb.Build().(*Fetcher)
|
|
||||||
fetcher.res = res
|
|
||||||
fetcher.opts = opts
|
|
||||||
fetcher.status = base.DownloadStatusPause
|
|
||||||
base.ParseReqExtra[fhttp.ReqExtra](res.Req)
|
|
||||||
base.ParseOptsExtra[fhttp.OptsExtra](opts)
|
|
||||||
if len(fd.Chunks) == 0 {
|
|
||||||
fetcher.chunks = fetcher.splitChunk()
|
|
||||||
} else {
|
|
||||||
fetcher.chunks = fd.Chunks
|
|
||||||
}
|
|
||||||
return fetcher
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,292 +0,0 @@
|
|||||||
package http
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"github.com/monkeyWie/gopeed/internal/controller"
|
|
||||||
"github.com/monkeyWie/gopeed/internal/fetcher"
|
|
||||||
"github.com/monkeyWie/gopeed/internal/test"
|
|
||||||
"github.com/monkeyWie/gopeed/pkg/base"
|
|
||||||
"github.com/monkeyWie/gopeed/pkg/protocol/http"
|
|
||||||
"net"
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestFetcher_Resolve(t *testing.T) {
|
|
||||||
testResolve(test.StartTestFileServer, &base.Resource{
|
|
||||||
Name: test.BuildName,
|
|
||||||
Size: test.BuildSize,
|
|
||||||
Range: true,
|
|
||||||
Files: []*base.FileInfo{
|
|
||||||
{
|
|
||||||
Name: test.BuildName,
|
|
||||||
Size: test.BuildSize,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}, t)
|
|
||||||
testResolve(test.StartTestChunkedServer, &base.Resource{
|
|
||||||
Name: test.BuildName,
|
|
||||||
Size: 0,
|
|
||||||
Range: false,
|
|
||||||
Files: []*base.FileInfo{
|
|
||||||
{
|
|
||||||
Name: test.BuildName,
|
|
||||||
Size: 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func testResolve(startTestServer func() net.Listener, want *base.Resource, t *testing.T) {
|
|
||||||
listener := startTestServer()
|
|
||||||
defer listener.Close()
|
|
||||||
fetcher := buildFetcher()
|
|
||||||
res, err := fetcher.Resolve(&base.Request{
|
|
||||||
URL: "http://" + listener.Addr().String() + "/" + test.BuildName,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
res.Req = nil
|
|
||||||
if !reflect.DeepEqual(want, res) {
|
|
||||||
t.Errorf("Resolve() got = %v, want %v", res, want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFetcher_DownloadNormal(t *testing.T) {
|
|
||||||
listener := test.StartTestFileServer()
|
|
||||||
defer listener.Close()
|
|
||||||
// 正常下载
|
|
||||||
downloadNormal(listener, 1, t)
|
|
||||||
downloadNormal(listener, 5, t)
|
|
||||||
downloadNormal(listener, 8, t)
|
|
||||||
downloadNormal(listener, 16, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFetcher_DownloadContinue(t *testing.T) {
|
|
||||||
listener := test.StartTestFileServer()
|
|
||||||
defer listener.Close()
|
|
||||||
// 暂停继续
|
|
||||||
downloadContinue(listener, 1, t)
|
|
||||||
downloadContinue(listener, 5, t)
|
|
||||||
downloadContinue(listener, 8, t)
|
|
||||||
downloadContinue(listener, 16, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFetcher_DownloadChunked(t *testing.T) {
|
|
||||||
listener := test.StartTestChunkedServer()
|
|
||||||
defer listener.Close()
|
|
||||||
// chunked编码下载
|
|
||||||
downloadNormal(listener, 1, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFetcher_DownloadPost(t *testing.T) {
|
|
||||||
listener := test.StartTestPostServer()
|
|
||||||
defer listener.Close()
|
|
||||||
// post下载
|
|
||||||
downloadPost(listener, 1, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFetcher_DownloadRetry(t *testing.T) {
|
|
||||||
listener := test.StartTestRetryServer()
|
|
||||||
defer listener.Close()
|
|
||||||
// chunked编码下载
|
|
||||||
downloadNormal(listener, 1, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFetcher_DownloadError(t *testing.T) {
|
|
||||||
listener := test.StartTestErrorServer()
|
|
||||||
defer listener.Close()
|
|
||||||
// chunked编码下载
|
|
||||||
downloadError(listener, 1, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFetcher_DownloadResume(t *testing.T) {
|
|
||||||
listener := test.StartTestFileServer()
|
|
||||||
defer listener.Close()
|
|
||||||
|
|
||||||
downloadResume(listener, 1, t)
|
|
||||||
downloadResume(listener, 5, t)
|
|
||||||
downloadResume(listener, 8, t)
|
|
||||||
downloadResume(listener, 16, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFetcher_Config(t *testing.T) {
|
|
||||||
fetcher, _, _ := doDownloadReady(buildConfigFetcher(), test.StartTestFileServer(), 0, t)
|
|
||||||
err := fetcher.Start()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
err = fetcher.Wait()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
want := test.FileMd5(test.BuildFile)
|
|
||||||
got := test.FileMd5(test.DownloadFile)
|
|
||||||
if want != got {
|
|
||||||
t.Errorf("Download() got = %v, want %v", got, want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func downloadReady(listener net.Listener, connections int, t *testing.T) (fetcher fetcher.Fetcher, res *base.Resource, opts *base.Options) {
|
|
||||||
return doDownloadReady(buildFetcher(), listener, connections, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func doDownloadReady(f fetcher.Fetcher, listener net.Listener, connections int, t *testing.T) (fetcher fetcher.Fetcher, res *base.Resource, opts *base.Options) {
|
|
||||||
fetcher = f
|
|
||||||
res, err := fetcher.Resolve(&base.Request{
|
|
||||||
URL: "http://" + listener.Addr().String() + "/" + test.BuildName,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
opts = &base.Options{
|
|
||||||
Name: test.DownloadName,
|
|
||||||
Path: test.Dir,
|
|
||||||
Extra: http.OptsExtra{
|
|
||||||
Connections: connections,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
err = fetcher.Create(res, opts)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func downloadNormal(listener net.Listener, connections int, t *testing.T) {
|
|
||||||
fetcher, _, _ := downloadReady(listener, connections, t)
|
|
||||||
err := fetcher.Start()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
err = fetcher.Wait()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
want := test.FileMd5(test.BuildFile)
|
|
||||||
got := test.FileMd5(test.DownloadFile)
|
|
||||||
if want != got {
|
|
||||||
t.Errorf("Download() got = %v, want %v", got, want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func downloadPost(listener net.Listener, connections int, t *testing.T) {
|
|
||||||
fetcher, _, _ := downloadReady(listener, connections, t)
|
|
||||||
fetcher.(*Fetcher).res.Req.Extra = &http.ReqExtra{
|
|
||||||
Method: "POST",
|
|
||||||
Header: map[string]string{
|
|
||||||
"Authorization": "Bearer 123456",
|
|
||||||
},
|
|
||||||
Body: fmt.Sprintf(`{"name":"%s"}`, test.BuildName),
|
|
||||||
}
|
|
||||||
err := fetcher.Start()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
err = fetcher.Wait()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
want := test.FileMd5(test.BuildFile)
|
|
||||||
got := test.FileMd5(test.DownloadFile)
|
|
||||||
if want != got {
|
|
||||||
t.Errorf("Download() got = %v, want %v", got, want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func downloadContinue(listener net.Listener, connections int, t *testing.T) {
|
|
||||||
fetcher, _, _ := downloadReady(listener, connections, t)
|
|
||||||
err := fetcher.Start()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
time.Sleep(time.Millisecond * 50)
|
|
||||||
if err := fetcher.Pause(); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
time.Sleep(time.Millisecond * 50)
|
|
||||||
if err := fetcher.Continue(); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
err = fetcher.Wait()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
want := test.FileMd5(test.BuildFile)
|
|
||||||
got := test.FileMd5(test.DownloadFile)
|
|
||||||
if want != got {
|
|
||||||
t.Errorf("Download() got = %v, want %v", got, want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func downloadError(listener net.Listener, connections int, t *testing.T) {
|
|
||||||
fetcher, _, _ := downloadReady(listener, connections, t)
|
|
||||||
err := fetcher.Start()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
err = fetcher.Wait()
|
|
||||||
if err == nil {
|
|
||||||
t.Errorf("Download() got = %v, want %v", err, nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func downloadResume(listener net.Listener, connections int, t *testing.T) {
|
|
||||||
fetcher, res, opts := downloadReady(listener, connections, t)
|
|
||||||
err := fetcher.Start()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fb := new(FetcherBuilder)
|
|
||||||
time.Sleep(time.Millisecond * 50)
|
|
||||||
data, err := fb.Store(fetcher)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
time.Sleep(time.Millisecond * 50)
|
|
||||||
fetcher.Pause()
|
|
||||||
|
|
||||||
_, f := fb.Restore()
|
|
||||||
f(res, opts, data)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
fetcher.Setup(controller.NewController())
|
|
||||||
fetcher.Continue()
|
|
||||||
|
|
||||||
err = fetcher.Wait()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
want := test.FileMd5(test.BuildFile)
|
|
||||||
got := test.FileMd5(test.DownloadFile)
|
|
||||||
if want != got {
|
|
||||||
t.Errorf("Download() got = %v, want %v", got, want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildFetcher() *Fetcher {
|
|
||||||
fetcher := new(FetcherBuilder).Build()
|
|
||||||
fetcher.Setup(controller.NewController())
|
|
||||||
return fetcher.(*Fetcher)
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildConfigFetcher() fetcher.Fetcher {
|
|
||||||
fetcher := new(FetcherBuilder).Build()
|
|
||||||
newController := controller.NewController()
|
|
||||||
mockCfg := config{
|
|
||||||
Connections: 16,
|
|
||||||
}
|
|
||||||
newController.GetConfig = func(v any) (bool, error) {
|
|
||||||
if err := json.Unmarshal([]byte(test.ToJson(mockCfg)), v); err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
fetcher.Setup(newController)
|
|
||||||
return fetcher
|
|
||||||
}
|
|
||||||
@@ -1,181 +0,0 @@
|
|||||||
package test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"math/rand"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
BuildName = "build.data"
|
|
||||||
BuildSize = 200 * 1024 * 1024
|
|
||||||
Dir = "./"
|
|
||||||
BuildFile = Dir + BuildName
|
|
||||||
|
|
||||||
DownloadName = "download.data"
|
|
||||||
DownloadFile = Dir + DownloadName
|
|
||||||
)
|
|
||||||
|
|
||||||
func StartTestFileServer() net.Listener {
|
|
||||||
return startTestServer(func() http.Handler {
|
|
||||||
return http.FileServer(http.Dir(Dir))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
type SlowFileServer struct {
|
|
||||||
delay time.Duration
|
|
||||||
handler http.Handler
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SlowFileServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
||||||
time.Sleep(s.delay)
|
|
||||||
s.handler.ServeHTTP(w, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func StartTestSlowFileServer(delay time.Duration) net.Listener {
|
|
||||||
return startTestServer(func() http.Handler {
|
|
||||||
return &SlowFileServer{
|
|
||||||
delay: delay,
|
|
||||||
handler: http.FileServer(http.Dir(Dir)),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func StartTestChunkedServer() net.Listener {
|
|
||||||
return startTestServer(func() http.Handler {
|
|
||||||
mux := http.NewServeMux()
|
|
||||||
mux.HandleFunc("/"+BuildName, func(writer http.ResponseWriter, request *http.Request) {
|
|
||||||
file, err := os.Open(BuildFile)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
io.Copy(writer, file)
|
|
||||||
})
|
|
||||||
return mux
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func StartTestRetryServer() net.Listener {
|
|
||||||
counter := 0
|
|
||||||
return startTestServer(func() http.Handler {
|
|
||||||
mux := http.NewServeMux()
|
|
||||||
mux.HandleFunc("/"+BuildName, func(writer http.ResponseWriter, request *http.Request) {
|
|
||||||
counter++
|
|
||||||
if counter != 1 && counter < 5 {
|
|
||||||
writer.WriteHeader(500)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
file, err := os.Open(BuildFile)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
io.Copy(writer, file)
|
|
||||||
})
|
|
||||||
return mux
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func StartTestPostServer() net.Listener {
|
|
||||||
return startTestServer(func() http.Handler {
|
|
||||||
mux := http.NewServeMux()
|
|
||||||
mux.HandleFunc("/"+BuildName, func(writer http.ResponseWriter, request *http.Request) {
|
|
||||||
if request.Method == "POST" && request.Header.Get("Authorization") != "" {
|
|
||||||
var data map[string]interface{}
|
|
||||||
if err := json.NewDecoder(request.Body).Decode(&data); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
if data["name"] == BuildName {
|
|
||||||
file, err := os.Open(BuildFile)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
io.Copy(writer, file)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return mux
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func StartTestErrorServer() net.Listener {
|
|
||||||
counter := 0
|
|
||||||
return startTestServer(func() http.Handler {
|
|
||||||
mux := http.NewServeMux()
|
|
||||||
mux.HandleFunc("/"+BuildName, func(writer http.ResponseWriter, request *http.Request) {
|
|
||||||
counter++
|
|
||||||
if counter != 1 {
|
|
||||||
writer.WriteHeader(500)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return mux
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func startTestServer(serverHandle func() http.Handler) net.Listener {
|
|
||||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
file, err := os.Create(BuildFile)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
// 随机生成一个文件
|
|
||||||
l := int64(8192)
|
|
||||||
buf := make([]byte, l)
|
|
||||||
size := int64(0)
|
|
||||||
for {
|
|
||||||
_, err := rand.Read(buf)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
if size+l >= BuildSize {
|
|
||||||
file.WriteAt(buf[0:BuildSize-size], size)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
file.WriteAt(buf, size)
|
|
||||||
size += l
|
|
||||||
}
|
|
||||||
server := &http.Server{}
|
|
||||||
server.Handler = serverHandle()
|
|
||||||
go server.Serve(listener)
|
|
||||||
|
|
||||||
return &delFileListener{
|
|
||||||
File: file,
|
|
||||||
Listener: listener,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type delFileListener struct {
|
|
||||||
*os.File
|
|
||||||
net.Listener
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *delFileListener) Close() error {
|
|
||||||
defer func() {
|
|
||||||
c.File.Close()
|
|
||||||
if err := ifExistAndRemove(c.File.Name()); err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
}
|
|
||||||
if err := ifExistAndRemove(DownloadFile); err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
return c.Listener.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func ifExistAndRemove(name string) error {
|
|
||||||
if _, err := os.Stat(name); !os.IsNotExist(err) {
|
|
||||||
return os.Remove(name)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
Loaded 50 of 290 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user