mirror of
https://github.com/GopeedLab/gopeed.git
synced 2026-11-12 19:33:41 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
11015c7be1 | ||
|
|
57befd2c03 | ||
|
|
35156ed968 | ||
|
|
ee6c36174f | ||
|
|
fdfd95555d | ||
|
|
7ec824edda | ||
|
|
1f8b1be669 | ||
|
|
b0bcfeae34 | ||
|
|
10b346a326 | ||
|
|
a85af39b35 | ||
|
|
576f0d2e24 | ||
|
|
95eb3a427c | ||
|
|
e9442edd94 | ||
|
|
77e59f7376 | ||
|
|
135fba1b54 | ||
|
|
ff813e3bc9 | ||
|
|
babfe79023 | ||
|
|
89ac52bebd | ||
|
|
15e1c2f2d5 | ||
|
|
203f0ccc82 | ||
|
|
2422bd2c66 | ||
|
|
0306a1ed6b | ||
|
|
8a94650370 | ||
|
|
837a193e96 | ||
|
|
fcf0746f9a | ||
|
|
9eb466ccd3 | ||
|
|
c54f22613e | ||
|
|
3e1cfa0a27 | ||
|
|
9d22b49e41 | ||
|
|
1c6df55975 | ||
|
|
237616ded2 | ||
|
|
cebdda564e | ||
|
|
08ffb51946 | ||
|
|
8378f51f8e | ||
|
|
978d786f99 | ||
|
|
8b997fa7f2 | ||
|
|
745afb600c | ||
|
|
b45b840afb | ||
|
|
2a7a136203 | ||
|
|
dfb8958999 | ||
|
|
1568f4d6ff | ||
|
|
12ebceecb0 | ||
|
|
8cfe46cb71 | ||
|
|
2f1b929bcd | ||
|
|
2144cd8c3a | ||
|
|
080b34d070 | ||
|
|
de73d2a29f | ||
|
|
9de2b765fb | ||
|
|
be75b50521 | ||
|
|
39aec19471 | ||
|
|
9884001d54 | ||
|
|
29c834a070 | ||
|
|
b16f348676 | ||
|
|
7b46c2a2f6 | ||
|
|
1716d8b33e | ||
|
|
855f0b4002 | ||
|
|
ee0d081b83 | ||
|
|
d2f91a49ad | ||
|
|
fb6f889ddb | ||
|
|
0e6f1013cb | ||
|
|
fd10b18707 | ||
|
|
f423cf5b6d | ||
|
|
37b54b0c0f | ||
|
|
cd74c895d3 | ||
|
|
450027a8b0 | ||
|
|
48821a519e | ||
|
|
423ee5c3d1 | ||
|
|
96c8945ac8 | ||
|
|
b2b714ae88 | ||
|
|
2793ff696e | ||
|
|
2f81c5ef04 | ||
|
|
2eed470d5c | ||
|
|
34f8df1999 | ||
|
|
e901e08534 | ||
|
|
56561ba764 | ||
|
|
cf60e5a60a | ||
|
|
bf8e24a6af | ||
|
|
ff4e6f448c | ||
|
|
dccf080fbb | ||
|
|
9d7094133d | ||
|
|
ea6277805c | ||
|
|
3ad3060d71 | ||
|
|
c1545adeb2 | ||
|
|
9659176752 | ||
|
|
4371345c7d | ||
|
|
655f9d99c7 | ||
|
|
a9c6705bd3 | ||
|
|
46e0c6258f | ||
|
|
7c596c6b91 | ||
|
|
5e92379aa2 | ||
|
|
8a47b6cfb5 | ||
|
|
97eca4ec9c | ||
|
|
b0a7a5569a | ||
|
|
5073ae6166 | ||
|
|
81c6fcf15d | ||
|
|
2c6f821c4b | ||
|
|
236423fe3c | ||
|
|
a8136afc31 | ||
|
|
4d4d3b446a | ||
|
|
c4a30dc4c2 | ||
|
|
0fc133e46d | ||
|
|
c6aa83194f | ||
|
|
e292926697 | ||
|
|
8100bafdf9 | ||
|
|
e0582b5442 | ||
|
|
034137322f | ||
|
|
43378c7dc8 | ||
|
|
30b7189d59 | ||
|
|
bbd553d910 | ||
|
|
3996574b95 |
No files matched your search
@@ -0,0 +1,8 @@
|
|||||||
|
<!--
|
||||||
|
请根据以下模版提issue
|
||||||
|
-->
|
||||||
|
### 问题描述(必要)
|
||||||
|
### 版本号(必要)
|
||||||
|
### 操作系统(必要)
|
||||||
|
### 相关截图
|
||||||
|
### 相关日志
|
||||||
@@ -0,0 +1,336 @@
|
|||||||
|
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
|
||||||
@@ -3,52 +3,30 @@ name: release
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- main
|
||||||
|
paths:
|
||||||
|
- "bind/**"
|
||||||
|
- "cmd/**"
|
||||||
|
- "internal/**"
|
||||||
|
- "pkg/**"
|
||||||
|
- "ui/**"
|
||||||
|
- ".github/workflows/release.yml"
|
||||||
|
- "go.mod"
|
||||||
|
- "go.sum"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
tag_name: ${{ steps.create_release.outputs.tag_name }}
|
||||||
|
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/setup-go@v2
|
- uses: actions/checkout@v3
|
||||||
|
- uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: '^1.15.2'
|
go-version: "^1.19"
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- name: Build Cli
|
|
||||||
run: |
|
|
||||||
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags="-w -s" -o gopeed.exe cmd/gopeed/*
|
|
||||||
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" -o gopeed cmd/gopeed/*
|
|
||||||
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags="-w -s" -o gopeed cmd/gopeed/*
|
|
||||||
zip gopeed-windows-x64.zip gopeed.exe
|
|
||||||
zip gopeed-linux-x64.zip gopeed
|
|
||||||
zip gopeed-darwin-x64.zip gopeed
|
|
||||||
- uses: release-drafter/release-drafter@v5
|
- uses: release-drafter/release-drafter@v5
|
||||||
id: create_release
|
id: create_release
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: Upload Cli Windows
|
|
||||||
uses: actions/[email protected]
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
with:
|
|
||||||
upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps
|
|
||||||
asset_path: ./gopeed-windows-x64.zip
|
|
||||||
asset_name: gopeed-${{ steps.create_release.outputs.name }}-windows-x64.zip
|
|
||||||
asset_content_type: application/zip
|
|
||||||
- name: Upload Cli Linux
|
|
||||||
uses: actions/[email protected]
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
with:
|
|
||||||
upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps
|
|
||||||
asset_path: ./gopeed-linux-x64.zip
|
|
||||||
asset_name: gopeed-${{ steps.create_release.outputs.name }}-linux-x64.zip
|
|
||||||
asset_content_type: application/zip
|
|
||||||
- name: Upload Cli Darwin
|
|
||||||
uses: actions/[email protected]
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
with:
|
|
||||||
upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps
|
|
||||||
asset_path: ./gopeed-darwin-x64.zip
|
|
||||||
asset_name: gopeed-${{ steps.create_release.outputs.name }}-darwin-x64.zip
|
|
||||||
asset_content_type: application/zip
|
|
||||||
+46
-26
@@ -3,48 +3,68 @@ name: test
|
|||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- dev
|
- main
|
||||||
|
paths:
|
||||||
|
- "bind/**"
|
||||||
|
- "cmd/**"
|
||||||
|
- "internal/**"
|
||||||
|
- "pkg/**"
|
||||||
|
- "ui/**"
|
||||||
|
- ".github/workflows/test.yml"
|
||||||
|
- "go.mod"
|
||||||
|
- "go.sum"
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- dev
|
- main
|
||||||
|
paths:
|
||||||
|
- "bind/**"
|
||||||
|
- "cmd/**"
|
||||||
|
- "internal/**"
|
||||||
|
- "pkg/**"
|
||||||
|
- "ui/**"
|
||||||
|
- ".github/workflows/test.yml"
|
||||||
|
- "go.mod"
|
||||||
|
- "go.sum"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
# lint:
|
# lint:
|
||||||
# name: Lint
|
# name: Lint
|
||||||
# runs-on: ubuntu-latest
|
# runs-on: ubuntu-latest
|
||||||
# steps:
|
# steps:
|
||||||
# - uses: actions/setup-go@v2
|
# - uses: actions/setup-go@v2
|
||||||
# with:
|
# with:
|
||||||
# go-version: '^1.15.2'
|
# go-version: '^1.19'
|
||||||
# - uses: actions/checkout@v2
|
# - uses: actions/checkout@v2
|
||||||
# - name: Lint Go Code
|
# - name: Lint Go Code
|
||||||
# run: |
|
# run: |
|
||||||
# export PATH=$PATH:$(go env GOPATH)/bin # temporary fix. See https://github.com/actions/setup-go/issues/14
|
# 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
|
# go get -u golang.org/x/lint/golint
|
||||||
# golint -set_exit_status ./...
|
# golint -set_exit_status ./...
|
||||||
test:
|
test:
|
||||||
name: Test
|
name: Test
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/setup-go@v2
|
- uses: actions/checkout@v3
|
||||||
|
- uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: '^1.15.2'
|
go-version: "^1.19"
|
||||||
- uses: actions/checkout@v2
|
cache: true
|
||||||
- name: Run Unit tests.
|
- name: Run Unit tests.
|
||||||
run: go test ./... -v -covermode=count -coverprofile=coverage.txt
|
run: go test $(go list ./... | grep -v /cmd/ | grep -v /bind/ ) -v -covermode=count -coverprofile=coverage.txt
|
||||||
- uses: codecov/codecov-action@v1
|
- uses: codecov/codecov-action@v3
|
||||||
with:
|
with:
|
||||||
file: ./coverage.txt
|
files: ./coverage.txt
|
||||||
fail_ci_if_error: true
|
fail_ci_if_error: true
|
||||||
build:
|
build:
|
||||||
name: Build
|
name: Build
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
# needs: [lint, test]
|
# needs: [lint, test]
|
||||||
needs: [test]
|
needs: [test]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/setup-go@v2
|
- uses: actions/checkout@v3
|
||||||
|
- uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: '^1.15.2'
|
go-version: "^1.19"
|
||||||
- uses: actions/checkout@v2
|
cache: true
|
||||||
- name: Build
|
- name: Build
|
||||||
run: go build -ldflags="-s -w" -o gopeed cmd/gopeed/*
|
run: go build github.com/monkeyWie/gopeed/cmd/gopeed
|
||||||
+13
-2
@@ -11,6 +11,17 @@
|
|||||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||||
*.out
|
*.out
|
||||||
|
|
||||||
debug
|
|
||||||
|
|
||||||
.idea/
|
.idea/
|
||||||
|
|
||||||
|
bin/
|
||||||
|
|
||||||
|
.torrent.db
|
||||||
|
.torrent.db-shm
|
||||||
|
.torrent.db-wal
|
||||||
|
|
||||||
|
*.data
|
||||||
|
*.db
|
||||||
|
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
dist/
|
||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
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 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,46 +1,135 @@
|
|||||||
[](https://github.com/monkeyWie/gopeed-core/actions?query=workflow%3Abuild)
|
[](https://github.com/monkeyWie/gopeed/actions?query=workflow%3Atest)
|
||||||
[](https://codecov.io/gh/monkeyWie/gopeed-core)
|
[](https://codecov.io/gh/monkeyWie/gopeed)
|
||||||
|
[](https://github.com/monkeyWie/gopeed/releases)
|
||||||
|
[](https://discord.gg/ZUJqJrwCGB)
|
||||||
|
|
||||||
## gopeed
|

|
||||||
|
|
||||||
支持多协议(HTTP、BitTorrent)下载的客户端,提供命令行、RESTful API、WebSocket、Go 类库方式来使用。
|
[English](/README.md) | [中文](/README_zh-CN.md)
|
||||||
|
|
||||||
## 安装
|
## Introduction
|
||||||
|
|
||||||
```sh
|
Gopeed is a high-speed downloader developed by `Golang`+`Flutter`, which supports (HTTP, BitTorrent, Magnet) protocol downloads and supports all platforms.
|
||||||
go install github.com/monkeyWie/gopeed-core
|
|
||||||
|
## 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
|
||||||
|
|
||||||
可以在本仓库的[\_examples](_examples)目录下查看
|
```bash
|
||||||
|
docker run liwei2633/gopeed:latest -v /path/to/download:/download -p 9999:9999
|
||||||
|
```
|
||||||
|
|
||||||
## 命令行工具
|
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
|
||||||
|
|
||||||
前往[releases](https://github.com/monkeyWie/gopeed-core/releases)页面
|
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.
|
||||||
|
|
||||||
## TODO
|
> The front code is located in the `ui/flutter` directory.
|
||||||
|
|
||||||
- [x] HTTP 下载实现
|
### Environment
|
||||||
- [ ] BitTorrent 下载实现
|
|
||||||
- [x] .torrent 文件解析
|
|
||||||
- [x] tracker 协议实现
|
|
||||||
- [x] peer wire protocol 协议实现
|
|
||||||
- [ ] DHT 协议实现
|
|
||||||
- [ ] 磁力链接支持
|
|
||||||
- [ ] uTP 协议实现
|
|
||||||
- [x] 下载接口抽象(不关心具体协议)
|
|
||||||
- [ ] 支持自定义配置
|
|
||||||
- [ ] 限速功能实现
|
|
||||||
- [x] 命令行工具提供
|
|
||||||
- [ ] RESTful 服务提供
|
|
||||||
|
|
||||||
## 参与
|
1. Golang 1.19+
|
||||||
|
2. Flutter 3.0+
|
||||||
|
|
||||||
由于项目目前还未定型,代码可能随时有大的调整,所以暂不接受 PR,当然如果有什么好的想法可以在 issue 区提出来。
|
### 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 ../../
|
||||||
|
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)
|
||||||
+135
@@ -0,0 +1,135 @@
|
|||||||
|
[](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 liwei2633/gopeed:latest -v /path/to/download:/download -p 9999:9999
|
||||||
|
```
|
||||||
|
|
||||||
|
当 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 ../../
|
||||||
|
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.
|
After Width: | Height: | Size: 26 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 29 KiB |
@@ -2,13 +2,13 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/monkeyWie/gopeed-core/pkg/base"
|
"github.com/monkeyWie/gopeed/pkg/base"
|
||||||
"github.com/monkeyWie/gopeed-core/pkg/download"
|
"github.com/monkeyWie/gopeed/pkg/download"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
finallyCh := make(chan error)
|
finallyCh := make(chan error)
|
||||||
err := download.Boot().
|
_, err := download.Boot().
|
||||||
URL("https://www.baidu.com/index.html").
|
URL("https://www.baidu.com/index.html").
|
||||||
Listener(func(event *download.Event) {
|
Listener(func(event *download.Event) {
|
||||||
if event.Key == download.EventKeyFinally {
|
if event.Key == download.EventKeyFinally {
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
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()
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
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()
|
||||||
|
}
|
||||||
+2
-3
@@ -4,7 +4,6 @@ import (
|
|||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type args struct {
|
type args struct {
|
||||||
@@ -14,13 +13,13 @@ type args struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func parse() *args {
|
func parse() *args {
|
||||||
dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
|
dir, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
var args args
|
var args args
|
||||||
args.connections = flag.Int("C", 16, "Concurrent connections.")
|
args.connections = flag.Int("C", 16, "Concurrent connections.")
|
||||||
args.dir = flag.String("D", dir, "Save directory.")
|
args.dir = flag.String("D", dir, "Store directory.")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
t := flag.Args()
|
t := flag.Args()
|
||||||
if len(t) > 0 {
|
if len(t) > 0 {
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/monkeyWie/gopeed-core/pkg/base"
|
"github.com/monkeyWie/gopeed/pkg/base"
|
||||||
"github.com/monkeyWie/gopeed-core/pkg/download"
|
"github.com/monkeyWie/gopeed/pkg/download"
|
||||||
"github.com/monkeyWie/gopeed-core/pkg/util"
|
"github.com/monkeyWie/gopeed/pkg/util"
|
||||||
"path/filepath"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -15,7 +15,7 @@ func main() {
|
|||||||
args := parse()
|
args := parse()
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
err := download.Boot().
|
_, err := download.Boot().
|
||||||
URL(args.url).
|
URL(args.url).
|
||||||
Listener(func(event *download.Event) {
|
Listener(func(event *download.Event) {
|
||||||
if event.Key == download.EventKeyProgress {
|
if event.Key == download.EventKeyProgress {
|
||||||
@@ -31,9 +31,9 @@ func main() {
|
|||||||
printProgress(event.Task, title)
|
printProgress(event.Task, title)
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
if event.Err != nil {
|
if event.Err != nil {
|
||||||
gPrint("reason: " + event.Err.Error())
|
fmt.Printf("reason: %s", event.Err.Error())
|
||||||
} else {
|
} else {
|
||||||
gPrint("saving file " + filepath.Join(*args.dir, event.Task.Res.Files[0].Name))
|
fmt.Printf("saving path: %s", *args.dir)
|
||||||
}
|
}
|
||||||
wg.Done()
|
wg.Done()
|
||||||
}
|
}
|
||||||
@@ -49,18 +49,32 @@ func main() {
|
|||||||
wg.Wait()
|
wg.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
func printProgress(task *download.TaskInfo, title string) {
|
var (
|
||||||
rate := float64(task.Progress.Downloaded) / float64(task.Res.TotalSize)
|
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)
|
completeWidth := int(progressWidth * rate)
|
||||||
speed := util.ByteFmt(task.Progress.Speed)
|
speed := util.ByteFmt(task.Progress.Speed)
|
||||||
totalSize := util.ByteFmt(task.Res.TotalSize)
|
totalSize := util.ByteFmt(task.Res.Size)
|
||||||
fmt.Printf("\r%s [", title)
|
sb.WriteString(fmt.Sprintf("\r%s [", title))
|
||||||
for i := 0; i < progressWidth; i++ {
|
for i := 0; i < progressWidth; i++ {
|
||||||
if i < completeWidth {
|
if i < completeWidth {
|
||||||
fmt.Print("■")
|
sb.WriteString("■")
|
||||||
} else {
|
} else {
|
||||||
fmt.Print("□")
|
sb.WriteString("□")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fmt.Printf("] %.1f%% %s/s %s", rate*100, speed, totalSize)
|
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()
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
|
||||||
|
_______ ______ .______ _______ _______ _______
|
||||||
|
/ _____| / __ \ | _ \ | ____|| ____|| \
|
||||||
|
| | __ | | | | | |_) | | |__ | |__ | .--. |
|
||||||
|
| | |_ | | | | | | ___/ | __| | __| | | | |
|
||||||
|
| |__| | | `--' | | | | |____ | |____ | '--' |
|
||||||
|
\______| \______/ | _| |_______||_______||_______/
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
//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: "127.0.0.1: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,9 +1,76 @@
|
|||||||
module github.com/monkeyWie/gopeed-core
|
module github.com/monkeyWie/gopeed
|
||||||
|
|
||||||
go 1.15
|
go 1.19
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/google/uuid v1.1.1
|
github.com/anacrolix/torrent v1.46.0
|
||||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381
|
github.com/gorilla/mux v1.8.0
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
github.com/matoous/go-nanoid/v2 v2.0.0
|
||||||
|
go.etcd.io/bbolt v1.3.6
|
||||||
|
golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde
|
||||||
|
)
|
||||||
|
|
||||||
|
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,15 +1,470 @@
|
|||||||
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
|
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
|
crawshaw.io/iox v0.0.0-20181124134642-c51c3df30797 h1:yDf7ARQc637HoxDho7xjqdvO5ZA2Yb+xzv/fOnnvZzw=
|
||||||
|
crawshaw.io/iox v0.0.0-20181124134642-c51c3df30797/go.mod h1:sXBiorCo8c46JlQV3oXPKINnZ8mcqnye1EkVkqsectk=
|
||||||
|
crawshaw.io/sqlite v0.3.2/go.mod h1:igAO5JulrQ1DbdZdtVq48mnZUBAPOeFzer7VhDWNtW4=
|
||||||
|
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.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
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/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-20190901134440-81cf024a9e0a/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE=
|
||||||
|
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/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
|
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
|
github.com/go-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/google/btree v0.0.0-20180124185431-e89373fe6b4a/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
|
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
|
github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
|
||||||
|
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/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||||
|
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/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||||
|
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/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||||
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
|
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.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM=
|
||||||
|
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/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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||||
|
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/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.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.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.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.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
|
||||||
|
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/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-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
|
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-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU=
|
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/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
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-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-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
|
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-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=
|
||||||
|
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 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
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.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,61 +1,42 @@
|
|||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"golang.org/x/net/proxy"
|
"errors"
|
||||||
"net"
|
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"path/filepath"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Controller interface {
|
type Controller interface {
|
||||||
Touch(name string, size int64) (file *os.File, err error)
|
Touch(name string, size int64) (file *os.File, err error)
|
||||||
Open(name string) (file *os.File, err error)
|
//ContextDialer() (proxy.Dialer, error)
|
||||||
Write(name string, offset int64, buf []byte) (int, error)
|
|
||||||
Close(name string) error
|
|
||||||
ContextDialer() (proxy.Dialer, error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type DefaultController struct {
|
type DefaultController struct {
|
||||||
Files map[string]*os.File
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewController() *DefaultController {
|
func NewController() *DefaultController {
|
||||||
return &DefaultController{Files: make(map[string]*os.File)}
|
return &DefaultController{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *DefaultController) Touch(name string, size int64) (file *os.File, err error) {
|
func (c *DefaultController) 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)
|
file, err = os.Create(name)
|
||||||
|
if err != nil && !errors.Is(err, os.ErrExist) {
|
||||||
|
return
|
||||||
|
}
|
||||||
if size > 0 {
|
if size > 0 {
|
||||||
err = os.Truncate(name, size)
|
err = os.Truncate(name, size)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err == nil {
|
|
||||||
c.Files[name] = file
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *DefaultController) Open(name string) (file *os.File, err error) {
|
/*func (c *DefaultController) ContextDialer() (proxy.Dialer, error) {
|
||||||
file, err = os.OpenFile(name, os.O_RDWR, os.ModePerm)
|
|
||||||
if err == nil {
|
|
||||||
c.Files[name] = file
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *DefaultController) Write(name string, offset int64, buf []byte) (int, error) {
|
|
||||||
return c.Files[name].WriteAt(buf, offset)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *DefaultController) Close(name string) error {
|
|
||||||
err := c.Files[name].Close()
|
|
||||||
delete(c.Files, name)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *DefaultController) ContextDialer() (proxy.Dialer, error) {
|
|
||||||
// return proxy.SOCKS5("tpc", "127.0.0.1:9999", nil, nil)
|
// return proxy.SOCKS5("tpc", "127.0.0.1:9999", nil, nil)
|
||||||
var dialer proxy.Dialer
|
var dialer proxy.Dialer
|
||||||
return &DialerWarp{dialer: dialer}, nil
|
return &DialerWarp{dialer: dialer}, nil
|
||||||
@@ -107,4 +88,4 @@ func (d *DialerWarp) Dial(network, addr string) (c net.Conn, err error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &ConnWarp{conn: conn}, nil
|
return &ConnWarp{conn: conn}, nil
|
||||||
}
|
}*/
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
package fetcher
|
package fetcher
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/monkeyWie/gopeed-core/internal/controller"
|
"github.com/monkeyWie/gopeed/internal/controller"
|
||||||
"github.com/monkeyWie/gopeed-core/pkg/base"
|
"github.com/monkeyWie/gopeed/pkg/base"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 对应协议的下载支持
|
// 对应协议的下载支持
|
||||||
type Fetcher interface {
|
type Fetcher interface {
|
||||||
Setup(ctl controller.Controller)
|
Setup(ctl controller.Controller) error
|
||||||
// 解析请求
|
// 解析请求
|
||||||
Resolve(req *base.Request) (res *base.Resource, err error)
|
Resolve(req *base.Request) (res *base.Resource, err error)
|
||||||
// 创建任务
|
// 创建任务
|
||||||
@@ -15,6 +15,7 @@ type Fetcher interface {
|
|||||||
Start() (err error)
|
Start() (err error)
|
||||||
Pause() (err error)
|
Pause() (err error)
|
||||||
Continue() (err error)
|
Continue() (err error)
|
||||||
|
Close() (err error)
|
||||||
|
|
||||||
// 获取任务各个文件下载进度
|
// 获取任务各个文件下载进度
|
||||||
Progress() Progress
|
Progress() Progress
|
||||||
@@ -22,14 +23,25 @@ type Fetcher interface {
|
|||||||
Wait() (err error)
|
Wait() (err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type FetcherBuilder interface {
|
||||||
|
Schemes() []string
|
||||||
|
Build() Fetcher
|
||||||
|
|
||||||
|
// Store 存储任务
|
||||||
|
Store(fetcher Fetcher) (any, error)
|
||||||
|
// Restore 恢复任务
|
||||||
|
Restore() (v any, f func(res *base.Resource, opts *base.Options, v any) Fetcher)
|
||||||
|
}
|
||||||
|
|
||||||
type DefaultFetcher struct {
|
type DefaultFetcher struct {
|
||||||
Ctl controller.Controller
|
Ctl controller.Controller
|
||||||
DoneCh chan error
|
DoneCh chan error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *DefaultFetcher) Setup(ctl controller.Controller) {
|
func (f *DefaultFetcher) Setup(ctl controller.Controller) (err error) {
|
||||||
f.Ctl = ctl
|
f.Ctl = ctl
|
||||||
f.DoneCh = make(chan error, 1)
|
f.DoneCh = make(chan error, 1)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *DefaultFetcher) Wait() (err error) {
|
func (f *DefaultFetcher) Wait() (err error) {
|
||||||
|
|||||||
@@ -0,0 +1,222 @@
|
|||||||
|
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) 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())),
|
||||||
|
Extra: map[string]any{
|
||||||
|
"infoHash": 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 != "" {
|
||||||
|
if extra, ok := res.Extra.(map[string]any); ok {
|
||||||
|
f.torrentPaths[extra["infoHash"].(string)] = 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
|
||||||
|
}
|
||||||
|
<-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
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package bt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/monkeyWie/gopeed/internal/controller"
|
||||||
|
"github.com/monkeyWie/gopeed/internal/fetcher"
|
||||||
|
"github.com/monkeyWie/gopeed/pkg/base"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFetcher_Resolve_Torrent(t *testing.T) {
|
||||||
|
fetcher := buildFetcher()
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Extra: map[string]any{
|
||||||
|
"infoHash": "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
|
||||||
|
}
|
||||||
Binary file not shown.
@@ -4,15 +4,16 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/monkeyWie/gopeed-core/internal/fetcher"
|
"github.com/monkeyWie/gopeed/internal/fetcher"
|
||||||
"github.com/monkeyWie/gopeed-core/pkg/base"
|
"github.com/monkeyWie/gopeed/pkg/base"
|
||||||
|
"github.com/monkeyWie/gopeed/pkg/util"
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"mime"
|
"mime"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/cookiejar"
|
"net/http/cookiejar"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -36,32 +37,17 @@ func (re *RequestError) Error() string {
|
|||||||
type Fetcher struct {
|
type Fetcher struct {
|
||||||
*fetcher.DefaultFetcher
|
*fetcher.DefaultFetcher
|
||||||
|
|
||||||
res *base.Resource
|
res *base.Resource
|
||||||
opts *base.Options
|
opts *base.Options
|
||||||
status base.Status
|
status base.Status
|
||||||
clients []*http.Response
|
chunks []*chunk
|
||||||
chunks []*Chunk
|
|
||||||
|
|
||||||
|
file *os.File
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
pauseCh chan interface{}
|
pauseCh chan interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewFetcher() *Fetcher {
|
|
||||||
return &Fetcher{
|
|
||||||
DefaultFetcher: new(fetcher.DefaultFetcher),
|
|
||||||
pauseCh: make(chan interface{}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var protocols = []string{"HTTP", "HTTPS"}
|
|
||||||
|
|
||||||
func FetcherBuilder() ([]string, func() fetcher.Fetcher) {
|
|
||||||
return protocols, func() fetcher.Fetcher {
|
|
||||||
return NewFetcher()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *Fetcher) Resolve(req *base.Request) (*base.Resource, error) {
|
func (f *Fetcher) Resolve(req *base.Request) (*base.Resource, error) {
|
||||||
httpReq, err := buildRequest(nil, req)
|
httpReq, err := buildRequest(nil, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -91,7 +77,7 @@ func (f *Fetcher) Resolve(req *base.Request) (*base.Resource, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
res.TotalSize = parse
|
res.Size = parse
|
||||||
}
|
}
|
||||||
} else if base.HttpCodeOK == httpResp.StatusCode {
|
} else if base.HttpCodeOK == httpResp.StatusCode {
|
||||||
// 返回200响应码,不支持断点下载,通过Content-Length头获取文件大小,获取不到的话可能是chunked编码
|
// 返回200响应码,不支持断点下载,通过Content-Length头获取文件大小,获取不到的话可能是chunked编码
|
||||||
@@ -101,31 +87,32 @@ func (f *Fetcher) Resolve(req *base.Request) (*base.Resource, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
res.TotalSize = parse
|
res.Size = parse
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return nil, NewRequestError(httpResp.StatusCode, httpResp.Status)
|
return nil, NewRequestError(httpResp.StatusCode, httpResp.Status)
|
||||||
}
|
}
|
||||||
file := &base.FileInfo{
|
file := &base.FileInfo{
|
||||||
Size: res.TotalSize,
|
Size: res.Size,
|
||||||
}
|
}
|
||||||
contentDisposition := httpResp.Header.Get(base.HttpHeaderContentDisposition)
|
contentDisposition := httpResp.Header.Get(base.HttpHeaderContentDisposition)
|
||||||
if contentDisposition != "" {
|
if contentDisposition != "" {
|
||||||
_, params, _ := mime.ParseMediaType(contentDisposition)
|
_, params, _ := mime.ParseMediaType(contentDisposition)
|
||||||
filename := params["filename"]
|
filename := params["filePath"]
|
||||||
if filename != "" {
|
if filename != "" {
|
||||||
file.Name = filename
|
file.Name = filename
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Get file filename by URL
|
// Get file filePath by URL
|
||||||
if file.Name == "" && strings.Count(req.URL, "/") > 2 {
|
if file.Name == "" && strings.Count(req.URL, "/") > 2 {
|
||||||
file.Name = filepath.Base(req.URL)
|
file.Name = filepath.Base(req.URL)
|
||||||
}
|
}
|
||||||
// unknown file filename
|
// unknown file filePath
|
||||||
if file.Name == "" {
|
if file.Name == "" {
|
||||||
file.Name = "unknown"
|
file.Name = "unknown"
|
||||||
}
|
}
|
||||||
res.Files = append(res.Files, file)
|
res.Files = append(res.Files, file)
|
||||||
|
res.Name = file.Name
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,58 +125,36 @@ func (f *Fetcher) Create(res *base.Resource, opts *base.Options) error {
|
|||||||
|
|
||||||
func (f *Fetcher) Start() (err error) {
|
func (f *Fetcher) Start() (err error) {
|
||||||
// 创建文件
|
// 创建文件
|
||||||
name := f.filename()
|
name := f.filepath()
|
||||||
_, err = f.Ctl.Touch(name, f.res.TotalSize)
|
f.file, err = f.Ctl.Touch(name, f.res.Size)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
f.status = base.DownloadStatusStart
|
f.status = base.DownloadStatusRunning
|
||||||
if f.res.Range {
|
f.chunks = splitChunk(f.res, f.opts)
|
||||||
// 每个连接平均需要下载的分块大小
|
|
||||||
chunkSize := f.res.TotalSize / int64(f.opts.Connections)
|
|
||||||
f.chunks = make([]*Chunk, f.opts.Connections)
|
|
||||||
f.clients = make([]*http.Response, f.opts.Connections)
|
|
||||||
for i := 0; i < f.opts.Connections; i++ {
|
|
||||||
var (
|
|
||||||
begin = chunkSize * int64(i)
|
|
||||||
end int64
|
|
||||||
)
|
|
||||||
if i == f.opts.Connections-1 {
|
|
||||||
// 最后一个分块需要保证把文件下载完
|
|
||||||
end = f.res.TotalSize - 1
|
|
||||||
} else {
|
|
||||||
end = begin + chunkSize - 1
|
|
||||||
}
|
|
||||||
chunk := NewChunk(begin, end)
|
|
||||||
f.chunks[i] = chunk
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 只支持单连接下载
|
|
||||||
f.chunks = make([]*Chunk, 1)
|
|
||||||
f.clients = make([]*http.Response, 1)
|
|
||||||
f.chunks[0] = NewChunk(0, 0)
|
|
||||||
}
|
|
||||||
f.fetch()
|
f.fetch()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Fetcher) Pause() (err error) {
|
func (f *Fetcher) Pause() (err error) {
|
||||||
if base.DownloadStatusStart != f.status {
|
if base.DownloadStatusRunning != f.status {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
f.status = base.DownloadStatusPause
|
f.status = base.DownloadStatusPause
|
||||||
f.cancel()
|
|
||||||
<-f.pauseCh
|
if f.cancel != nil {
|
||||||
|
f.cancel()
|
||||||
|
<-f.pauseCh
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Fetcher) Continue() (err error) {
|
func (f *Fetcher) Continue() (err error) {
|
||||||
if base.DownloadStatusStart == f.status || base.DownloadStatusDone == f.status {
|
if base.DownloadStatusRunning == f.status || base.DownloadStatusDone == f.status {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
f.status = base.DownloadStatusStart
|
f.status = base.DownloadStatusRunning
|
||||||
var name = f.filename()
|
f.file, err = os.OpenFile(f.filepath(), os.O_RDWR, os.ModeAppend)
|
||||||
_, err = f.Ctl.Open(name)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -197,6 +162,13 @@ func (f *Fetcher) Continue() (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *Fetcher) Close() (err error) {
|
||||||
|
if err = f.Pause(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (f *Fetcher) Progress() fetcher.Progress {
|
func (f *Fetcher) Progress() fetcher.Progress {
|
||||||
p := make(fetcher.Progress, 0)
|
p := make(fetcher.Progress, 0)
|
||||||
if len(f.chunks) > 0 {
|
if len(f.chunks) > 0 {
|
||||||
@@ -209,13 +181,8 @@ func (f *Fetcher) Progress() fetcher.Progress {
|
|||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Fetcher) filename() string {
|
func (f *Fetcher) filepath() string {
|
||||||
// 创建文件
|
return util.Filepath(f.opts.Path, f.res.Files[0].Name, f.opts.Name)
|
||||||
var filename = f.opts.Name
|
|
||||||
if filename == "" {
|
|
||||||
filename = f.res.Files[0].Name
|
|
||||||
}
|
|
||||||
return filepath.Join(f.opts.Path, filename)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Fetcher) fetch() {
|
func (f *Fetcher) fetch() {
|
||||||
@@ -231,7 +198,7 @@ func (f *Fetcher) fetch() {
|
|||||||
go func() {
|
go func() {
|
||||||
err := eg.Wait()
|
err := eg.Wait()
|
||||||
// 下载停止,关闭文件句柄
|
// 下载停止,关闭文件句柄
|
||||||
f.Ctl.Close(f.filename())
|
f.file.Close()
|
||||||
if f.status == base.DownloadStatusPause {
|
if f.status == base.DownloadStatusPause {
|
||||||
f.pauseCh <- nil
|
f.pauseCh <- nil
|
||||||
} else {
|
} else {
|
||||||
@@ -246,7 +213,6 @@ func (f *Fetcher) fetch() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f *Fetcher) fetchChunk(index int) (err error) {
|
func (f *Fetcher) fetchChunk(index int) (err error) {
|
||||||
filename := f.filename()
|
|
||||||
chunk := f.chunks[index]
|
chunk := f.chunks[index]
|
||||||
|
|
||||||
httpReq, err := buildRequest(f.ctx, f.res.Req)
|
httpReq, err := buildRequest(f.ctx, f.res.Req)
|
||||||
@@ -282,7 +248,6 @@ func (f *Fetcher) fetchChunk(index int) (err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
f.clients[index] = resp
|
|
||||||
if resp.StatusCode != base.HttpCodeOK && resp.StatusCode != base.HttpCodePartialContent {
|
if resp.StatusCode != base.HttpCodeOK && resp.StatusCode != base.HttpCodePartialContent {
|
||||||
err = NewRequestError(resp.StatusCode, resp.Status)
|
err = NewRequestError(resp.StatusCode, resp.Status)
|
||||||
return err
|
return err
|
||||||
@@ -300,7 +265,7 @@ func (f *Fetcher) fetchChunk(index int) (err error) {
|
|||||||
for {
|
for {
|
||||||
n, err := resp.Body.Read(buf)
|
n, err := resp.Body.Read(buf)
|
||||||
if n > 0 {
|
if n > 0 {
|
||||||
_, err := f.Ctl.Write(filename, chunk.Begin+chunk.Downloaded, buf[:n])
|
_, err := f.file.WriteAt(buf[:n], chunk.Begin+chunk.Downloaded)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
@@ -321,16 +286,44 @@ func (f *Fetcher) fetchChunk(index int) (err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if f.status == base.DownloadStatusPause {
|
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
|
chunk.Status = base.DownloadStatusPause
|
||||||
} else if chunk.Downloaded >= chunk.End-chunk.Begin+1 {
|
return
|
||||||
chunk.Status = base.DownloadStatusDone
|
}
|
||||||
} else {
|
|
||||||
if err != nil {
|
chunk.Status = base.DownloadStatusDone
|
||||||
chunk.Status = base.DownloadStatusError
|
return
|
||||||
} else {
|
}
|
||||||
chunk.Status = base.DownloadStatusDone
|
|
||||||
|
func splitChunk(res *base.Resource, opts *base.Options) (chunks []*chunk) {
|
||||||
|
if res.Range {
|
||||||
|
// 每个连接平均需要下载的分块大小
|
||||||
|
chunkSize := res.Size / int64(opts.Connections)
|
||||||
|
chunks = make([]*chunk, opts.Connections)
|
||||||
|
for i := 0; i < opts.Connections; i++ {
|
||||||
|
var (
|
||||||
|
begin = chunkSize * int64(i)
|
||||||
|
end int64
|
||||||
|
)
|
||||||
|
if i == opts.Connections-1 {
|
||||||
|
// 最后一个分块需要保证把文件下载完
|
||||||
|
end = 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
|
return
|
||||||
}
|
}
|
||||||
@@ -340,7 +333,7 @@ func buildClient() *http.Client {
|
|||||||
jar, _ := cookiejar.New(nil)
|
jar, _ := cookiejar.New(nil)
|
||||||
return &http.Client{
|
return &http.Client{
|
||||||
Jar: jar,
|
Jar: jar,
|
||||||
Timeout: time.Second * 10,
|
Timeout: time.Second * 60,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -358,7 +351,7 @@ func buildRequest(ctx context.Context, req *base.Request) (httpReq *http.Request
|
|||||||
if req.Extra == nil {
|
if req.Extra == nil {
|
||||||
method = http.MethodGet
|
method = http.MethodGet
|
||||||
} else {
|
} else {
|
||||||
extra := req.Extra.(Extra)
|
extra := req.Extra.(extra)
|
||||||
if extra.Method != "" {
|
if extra.Method != "" {
|
||||||
method = extra.Method
|
method = extra.Method
|
||||||
} else {
|
} else {
|
||||||
@@ -370,7 +363,7 @@ func buildRequest(ctx context.Context, req *base.Request) (httpReq *http.Request
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if extra.Body != "" {
|
if extra.Body != "" {
|
||||||
body = ioutil.NopCloser(bytes.NewBufferString(extra.Body))
|
body = bytes.NewBufferString(extra.Body)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -385,3 +378,47 @@ func buildRequest(ctx context.Context, req *base.Request) (httpReq *http.Request
|
|||||||
httpReq.Header = headers
|
httpReq.Header = headers
|
||||||
return httpReq, nil
|
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
|
||||||
|
if len(fd.Chunks) == 0 {
|
||||||
|
fetcher.chunks = splitChunk(res, opts)
|
||||||
|
} else {
|
||||||
|
fetcher.chunks = fd.Chunks
|
||||||
|
}
|
||||||
|
return fetcher
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
package http
|
package http
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/monkeyWie/gopeed-core/internal/controller"
|
"fmt"
|
||||||
"github.com/monkeyWie/gopeed-core/internal/fetcher"
|
"github.com/monkeyWie/gopeed/internal/controller"
|
||||||
"github.com/monkeyWie/gopeed-core/pkg/base"
|
"github.com/monkeyWie/gopeed/internal/fetcher"
|
||||||
"github.com/monkeyWie/gopeed-core/pkg/test"
|
"github.com/monkeyWie/gopeed/internal/test"
|
||||||
|
"github.com/monkeyWie/gopeed/pkg/base"
|
||||||
"net"
|
"net"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -13,8 +14,9 @@ import (
|
|||||||
|
|
||||||
func TestFetcher_Resolve(t *testing.T) {
|
func TestFetcher_Resolve(t *testing.T) {
|
||||||
testResolve(test.StartTestFileServer, &base.Resource{
|
testResolve(test.StartTestFileServer, &base.Resource{
|
||||||
TotalSize: test.BuildSize,
|
Name: test.BuildName,
|
||||||
Range: true,
|
Size: test.BuildSize,
|
||||||
|
Range: true,
|
||||||
Files: []*base.FileInfo{
|
Files: []*base.FileInfo{
|
||||||
{
|
{
|
||||||
Name: test.BuildName,
|
Name: test.BuildName,
|
||||||
@@ -23,8 +25,9 @@ func TestFetcher_Resolve(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}, t)
|
}, t)
|
||||||
testResolve(test.StartTestChunkedServer, &base.Resource{
|
testResolve(test.StartTestChunkedServer, &base.Resource{
|
||||||
TotalSize: 0,
|
Name: test.BuildName,
|
||||||
Range: false,
|
Size: 0,
|
||||||
|
Range: false,
|
||||||
Files: []*base.FileInfo{
|
Files: []*base.FileInfo{
|
||||||
{
|
{
|
||||||
Name: test.BuildName,
|
Name: test.BuildName,
|
||||||
@@ -37,7 +40,7 @@ func TestFetcher_Resolve(t *testing.T) {
|
|||||||
func testResolve(startTestServer func() net.Listener, want *base.Resource, t *testing.T) {
|
func testResolve(startTestServer func() net.Listener, want *base.Resource, t *testing.T) {
|
||||||
listener := startTestServer()
|
listener := startTestServer()
|
||||||
defer listener.Close()
|
defer listener.Close()
|
||||||
fetcher := NewFetcher()
|
fetcher := buildFetcher()
|
||||||
res, err := fetcher.Resolve(&base.Request{
|
res, err := fetcher.Resolve(&base.Request{
|
||||||
URL: "http://" + listener.Addr().String() + "/" + test.BuildName,
|
URL: "http://" + listener.Addr().String() + "/" + test.BuildName,
|
||||||
})
|
})
|
||||||
@@ -46,7 +49,7 @@ func testResolve(startTestServer func() net.Listener, want *base.Resource, t *te
|
|||||||
}
|
}
|
||||||
res.Req = nil
|
res.Req = nil
|
||||||
if !reflect.DeepEqual(want, res) {
|
if !reflect.DeepEqual(want, res) {
|
||||||
t.Errorf("Resolve error = %v, want %v", res, want)
|
t.Errorf("Resolve() got = %v, want %v", res, want)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,7 +78,13 @@ func TestFetcher_DownloadChunked(t *testing.T) {
|
|||||||
defer listener.Close()
|
defer listener.Close()
|
||||||
// chunked编码下载
|
// chunked编码下载
|
||||||
downloadNormal(listener, 1, t)
|
downloadNormal(listener, 1, t)
|
||||||
downloadContinue(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) {
|
func TestFetcher_DownloadRetry(t *testing.T) {
|
||||||
@@ -92,29 +101,38 @@ func TestFetcher_DownloadError(t *testing.T) {
|
|||||||
downloadError(listener, 1, t)
|
downloadError(listener, 1, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
func downloadReady(listener net.Listener, connections int, t *testing.T) fetcher.Fetcher {
|
func TestFetcher_DownloadResume(t *testing.T) {
|
||||||
fetcher := NewFetcher()
|
listener := test.StartTestFileServer()
|
||||||
fetcher.Setup(controller.NewController())
|
defer listener.Close()
|
||||||
|
|
||||||
|
downloadResume(listener, 1, t)
|
||||||
|
downloadResume(listener, 5, t)
|
||||||
|
downloadResume(listener, 8, t)
|
||||||
|
downloadResume(listener, 16, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func downloadReady(listener net.Listener, connections int, t *testing.T) (fetcher fetcher.Fetcher, res *base.Resource, opts *base.Options) {
|
||||||
|
fetcher = buildFetcher()
|
||||||
res, err := fetcher.Resolve(&base.Request{
|
res, err := fetcher.Resolve(&base.Request{
|
||||||
URL: "http://" + listener.Addr().String() + "/" + test.BuildName,
|
URL: "http://" + listener.Addr().String() + "/" + test.BuildName,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
err = fetcher.Create(res, &base.Options{
|
opts = &base.Options{
|
||||||
Name: test.DownloadName,
|
Name: test.DownloadName,
|
||||||
Path: test.Dir,
|
Path: test.Dir,
|
||||||
Connections: connections,
|
Connections: connections,
|
||||||
})
|
}
|
||||||
|
err = fetcher.Create(res, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
return fetcher
|
return
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func downloadNormal(listener net.Listener, connections int, t *testing.T) {
|
func downloadNormal(listener net.Listener, connections int, t *testing.T) {
|
||||||
fetcher := downloadReady(listener, connections, t)
|
fetcher, _, _ := downloadReady(listener, connections, t)
|
||||||
err := fetcher.Start()
|
err := fetcher.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -126,21 +144,45 @@ func downloadNormal(listener net.Listener, connections int, t *testing.T) {
|
|||||||
want := test.FileMd5(test.BuildFile)
|
want := test.FileMd5(test.BuildFile)
|
||||||
got := test.FileMd5(test.DownloadFile)
|
got := test.FileMd5(test.DownloadFile)
|
||||||
if want != got {
|
if want != got {
|
||||||
t.Errorf("Download error = %v, want %v", got, want)
|
t.Errorf("Download() got = %v, want %v", got, want)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func downloadContinue(listener net.Listener, connections int, t *testing.T) {
|
func downloadPost(listener net.Listener, connections int, t *testing.T) {
|
||||||
fetcher := downloadReady(listener, connections, t)
|
fetcher, _, _ := downloadReady(listener, connections, t)
|
||||||
|
fetcher.(*Fetcher).res.Req.Extra = extra{
|
||||||
|
Method: "POST",
|
||||||
|
Header: map[string]string{
|
||||||
|
"Authorization": "Bearer 123456",
|
||||||
|
},
|
||||||
|
Body: fmt.Sprintf(`{"name":"%s"}`, test.BuildName),
|
||||||
|
}
|
||||||
err := fetcher.Start()
|
err := fetcher.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
time.Sleep(time.Millisecond * 200)
|
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 {
|
if err := fetcher.Pause(); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
time.Sleep(time.Millisecond * 200)
|
time.Sleep(time.Millisecond * 50)
|
||||||
if err := fetcher.Continue(); err != nil {
|
if err := fetcher.Continue(); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -151,18 +193,59 @@ func downloadContinue(listener net.Listener, connections int, t *testing.T) {
|
|||||||
want := test.FileMd5(test.BuildFile)
|
want := test.FileMd5(test.BuildFile)
|
||||||
got := test.FileMd5(test.DownloadFile)
|
got := test.FileMd5(test.DownloadFile)
|
||||||
if want != got {
|
if want != got {
|
||||||
t.Errorf("Download error = %v, want %v", got, want)
|
t.Errorf("Download() got = %v, want %v", got, want)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func downloadError(listener net.Listener, connections int, t *testing.T) {
|
func downloadError(listener net.Listener, connections int, t *testing.T) {
|
||||||
fetcher := downloadReady(listener, connections, t)
|
fetcher, _, _ := downloadReady(listener, connections, t)
|
||||||
err := fetcher.Start()
|
err := fetcher.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
err = fetcher.Wait()
|
err = fetcher.Wait()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Errorf("Download error = %v, want %v", 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)
|
||||||
|
}
|
||||||
@@ -1,23 +1,23 @@
|
|||||||
package http
|
package http
|
||||||
|
|
||||||
import "github.com/monkeyWie/gopeed-core/pkg/base"
|
import "github.com/monkeyWie/gopeed/pkg/base"
|
||||||
|
|
||||||
type Chunk struct {
|
type chunk struct {
|
||||||
Status base.Status
|
Status base.Status
|
||||||
Begin int64
|
Begin int64
|
||||||
End int64
|
End int64
|
||||||
Downloaded int64
|
Downloaded int64
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewChunk(begin int64, end int64) *Chunk {
|
func newChunk(begin int64, end int64) *chunk {
|
||||||
return &Chunk{
|
return &chunk{
|
||||||
Status: base.DownloadStatusReady,
|
Status: base.DownloadStatusReady,
|
||||||
Begin: begin,
|
Begin: begin,
|
||||||
End: end,
|
End: end,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Extra struct {
|
type extra struct {
|
||||||
Method string
|
Method string
|
||||||
Header map[string]string
|
Header map[string]string
|
||||||
Body string
|
Body string
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
package test
|
package test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -25,6 +27,25 @@ func StartTestFileServer() net.Listener {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
func StartTestChunkedServer() net.Listener {
|
||||||
return startTestServer(func() http.Handler {
|
return startTestServer(func() http.Handler {
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
@@ -61,6 +82,29 @@ func StartTestRetryServer() net.Listener {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
func StartTestErrorServer() net.Listener {
|
||||||
counter := 0
|
counter := 0
|
||||||
return startTestServer(func() http.Handler {
|
return startTestServer(func() http.Handler {
|
||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
)
|
)
|
||||||
|
|
||||||
func FileMd5(filePath string) string {
|
func FileMd5(filePath string) string {
|
||||||
@@ -26,6 +27,24 @@ func FileMd5(filePath string) string {
|
|||||||
return hex.EncodeToString(hash.Sum(nil))
|
return hex.EncodeToString(hash.Sum(nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func DirMd5(dirPath string) string {
|
||||||
|
hash := md5.New()
|
||||||
|
filepath.Walk(dirPath, func(path string, info os.FileInfo, err error) error {
|
||||||
|
if info.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
file, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := io.Copy(hash, file); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return hex.EncodeToString(hash.Sum(nil))
|
||||||
|
}
|
||||||
|
|
||||||
func ToJson(v interface{}) string {
|
func ToJson(v interface{}) string {
|
||||||
buf, _ := json.Marshal(v)
|
buf, _ := json.Marshal(v)
|
||||||
return string(buf)
|
return string(buf)
|
||||||
+6
-12
@@ -1,15 +1,13 @@
|
|||||||
package base
|
package base
|
||||||
|
|
||||||
import "errors"
|
type Status string
|
||||||
|
|
||||||
type Status int
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
DownloadStatusReady Status = iota
|
DownloadStatusReady Status = "ready"
|
||||||
DownloadStatusStart
|
DownloadStatusRunning Status = "running"
|
||||||
DownloadStatusPause
|
DownloadStatusPause Status = "pause"
|
||||||
DownloadStatusError
|
DownloadStatusError Status = "error"
|
||||||
DownloadStatusDone
|
DownloadStatusDone Status = "done"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -23,7 +21,3 @@ const (
|
|||||||
|
|
||||||
HttpHeaderRangeFormat = "bytes=%d-%d"
|
HttpHeaderRangeFormat = "bytes=%d-%d"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
DeleteErr = errors.New("delete")
|
|
||||||
)
|
|
||||||
+18
-12
@@ -3,34 +3,40 @@ package base
|
|||||||
// 下载请求
|
// 下载请求
|
||||||
type Request struct {
|
type Request struct {
|
||||||
// 下载链接
|
// 下载链接
|
||||||
URL string
|
URL string `json:"url"`
|
||||||
// 附加信息
|
// 附加信息
|
||||||
Extra interface{}
|
Extra interface{} `json:"extra"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// 资源信息
|
// 资源信息
|
||||||
type Resource struct {
|
type Resource struct {
|
||||||
Req *Request
|
Req *Request `json:"req"`
|
||||||
|
// 资源名称
|
||||||
|
Name string `json:"name"`
|
||||||
// 资源总大小
|
// 资源总大小
|
||||||
TotalSize int64
|
Size int64 `json:"size"`
|
||||||
// 是否支持断点下载
|
// 是否支持断点下载
|
||||||
Range bool
|
Range bool `json:"range"`
|
||||||
// 资源所包含的文件列表
|
// 资源所包含的文件列表
|
||||||
Files []*FileInfo
|
Files []*FileInfo `json:"files"`
|
||||||
|
// 附加信息
|
||||||
|
Extra interface{} `json:"extra"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type FileInfo struct {
|
type FileInfo struct {
|
||||||
Name string
|
Name string `json:"name"`
|
||||||
Path string
|
Path string `json:"path"`
|
||||||
Size int64
|
Size int64 `json:"size"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// 下载选项
|
// 下载选项
|
||||||
type Options struct {
|
type Options struct {
|
||||||
// 保存文件名
|
// 保存文件名
|
||||||
Name string
|
Name string `json:"name"`
|
||||||
// 保存目录
|
// 保存目录
|
||||||
Path string
|
Path string `json:"path"`
|
||||||
// 并发连接数
|
// 并发连接数
|
||||||
Connections int
|
Connections int `json:"connections"`
|
||||||
|
// 选择下载的文件下标列表
|
||||||
|
SelectFiles []int `json:"selectFiles"`
|
||||||
}
|
}
|
||||||
+377
-114
File diff suppressed because it is too large.
Load diff
@@ -1,19 +1,23 @@
|
|||||||
package download
|
package download
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/monkeyWie/gopeed-core/internal/protocol/http"
|
"github.com/monkeyWie/gopeed/internal/test"
|
||||||
"github.com/monkeyWie/gopeed-core/pkg/base"
|
"github.com/monkeyWie/gopeed/pkg/base"
|
||||||
"github.com/monkeyWie/gopeed-core/pkg/test"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestDownloader_Resolve(t *testing.T) {
|
func TestDownloader_Resolve(t *testing.T) {
|
||||||
listener := test.StartTestFileServer()
|
listener := test.StartTestFileServer()
|
||||||
defer listener.Close()
|
defer listener.Close()
|
||||||
|
|
||||||
downloader := NewDownloader(http.FetcherBuilder)
|
downloader := NewDownloader(nil)
|
||||||
|
if err := downloader.Setup(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer downloader.Clear()
|
||||||
req := &base.Request{
|
req := &base.Request{
|
||||||
URL: "http://" + listener.Addr().String() + "/" + test.BuildName,
|
URL: "http://" + listener.Addr().String() + "/" + test.BuildName,
|
||||||
}
|
}
|
||||||
@@ -22,9 +26,10 @@ func TestDownloader_Resolve(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
want := &base.Resource{
|
want := &base.Resource{
|
||||||
Req: req,
|
Name: test.BuildName,
|
||||||
TotalSize: test.BuildSize,
|
Req: req,
|
||||||
Range: true,
|
Size: test.BuildSize,
|
||||||
|
Range: true,
|
||||||
Files: []*base.FileInfo{
|
Files: []*base.FileInfo{
|
||||||
{
|
{
|
||||||
Name: test.BuildName,
|
Name: test.BuildName,
|
||||||
@@ -33,8 +38,8 @@ func TestDownloader_Resolve(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
if !reflect.DeepEqual(res, want) {
|
if !reflect.DeepEqual(want, res) {
|
||||||
t.Errorf("Resolve error = %s, want %s", test.ToJson(res), test.ToJson(want))
|
t.Errorf("Resolve() got = %v, want %v", res, want)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,7 +47,11 @@ func TestDownloader_Create(t *testing.T) {
|
|||||||
listener := test.StartTestFileServer()
|
listener := test.StartTestFileServer()
|
||||||
defer listener.Close()
|
defer listener.Close()
|
||||||
|
|
||||||
downloader := NewDownloader(http.FetcherBuilder)
|
downloader := NewDownloader(nil)
|
||||||
|
if err := downloader.Setup(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer downloader.Clear()
|
||||||
req := &base.Request{
|
req := &base.Request{
|
||||||
URL: "http://" + listener.Addr().String() + "/" + test.BuildName,
|
URL: "http://" + listener.Addr().String() + "/" + test.BuildName,
|
||||||
}
|
}
|
||||||
@@ -58,7 +67,7 @@ func TestDownloader_Create(t *testing.T) {
|
|||||||
wg.Done()
|
wg.Done()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
err = downloader.Create(res, &base.Options{
|
_, err = downloader.Create(res, &base.Options{
|
||||||
Path: test.Dir,
|
Path: test.Dir,
|
||||||
Name: test.DownloadName,
|
Name: test.DownloadName,
|
||||||
Connections: 4,
|
Connections: 4,
|
||||||
@@ -70,6 +79,72 @@ func TestDownloader_Create(t *testing.T) {
|
|||||||
want := test.FileMd5(test.BuildFile)
|
want := test.FileMd5(test.BuildFile)
|
||||||
got := test.FileMd5(test.DownloadFile)
|
got := test.FileMd5(test.DownloadFile)
|
||||||
if want != got {
|
if want != got {
|
||||||
t.Errorf("Download error = %v, want %v", got, want)
|
t.Errorf("Download() got = %v, want %v", got, want)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDownloader_StoreAndRestore(t *testing.T) {
|
||||||
|
listener := test.StartTestSlowFileServer(time.Millisecond * 2000)
|
||||||
|
defer listener.Close()
|
||||||
|
|
||||||
|
downloader := NewDownloader(&DownloaderConfig{
|
||||||
|
Storage: NewBoltStorage("./"),
|
||||||
|
})
|
||||||
|
if err := downloader.Setup(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer downloader.Clear()
|
||||||
|
req := &base.Request{
|
||||||
|
URL: "http://" + listener.Addr().String() + "/" + test.BuildName,
|
||||||
|
}
|
||||||
|
res, err := downloader.Resolve(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
id, err := downloader.Create(res, &base.Options{
|
||||||
|
Path: test.Dir,
|
||||||
|
Name: test.DownloadName,
|
||||||
|
Connections: 4,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
time.Sleep(time.Millisecond * 1001)
|
||||||
|
err = downloader.Pause(id)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
downloader.Close()
|
||||||
|
|
||||||
|
downloader = NewDownloader(&DownloaderConfig{
|
||||||
|
Storage: NewBoltStorage("./"),
|
||||||
|
})
|
||||||
|
if err := downloader.Setup(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
task := downloader.GetTask(id)
|
||||||
|
|
||||||
|
if task == nil {
|
||||||
|
t.Fatal("task is nil")
|
||||||
|
}
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(1)
|
||||||
|
downloader.Listener(func(event *Event) {
|
||||||
|
if event.Key == EventKeyDone {
|
||||||
|
wg.Done()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
err = downloader.Continue(id)
|
||||||
|
wg.Wait()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
want := test.FileMd5(test.BuildFile)
|
||||||
|
got := test.FileMd5(test.DownloadFile)
|
||||||
|
if want != got {
|
||||||
|
t.Errorf("StoreAndResume() got = %v, want %v", got, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
downloader.Clear()
|
||||||
|
}
|
||||||
@@ -8,12 +8,13 @@ const (
|
|||||||
EventKeyContinue = "continue"
|
EventKeyContinue = "continue"
|
||||||
EventKeyProgress = "progress"
|
EventKeyProgress = "progress"
|
||||||
EventKeyError = "error"
|
EventKeyError = "error"
|
||||||
|
EventKeyDelete = "delete"
|
||||||
EventKeyDone = "done"
|
EventKeyDone = "done"
|
||||||
EventKeyFinally = "finally"
|
EventKeyFinally = "finally"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Event struct {
|
type Event struct {
|
||||||
Key EventKey
|
Key EventKey
|
||||||
Task *TaskInfo
|
Task *Task
|
||||||
Err error
|
Err error
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,228 @@
|
|||||||
|
package download
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"go.etcd.io/bbolt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Storage interface {
|
||||||
|
Setup(buckets []string) error
|
||||||
|
Put(bucket string, key string, v any) error
|
||||||
|
Get(bucket string, key string, v any) (bool, error)
|
||||||
|
List(bucket string, v any) error
|
||||||
|
Pop(bucket string, key string, v any) error
|
||||||
|
Delete(bucket string, key string) error
|
||||||
|
|
||||||
|
Close() error
|
||||||
|
Clear() error
|
||||||
|
}
|
||||||
|
|
||||||
|
func changeValue(p any, v any) {
|
||||||
|
if v == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rp := reflect.ValueOf(p)
|
||||||
|
rv := reflect.ValueOf(v)
|
||||||
|
if rv.Kind() == reflect.Slice {
|
||||||
|
if rv.Len() == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// get underlying type
|
||||||
|
tp := reflect.TypeOf(p).Elem().Elem()
|
||||||
|
for i := 0; i < rv.Len(); i++ {
|
||||||
|
// convert to underlying type
|
||||||
|
vv := rv.Index(i).Elem().Convert(tp)
|
||||||
|
rp.Elem().Set(reflect.Append(rp.Elem(), vv))
|
||||||
|
}
|
||||||
|
} else if rv.Kind() == reflect.Ptr {
|
||||||
|
rp.Elem().Set(rv.Elem())
|
||||||
|
} else {
|
||||||
|
rp.Elem().Set(rv)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var memData = make(map[string]map[string]any)
|
||||||
|
|
||||||
|
type MemStorage struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMemStorage() *MemStorage {
|
||||||
|
return &MemStorage{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *MemStorage) Setup(buckets []string) error {
|
||||||
|
for _, bucket := range buckets {
|
||||||
|
if _, ok := memData[bucket]; !ok {
|
||||||
|
memData[bucket] = make(map[string]any)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *MemStorage) Put(bucket string, key string, v any) error {
|
||||||
|
memData[bucket][key] = v
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *MemStorage) Get(bucket string, key string, v any) (bool, error) {
|
||||||
|
if dv, ok := memData[bucket][key]; ok {
|
||||||
|
changeValue(v, dv)
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *MemStorage) List(bucket string, v any) error {
|
||||||
|
data := memData[bucket]
|
||||||
|
list := make([]any, 0)
|
||||||
|
for _, v := range data {
|
||||||
|
list = append(list, v)
|
||||||
|
}
|
||||||
|
changeValue(v, list)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *MemStorage) Pop(bucket string, key string, v any) error {
|
||||||
|
data := memData[bucket]
|
||||||
|
changeValue(v, data[key])
|
||||||
|
delete(data, key)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *MemStorage) Delete(bucket string, key string) error {
|
||||||
|
delete(memData[bucket], key)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *MemStorage) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *MemStorage) Clear() error {
|
||||||
|
memData = make(map[string]map[string]any)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
dbFile = "gopeed.db"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BoltStorage struct {
|
||||||
|
db *bbolt.DB
|
||||||
|
path string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBoltStorage(dir string) *BoltStorage {
|
||||||
|
path := filepath.Join(dir, dbFile)
|
||||||
|
db, err := bbolt.Open(path, 0600, nil)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return &BoltStorage{
|
||||||
|
db: db,
|
||||||
|
path: path,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BoltStorage) Setup(buckets []string) error {
|
||||||
|
return b.db.Update(func(tx *bbolt.Tx) error {
|
||||||
|
for _, bucket := range buckets {
|
||||||
|
_, err := tx.CreateBucketIfNotExists([]byte(bucket))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BoltStorage) Put(bucket string, key string, v any) error {
|
||||||
|
buf, err := json.Marshal(v)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return b.db.Update(func(tx *bbolt.Tx) error {
|
||||||
|
b := tx.Bucket([]byte(bucket))
|
||||||
|
return b.Put([]byte(key), buf)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BoltStorage) Get(bucket string, key string, v any) (bool, error) {
|
||||||
|
var data []byte
|
||||||
|
err := b.db.View(func(tx *bbolt.Tx) error {
|
||||||
|
b := tx.Bucket([]byte(bucket))
|
||||||
|
data = b.Get([]byte(key))
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if data == nil {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(data, v); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BoltStorage) List(bucket string, v any) error {
|
||||||
|
list := make([]any, 0)
|
||||||
|
tv := reflect.TypeOf(v).Elem().Elem()
|
||||||
|
if err := b.db.View(func(tx *bbolt.Tx) error {
|
||||||
|
b := tx.Bucket([]byte(bucket))
|
||||||
|
if err := b.ForEach(func(k, v []byte) error {
|
||||||
|
data := reflect.New(tv.Elem()).Interface()
|
||||||
|
if err := json.Unmarshal(v, &data); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
list = append(list, data)
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
changeValue(v, list)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BoltStorage) Pop(bucket string, key string, v any) error {
|
||||||
|
var data []byte
|
||||||
|
err := b.db.Update(func(tx *bbolt.Tx) error {
|
||||||
|
b := tx.Bucket([]byte(bucket))
|
||||||
|
kb := []byte(key)
|
||||||
|
data = b.Get(kb)
|
||||||
|
return b.Delete(kb)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return json.Unmarshal(data, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BoltStorage) Delete(bucket string, key string) error {
|
||||||
|
return b.db.Update(func(tx *bbolt.Tx) error {
|
||||||
|
b := tx.Bucket([]byte(bucket))
|
||||||
|
return b.Delete([]byte(key))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BoltStorage) Close() error {
|
||||||
|
return b.db.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BoltStorage) Clear() error {
|
||||||
|
if err := b.Close(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := os.Remove(b.path); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package download
|
||||||
|
|
||||||
|
import (
|
||||||
|
gonanoid "github.com/matoous/go-nanoid/v2"
|
||||||
|
"github.com/monkeyWie/gopeed/internal/fetcher"
|
||||||
|
"github.com/monkeyWie/gopeed/pkg/base"
|
||||||
|
"github.com/monkeyWie/gopeed/pkg/util"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Task struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Res *base.Resource `json:"res"`
|
||||||
|
Opts *base.Options `json:"opts"`
|
||||||
|
Status base.Status `json:"status"`
|
||||||
|
Progress *Progress `json:"progress"`
|
||||||
|
Size int64 `json:"size"`
|
||||||
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
|
|
||||||
|
fetcherBuilder fetcher.FetcherBuilder
|
||||||
|
fetcher fetcher.Fetcher
|
||||||
|
timer *util.Timer
|
||||||
|
lock *sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTask() *Task {
|
||||||
|
id, err := gonanoid.New()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return &Task{
|
||||||
|
ID: id,
|
||||||
|
Status: base.DownloadStatusReady,
|
||||||
|
CreatedAt: time.Now(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Task) clone() *Task {
|
||||||
|
return &Task{
|
||||||
|
ID: t.ID,
|
||||||
|
Res: t.Res,
|
||||||
|
Opts: t.Opts,
|
||||||
|
Status: t.Status,
|
||||||
|
Progress: t.Progress,
|
||||||
|
Size: t.Size,
|
||||||
|
CreatedAt: t.CreatedAt,
|
||||||
|
}
|
||||||
|
}
|
||||||
+123
@@ -0,0 +1,123 @@
|
|||||||
|
package rest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/monkeyWie/gopeed/pkg/base"
|
||||||
|
"github.com/monkeyWie/gopeed/pkg/download"
|
||||||
|
"github.com/monkeyWie/gopeed/pkg/rest/model"
|
||||||
|
"github.com/monkeyWie/gopeed/pkg/rest/util"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Resolve(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req base.Request
|
||||||
|
if util.ReadJson(w, r, &req) {
|
||||||
|
resource, err := Downloader.Resolve(&req)
|
||||||
|
if err != nil {
|
||||||
|
util.WriteJson(w, http.StatusInternalServerError, model.NewResultWithMsg(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
util.WriteJsonOk(w, model.NewResultWithData(resource))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateTask(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req model.CreateTask
|
||||||
|
if util.ReadJson(w, r, &req) {
|
||||||
|
if req.Opts.Connections == 0 {
|
||||||
|
req.Opts.Connections = getServerConfig().Connections
|
||||||
|
}
|
||||||
|
if req.Opts.Path == "" {
|
||||||
|
req.Opts.Path = getServerConfig().DownloadDir
|
||||||
|
}
|
||||||
|
taskId, err := Downloader.Create(req.Res, req.Opts)
|
||||||
|
if err != nil {
|
||||||
|
util.WriteJson(w, http.StatusInternalServerError, model.NewResultWithMsg(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
util.WriteJsonOk(w, model.NewResultWithData(taskId))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func PauseTask(w http.ResponseWriter, r *http.Request) {
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
taskId := vars["id"]
|
||||||
|
if err := Downloader.Pause(taskId); err != nil {
|
||||||
|
util.WriteJson(w, http.StatusInternalServerError, model.NewResultWithMsg(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
util.WriteJsonOk(w, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ContinueTask(w http.ResponseWriter, r *http.Request) {
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
taskId := vars["id"]
|
||||||
|
if err := Downloader.Continue(taskId); err != nil {
|
||||||
|
util.WriteJson(w, http.StatusInternalServerError, model.NewResultWithMsg(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
util.WriteJsonOk(w, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteTask(w http.ResponseWriter, r *http.Request) {
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
taskId := vars["id"]
|
||||||
|
force := r.FormValue("force")
|
||||||
|
if err := Downloader.Delete(taskId, force == "true"); err != nil {
|
||||||
|
util.WriteJson(w, http.StatusInternalServerError, model.NewResultWithMsg(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
util.WriteJsonOk(w, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetTask(w http.ResponseWriter, r *http.Request) {
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
taskId := vars["id"]
|
||||||
|
task := Downloader.GetTask(taskId)
|
||||||
|
if task == nil {
|
||||||
|
util.WriteJson(w, http.StatusNotFound, model.NewResultWithMsg("task not found"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
util.WriteJsonOk(w, model.NewResultWithData(task))
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetTasks(w http.ResponseWriter, r *http.Request) {
|
||||||
|
status := r.FormValue("status")
|
||||||
|
if status == "" {
|
||||||
|
util.WriteJson(w, http.StatusBadRequest, model.NewResultWithMsg("param is required: status"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
statusArr := strings.Split(status, ",")
|
||||||
|
tasks := Downloader.GetTasks()
|
||||||
|
result := make([]*download.Task, 0)
|
||||||
|
for _, task := range tasks {
|
||||||
|
for _, s := range statusArr {
|
||||||
|
if task.Status == base.Status(s) {
|
||||||
|
result = append(result, task)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
util.WriteJsonOk(w, model.NewResultWithData(result))
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetConfig(w http.ResponseWriter, r *http.Request) {
|
||||||
|
util.WriteJsonOk(w, model.NewResultWithData(getServerConfig()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func PutConfig(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var cfg model.ServerConfig
|
||||||
|
if util.ReadJson(w, r, &cfg) {
|
||||||
|
if err := Downloader.PutConfig(&cfg); err != nil {
|
||||||
|
util.WriteJson(w, http.StatusInternalServerError, model.NewResultWithMsg(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
util.WriteJsonOk(w, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getServerConfig() *model.ServerConfig {
|
||||||
|
var cfg model.ServerConfig
|
||||||
|
Downloader.GetConfig(&cfg)
|
||||||
|
return &cfg
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
type Result[T any] struct {
|
||||||
|
Msg string `json:"msg"`
|
||||||
|
Data T `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewResult[T any](msg string, data T) *Result[T] {
|
||||||
|
return &Result[T]{
|
||||||
|
Msg: msg,
|
||||||
|
Data: data,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewResultWithMsg(msg string) *Result[any] {
|
||||||
|
return NewResult[any](msg, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewResultWithData[T any](data T) *Result[T] {
|
||||||
|
return NewResult[T]("", data)
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import "io/fs"
|
||||||
|
|
||||||
|
type Storage string
|
||||||
|
|
||||||
|
const (
|
||||||
|
StorageMem Storage = "mem"
|
||||||
|
StorageBolt Storage = "bolt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type StartConfig struct {
|
||||||
|
Network string `json:"network"`
|
||||||
|
Address string `json:"address"`
|
||||||
|
Storage Storage `json:"storage"`
|
||||||
|
StorageDir string `json:"storageDir"`
|
||||||
|
RefreshInterval int `json:"refreshInterval"`
|
||||||
|
|
||||||
|
WebEnable bool
|
||||||
|
WebFS fs.FS
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *StartConfig) Init() *StartConfig {
|
||||||
|
if cfg.Network == "" {
|
||||||
|
cfg.Network = "tcp"
|
||||||
|
}
|
||||||
|
if cfg.Storage == "" {
|
||||||
|
cfg.Storage = StorageBolt
|
||||||
|
}
|
||||||
|
if cfg.StorageDir == "" {
|
||||||
|
cfg.StorageDir = "./"
|
||||||
|
}
|
||||||
|
return cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServerConfig is present in the database
|
||||||
|
type ServerConfig struct {
|
||||||
|
Host string `json:"host"`
|
||||||
|
Port int `json:"port"`
|
||||||
|
|
||||||
|
Connections int `json:"connections"`
|
||||||
|
DownloadDir string `json:"downloadDir"`
|
||||||
|
|
||||||
|
Extra map[string]any `json:"extra"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *ServerConfig) Init() *ServerConfig {
|
||||||
|
if cfg.Host == "" {
|
||||||
|
cfg.Host = "127.0.0.1"
|
||||||
|
}
|
||||||
|
if cfg.Port < 0 {
|
||||||
|
cfg.Port = 0
|
||||||
|
}
|
||||||
|
if cfg.Connections <= 0 {
|
||||||
|
cfg.Connections = 16
|
||||||
|
}
|
||||||
|
return cfg
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import "github.com/monkeyWie/gopeed/pkg/base"
|
||||||
|
|
||||||
|
type CreateTask struct {
|
||||||
|
Res *base.Resource `json:"res"`
|
||||||
|
Opts *base.Options `json:"opts"`
|
||||||
|
}
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
package rest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/gorilla/handlers"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/monkeyWie/gopeed/pkg/download"
|
||||||
|
"github.com/monkeyWie/gopeed/pkg/rest/model"
|
||||||
|
"github.com/monkeyWie/gopeed/pkg/util"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
srv *http.Server
|
||||||
|
|
||||||
|
Downloader *download.Downloader
|
||||||
|
)
|
||||||
|
|
||||||
|
func Start(startCfg *model.StartConfig) (int, error) {
|
||||||
|
srv, listener, err := BuildServer(startCfg)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
if err := srv.Serve(listener); err != nil && err != http.ErrServerClosed {
|
||||||
|
// TODO log
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
port := 0
|
||||||
|
if addr, ok := listener.Addr().(*net.TCPAddr); ok {
|
||||||
|
port = addr.Port
|
||||||
|
}
|
||||||
|
return port, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Stop() {
|
||||||
|
if srv != nil {
|
||||||
|
if err := srv.Shutdown(context.TODO()); err != nil {
|
||||||
|
// TODO log
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if Downloader != nil {
|
||||||
|
if err := Downloader.Close(); err != nil {
|
||||||
|
// TODO log
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BuildServer(startCfg *model.StartConfig) (*http.Server, net.Listener, error) {
|
||||||
|
if startCfg == nil {
|
||||||
|
startCfg = &model.StartConfig{}
|
||||||
|
}
|
||||||
|
startCfg.Init()
|
||||||
|
|
||||||
|
downloadCfg := &download.DownloaderConfig{}
|
||||||
|
if startCfg.Storage == model.StorageBolt {
|
||||||
|
downloadCfg.Storage = download.NewBoltStorage(startCfg.StorageDir)
|
||||||
|
} else {
|
||||||
|
downloadCfg.Storage = download.NewMemStorage()
|
||||||
|
}
|
||||||
|
downloadCfg.RefreshInterval = startCfg.RefreshInterval
|
||||||
|
Downloader = download.NewDownloader(downloadCfg.Init())
|
||||||
|
if err := Downloader.Setup(); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
serverConfig := getAndPutServerConfig(startCfg)
|
||||||
|
var address string
|
||||||
|
if startCfg.Network == "unix" {
|
||||||
|
address = startCfg.Address
|
||||||
|
util.SafeRemove(address)
|
||||||
|
} else {
|
||||||
|
address = net.JoinHostPort(serverConfig.Host, strconv.Itoa(serverConfig.Port))
|
||||||
|
}
|
||||||
|
|
||||||
|
listener, err := net.Listen(startCfg.Network, address)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var r = mux.NewRouter()
|
||||||
|
r.Methods(http.MethodPost).Path("/api/v1/resolve").HandlerFunc(Resolve)
|
||||||
|
r.Methods(http.MethodPost).Path("/api/v1/tasks").HandlerFunc(CreateTask)
|
||||||
|
r.Methods(http.MethodPut).Path("/api/v1/tasks/{id}/pause").HandlerFunc(PauseTask)
|
||||||
|
r.Methods(http.MethodPut).Path("/api/v1/tasks/{id}/continue").HandlerFunc(ContinueTask)
|
||||||
|
r.Methods(http.MethodDelete).Path("/api/v1/tasks/{id}").HandlerFunc(DeleteTask)
|
||||||
|
r.Methods(http.MethodGet).Path("/api/v1/tasks/{id}").HandlerFunc(GetTask)
|
||||||
|
r.Methods(http.MethodGet).Path("/api/v1/tasks").HandlerFunc(GetTasks)
|
||||||
|
r.Methods(http.MethodGet).Path("/api/v1/config").HandlerFunc(GetConfig)
|
||||||
|
r.Methods(http.MethodPut).Path("/api/v1/config").HandlerFunc(PutConfig)
|
||||||
|
if startCfg.WebEnable {
|
||||||
|
r.PathPrefix("/").Handler(http.FileServer(http.FS(startCfg.WebFS)))
|
||||||
|
}
|
||||||
|
|
||||||
|
srv = &http.Server{Handler: handlers.CORS(
|
||||||
|
handlers.AllowedHeaders([]string{"X-Requested-With", "Content-Type", "Authorization"}),
|
||||||
|
handlers.AllowedMethods([]string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}),
|
||||||
|
handlers.AllowedOrigins([]string{"*"}),
|
||||||
|
)(r)}
|
||||||
|
return srv, listener, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAndPutServerConfig(startCfg *model.StartConfig) *model.ServerConfig {
|
||||||
|
var serverConfig model.ServerConfig
|
||||||
|
exist, err := Downloader.GetConfig(&serverConfig)
|
||||||
|
if err != nil {
|
||||||
|
// TODO log
|
||||||
|
}
|
||||||
|
// first start
|
||||||
|
if !exist {
|
||||||
|
if startCfg.Network == "tcp" {
|
||||||
|
host, port, _ := net.SplitHostPort(startCfg.Address)
|
||||||
|
serverConfig.Host = host
|
||||||
|
serverConfig.Port, _ = strconv.Atoi(port)
|
||||||
|
}
|
||||||
|
serverConfig.Init()
|
||||||
|
Downloader.PutConfig(serverConfig)
|
||||||
|
}
|
||||||
|
return &serverConfig
|
||||||
|
}
|
||||||
@@ -0,0 +1,273 @@
|
|||||||
|
package rest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/monkeyWie/gopeed/internal/test"
|
||||||
|
"github.com/monkeyWie/gopeed/pkg/base"
|
||||||
|
"github.com/monkeyWie/gopeed/pkg/download"
|
||||||
|
"github.com/monkeyWie/gopeed/pkg/rest/model"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
restPort int
|
||||||
|
|
||||||
|
taskReq = &base.Request{}
|
||||||
|
taskRes = &base.Resource{
|
||||||
|
Name: test.BuildName,
|
||||||
|
Req: taskReq,
|
||||||
|
Size: test.BuildSize,
|
||||||
|
Range: true,
|
||||||
|
Files: []*base.FileInfo{
|
||||||
|
{
|
||||||
|
Name: test.BuildName,
|
||||||
|
Path: "",
|
||||||
|
Size: test.BuildSize,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
createReq = &model.CreateTask{
|
||||||
|
Res: taskRes,
|
||||||
|
Opts: &base.Options{
|
||||||
|
Path: test.Dir,
|
||||||
|
Name: test.DownloadName,
|
||||||
|
Connections: 2,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestResolve(t *testing.T) {
|
||||||
|
doTest(func() {
|
||||||
|
resp := httpRequestCheckOk[*base.Resource](http.MethodPost, "/api/v1/resolve", taskReq)
|
||||||
|
if !reflect.DeepEqual(taskRes, resp) {
|
||||||
|
t.Errorf("Resolve() got = %v, want %v", resp, taskRes)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateTask(t *testing.T) {
|
||||||
|
doTest(func() {
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(1)
|
||||||
|
Downloader.Listener(func(event *download.Event) {
|
||||||
|
if event.Key == download.EventKeyFinally {
|
||||||
|
wg.Done()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
taskId := httpRequestCheckOk[string](http.MethodPost, "/api/v1/tasks", createReq)
|
||||||
|
if taskId == "" {
|
||||||
|
t.Fatal("create task failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
want := test.FileMd5(test.BuildFile)
|
||||||
|
got := test.FileMd5(test.DownloadFile)
|
||||||
|
if want != got {
|
||||||
|
t.Errorf("CreateTask() got = %v, want %v", got, want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPauseAndContinueTask(t *testing.T) {
|
||||||
|
doTest(func() {
|
||||||
|
type result struct {
|
||||||
|
pauseCount int
|
||||||
|
continueCount int
|
||||||
|
md5 string
|
||||||
|
}
|
||||||
|
|
||||||
|
var got = result{
|
||||||
|
pauseCount: 0,
|
||||||
|
continueCount: 0,
|
||||||
|
md5: "",
|
||||||
|
}
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(1)
|
||||||
|
Downloader.Listener(func(event *download.Event) {
|
||||||
|
switch event.Key {
|
||||||
|
case download.EventKeyPause:
|
||||||
|
got.pauseCount++
|
||||||
|
case download.EventKeyContinue:
|
||||||
|
got.continueCount++
|
||||||
|
case download.EventKeyFinally:
|
||||||
|
wg.Done()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
taskId := httpRequestCheckOk[string](http.MethodPost, "/api/v1/tasks", createReq)
|
||||||
|
t1 := httpRequestCheckOk[*download.Task](http.MethodGet, "/api/v1/tasks/"+taskId, nil)
|
||||||
|
if t1.Status != base.DownloadStatusRunning {
|
||||||
|
t.Errorf("CreateTask() got = %v, want %v", t1.Status, base.DownloadStatusRunning)
|
||||||
|
}
|
||||||
|
httpRequestCheckOk[any](http.MethodPut, "/api/v1/tasks/"+taskId+"/pause", nil)
|
||||||
|
t2 := httpRequestCheckOk[*download.Task](http.MethodGet, "/api/v1/tasks/"+taskId, nil)
|
||||||
|
if t2.Status != base.DownloadStatusPause {
|
||||||
|
t.Errorf("PauseTask() got = %v, want %v", t2.Status, base.DownloadStatusPause)
|
||||||
|
}
|
||||||
|
httpRequestCheckOk[any](http.MethodPut, "/api/v1/tasks/"+taskId+"/continue", nil)
|
||||||
|
t3 := httpRequestCheckOk[*download.Task](http.MethodGet, "/api/v1/tasks/"+taskId, nil)
|
||||||
|
if t3.Status != base.DownloadStatusRunning {
|
||||||
|
t.Errorf("ContinueTask() got = %v, want %v", t3.Status, base.DownloadStatusRunning)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
want := result{
|
||||||
|
pauseCount: 1,
|
||||||
|
continueCount: 1,
|
||||||
|
md5: test.FileMd5(test.BuildFile),
|
||||||
|
}
|
||||||
|
got.md5 = test.FileMd5(test.DownloadFile)
|
||||||
|
if !reflect.DeepEqual(got, want) {
|
||||||
|
t.Errorf("PauseAndContinueTask() got = %v, want %v", got, want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDeleteTask(t *testing.T) {
|
||||||
|
doTest(func() {
|
||||||
|
taskId := httpRequestCheckOk[string](http.MethodPost, "/api/v1/tasks", createReq)
|
||||||
|
httpRequestCheckOk[any](http.MethodDelete, "/api/v1/tasks/"+taskId, nil)
|
||||||
|
code, _ := httpRequest[*download.Task](http.MethodGet, "/api/v1/tasks/"+taskId, nil)
|
||||||
|
checkCode(code, http.StatusNotFound)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDeleteTaskForce(t *testing.T) {
|
||||||
|
doTest(func() {
|
||||||
|
taskId := httpRequestCheckOk[string](http.MethodPost, "/api/v1/tasks", createReq)
|
||||||
|
time.Sleep(time.Millisecond * 500)
|
||||||
|
httpRequestCheckOk[any](http.MethodDelete, "/api/v1/tasks/"+taskId+"?force=true", nil)
|
||||||
|
code, _ := httpRequest[*download.Task](http.MethodGet, "/api/v1/tasks/"+taskId, nil)
|
||||||
|
checkCode(code, http.StatusNotFound)
|
||||||
|
if _, err := os.Stat(test.DownloadFile); !errors.Is(err, os.ErrNotExist) {
|
||||||
|
t.Errorf("DeleteTaskForce() got = %v, want %v", err, os.ErrNotExist)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetTasks(t *testing.T) {
|
||||||
|
doTest(func() {
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(1)
|
||||||
|
Downloader.Listener(func(event *download.Event) {
|
||||||
|
if event.Key == download.EventKeyFinally {
|
||||||
|
wg.Done()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
httpRequestCheckOk[string](http.MethodPost, fmt.Sprintf("/api/v1/tasks?status=%s,%s",
|
||||||
|
base.DownloadStatusReady, base.DownloadStatusRunning), createReq)
|
||||||
|
code, _ := httpRequest[[]*download.Task](http.MethodGet, "/api/v1/tasks", nil)
|
||||||
|
checkCode(code, http.StatusBadRequest)
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
r := httpRequestCheckOk[[]*download.Task](http.MethodGet, fmt.Sprintf("/api/v1/tasks?status=%s",
|
||||||
|
base.DownloadStatusDone), nil)
|
||||||
|
if r[0].Status != base.DownloadStatusDone {
|
||||||
|
t.Errorf("GetTasks() got = %v, want %v", r[0].Status, base.DownloadStatusDone)
|
||||||
|
}
|
||||||
|
r = httpRequestCheckOk[[]*download.Task](http.MethodGet, fmt.Sprintf("/api/v1/tasks?status=%s,%s",
|
||||||
|
base.DownloadStatusReady, base.DownloadStatusRunning), nil)
|
||||||
|
if len(r) > 0 {
|
||||||
|
t.Errorf("GetTasks() got = %v, want %v", len(r), 0)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetAndPutConfig(t *testing.T) {
|
||||||
|
doTest(func() {
|
||||||
|
cfg := httpRequestCheckOk[*model.ServerConfig](http.MethodGet, "/api/v1/config", nil)
|
||||||
|
|
||||||
|
cfg.Port = 8888
|
||||||
|
cfg.Connections = 32
|
||||||
|
cfg.DownloadDir = "./download"
|
||||||
|
cfg.Extra = map[string]any{
|
||||||
|
"theme": "dark",
|
||||||
|
}
|
||||||
|
httpRequestCheckOk[any](http.MethodPut, "/api/v1/config", cfg)
|
||||||
|
|
||||||
|
newCfg := httpRequestCheckOk[*model.ServerConfig](http.MethodGet, "/api/v1/config", nil)
|
||||||
|
if !reflect.DeepEqual(newCfg, cfg) {
|
||||||
|
t.Errorf("GetAndPutConfig() got = %v, want %v", newCfg, cfg)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func doTest(handler func()) {
|
||||||
|
testFunc := func(storage model.Storage) {
|
||||||
|
fileListener := doStart(storage)
|
||||||
|
defer func() {
|
||||||
|
if err := fileListener.Close(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
Stop()
|
||||||
|
}()
|
||||||
|
defer Downloader.Clear()
|
||||||
|
taskReq.URL = "http://" + fileListener.Addr().String() + "/" + test.BuildName
|
||||||
|
handler()
|
||||||
|
}
|
||||||
|
testFunc(model.StorageMem)
|
||||||
|
testFunc(model.StorageBolt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func doStart(storage model.Storage) net.Listener {
|
||||||
|
port, err := Start((&model.StartConfig{
|
||||||
|
Storage: storage,
|
||||||
|
}).Init())
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
restPort = port
|
||||||
|
return test.StartTestFileServer()
|
||||||
|
}
|
||||||
|
|
||||||
|
func httpRequest[T any](method string, path string, req any) (int, *model.Result[T]) {
|
||||||
|
var body io.Reader
|
||||||
|
if req != nil {
|
||||||
|
buf, _ := json.Marshal(req)
|
||||||
|
body = bytes.NewBuffer(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
request, err := http.NewRequest(method, fmt.Sprintf("http://127.0.0.1:%d%s", restPort, path), body)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
response, err := http.DefaultClient.Do(request)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer response.Body.Close()
|
||||||
|
|
||||||
|
var r model.Result[T]
|
||||||
|
if err := json.NewDecoder(response.Body).Decode(&r); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return response.StatusCode, &r
|
||||||
|
}
|
||||||
|
|
||||||
|
func httpRequestCheckOk[T any](method string, path string, req any) T {
|
||||||
|
code, result := httpRequest[T](method, path, req)
|
||||||
|
checkOk(code)
|
||||||
|
return result.Data
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkOk(code int) {
|
||||||
|
checkCode(code, 200)
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkCode(code int, exceptCode int) {
|
||||||
|
if code != exceptCode {
|
||||||
|
panic(fmt.Sprintf("code got = %d, want %d", code, exceptCode))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"github.com/monkeyWie/gopeed/pkg/rest/model"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ReadJson(w http.ResponseWriter, r *http.Request, v any) bool {
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(v); err != nil {
|
||||||
|
WriteJson(w, 500, model.NewResultWithMsg(err.Error()))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func WriteJsonOk(w http.ResponseWriter, v any) {
|
||||||
|
WriteJson(w, 200, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func WriteJson(w http.ResponseWriter, code int, v any) {
|
||||||
|
w.WriteHeader(code)
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
json.NewEncoder(w).Encode(v)
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Dir(path string) string {
|
||||||
|
dir := filepath.Dir(path)
|
||||||
|
if dir == "." {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return dir
|
||||||
|
}
|
||||||
|
|
||||||
|
func Filepath(path string, originName string, customName string) string {
|
||||||
|
if customName == "" {
|
||||||
|
customName = originName
|
||||||
|
}
|
||||||
|
return filepath.Join(path, customName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SafeRemove remove file safely, ignoring errors if the path does not exist.
|
||||||
|
func SafeRemove(name string) error {
|
||||||
|
if err := os.Remove(name); err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SafeRemoveAll remove file and parent directories safely
|
||||||
|
func SafeRemoveAll(path string, names []string) error {
|
||||||
|
for _, name := range names {
|
||||||
|
err := SafeRemove(filepath.Join(path, name))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := safeRemoveParent(path, name); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func safeRemoveParent(path string, subPath string) error {
|
||||||
|
currPath := filepath.Dir(subPath)
|
||||||
|
if currPath == "." {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// if directory is empty, remove it
|
||||||
|
dir := filepath.Join(path, filepath.Dir(subPath))
|
||||||
|
empty, err := isEmpty(dir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if empty {
|
||||||
|
err = SafeRemove(dir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return safeRemoveParent(path, currPath)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// check directory is empty
|
||||||
|
func isEmpty(name string) (bool, error) {
|
||||||
|
f, err := os.Open(name)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
_, err = f.Readdirnames(1) // Or f.Readdir(1)
|
||||||
|
if err == io.EOF {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
return false, err // Either not empty or error, suits both cases
|
||||||
|
}
|
||||||
@@ -0,0 +1,198 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDir(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
path string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty path",
|
||||||
|
args: args{
|
||||||
|
path: ".",
|
||||||
|
},
|
||||||
|
want: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "normal path case 1",
|
||||||
|
args: args{
|
||||||
|
path: "./a/b/c/1.txt",
|
||||||
|
},
|
||||||
|
want: "a/b/c",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "normal path case 2",
|
||||||
|
args: args{
|
||||||
|
path: "a/b/c/1.txt",
|
||||||
|
},
|
||||||
|
want: "a/b/c",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := Dir(tt.args.path); got != tt.want {
|
||||||
|
t.Errorf("Dir() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFilepath(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
path string
|
||||||
|
originName string
|
||||||
|
customName string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "origin name",
|
||||||
|
args: args{
|
||||||
|
path: "/Downloads",
|
||||||
|
originName: "1.txt",
|
||||||
|
customName: "",
|
||||||
|
},
|
||||||
|
want: "/Downloads/1.txt",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "origin name",
|
||||||
|
args: args{
|
||||||
|
path: "/Downloads",
|
||||||
|
originName: "1.txt",
|
||||||
|
customName: "2.txt",
|
||||||
|
},
|
||||||
|
want: "/Downloads/2.txt",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := Filepath(tt.args.path, tt.args.originName, tt.args.customName); got != tt.want {
|
||||||
|
t.Errorf("Filepath() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSafeRemove(t *testing.T) {
|
||||||
|
name := "test_safe_remove.data"
|
||||||
|
_, err := os.Create(name)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := SafeRemove(name); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if _, err := os.Stat(name); err == nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := SafeRemove("test_safe_remove_not_exist.data"); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSafeRemoveAll(t *testing.T) {
|
||||||
|
testDir := "test_dir"
|
||||||
|
err := os.Mkdir(testDir, 0755)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(testDir)
|
||||||
|
|
||||||
|
doSafeRemoveAll(t, testDir, []string{
|
||||||
|
"1.txt",
|
||||||
|
}, []string{})
|
||||||
|
|
||||||
|
doSafeRemoveAll(t, testDir, []string{
|
||||||
|
"1.txt",
|
||||||
|
}, []string{
|
||||||
|
"2.txt",
|
||||||
|
}, "2.txt")
|
||||||
|
|
||||||
|
doSafeRemoveAll(t, testDir, []string{
|
||||||
|
"1.txt",
|
||||||
|
"a/b/c/1.txt",
|
||||||
|
"a/b/1.txt",
|
||||||
|
"a/1.txt",
|
||||||
|
}, []string{})
|
||||||
|
|
||||||
|
doSafeRemoveAll(t, testDir, []string{
|
||||||
|
"1.txt",
|
||||||
|
"a/b/c/1.txt",
|
||||||
|
"a/b/1.txt",
|
||||||
|
"a/1.txt",
|
||||||
|
}, []string{
|
||||||
|
"a/b/2.txt",
|
||||||
|
}, "a/b/2.txt")
|
||||||
|
|
||||||
|
doSafeRemoveAll(t, testDir, []string{
|
||||||
|
"1.txt",
|
||||||
|
"a/b/c/1.txt",
|
||||||
|
"a/b/1.txt",
|
||||||
|
"a/1.txt",
|
||||||
|
}, []string{
|
||||||
|
"a/2.txt",
|
||||||
|
}, "a/2.txt")
|
||||||
|
}
|
||||||
|
|
||||||
|
func doSafeRemoveAll(t *testing.T, path string, downloadNames []string, otherNames []string, exist ...string) {
|
||||||
|
preCreate(t, path, downloadNames)
|
||||||
|
preCreate(t, path, otherNames)
|
||||||
|
|
||||||
|
if err := SafeRemoveAll(path, downloadNames); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(exist) == 0 {
|
||||||
|
for _, name := range downloadNames {
|
||||||
|
filePath := filepath.Join(path, name)
|
||||||
|
if isExist(filePath) {
|
||||||
|
t.Fatalf("file %s should not exist", filePath)
|
||||||
|
}
|
||||||
|
subDirPath := filepath.Dir(name)
|
||||||
|
if subDirPath != "." {
|
||||||
|
dirPath := filepath.Join(path, subDirPath)
|
||||||
|
if isExist(dirPath) {
|
||||||
|
t.Fatalf("dir %s should not exist", dirPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, name := range exist {
|
||||||
|
if !isExist(filepath.Join(path, name)) {
|
||||||
|
t.Fatalf("file %s should exist", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func preCreate(t *testing.T, path string, names []string) {
|
||||||
|
for _, name := range names {
|
||||||
|
fullPath := filepath.Join(path, name)
|
||||||
|
if err := os.MkdirAll(filepath.Dir(fullPath), 0755); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
_, err := os.Create(fullPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func isExist(path string) bool {
|
||||||
|
_, err := os.Stat(path)
|
||||||
|
return err == nil || os.IsExist(err)
|
||||||
|
}
|
||||||
@@ -16,10 +16,6 @@ func (t *Timer) Pause() {
|
|||||||
t.used += time.Now().UnixNano() - t.t
|
t.used += time.Now().UnixNano() - t.t
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Timer) Continue() {
|
|
||||||
t.t = time.Now().UnixNano()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Timer) Used() int64 {
|
func (t *Timer) Used() int64 {
|
||||||
return (time.Now().UnixNano() - t.t) + t.used
|
return (time.Now().UnixNano() - t.t) + t.used
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
const FileSchema = "FILE"
|
||||||
|
|
||||||
|
func ParseSchema(url string) string {
|
||||||
|
index := strings.Index(url, ":")
|
||||||
|
if index == -1 {
|
||||||
|
return FileSchema
|
||||||
|
}
|
||||||
|
return strings.ToUpper(url[:index])
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
# Miscellaneous
|
||||||
|
*.class
|
||||||
|
*.log
|
||||||
|
*.pyc
|
||||||
|
*.swp
|
||||||
|
.DS_Store
|
||||||
|
.atom/
|
||||||
|
.buildlog/
|
||||||
|
.history
|
||||||
|
.svn/
|
||||||
|
|
||||||
|
# IntelliJ related
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
*.iws
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# The .vscode folder contains launch configuration and tasks you configure in
|
||||||
|
# VS Code which you may wish to be included in version control, so this line
|
||||||
|
# is commented out by default.
|
||||||
|
#.vscode/
|
||||||
|
|
||||||
|
# Flutter/Dart/Pub related
|
||||||
|
**/doc/api/
|
||||||
|
**/ios/Flutter/.last_build_id
|
||||||
|
.dart_tool/
|
||||||
|
.flutter-plugins
|
||||||
|
.flutter-plugins-dependencies
|
||||||
|
.packages
|
||||||
|
.pub-cache/
|
||||||
|
.pub/
|
||||||
|
/build/
|
||||||
|
|
||||||
|
# Web related
|
||||||
|
|
||||||
|
# Symbolication related
|
||||||
|
app.*.symbols
|
||||||
|
|
||||||
|
# Obfuscation related
|
||||||
|
app.*.map.json
|
||||||
|
|
||||||
|
# Exceptions to above rules.
|
||||||
|
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
|
||||||
|
|
||||||
|
# Libgopeed
|
||||||
|
windows/libgopeed.h
|
||||||
|
windows/libgopeed.dll
|
||||||
|
macos/Frameworks/libgopeed.h
|
||||||
|
macos/Frameworks/libgopeed.dylib
|
||||||
|
linux/bundle/lib/libgopeed.h
|
||||||
|
linux/bundle/lib/libgopeed.dylib
|
||||||
|
android/app/libs/libgopeed.aar
|
||||||
|
android/app/libs/libgopeed-sources.jar
|
||||||
|
|
||||||
|
debian/packages
|
||||||
|
|
||||||
|
linux/flutter/generated_plugin_registrant.cc
|
||||||
|
linux/flutter/generated_plugin_registrant.h
|
||||||
|
linux/flutter/generated_plugins.cmake
|
||||||
|
macos/Flutter/GeneratedPluginRegistrant.swift
|
||||||
|
windows/flutter/generated_plugin_registrant.cc
|
||||||
|
windows/flutter/generated_plugin_registrant.h
|
||||||
|
windows/flutter/generated_plugins.cmake
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
# This file tracks properties of this Flutter project.
|
||||||
|
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||||
|
#
|
||||||
|
# This file should be version controlled.
|
||||||
|
|
||||||
|
version:
|
||||||
|
revision: f1875d570e39de09040c8f79aa13cc56baab8db1
|
||||||
|
channel: stable
|
||||||
|
|
||||||
|
project_type: app
|
||||||
|
|
||||||
|
# Tracks metadata for the flutter migrate command
|
||||||
|
migration:
|
||||||
|
platforms:
|
||||||
|
- platform: root
|
||||||
|
create_revision: f1875d570e39de09040c8f79aa13cc56baab8db1
|
||||||
|
base_revision: f1875d570e39de09040c8f79aa13cc56baab8db1
|
||||||
|
- platform: android
|
||||||
|
create_revision: f1875d570e39de09040c8f79aa13cc56baab8db1
|
||||||
|
base_revision: f1875d570e39de09040c8f79aa13cc56baab8db1
|
||||||
|
- platform: ios
|
||||||
|
create_revision: f1875d570e39de09040c8f79aa13cc56baab8db1
|
||||||
|
base_revision: f1875d570e39de09040c8f79aa13cc56baab8db1
|
||||||
|
- platform: linux
|
||||||
|
create_revision: f1875d570e39de09040c8f79aa13cc56baab8db1
|
||||||
|
base_revision: f1875d570e39de09040c8f79aa13cc56baab8db1
|
||||||
|
- platform: macos
|
||||||
|
create_revision: f1875d570e39de09040c8f79aa13cc56baab8db1
|
||||||
|
base_revision: f1875d570e39de09040c8f79aa13cc56baab8db1
|
||||||
|
- platform: web
|
||||||
|
create_revision: f1875d570e39de09040c8f79aa13cc56baab8db1
|
||||||
|
base_revision: f1875d570e39de09040c8f79aa13cc56baab8db1
|
||||||
|
- platform: windows
|
||||||
|
create_revision: f1875d570e39de09040c8f79aa13cc56baab8db1
|
||||||
|
base_revision: f1875d570e39de09040c8f79aa13cc56baab8db1
|
||||||
|
|
||||||
|
# User provided section
|
||||||
|
|
||||||
|
# List of Local paths (relative to this file) that should be
|
||||||
|
# ignored by the migrate tool.
|
||||||
|
#
|
||||||
|
# Files that are not part of the templates will be ignored by default.
|
||||||
|
unmanaged_files:
|
||||||
|
- 'lib/main.dart'
|
||||||
|
- 'ios/Runner.xcodeproj/project.pbxproj'
|
||||||
Loaded 50 of 219 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user