mirror of
https://github.com/GopeedLab/gopeed.git
synced 2026-11-14 12:23:43 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4736a91904 | ||
|
|
0a4d74a513 | ||
|
|
a06549a11a | ||
|
|
63296f6950 | ||
|
|
4b4c03fd90 | ||
|
|
91dfd78b77 | ||
|
|
467ada5500 |
No files matched your search
+26
-161
@@ -1,177 +1,42 @@
|
|||||||
name-template: "v$NEXT_PATCH_VERSION 🌈"
|
name-template: 'v$NEXT_PATCH_VERSION 🌈'
|
||||||
tag-template: "v$NEXT_PATCH_VERSION"
|
tag-template: 'v$NEXT_PATCH_VERSION'
|
||||||
categories:
|
categories:
|
||||||
- title: "🆕 Features"
|
- title: '🆕 Features'
|
||||||
labels:
|
labels:
|
||||||
- "feat"
|
- 'feat'
|
||||||
- "feature"
|
- 'feature'
|
||||||
- "enhancement"
|
- 'enhancement'
|
||||||
- title: "🐛 Bug Fixes"
|
- title: '🐛 Bug Fixes'
|
||||||
labels:
|
labels:
|
||||||
- "fix"
|
- 'fix'
|
||||||
- "bugfix"
|
- 'bugfix'
|
||||||
- "bug"
|
- 'bug'
|
||||||
- title: "🔧 Performance Improvements"
|
- title: '🔧 Performance Improvements'
|
||||||
labels:
|
labels:
|
||||||
- "perf"
|
- 'perf'
|
||||||
- title: "🧪 Tests"
|
- title: '🧪 Tests'
|
||||||
label: "test"
|
label: 'test'
|
||||||
- title: "🧰 Maintenance"
|
- title: '🧰 Maintenance'
|
||||||
label: "chore"
|
label: 'chore'
|
||||||
- title: "📖 Document"
|
- title: '📖 Document'
|
||||||
label: "docs"
|
label: 'docs'
|
||||||
- title: "🚀 CI/CD"
|
- title: '🚀 CI/CD'
|
||||||
label: "ci"
|
label: 'ci'
|
||||||
- title: "🌎 Internationalization"
|
- title: '🌎 Internationalization'
|
||||||
label: "translation"
|
label: 'translation'
|
||||||
change-template: "- $TITLE @$AUTHOR (#$NUMBER)"
|
change-template: '- $TITLE @$AUTHOR (#$NUMBER)'
|
||||||
version-resolver:
|
version-resolver:
|
||||||
major:
|
major:
|
||||||
labels:
|
labels:
|
||||||
- "major"
|
- 'major'
|
||||||
minor:
|
minor:
|
||||||
labels:
|
labels:
|
||||||
- "minor"
|
- 'minor'
|
||||||
patch:
|
patch:
|
||||||
labels:
|
labels:
|
||||||
- "patch"
|
- 'patch'
|
||||||
default: patch
|
default: patch
|
||||||
template: |
|
template: |
|
||||||
<table>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td rowspan="2">🪟 Windows</td>
|
|
||||||
<td><code>EXE</code></td>
|
|
||||||
<td>amd64</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-v$NEXT_PATCH_VERSION-windows-amd64.zip">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><code>Portable</code></td>
|
|
||||||
<td>amd64</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-v$NEXT_PATCH_VERSION-windows-amd64-portable.zip">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td rowspan="3">🍎 MacOS</td>
|
|
||||||
<td rowspan="3"><code>DMG</code></td>
|
|
||||||
<td>universal</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-v$NEXT_PATCH_VERSION-macos.dmg">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>amd64</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-v$NEXT_PATCH_VERSION-macos-amd64.dmg">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>arm64</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-v$NEXT_PATCH_VERSION-macos-arm64.dmg">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td rowspan="6">🐧 Linux</td>
|
|
||||||
<td><code>Flathub</code></td>
|
|
||||||
<td>amd64</td>
|
|
||||||
<td><a href="https://flathub.org/apps/com.gopeed.Gopeed">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><code>SNAP</code></td>
|
|
||||||
<td>amd64</td>
|
|
||||||
<td><a href="https://snapcraft.io/gopeed">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td rowspan="2"><code>DEB</code></td>
|
|
||||||
<td>amd64</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-v$NEXT_PATCH_VERSION-linux-amd64.deb">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>arm64</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-v$NEXT_PATCH_VERSION-linux-arm64.deb">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td rowspan="2"><code>AppImage</code></td>
|
|
||||||
<td>amd64</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-v$NEXT_PATCH_VERSION-linux-amd64.AppImage">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>arm64</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-v$NEXT_PATCH_VERSION-linux-arm64.AppImage">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td rowspan="4">🤖 Android</td>
|
|
||||||
<td rowspan="4"><code>APK</code></td>
|
|
||||||
<td>universal</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-v$NEXT_PATCH_VERSION-android.apk">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>armeabi-v7a</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-v$NEXT_PATCH_VERSION-android-armeabi-v7a.apk">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>arm64-v8a</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-v$NEXT_PATCH_VERSION-android-arm64-v8a.apk">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>x86_64</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-v$NEXT_PATCH_VERSION-android-x86_64.apk">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>📱 iOS</td>
|
|
||||||
<td><code>IPA</code></td>
|
|
||||||
<td>universal</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-v$NEXT_PATCH_VERSION-ios.ipa">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>🐳 Docker</td>
|
|
||||||
<td>-</td>
|
|
||||||
<td>universal</td>
|
|
||||||
<td><a href="https://hub.docker.com/r/liwei2633/gopeed">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td rowspan="2">💾 Qnap</td>
|
|
||||||
<td rowspan="2"><code>QPKG</code></td>
|
|
||||||
<td>amd64</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=gopeed-v$NEXT_PATCH_VERSION-qnap-amd64.qpkg">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>arm64</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=gopeed-v$NEXT_PATCH_VERSION-qnap-arm64.qpkg">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td rowspan="8">🌐 Web</td>
|
|
||||||
<td rowspan="3"><code>Windows</code></td>
|
|
||||||
<td>amd64</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-v$NEXT_PATCH_VERSION-windows-amd64.zip">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>arm64</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-v$NEXT_PATCH_VERSION-windows-arm64.zip">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>386</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-v$NEXT_PATCH_VERSION-windows-386.zip">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td rowspan="2"><code>MacOS</code></td>
|
|
||||||
<td>amd64</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-v$NEXT_PATCH_VERSION-macos-amd64.zip">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>arm64</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-v$NEXT_PATCH_VERSION-macos-arm64.zip">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td rowspan="3"><code>Linux</code></td>
|
|
||||||
<td>amd64</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-v$NEXT_PATCH_VERSION-linux-amd64.zip">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>arm64</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-v$NEXT_PATCH_VERSION-linux-arm64.zip">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>386</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-v$NEXT_PATCH_VERSION-linux-386.zip">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
# Release notes
|
# Release notes
|
||||||
|
|
||||||
$CHANGES
|
$CHANGES
|
||||||
|
|||||||
+59
-192
@@ -4,17 +4,17 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
platform:
|
platform:
|
||||||
description: "Build platform"
|
description: 'Build platform'
|
||||||
required: true
|
required: true
|
||||||
default: "all"
|
default: 'all'
|
||||||
test:
|
test:
|
||||||
description: "Test mode"
|
description: 'Test mode'
|
||||||
required: true
|
required: true
|
||||||
default: "false"
|
default: "false"
|
||||||
|
|
||||||
env:
|
env:
|
||||||
GO_VERSION: "1.24"
|
GO_VERSION: "1.23"
|
||||||
FLUTTER_VERSION: "3.24.5"
|
FLUTTER_VERSION: "3.16.8"
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
@@ -34,7 +34,7 @@ jobs:
|
|||||||
|
|
||||||
build-windows:
|
build-windows:
|
||||||
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'windows' }}
|
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'windows' }}
|
||||||
runs-on: windows-2022
|
runs-on: windows-latest
|
||||||
needs: [ get-release ]
|
needs: [ get-release ]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
@@ -52,11 +52,9 @@ jobs:
|
|||||||
mv ChineseSimplified.isl "C:\Program Files (x86)\Inno Setup 6\Languages\"
|
mv ChineseSimplified.isl "C:\Program Files (x86)\Inno Setup 6\Languages\"
|
||||||
|
|
||||||
go build -tags nosqlite -ldflags="-w -s -X github.com/GopeedLab/gopeed/pkg/base.Version=$env:VERSION" -buildmode=c-shared -o ui/flutter/windows/libgopeed.dll github.com/GopeedLab/gopeed/bind/desktop
|
go build -tags nosqlite -ldflags="-w -s -X github.com/GopeedLab/gopeed/pkg/base.Version=$env:VERSION" -buildmode=c-shared -o ui/flutter/windows/libgopeed.dll github.com/GopeedLab/gopeed/bind/desktop
|
||||||
go build -ldflags="-w -s" -o ui/flutter/assets/exec/host.exe github.com/GopeedLab/gopeed/cmd/host
|
|
||||||
go build -ldflags="-w -s" -o ui/flutter/assets/exec/updater.exe github.com/GopeedLab/gopeed/cmd/updater
|
|
||||||
cd ui/flutter
|
cd ui/flutter
|
||||||
$TAG = "v$env:VERSION"
|
$TAG = "v$env:VERSION"
|
||||||
flutter build windows --dart-define="UPDATE_CHANNEL=windowsPortable"
|
flutter build windows
|
||||||
$mingw = "C:\Program Files\Git\mingw64\bin"
|
$mingw = "C:\Program Files\Git\mingw64\bin"
|
||||||
$system = "C:\Windows\System32"
|
$system = "C:\Windows\System32"
|
||||||
$release = "build\windows\x64\runner\Release\"
|
$release = "build\windows\x64\runner\Release\"
|
||||||
@@ -70,7 +68,6 @@ jobs:
|
|||||||
New-Item -Path build\windows\Output -ItemType Directory
|
New-Item -Path build\windows\Output -ItemType Directory
|
||||||
Compress-Archive -Path "$release*" -DestinationPath "build\windows\Output\Gopeed-$TAG-windows-amd64-portable.zip"
|
Compress-Archive -Path "$release*" -DestinationPath "build\windows\Output\Gopeed-$TAG-windows-amd64-portable.zip"
|
||||||
|
|
||||||
flutter build windows --dart-define="UPDATE_CHANNEL=windowsInstaller"
|
|
||||||
cd build/windows
|
cd build/windows
|
||||||
echo @"
|
echo @"
|
||||||
; Script generated by the Inno Setup Script Wizard.
|
; Script generated by the Inno Setup Script Wizard.
|
||||||
@@ -100,13 +97,11 @@ jobs:
|
|||||||
PrivilegesRequired=lowest
|
PrivilegesRequired=lowest
|
||||||
OutputBaseFilename=gopeed
|
OutputBaseFilename=gopeed
|
||||||
SetupIconFile=..\..\assets\icon\icon.ico
|
SetupIconFile=..\..\assets\icon\icon.ico
|
||||||
UninstallDisplayIcon={app}\{#MyAppExeName}
|
|
||||||
Compression=lzma
|
Compression=lzma
|
||||||
SolidCompression=yes
|
SolidCompression=yes
|
||||||
WizardStyle=modern
|
WizardStyle=modern
|
||||||
LanguageDetectionMethod=uilanguage
|
LanguageDetectionMethod=uilanguage
|
||||||
ShowLanguageDialog=yes
|
ShowLanguageDialog=yes
|
||||||
CloseApplications=force
|
|
||||||
|
|
||||||
[Languages]
|
[Languages]
|
||||||
Name: "english"; MessagesFile: "compiler:Default.isl"
|
Name: "english"; MessagesFile: "compiler:Default.isl"
|
||||||
@@ -116,7 +111,7 @@ jobs:
|
|||||||
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
|
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
|
||||||
|
|
||||||
[Files]
|
[Files]
|
||||||
Source: ".\x64\runner\Release\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs;
|
Source: ".\x64\runner\Release\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs
|
||||||
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
|
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
|
||||||
|
|
||||||
[UninstallDelete]
|
[UninstallDelete]
|
||||||
@@ -139,9 +134,9 @@ jobs:
|
|||||||
upload_url: ${{ needs.get-release.outputs.upload_url }}
|
upload_url: ${{ needs.get-release.outputs.upload_url }}
|
||||||
asset_path: ui/flutter/build/windows/Output/*
|
asset_path: ui/flutter/build/windows/Output/*
|
||||||
overwrite: true
|
overwrite: true
|
||||||
build-macos-amd64-lib:
|
build-macos-arm64-lib:
|
||||||
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'macos' }}
|
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'macos' }}
|
||||||
runs-on: macos-13
|
runs-on: macos-latest
|
||||||
needs: [ get-release ]
|
needs: [ get-release ]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
@@ -153,22 +148,14 @@ jobs:
|
|||||||
VERSION: ${{ needs.get-release.outputs.version }}
|
VERSION: ${{ needs.get-release.outputs.version }}
|
||||||
run: |
|
run: |
|
||||||
go build -tags nosqlite -ldflags="-w -s -X github.com/GopeedLab/gopeed/pkg/base.Version=$VERSION" -buildmode=c-shared -o libgopeed.dylib github.com/GopeedLab/gopeed/bind/desktop
|
go build -tags nosqlite -ldflags="-w -s -X github.com/GopeedLab/gopeed/pkg/base.Version=$VERSION" -buildmode=c-shared -o libgopeed.dylib github.com/GopeedLab/gopeed/bind/desktop
|
||||||
go build -ldflags="-w -s" github.com/GopeedLab/gopeed/cmd/host
|
- uses: actions/upload-artifact@v3
|
||||||
go build -ldflags="-w -s" github.com/GopeedLab/gopeed/cmd/updater
|
|
||||||
- uses: actions/upload-artifact@v4
|
|
||||||
with:
|
with:
|
||||||
name: macos-amd64-lib
|
name: macos-arm64-lib
|
||||||
path: |
|
path: libgopeed.dylib
|
||||||
libgopeed.dylib
|
|
||||||
host
|
|
||||||
updater
|
|
||||||
build-macos:
|
build-macos:
|
||||||
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'macos' }}
|
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'macos' }}
|
||||||
runs-on: macos-latest
|
runs-on: macos-13
|
||||||
strategy:
|
needs: [ get-release, build-macos-arm64-lib ]
|
||||||
matrix:
|
|
||||||
channel: [ arm64, amd64, universal ]
|
|
||||||
needs: [get-release, build-macos-amd64-lib]
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-go@v4
|
- uses: actions/setup-go@v4
|
||||||
@@ -184,49 +171,27 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
python3 -m pip install setuptools --break-system-packages
|
python3 -m pip install setuptools --break-system-packages
|
||||||
npm install -g appdmg
|
npm install -g appdmg
|
||||||
- uses: actions/download-artifact@v4
|
- uses: actions/download-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: macos-amd64-lib
|
name: macos-arm64-lib
|
||||||
path: ui/flutter/lib-amd64
|
path: ui/flutter/lib-arm64
|
||||||
- if: ${{ matrix.channel == 'arm64' }}
|
|
||||||
name: Build Arm64 Libraries
|
|
||||||
run: |
|
|
||||||
go build -tags nosqlite -ldflags="-w -s -X github.com/GopeedLab/gopeed/pkg/base.Version=$VERSION" -buildmode=c-shared -o ui/flutter/macos/Frameworks/libgopeed.dylib github.com/GopeedLab/gopeed/bind/desktop
|
|
||||||
go build -ldflags="-w -s" -o ui/flutter/assets/exec/host github.com/GopeedLab/gopeed/cmd/host
|
|
||||||
go build -ldflags="-w -s" -o ui/flutter/assets/exec/updater github.com/GopeedLab/gopeed/cmd/updater
|
|
||||||
- if: ${{ matrix.channel == 'amd64' }}
|
|
||||||
name: Build Amd64 Libraries
|
|
||||||
run: |
|
|
||||||
mkdir -p ui/flutter/macos/Frameworks
|
|
||||||
|
|
||||||
cp ui/flutter/lib-amd64/libgopeed.dylib ui/flutter/macos/Frameworks/libgopeed.dylib
|
|
||||||
cp ui/flutter/lib-amd64/host ui/flutter/assets/exec/host
|
|
||||||
cp ui/flutter/lib-amd64/updater ui/flutter/assets/exec/updater
|
|
||||||
- if: ${{ matrix.channel == 'universal' }}
|
|
||||||
name: Build Universal Libraries
|
|
||||||
run: |
|
|
||||||
mkdir -p ui/flutter/macos/Frameworks
|
|
||||||
|
|
||||||
# arm64 lib
|
|
||||||
go build -tags nosqlite -ldflags="-w -s -X github.com/GopeedLab/gopeed/pkg/base.Version=$VERSION" -buildmode=c-shared -o ui/flutter/.temp/libgopeed.dylib.arm64 github.com/GopeedLab/gopeed/bind/desktop
|
|
||||||
go build -ldflags="-w -s" -o ui/flutter/.temp/host.arm64 github.com/GopeedLab/gopeed/cmd/host
|
|
||||||
go build -ldflags="-w -s" -o ui/flutter/.temp/updater.arm64 github.com/GopeedLab/gopeed/cmd/updater
|
|
||||||
|
|
||||||
# amd64 lib
|
|
||||||
cp ui/flutter/lib-amd64/libgopeed.dylib ui/flutter/.temp/libgopeed.dylib.amd64
|
|
||||||
cp ui/flutter/lib-amd64/host ui/flutter/.temp/host.amd64
|
|
||||||
cp ui/flutter/lib-amd64/updater ui/flutter/.temp/updater.amd64
|
|
||||||
|
|
||||||
# universal lib
|
|
||||||
lipo -create -output ui/flutter/macos/Frameworks/libgopeed.dylib ui/flutter/.temp/libgopeed.dylib.arm64 ui/flutter/.temp/libgopeed.dylib.amd64
|
|
||||||
lipo -create -output ui/flutter/assets/exec/host ui/flutter/.temp/host.arm64 ui/flutter/.temp/host.amd64
|
|
||||||
lipo -create -output ui/flutter/assets/exec/updater ui/flutter/.temp/updater.arm64 ui/flutter/.temp/updater.amd64
|
|
||||||
- name: Build
|
- name: Build
|
||||||
env:
|
env:
|
||||||
VERSION: ${{ needs.get-release.outputs.version }}
|
VERSION: ${{ needs.get-release.outputs.version }}
|
||||||
run: |
|
run: |
|
||||||
cd ui/flutter
|
PROJECT_DIR=$(pwd)
|
||||||
flutter build macos --dart-define="UPDATE_CHANNEL=macosDmg"
|
|
||||||
|
# amd64 lib
|
||||||
|
go build -tags nosqlite -ldflags="-w -s -X github.com/GopeedLab/gopeed/pkg/base.Version=$VERSION" -buildmode=c-shared -o ui/flutter/lib-amd64/libgopeed.dylib github.com/GopeedLab/gopeed/bind/desktop
|
||||||
|
# universal binary
|
||||||
|
mkdir -p ui/flutter/macos/Frameworks
|
||||||
|
cp ui/flutter/lib-amd64/libgopeed.dylib ui/flutter/macos/Frameworks/amd64-lib
|
||||||
|
cp ui/flutter/lib-arm64/libgopeed.dylib ui/flutter/macos/Frameworks/arm64-lib
|
||||||
|
cd ui/flutter/macos/Frameworks
|
||||||
|
lipo -create -output libgopeed.dylib amd64-lib arm64-lib
|
||||||
|
|
||||||
|
cd $PROJECT_DIR/ui/flutter
|
||||||
|
flutter build macos
|
||||||
cd build/macos/Build/Products/Release
|
cd build/macos/Build/Products/Release
|
||||||
cat>appdmg.json<<EOF
|
cat>appdmg.json<<EOF
|
||||||
{
|
{
|
||||||
@@ -240,13 +205,6 @@ jobs:
|
|||||||
EOF
|
EOF
|
||||||
mkdir dist
|
mkdir dist
|
||||||
appdmg appdmg.json dist/Gopeed-v$VERSION-macos.dmg
|
appdmg appdmg.json dist/Gopeed-v$VERSION-macos.dmg
|
||||||
|
|
||||||
if [[ "${{ matrix.channel }}" == "arm64" ]]; then
|
|
||||||
mv dist/Gopeed-v$VERSION-macos.dmg dist/Gopeed-v$VERSION-macos-arm64.dmg
|
|
||||||
fi
|
|
||||||
if [[ "${{ matrix.channel }}" == "amd64" ]]; then
|
|
||||||
mv dist/Gopeed-v$VERSION-macos.dmg dist/Gopeed-v$VERSION-macos-amd64.dmg
|
|
||||||
fi
|
|
||||||
- name: Upload
|
- name: Upload
|
||||||
uses: shogo82148/actions-upload-release-asset@v1
|
uses: shogo82148/actions-upload-release-asset@v1
|
||||||
with:
|
with:
|
||||||
@@ -255,10 +213,7 @@ jobs:
|
|||||||
overwrite: true
|
overwrite: true
|
||||||
build-linux:
|
build-linux:
|
||||||
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'linux' }}
|
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'linux' }}
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
os: [ubuntu-22.04, ubuntu-22.04-arm]
|
|
||||||
needs: [ get-release ]
|
needs: [ get-release ]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
@@ -268,15 +223,13 @@ jobs:
|
|||||||
- uses: subosito/flutter-action@v2
|
- uses: subosito/flutter-action@v2
|
||||||
with:
|
with:
|
||||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||||
channel: master
|
|
||||||
- run: |
|
- run: |
|
||||||
sudo apt update -y
|
sudo apt update -y
|
||||||
# flutter build dependencies
|
# flutter build dependencies
|
||||||
sudo apt install -y ninja-build libgtk-3-dev libayatana-appindicator3-1 libayatana-appindicator3-dev
|
sudo apt install -y ninja-build libgtk-3-dev libayatana-appindicator3-1 libayatana-appindicator3-dev
|
||||||
# appimage build dependencies
|
# appimage build dependencies
|
||||||
sudo apt install -y libfuse2 locate
|
sudo apt install -y libfuse2 locate
|
||||||
arch=$(uname -m)
|
wget -O appimagetool "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage"
|
||||||
wget -O appimagetool "https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-${arch}.AppImage"
|
|
||||||
chmod +x appimagetool
|
chmod +x appimagetool
|
||||||
sudo mv appimagetool /usr/local/bin/
|
sudo mv appimagetool /usr/local/bin/
|
||||||
- name: Build
|
- name: Build
|
||||||
@@ -284,42 +237,12 @@ jobs:
|
|||||||
VERSION: ${{ needs.get-release.outputs.version }}
|
VERSION: ${{ needs.get-release.outputs.version }}
|
||||||
run: |
|
run: |
|
||||||
go build -tags nosqlite -ldflags="-w -s -X github.com/GopeedLab/gopeed/pkg/base.Version=$VERSION" -buildmode=c-shared -o ui/flutter/linux/bundle/lib/libgopeed.so github.com/GopeedLab/gopeed/bind/desktop
|
go build -tags nosqlite -ldflags="-w -s -X github.com/GopeedLab/gopeed/pkg/base.Version=$VERSION" -buildmode=c-shared -o ui/flutter/linux/bundle/lib/libgopeed.so github.com/GopeedLab/gopeed/bind/desktop
|
||||||
go build -ldflags="-w -s" -o ui/flutter/assets/exec/host github.com/GopeedLab/gopeed/cmd/host
|
|
||||||
go build -ldflags="-w -s" -o ui/flutter/assets/exec/updater github.com/GopeedLab/gopeed/cmd/updater
|
|
||||||
|
|
||||||
cd ui/flutter
|
cd ui/flutter
|
||||||
cat << EOF > distribute_options.yaml
|
|
||||||
output: dist/
|
|
||||||
releases:
|
|
||||||
- name: linux
|
|
||||||
jobs:
|
|
||||||
- name: appimage
|
|
||||||
package:
|
|
||||||
platform: linux
|
|
||||||
target: appimage
|
|
||||||
build_args:
|
|
||||||
dart-define:
|
|
||||||
UPDATE_CHANNEL: linuxAppImage
|
|
||||||
- name: deb
|
|
||||||
package:
|
|
||||||
platform: linux
|
|
||||||
target: deb
|
|
||||||
build_args:
|
|
||||||
dart-define:
|
|
||||||
UPDATE_CHANNEL: linuxDeb
|
|
||||||
EOF
|
|
||||||
dart pub global activate -sgit https://github.com/GopeedLab/flutter_distributor.git --git-path packages/flutter_distributor
|
dart pub global activate -sgit https://github.com/GopeedLab/flutter_distributor.git --git-path packages/flutter_distributor
|
||||||
flutter_distributor release --name linux
|
flutter_distributor package --platform linux --targets appimage,deb
|
||||||
cd dist/*
|
cd dist/*
|
||||||
|
mv gopeed-*-linux.AppImage Gopeed-v${VERSION}-linux-x86_64.AppImage
|
||||||
# Get current running architecture, x86_64 -> amd64 and aarch64 -> arm64
|
mv gopeed-*-linux.deb Gopeed-v${VERSION}-linux-amd64.deb
|
||||||
ARCH="amd64"
|
|
||||||
if [[ "${{ matrix.os }}" == *-arm ]]; then
|
|
||||||
ARCH="arm64"
|
|
||||||
fi
|
|
||||||
|
|
||||||
mv gopeed-*-linux.AppImage Gopeed-v${VERSION}-linux-${ARCH}.AppImage
|
|
||||||
mv gopeed-*-linux.deb Gopeed-v${VERSION}-linux-${ARCH}.deb
|
|
||||||
- name: Upload
|
- name: Upload
|
||||||
uses: shogo82148/actions-upload-release-asset@v1
|
uses: shogo82148/actions-upload-release-asset@v1
|
||||||
with:
|
with:
|
||||||
@@ -344,8 +267,6 @@ jobs:
|
|||||||
VERSION: ${{ needs.get-release.outputs.version }}
|
VERSION: ${{ needs.get-release.outputs.version }}
|
||||||
run: |
|
run: |
|
||||||
go build -tags nosqlite -ldflags="-w -s -X github.com/GopeedLab/gopeed/pkg/base.Version=$VERSION" -buildmode=c-shared -o ui/flutter/linux/bundle/lib/libgopeed.so github.com/GopeedLab/gopeed/bind/desktop
|
go build -tags nosqlite -ldflags="-w -s -X github.com/GopeedLab/gopeed/pkg/base.Version=$VERSION" -buildmode=c-shared -o ui/flutter/linux/bundle/lib/libgopeed.so github.com/GopeedLab/gopeed/bind/desktop
|
||||||
go build -ldflags="-w -s" -o ui/flutter/assets/exec/host github.com/GopeedLab/gopeed/cmd/host
|
|
||||||
go build -ldflags="-w -s" -o ui/flutter/assets/exec/updater github.com/GopeedLab/gopeed/cmd/updater
|
|
||||||
cd ui/flutter
|
cd ui/flutter
|
||||||
|
|
||||||
sudo snap install snapcraft --classic
|
sudo snap install snapcraft --classic
|
||||||
@@ -359,12 +280,11 @@ jobs:
|
|||||||
description: High speed downloader that supports all platforms.
|
description: High speed downloader that supports all platforms.
|
||||||
|
|
||||||
confinement: strict
|
confinement: strict
|
||||||
base: core24
|
base: core22
|
||||||
grade: stable
|
grade: stable
|
||||||
|
|
||||||
platforms:
|
architectures:
|
||||||
amd64:
|
- build-on: amd64
|
||||||
build-on: amd64
|
|
||||||
build-for: amd64
|
build-for: amd64
|
||||||
|
|
||||||
slots:
|
slots:
|
||||||
@@ -375,14 +295,13 @@ jobs:
|
|||||||
|
|
||||||
apps:
|
apps:
|
||||||
gopeed:
|
gopeed:
|
||||||
command-chain:
|
|
||||||
- bin/gpu-2404-wrapper
|
|
||||||
command: bin/gopeed
|
command: bin/gopeed
|
||||||
extensions: [gnome]
|
extensions: [gnome]
|
||||||
common-id: com.gopeed.Gopeed
|
|
||||||
plugs:
|
plugs:
|
||||||
- network
|
- network
|
||||||
- home
|
- home
|
||||||
|
slots:
|
||||||
|
- dbus-gopeed
|
||||||
parts:
|
parts:
|
||||||
flutter-git:
|
flutter-git:
|
||||||
source: https://github.com/flutter/flutter.git
|
source: https://github.com/flutter/flutter.git
|
||||||
@@ -422,32 +341,9 @@ jobs:
|
|||||||
set +e
|
set +e
|
||||||
dart pub get
|
dart pub get
|
||||||
set -eux
|
set -eux
|
||||||
flutter build linux --dart-define="UPDATE_CHANNEL=linuxSnap"
|
flutter build linux --release -v
|
||||||
mkdir -p \$CRAFT_PART_INSTALL/bin/
|
mkdir -p \$CRAFT_PART_INSTALL/bin/
|
||||||
cp -r build/linux/*/release/bundle/* \$CRAFT_PART_INSTALL/bin/
|
cp -r build/linux/*/release/bundle/* \$CRAFT_PART_INSTALL/bin/
|
||||||
gpu-2404:
|
|
||||||
after: [gopeed]
|
|
||||||
source: https://github.com/canonical/gpu-snap.git
|
|
||||||
plugin: dump
|
|
||||||
override-prime: |
|
|
||||||
craftctl default
|
|
||||||
\${CRAFT_PART_SRC}/bin/gpu-2404-cleanup mesa-2404
|
|
||||||
prime:
|
|
||||||
- bin/gpu-2404-wrapper
|
|
||||||
|
|
||||||
plugs:
|
|
||||||
gpu-2404:
|
|
||||||
interface: content
|
|
||||||
target: \$SNAP/gpu-2404
|
|
||||||
default-provider: mesa-2404
|
|
||||||
|
|
||||||
layout:
|
|
||||||
/usr/share/libdrm:
|
|
||||||
bind: \$SNAP/gpu-2404/libdrm
|
|
||||||
/usr/share/drirc.d:
|
|
||||||
symlink: \$SNAP/gpu-2404/drirc.d
|
|
||||||
/usr/share/X11/XErrorDB:
|
|
||||||
symlink: \$SNAP/gpu-2404/X11/XErrorDB
|
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
cp linux/assets/com.gopeed.Gopeed.desktop snap/gui/gopeed.desktop
|
cp linux/assets/com.gopeed.Gopeed.desktop snap/gui/gopeed.desktop
|
||||||
@@ -470,7 +366,7 @@ jobs:
|
|||||||
- uses: actions/setup-java@v3
|
- uses: actions/setup-java@v3
|
||||||
with:
|
with:
|
||||||
distribution: "zulu"
|
distribution: "zulu"
|
||||||
java-version: "17"
|
java-version: "11"
|
||||||
- uses: subosito/flutter-action@v2
|
- uses: subosito/flutter-action@v2
|
||||||
with:
|
with:
|
||||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||||
@@ -487,13 +383,9 @@ jobs:
|
|||||||
gomobile bind -tags nosqlite -ldflags="-w -s -checklinkname=0 -X github.com/GopeedLab/gopeed/pkg/base.Version=$VERSION" -o ui/flutter/android/app/libs/libgopeed.aar -target=android -androidapi 21 -javapkg=com.gopeed github.com/GopeedLab/gopeed/bind/mobile
|
gomobile bind -tags nosqlite -ldflags="-w -s -checklinkname=0 -X github.com/GopeedLab/gopeed/pkg/base.Version=$VERSION" -o ui/flutter/android/app/libs/libgopeed.aar -target=android -androidapi 21 -javapkg=com.gopeed github.com/GopeedLab/gopeed/bind/mobile
|
||||||
cd ui/flutter
|
cd ui/flutter
|
||||||
echo $APK_KEYSTORE | base64 -di > android/app/upload-keystore.jks
|
echo $APK_KEYSTORE | base64 -di > android/app/upload-keystore.jks
|
||||||
flutter build apk --dart-define="UPDATE_CHANNEL=androidApk"
|
flutter build apk
|
||||||
flutter build apk --split-per-abi --dart-define="UPDATE_CHANNEL=androidApk"
|
|
||||||
mkdir dist
|
mkdir dist
|
||||||
cp build/app/outputs/flutter-apk/app-release.apk dist/Gopeed-v$VERSION-android.apk
|
cp build/app/outputs/flutter-apk/app-release.apk dist/Gopeed-v$VERSION-android.apk
|
||||||
cp build/app/outputs/flutter-apk/app-armeabi-v7a-release.apk dist/Gopeed-v$VERSION-android-armeabi-v7a.apk
|
|
||||||
cp build/app/outputs/flutter-apk/app-arm64-v8a-release.apk dist/Gopeed-v$VERSION-android-arm64-v8a.apk
|
|
||||||
cp build/app/outputs/flutter-apk/app-x86_64-release.apk dist/Gopeed-v$VERSION-android-x86_64.apk
|
|
||||||
- name: Upload
|
- name: Upload
|
||||||
uses: shogo82148/actions-upload-release-asset@v1
|
uses: shogo82148/actions-upload-release-asset@v1
|
||||||
with:
|
with:
|
||||||
@@ -502,7 +394,7 @@ jobs:
|
|||||||
overwrite: true
|
overwrite: true
|
||||||
build-ios:
|
build-ios:
|
||||||
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'ios' }}
|
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'ios' }}
|
||||||
runs-on: macos-14
|
runs-on: macos-latest
|
||||||
needs: [ get-release ]
|
needs: [ get-release ]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
@@ -521,7 +413,7 @@ jobs:
|
|||||||
gomobile init
|
gomobile init
|
||||||
gomobile bind -tags nosqlite -ldflags="-w -s -X github.com/GopeedLab/gopeed/pkg/base.Version=$VERSION" -o ui/flutter/ios/Frameworks/Libgopeed.xcframework -target=ios github.com/GopeedLab/gopeed/bind/mobile
|
gomobile bind -tags nosqlite -ldflags="-w -s -X github.com/GopeedLab/gopeed/pkg/base.Version=$VERSION" -o ui/flutter/ios/Frameworks/Libgopeed.xcframework -target=ios github.com/GopeedLab/gopeed/bind/mobile
|
||||||
cd ui/flutter
|
cd ui/flutter
|
||||||
flutter build ios --no-codesign --dart-define="UPDATE_CHANNEL=iosIpa"
|
flutter build ios --no-codesign
|
||||||
mkdir Payload
|
mkdir Payload
|
||||||
cp -r build/ios/iphoneos/Runner.app Payload
|
cp -r build/ios/iphoneos/Runner.app Payload
|
||||||
zip -r -y Payload.zip Payload/Runner.app
|
zip -r -y Payload.zip Payload/Runner.app
|
||||||
@@ -534,7 +426,7 @@ jobs:
|
|||||||
asset_path: ui/flutter/dist/*
|
asset_path: ui/flutter/dist/*
|
||||||
overwrite: true
|
overwrite: true
|
||||||
build-web:
|
build-web:
|
||||||
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'web' || github.event.inputs.platform == 'qnap' }}
|
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'web' }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [ get-release ]
|
needs: [ get-release ]
|
||||||
steps:
|
steps:
|
||||||
@@ -549,8 +441,9 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
VERSION: ${{ needs.get-release.outputs.version }}
|
VERSION: ${{ needs.get-release.outputs.version }}
|
||||||
run: |
|
run: |
|
||||||
|
VERSION=${VERSION:-$(date +%y%m%d%H%M)} # Use current time as version if not specified
|
||||||
cd ui/flutter
|
cd ui/flutter
|
||||||
flutter build web --no-web-resources-cdn
|
flutter build web --web-renderer html
|
||||||
cd ../../
|
cd ../../
|
||||||
cp -r ui/flutter/build/web cmd/web/dist
|
cp -r ui/flutter/build/web cmd/web/dist
|
||||||
mkdir -p dist/zip
|
mkdir -p dist/zip
|
||||||
@@ -576,70 +469,44 @@ jobs:
|
|||||||
|| true
|
|| true
|
||||||
done
|
done
|
||||||
done
|
done
|
||||||
- uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: web-dist
|
|
||||||
path: dist/zip/
|
|
||||||
- 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-qnap:
|
|
||||||
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'qnap' }}
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: [get-release, build-web]
|
|
||||||
steps:
|
|
||||||
- uses: actions/setup-python@v5
|
|
||||||
with:
|
|
||||||
python-version: "3.8.18"
|
|
||||||
- uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
name: web-dist
|
|
||||||
path: dist/zip/
|
|
||||||
- name: Build
|
|
||||||
env:
|
|
||||||
VERSION: ${{ needs.get-release.outputs.version }}
|
|
||||||
run: |
|
|
||||||
sudo apt update -y
|
sudo apt update -y
|
||||||
sudo apt install -y pv bsdmainutils
|
sudo apt install -y pv python2 bsdmainutils
|
||||||
wget -O qdk2_0.32.bionic_amd64.deb "https://github.com/qnap-dev/qdk2/releases/download/v0.32/qdk2_0.32.bionic_amd64.deb"
|
wget -O qdk2_0.32.bionic_amd64.deb "https://github.com/qnap-dev/qdk2/releases/download/v0.32/qdk2_0.32.bionic_amd64.deb"
|
||||||
dpkg -X qdk2_0.32.bionic_amd64.deb qdk2 # Direct installs will fail due to missing dependencies!
|
dpkg -X qdk2_0.32.bionic_amd64.deb qdk2 # Direct installs will fail due to missing dependencies!
|
||||||
[[ -d qdk2 ]] || exit 1
|
[[ -d qdk2 ]] || exit 1
|
||||||
|
|
||||||
export PATH=$(pwd)/qdk2/usr/bin:$(pwd)/qdk2/usr/share/qdk2/QDK/bin:${PATH}
|
export PATH=$(pwd)/qdk2/usr/bin:$(pwd)/qdk2/usr/share/qdk2/QDK/bin:${PATH}
|
||||||
wget -O Gopeed.template.tar.gz "https://github.com/GopeedLab/QpkgBuild/raw/refs/heads/master/template/Gopeed.template.tar.gz"
|
wget -O Gopeed.template.tar.gz "https://github.com/GopeedLab/QpkgBuild/raw/refs/heads/master/template/Gopeed.template.tar.gz"
|
||||||
tar -zxf Gopeed.template.tar.gz
|
tar -zxf Gopeed.template.tar.gz
|
||||||
[[ -d Gopeed ]] || exit 1
|
[[ -d Gopeed ]] || exit 1
|
||||||
|
|
||||||
goos=linux
|
goos=linux
|
||||||
goarch_arr=(amd64 arm64)
|
goarch_arr=(amd64 arm64)
|
||||||
for goarch in "${goarch_arr[@]}"; do
|
for goarch in "${goarch_arr[@]}"; do
|
||||||
qarch=x86_64
|
qarch=x86_64
|
||||||
[[ "${goarch}" == "arm64" ]] && qarch=arm_64
|
[[ "${goarch}" == "arm64" ]] && qarch=arm_64
|
||||||
name=gopeed-web-v${VERSION}-${goos}-${goarch}
|
name=gopeed-web-v${VERSION}-${goos}-${goarch}
|
||||||
unzip dist/zip/${name}.zip -d dist/${name}
|
|
||||||
cp dist/${name}/${name}/* Gopeed/${qarch}/
|
cp dist/${name}/${name}/* Gopeed/${qarch}/
|
||||||
done
|
done
|
||||||
cd Gopeed
|
cd Gopeed
|
||||||
sed -i -e 's/__QPKG_VER__/${VERSION}/g' qpkg.cfg
|
sed -i -e 's/__QPKG_VER__/${VERSION}/g' qpkg.cfg
|
||||||
qbuild || exit 1
|
qbuild || exit 1
|
||||||
|
for f in build/*; do echo ">>> build=[${f}]"; done
|
||||||
mkdir -p ../dist/qnap
|
|
||||||
goos=qnap
|
goos=qnap
|
||||||
|
goarch_arr=(amd64 arm64)
|
||||||
for goarch in "${goarch_arr[@]}"; do
|
for goarch in "${goarch_arr[@]}"; do
|
||||||
qarch=x86_64
|
qarch=x86_64
|
||||||
[[ "${goarch}" == "arm64" ]] && qarch=arm_64
|
[[ "${goarch}" == "arm64" ]] && qarch=arm_64
|
||||||
sname=Gopeed_${VERSION}_${qarch}.qpkg
|
sname=Gopeed_${VERSION}_${qarch}.qpkg
|
||||||
dname=gopeed-v${VERSION}-${goos}-${goarch}.qpkg
|
dname=gopeed-${goos}-v${VERSION}-${goarch}.qpkg
|
||||||
[[ -f build/${sname} ]] && cp -ra build/${sname} ../dist/qnap/${dname}
|
echo "sname=${sname}, dname=${dname}"
|
||||||
|
[[ -f build/${sname} ]] && cp -ra build/${sname} ../dist/zip/${dname}
|
||||||
done
|
done
|
||||||
|
for f in ../dist/zip/*; do echo ">>> dist=[${f}]"; done
|
||||||
|
cd ..
|
||||||
- name: Upload
|
- name: Upload
|
||||||
uses: shogo82148/actions-upload-release-asset@v1
|
uses: shogo82148/actions-upload-release-asset@v1
|
||||||
with:
|
with:
|
||||||
upload_url: ${{ needs.get-release.outputs.upload_url }}
|
upload_url: ${{ needs.get-release.outputs.upload_url }}
|
||||||
asset_path: dist/qnap/*
|
asset_path: dist/zip/*
|
||||||
overwrite: true
|
overwrite: true
|
||||||
build-docker:
|
build-docker:
|
||||||
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'docker' }}
|
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'docker' }}
|
||||||
@@ -666,7 +533,7 @@ jobs:
|
|||||||
- name: Build flutter web
|
- name: Build flutter web
|
||||||
run: |
|
run: |
|
||||||
cd ui/flutter
|
cd ui/flutter
|
||||||
flutter build web --no-web-resources-cdn --dart-define="UPDATE_CHANNEL=docker"
|
flutter build web --web-renderer html
|
||||||
cd ../../
|
cd ../../
|
||||||
rm -rf cmd/web/dist
|
rm -rf cmd/web/dist
|
||||||
cp -r ui/flutter/build/web cmd/web/dist
|
cp -r ui/flutter/build/web cmd/web/dist
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ on:
|
|||||||
- "Dockerfile"
|
- "Dockerfile"
|
||||||
|
|
||||||
env:
|
env:
|
||||||
GO_VERSION: "1.24"
|
GO_VERSION: "1.23"
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
|
|||||||
@@ -27,8 +27,8 @@ on:
|
|||||||
- "go.sum"
|
- "go.sum"
|
||||||
|
|
||||||
env:
|
env:
|
||||||
GO_VERSION: "1.24"
|
GO_VERSION: "1.23"
|
||||||
FLUTTER_VERSION: "3.24.5"
|
FLUTTER_VERSION: "3.16.8"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
# lint:
|
# lint:
|
||||||
@@ -53,13 +53,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
go-version: ${{ env.GO_VERSION }}
|
go-version: ${{ env.GO_VERSION }}
|
||||||
- name: Run Unit tests.
|
- name: Run Unit tests.
|
||||||
run: |
|
run: go test $(go list ./... | grep -v /cmd/ | grep -v /bind/ ) -v -covermode=count -coverprofile=coverage.txt
|
||||||
# Get packages for testing and coverage (including cmd/web for flags_test.go)
|
|
||||||
PACKAGES=$(go list ./... | grep -v /bind/ | grep -v -E '/cmd/(?!web)')
|
|
||||||
go test -v -coverpkg=$(echo "$PACKAGES" | paste -sd "," -) -covermode=count -coverprofile=coverage.txt $PACKAGES
|
|
||||||
# Filter out main.go from coverage report to avoid lowering coverage percentage
|
|
||||||
grep -v "main.go" coverage.txt > coverage_filtered.txt || true
|
|
||||||
mv coverage_filtered.txt coverage.txt
|
|
||||||
- uses: codecov/codecov-action@v4
|
- uses: codecov/codecov-action@v4
|
||||||
with:
|
with:
|
||||||
files: ./coverage.txt
|
files: ./coverage.txt
|
||||||
@@ -69,7 +63,7 @@ jobs:
|
|||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [windows-latest, macos-latest, ubuntu-22.04, ubuntu-22.04-arm]
|
os: [ windows-latest, macos-latest, ubuntu-latest ]
|
||||||
needs: [ test ]
|
needs: [ test ]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
@@ -79,8 +73,7 @@ jobs:
|
|||||||
- uses: subosito/flutter-action@v2
|
- uses: subosito/flutter-action@v2
|
||||||
with:
|
with:
|
||||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||||
channel: master
|
- if: runner.os == 'windows'
|
||||||
- if: runner.os == 'Windows'
|
|
||||||
run: |
|
run: |
|
||||||
go build -tags nosqlite -ldflags="-w -s" -buildmode=c-shared -o ui/flutter/windows/libgopeed.dll github.com/GopeedLab/gopeed/bind/desktop
|
go build -tags nosqlite -ldflags="-w -s" -buildmode=c-shared -o ui/flutter/windows/libgopeed.dll github.com/GopeedLab/gopeed/bind/desktop
|
||||||
cd ui/flutter
|
cd ui/flutter
|
||||||
@@ -102,7 +95,7 @@ jobs:
|
|||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [macos-14, ubuntu-latest]
|
os: [ macos-latest, ubuntu-latest ]
|
||||||
needs: [ test ]
|
needs: [ test ]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
@@ -147,7 +140,7 @@ jobs:
|
|||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
cd ui/flutter
|
cd ui/flutter
|
||||||
flutter build web --no-web-resources-cdn
|
flutter build web --web-renderer html
|
||||||
cd ../../
|
cd ../../
|
||||||
rm -rf cmd/web/dist
|
rm -rf cmd/web/dist
|
||||||
cp -r ui/flutter/build/web cmd/web/dist
|
cp -r ui/flutter/build/web cmd/web/dist
|
||||||
@@ -167,7 +160,7 @@ jobs:
|
|||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
cd ui/flutter
|
cd ui/flutter
|
||||||
flutter build web --no-web-resources-cdn
|
flutter build web --web-renderer html
|
||||||
cd ../../
|
cd ../../
|
||||||
rm -rf cmd/web/dist
|
rm -rf cmd/web/dist
|
||||||
cp -r ui/flutter/build/web cmd/web/dist
|
cp -r ui/flutter/build/web cmd/web/dist
|
||||||
|
|||||||
@@ -13,152 +13,80 @@
|
|||||||
|
|
||||||
[English](/README.md) | [中文](/README_zh-CN.md) | [日本語](/README_ja-JP.md) | [正體中文](/README_zh-TW.md) | [Tiếng Việt](/README_vi-VN.md)
|
[English](/README.md) | [中文](/README_zh-CN.md) | [日本語](/README_ja-JP.md) | [正體中文](/README_zh-TW.md) | [Tiếng Việt](/README_vi-VN.md)
|
||||||
|
|
||||||
## 🚀 Introduction
|
## Introduction
|
||||||
|
|
||||||
Gopeed (full name Go Speed), a high-speed downloader developed by `Golang` + `Flutter`, supports (HTTP, BitTorrent, Magnet) protocol, and supports all platforms. In addition to basic download functions, Gopeed is also a highly customizable downloader that supports implementing more features through integration with [APIs](https://docs.gopeed.com/dev-api.html) or installation and development of [extensions](https://docs.gopeed.com/dev-extension.html).
|
Gopeed (full name Go Speed), a high-speed downloader developed by `Golang` + `Flutter`, supports (HTTP, BitTorrent, Magnet) protocol, and supports all platforms. In addition to basic download functions, Gopeed is also a highly customizable downloader that supports implementing more features through integration with [APIs](https://docs.gopeed.com/dev-api.html) or installation and development of [extensions](https://docs.gopeed.com/dev-extension.html).
|
||||||
|
|
||||||
Visit ✈ [Official Website](https://gopeed.com) | 📖 [Official Docs](https://docs.gopeed.com)
|
Visit ✈ [Official Website](https://gopeed.com) | 📖 [Official Docs](https://docs.gopeed.com)
|
||||||
|
|
||||||
## ⬇️ Download
|
## Download
|
||||||
|
|
||||||
<table>
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Platform</th>
|
||||||
|
<th>Package Type</th>
|
||||||
|
<th>Download Link</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td rowspan="2">🪟 Windows</td>
|
<td rowspan=2>Windows</td>
|
||||||
<td><code>EXE</code></td>
|
<td><code>EXE Installer</code></td>
|
||||||
<td>amd64</td>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64.zip">Link</a></td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64.zip">📥</a></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><code>Portable</code></td>
|
<td><code>Portable ZIP</code></td>
|
||||||
<td>amd64</td>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64-portable.zip">Link</a></td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64-portable.zip">📥</a></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td rowspan="3">🍎 MacOS</td>
|
<td>MacOS</td>
|
||||||
<td rowspan="3"><code>DMG</code></td>
|
<td><code>DMG Installer</code></td>
|
||||||
<td>universal</td>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-macos.dmg">Link</a></td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-macos.dmg">📥</a></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>amd64</td>
|
<td rowspan=4>Linux</td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-macos-amd64.dmg">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>arm64</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-macos-arm64.dmg">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td rowspan="6">🐧 Linux</td>
|
|
||||||
<td><code>Flathub</code></td>
|
<td><code>Flathub</code></td>
|
||||||
<td>amd64</td>
|
<td><a href="https://flathub.org/apps/com.gopeed.Gopeed">Link</a></td>
|
||||||
<td><a href="https://flathub.org/apps/com.gopeed.Gopeed">📥</a></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><code>SNAP</code></td>
|
<td><code>SNAP</code></td>
|
||||||
<td>amd64</td>
|
<td><a href="https://snapcraft.io/gopeed">Link</a></td>
|
||||||
<td><a href="https://snapcraft.io/gopeed">📥</a></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td rowspan="2"><code>DEB</code></td>
|
<td><code>DEB</code></td>
|
||||||
<td>amd64</td>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-amd64.deb">Link</a></td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-amd64.deb">📥</a></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>arm64</td>
|
<td><code>AppImage</code></td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-arm64.deb">📥</a></td>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-x86_64.AppImage">Link</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td rowspan="2"><code>AppImage</code></td>
|
<td>Android</td>
|
||||||
<td>amd64</td>
|
<td><code>APK</code></td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-amd64.AppImage">📥</a></td>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android.apk">Link</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>arm64</td>
|
<td>iOS</td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-arm64.AppImage">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td rowspan="4">🤖 Android</td>
|
|
||||||
<td rowspan="4"><code>APK</code></td>
|
|
||||||
<td>universal</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android.apk">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>armeabi-v7a</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android-armeabi-v7a.apk">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>arm64-v8a</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android-arm64-v8a.apk">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>x86_64</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android-x86_64.apk">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>📱 iOS</td>
|
|
||||||
<td><code>IPA</code></td>
|
<td><code>IPA</code></td>
|
||||||
<td>universal</td>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-ios.ipa">Link</a></td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-ios.ipa">📥</a></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>🐳 Docker</td>
|
<td>Web</td>
|
||||||
<td>-</td>
|
<td></td>
|
||||||
<td>universal</td>
|
<td><a href="https://github.com/GopeedLab/gopeed/releases/latest">Link</a></td>
|
||||||
<td><a href="https://hub.docker.com/r/liwei2633/gopeed">📥</a></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td rowspan="2">💾 Qnap</td>
|
<td>Docker</td>
|
||||||
<td rowspan="2"><code>QPKG</code></td>
|
<td></td>
|
||||||
<td>amd64</td>
|
<td><a href="https://hub.docker.com/r/liwei2633/gopeed">Link</a></td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=gopeed-$version-qnap-amd64.qpkg">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>arm64</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=gopeed-$version-qnap-arm64.qpkg">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td rowspan="8">🌐 Web</td>
|
|
||||||
<td rowspan="3"><code>Windows</code></td>
|
|
||||||
<td>amd64</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-windows-amd64.zip">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>arm64</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-windows-arm64.zip">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>386</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-windows-386.zip">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td rowspan="2"><code>MacOS</code></td>
|
|
||||||
<td>amd64</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-macos-amd64.zip">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>arm64</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-macos-arm64.zip">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td rowspan="3"><code>Linux</code></td>
|
|
||||||
<td>amd64</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-linux-amd64.zip">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>arm64</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-linux-arm64.zip">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>386</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-linux-386.zip">📥</a></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
More about installation, please refer to [Installation](https://docs.gopeed.com/install.html)
|
More about installation, please refer to [Installation](https://docs.gopeed.com/install.html)
|
||||||
|
|
||||||
### 🛠️ Command tool
|
### Command tool
|
||||||
|
|
||||||
use `go install`:
|
use `go install`:
|
||||||
|
|
||||||
@@ -166,40 +94,40 @@ use `go install`:
|
|||||||
go install github.com/GopeedLab/gopeed/cmd/gopeed@latest
|
go install github.com/GopeedLab/gopeed/cmd/gopeed@latest
|
||||||
```
|
```
|
||||||
|
|
||||||
## 🔌 Browser Extension
|
## Browser Extension
|
||||||
|
|
||||||
Gopeed also provides a browser extension to take over browser downloads, supporting browsers such as Chrome, Edge, Firefox, etc., please refer to: [https://github.com/GopeedLab/browser-extension](https://github.com/GopeedLab/browser-extension)
|
Gopeed also provides a browser extension to take over browser downloads, supporting browsers such as Chrome, Edge, Firefox, etc., please refer to: [https://github.com/GopeedLab/browser-extension](https://github.com/GopeedLab/browser-extension)
|
||||||
|
|
||||||
## 💝 Donate
|
## Donate
|
||||||
|
|
||||||
If you like this project, please consider [donating](https://docs.gopeed.com/donate.html) to support the development of this project, thank you!
|
If you like this project, please consider [donating](https://docs.gopeed.com/donate.html) to support the development of this project, thank you!
|
||||||
|
|
||||||
## 🖼️ Showcase
|
## Showcase
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## 👨💻 Development
|
## Development
|
||||||
|
|
||||||
This project is divided into two parts, the front end uses `flutter`, the back end uses `Golang`, and the two sides communicate through the `http` protocol. On the unix system, `unix socket` is used, and on the windows system, `tcp` protocol is used.
|
This project is divided into two parts, the front end uses `flutter`, the back end uses `Golang`, and the two sides communicate through the `http` protocol. On the unix system, `unix socket` is used, and on the windows system, `tcp` protocol is used.
|
||||||
|
|
||||||
> The front code is located in the `ui/flutter` directory.
|
> The front code is located in the `ui/flutter` directory.
|
||||||
|
|
||||||
### 🌍 Environment
|
### Environment
|
||||||
|
|
||||||
1. Golang 1.23+
|
1. Golang 1.23+
|
||||||
2. Flutter 3.24+
|
2. Flutter 3.16+
|
||||||
|
|
||||||
### 📋 Clone
|
### Clone
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone [email protected]:GopeedLab/gopeed.git
|
git clone [email protected]:GopeedLab/gopeed.git
|
||||||
```
|
```
|
||||||
|
|
||||||
### 🤝 Contributing
|
### Contributing
|
||||||
|
|
||||||
Please refer to [CONTRIBUTING.md](/CONTRIBUTING.md)
|
Please refer to [CONTRIBUTING.md](/CONTRIBUTING.md)
|
||||||
|
|
||||||
### 🏗️ Build
|
### Build
|
||||||
|
|
||||||
#### Desktop
|
#### Desktop
|
||||||
|
|
||||||
@@ -246,7 +174,7 @@ command:
|
|||||||
- android
|
- android
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
gomobile bind -tags nosqlite -ldflags="-w -s -checklinkname=0" -o ui/flutter/android/app/libs/libgopeed.aar -target=android -androidapi 21 -javapkg="com.gopeed" github.com/GopeedLab/gopeed/bind/mobile
|
gomobile bind -tags nosqlite -ldflags="-w -s" -o ui/flutter/android/app/libs/libgopeed.aar -target=android -androidapi 21 -javapkg="com.gopeed" github.com/GopeedLab/gopeed/bind/mobile
|
||||||
cd ui/flutter
|
cd ui/flutter
|
||||||
flutter build apk
|
flutter build apk
|
||||||
```
|
```
|
||||||
@@ -272,18 +200,18 @@ cp -r ui/flutter/build/web cmd/web/dist
|
|||||||
go build -tags nosqlite,web -ldflags="-s -w" -o bin/ github.com/GopeedLab/gopeed/cmd/web
|
go build -tags nosqlite,web -ldflags="-s -w" -o bin/ github.com/GopeedLab/gopeed/cmd/web
|
||||||
```
|
```
|
||||||
|
|
||||||
## ❤️ Credits
|
## Credits
|
||||||
|
|
||||||
### 👥 Contributors
|
### Contributors
|
||||||
|
|
||||||
<a href="https://github.com/GopeedLab/gopeed/graphs/contributors">
|
<a href="https://github.com/GopeedLab/gopeed/graphs/contributors">
|
||||||
<img src="https://contrib.rocks/image?repo=GopeedLab/gopeed" />
|
<img src="https://contrib.rocks/image?repo=GopeedLab/gopeed" />
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
### 🏢 JetBrains
|
### JetBrains
|
||||||
|
|
||||||
[](https://www.jetbrains.com/?from=gopeed)
|
[](https://www.jetbrains.com/?from=gopeed)
|
||||||
|
|
||||||
## 📄 License
|
## License
|
||||||
|
|
||||||
[GPLv3](LICENSE)
|
[GPLv3](LICENSE)
|
||||||
+46
-119
@@ -13,182 +13,109 @@
|
|||||||
|
|
||||||
[English](/README.md) | [中文](/README_zh-CN.md) | [日本語](/README_ja-JP.md) | [正體中文](/README_zh-TW.md) | [Tiếng Việt](/README_vi-VN.md)
|
[English](/README.md) | [中文](/README_zh-CN.md) | [日本語](/README_ja-JP.md) | [正體中文](/README_zh-TW.md) | [Tiếng Việt](/README_vi-VN.md)
|
||||||
|
|
||||||
## 🚀 はじめに
|
## はじめに
|
||||||
|
|
||||||
Gopeed (正式名 Go Speed) は `Golang` + `Flutter` によって開発された高速ダウンローダーで、(HTTP、BitTorrent、Magnet) プロトコルをサポートし、すべてのプラットフォームをサポートします。基本的なダウンロード機能に加え、[APIs](https://docs.gopeed.com/dev-api.html)との連動や[拡張機能](https://docs.gopeed.com/dev-extension.html)のインストール・開発による追加機能にも対応した、カスタマイズ性の高いダウンローダーです。
|
Gopeed (正式名 Go Speed) は `Golang` + `Flutter` によって開発された高速ダウンローダーで、(HTTP、BitTorrent、Magnet) プロトコルをサポートし、すべてのプラットフォームをサポートします。基本的なダウンロード機能に加え、[APIs](https://docs.gopeed.com/dev-api.html)との連動や[拡張機能](https://docs.gopeed.com/dev-extension.html)のインストール・開発による追加機能にも対応した、カスタマイズ性の高いダウンローダーです。
|
||||||
|
|
||||||
見て下さい ✈ [公式ウェブサイト](https://gopeed.com) | 📖 [開発ドキュメント](https://docs.gopeed.com)
|
見て下さい ✈ [公式ウェブサイト](https://gopeed.com) | 📖 [開発ドキュメント](https://docs.gopeed.com)
|
||||||
|
|
||||||
## ⬇️ インストール
|
## インストール
|
||||||
|
|
||||||
<table>
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Platform</th>
|
||||||
|
<th>Package Type</th>
|
||||||
|
<th>Download Link</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td rowspan="2">🪟 Windows</td>
|
<td rowspan=2>Windows</td>
|
||||||
<td><code>EXE</code></td>
|
<td><code>EXE Installer</code></td>
|
||||||
<td>amd64</td>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64.zip">Link</a></td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64.zip">📥</a></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><code>Portable</code></td>
|
<td><code>Portable ZIP</code></td>
|
||||||
<td>amd64</td>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64-portable.zip">Link</a></td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64-portable.zip">📥</a></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td rowspan="3">🍎 MacOS</td>
|
<td>MacOS</td>
|
||||||
<td rowspan="3"><code>DMG</code></td>
|
<td><code>DMG Installer</code></td>
|
||||||
<td>universal</td>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-macos.dmg">Link</a></td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-macos.dmg">📥</a></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>amd64</td>
|
<td rowspan=4>Linux</td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-macos-amd64.dmg">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>arm64</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-macos-arm64.dmg">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td rowspan="6">🐧 Linux</td>
|
|
||||||
<td><code>Flathub</code></td>
|
<td><code>Flathub</code></td>
|
||||||
<td>amd64</td>
|
<td><a href="https://flathub.org/apps/com.gopeed.Gopeed">Link</a></td>
|
||||||
<td><a href="https://flathub.org/apps/com.gopeed.Gopeed">📥</a></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><code>SNAP</code></td>
|
<td><code>SNAP</code></td>
|
||||||
<td>amd64</td>
|
<td><a href="https://snapcraft.io/gopeed">Link</a></td>
|
||||||
<td><a href="https://snapcraft.io/gopeed">📥</a></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td rowspan="2"><code>DEB</code></td>
|
<td><code>DEB</code></td>
|
||||||
<td>amd64</td>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-amd64.deb">Link</a></td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-amd64.deb">📥</a></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>arm64</td>
|
<td><code>AppImage</code></td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-arm64.deb">📥</a></td>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-x86_64.AppImage">Link</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td rowspan="2"><code>AppImage</code></td>
|
<td>Android</td>
|
||||||
<td>amd64</td>
|
<td><code>APK</code></td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-amd64.AppImage">📥</a></td>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android.apk">Link</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>arm64</td>
|
<td>iOS</td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-arm64.AppImage">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td rowspan="4">🤖 Android</td>
|
|
||||||
<td rowspan="4"><code>APK</code></td>
|
|
||||||
<td>universal</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android.apk">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>armeabi-v7a</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android-armeabi-v7a.apk">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>arm64-v8a</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android-arm64-v8a.apk">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>x86_64</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android-x86_64.apk">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>📱 iOS</td>
|
|
||||||
<td><code>IPA</code></td>
|
<td><code>IPA</code></td>
|
||||||
<td>universal</td>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-ios.ipa">Link</a></td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-ios.ipa">📥</a></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>🐳 Docker</td>
|
<td>Web</td>
|
||||||
<td>-</td>
|
<td></td>
|
||||||
<td>universal</td>
|
<td><a href="https://github.com/GopeedLab/gopeed/releases/latest">Link</a></td>
|
||||||
<td><a href="https://hub.docker.com/r/liwei2633/gopeed">📥</a></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td rowspan="2">💾 Qnap</td>
|
<td>Docker</td>
|
||||||
<td rowspan="2"><code>QPKG</code></td>
|
<td></td>
|
||||||
<td>amd64</td>
|
<td><a href="https://hub.docker.com/r/liwei2633/gopeed">Link</a></td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=gopeed-$version-qnap-amd64.qpkg">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>arm64</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=gopeed-$version-qnap-arm64.qpkg">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td rowspan="8">🌐 Web</td>
|
|
||||||
<td rowspan="3"><code>Windows</code></td>
|
|
||||||
<td>amd64</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-windows-amd64.zip">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>arm64</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-windows-arm64.zip">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>386</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-windows-386.zip">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td rowspan="2"><code>MacOS</code></td>
|
|
||||||
<td>amd64</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-macos-amd64.zip">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>arm64</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-macos-arm64.zip">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td rowspan="3"><code>Linux</code></td>
|
|
||||||
<td>amd64</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-linux-amd64.zip">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>arm64</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-linux-arm64.zip">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>386</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-linux-386.zip">📥</a></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
インストールについての詳細は、[インストール](https://docs.gopeed.com/install.html)を参照してください。
|
インストールについての詳細は、[インストール](https://docs.gopeed.com/install.html)を参照してください。
|
||||||
|
|
||||||
### 🛠️ コマンドツール
|
## 寄付
|
||||||
|
|
||||||
## 💝 寄付
|
|
||||||
|
|
||||||
もしこのプロジェクトがお気に召しましたら、このプロジェクトの発展を支援するために[寄付](https://docs.gopeed.com/donate.html)をご検討ください!
|
もしこのプロジェクトがお気に召しましたら、このプロジェクトの発展を支援するために[寄付](https://docs.gopeed.com/donate.html)をご検討ください!
|
||||||
|
|
||||||
## 🖼️ ショーケース
|
## ショーケース
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## 👨💻 開発
|
## 開発
|
||||||
|
|
||||||
このプロジェクトは二つの部分に分かれており、フロントエンドでは `flutter` を、バックエンドでは `Golang` を使用し、両者は `http` プロトコルで通信する。ユニックスシステムでは `unix socket` を、ウィンドウズシステムでは `tcp` プロトコルを使用します。
|
このプロジェクトは二つの部分に分かれており、フロントエンドでは `flutter` を、バックエンドでは `Golang` を使用し、両者は `http` プロトコルで通信する。ユニックスシステムでは `unix socket` を、ウィンドウズシステムでは `tcp` プロトコルを使用します。
|
||||||
|
|
||||||
> フロントコードは `ui/flutter` ディレクトリにあります。
|
> フロントコードは `ui/flutter` ディレクトリにあります。
|
||||||
|
|
||||||
### 🌍 環境
|
### 環境
|
||||||
|
|
||||||
1. Go 言語 1.23+
|
1. Go 言語 1.23+
|
||||||
2. Flutter 3.24+
|
2. Flutter 3.16+
|
||||||
|
|
||||||
### 📋 クローン
|
### クローン
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone [email protected]:GopeedLab/gopeed.git
|
git clone [email protected]:GopeedLab/gopeed.git
|
||||||
```
|
```
|
||||||
|
|
||||||
### 🤝 コントリビュート
|
### コントリビュート
|
||||||
|
|
||||||
[CONTRIBUTING.md](/CONTRIBUTING_ja-JP.md) をご参照ください
|
[CONTRIBUTING.md](/CONTRIBUTING_ja-JP.md) をご参照ください
|
||||||
|
|
||||||
### 🏗️ ビルド
|
### ビルド
|
||||||
|
|
||||||
#### デスクトップ
|
#### デスクトップ
|
||||||
|
|
||||||
@@ -235,7 +162,7 @@ gomobile init
|
|||||||
- android
|
- android
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
gomobile bind -tags nosqlite -ldflags="-w -s -checklinkname=0" -o ui/flutter/android/app/libs/libgopeed.aar -target=android -androidapi 21 -javapkg="com.gopeed" github.com/GopeedLab/gopeed/bind/mobile
|
gomobile bind -tags nosqlite -ldflags="-w -s" -o ui/flutter/android/app/libs/libgopeed.aar -target=android -androidapi 21 -javapkg="com.gopeed" github.com/GopeedLab/gopeed/bind/mobile
|
||||||
cd ui/flutter
|
cd ui/flutter
|
||||||
flutter build apk
|
flutter build apk
|
||||||
```
|
```
|
||||||
@@ -261,7 +188,7 @@ cp -r ui/flutter/build/web cmd/web/dist
|
|||||||
go build -tags nosqlite,web -ldflags="-s -w" -o bin/ github.com/GopeedLab/gopeed/cmd/web
|
go build -tags nosqlite,web -ldflags="-s -w" -o bin/ github.com/GopeedLab/gopeed/cmd/web
|
||||||
```
|
```
|
||||||
|
|
||||||
## ❤️ 感謝
|
## 感謝
|
||||||
|
|
||||||
### コントリビューター
|
### コントリビューター
|
||||||
|
|
||||||
|
|||||||
+47
-118
@@ -13,151 +13,80 @@
|
|||||||
|
|
||||||
[English](/README.md) | [中文](/README_zh-CN.md) | [日本語](/README_ja-JP.md) | [正體中文](/README_zh-TW.md) | [Tiếng Việt](/README_vi-VN.md)
|
[English](/README.md) | [中文](/README_zh-CN.md) | [日本語](/README_ja-JP.md) | [正體中文](/README_zh-TW.md) | [Tiếng Việt](/README_vi-VN.md)
|
||||||
|
|
||||||
## 🚀 Giới thiệu
|
## Giới thiệu
|
||||||
|
|
||||||
Gopeed (tên đầy đủ Go Speed), một công cụ tải xuống tốc độ cao được phát triển bởi `Golang` + `Flutter`, hỗ trợ giao thức (HTTP, BitTorrent, Magnet) và hỗ trợ tất cả các nền tảng. Ngoài các chức năng tải xuống cơ bản, Gopeed còn là một công cụ tải xuống có thể tùy chỉnh cao cho phép triển khai thêm tính năng thông qua việc tích hợp với [APIs](https://docs.gopeed.com/dev-api.html) hoặc cài đặt và phát triển các [tiện ích mở rộng](https://docs.gopeed.com/dev-extension.html).
|
Gopeed (tên đầy đủ Go Speed), một công cụ tải xuống tốc độ cao được phát triển bởi `Golang` + `Flutter`, hỗ trợ giao thức (HTTP, BitTorrent, Magnet) và hỗ trợ tất cả các nền tảng. Ngoài các chức năng tải xuống cơ bản, Gopeed còn là một công cụ tải xuống có thể tùy chỉnh cao cho phép triển khai thêm tính năng thông qua việc tích hợp với [APIs](https://docs.gopeed.com/dev-api.html) hoặc cài đặt và phát triển các [tiện ích mở rộng](https://docs.gopeed.com/dev-extension.html).
|
||||||
|
|
||||||
Truy cập ✈ [Trang web chính thức](https://gopeed.com) | 📖 [Tài liệu chính thức](https://docs.gopeed.com)
|
Truy cập ✈ [Trang web chính thức](https://gopeed.com) | 📖 [Tài liệu chính thức](https://docs.gopeed.com)
|
||||||
|
|
||||||
## ⬇️ Tải về
|
## Tải về
|
||||||
|
|
||||||
<table>
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Nền tảng</th>
|
||||||
|
<th>Loại gói</th>
|
||||||
|
<th>Liên kết tải xuống</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td rowspan="2">🪟 Windows</td>
|
<td rowspan=2>Windows</td>
|
||||||
<td><code>EXE</code></td>
|
<td><code>Bộ cài đặt EXE</code></td>
|
||||||
<td>amd64</td>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64.zip">Liên kết</a></td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64.zip">📥</a></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><code>Portable</code></td>
|
<td><code>ZIP Portable</code></td>
|
||||||
<td>amd64</td>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64-portable.zip">Liên kết</a></td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64-portable.zip">📥</a></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td rowspan="3">🍎 MacOS</td>
|
<td>MacOS</td>
|
||||||
<td rowspan="3"><code>DMG</code></td>
|
<td><code>Bộ cài đặt DMG</code></td>
|
||||||
<td>universal</td>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-macos.dmg">Liên kết</a></td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-macos.dmg">📥</a></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>amd64</td>
|
<td rowspan=4>Linux</td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-macos-amd64.dmg">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>arm64</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-macos-arm64.dmg">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td rowspan="6">🐧 Linux</td>
|
|
||||||
<td><code>Flathub</code></td>
|
<td><code>Flathub</code></td>
|
||||||
<td>amd64</td>
|
<td><a href="https://flathub.org/apps/com.gopeed.Gopeed">Liên kết</a></td>
|
||||||
<td><a href="https://flathub.org/apps/com.gopeed.Gopeed">📥</a></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><code>SNAP</code></td>
|
<td><code>SNAP</code></td>
|
||||||
<td>amd64</td>
|
<td><a href="https://snapcraft.io/gopeed">Liên kết</a></td>
|
||||||
<td><a href="https://snapcraft.io/gopeed">📥</a></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td rowspan="2"><code>DEB</code></td>
|
<td><code>DEB</code></td>
|
||||||
<td>amd64</td>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-amd64.deb">Liên kết</a></td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-amd64.deb">📥</a></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>arm64</td>
|
<td><code>AppImage</code></td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-arm64.deb">📥</a></td>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-x86_64.AppImage">Liên kết</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td rowspan="2"><code>AppImage</code></td>
|
<td>Android</td>
|
||||||
<td>amd64</td>
|
<td><code>APK</code></td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-amd64.AppImage">📥</a></td>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android.apk">Liên kết</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>arm64</td>
|
<td>iOS</td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-arm64.AppImage">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td rowspan="4">🤖 Android</td>
|
|
||||||
<td rowspan="4"><code>APK</code></td>
|
|
||||||
<td>universal</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android.apk">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>armeabi-v7a</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android-armeabi-v7a.apk">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>arm64-v8a</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android-arm64-v8a.apk">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>x86_64</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android-x86_64.apk">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>📱 iOS</td>
|
|
||||||
<td><code>IPA</code></td>
|
<td><code>IPA</code></td>
|
||||||
<td>universal</td>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-ios.ipa">Liên kết</a></td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-ios.ipa">📥</a></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>🐳 Docker</td>
|
<td>Web</td>
|
||||||
<td>-</td>
|
<td></td>
|
||||||
<td>universal</td>
|
<td><a href="https://github.com/GopeedLab/gopeed/releases/latest">Liên kết</a></td>
|
||||||
<td><a href="https://hub.docker.com/r/liwei2633/gopeed">📥</a></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td rowspan="2">💾 Qnap</td>
|
<td>Docker</td>
|
||||||
<td rowspan="2"><code>QPKG</code></td>
|
<td></td>
|
||||||
<td>amd64</td>
|
<td><a href="https://hub.docker.com/r/liwei2633/gopeed">Liên kết</a></td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=gopeed-$version-qnap-amd64.qpkg">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>arm64</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=gopeed-$version-qnap-arm64.qpkg">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td rowspan="8">🌐 Web</td>
|
|
||||||
<td rowspan="3"><code>Windows</code></td>
|
|
||||||
<td>amd64</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-windows-amd64.zip">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>arm64</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-windows-arm64.zip">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>386</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-windows-386.zip">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td rowspan="2"><code>MacOS</code></td>
|
|
||||||
<td>amd64</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-macos-amd64.zip">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>arm64</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-macos-arm64.zip">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td rowspan="3"><code>Linux</code></td>
|
|
||||||
<td>amd64</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-linux-amd64.zip">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>arm64</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-linux-arm64.zip">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>386</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-linux-386.zip">📥</a></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
Thêm thông tin về cài đặt, vui lòng tham khảo [Cài đặt](https://docs.gopeed.com/install.html)
|
Thêm thông tin về cài đặt, vui lòng tham khảo [Cài đặt](https://docs.gopeed.com/install.html)
|
||||||
|
|
||||||
### 🛠️ Công cụ lệnh
|
### Command tool
|
||||||
|
|
||||||
Sử dụng `go install`:
|
Sử dụng `go install`:
|
||||||
|
|
||||||
@@ -165,36 +94,36 @@ Sử dụng `go install`:
|
|||||||
go install github.com/GopeedLab/gopeed/cmd/gopeed@latest
|
go install github.com/GopeedLab/gopeed/cmd/gopeed@latest
|
||||||
```
|
```
|
||||||
|
|
||||||
## 💝 Quyên góp
|
## Quyên góp
|
||||||
|
|
||||||
Nếu bạn thích dự án này, xin vui lòng xem xét [quyên góp](https://docs.gopeed.com/donate.html) để hỗ trợ phát triển dự án này, cảm ơn bạn!
|
Nếu bạn thích dự án này, xin vui lòng xem xét [quyên góp](https://docs.gopeed.com/donate.html) để hỗ trợ phát triển dự án này, cảm ơn bạn!
|
||||||
|
|
||||||
## 🖼️ Trưng bày
|
## Trưng bày
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## 👨💻 Development
|
## Development
|
||||||
|
|
||||||
Dự án này được chia thành hai phần, phần giao diện sử dụng `flutter`, phần backend sử dụng `Golang`, và hai phía giao tiếp thông qua giao thức `http`. Trên hệ thống unix, sử dụng `unix socket`, và trên hệ thống windows, sử dụng giao thức `tcp`.
|
Dự án này được chia thành hai phần, phần giao diện sử dụng `flutter`, phần backend sử dụng `Golang`, và hai phía giao tiếp thông qua giao thức `http`. Trên hệ thống unix, sử dụng `unix socket`, và trên hệ thống windows, sử dụng giao thức `tcp`.
|
||||||
|
|
||||||
> Mã giao diện nằm trong thư mục `ui/flutter`.
|
> Mã giao diện nằm trong thư mục `ui/flutter`.
|
||||||
|
|
||||||
### 🌍 Environment
|
### Environment
|
||||||
|
|
||||||
1. Golang 1.23+
|
1. Golang 1.23+
|
||||||
2. Flutter 3.24+
|
2. Flutter 3.16+
|
||||||
|
|
||||||
### 📋 Clone
|
### Clone
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone [email protected]:GopeedLab/gopeed.git
|
git clone [email protected]:GopeedLab/gopeed.git
|
||||||
```
|
```
|
||||||
|
|
||||||
### 🤝 Đóng góp
|
### Đóng góp
|
||||||
|
|
||||||
Vui lòng tham khảo [CONTRIBUTING_vi-VN.md](/CONTRIBUTING_vi-VN.md)
|
Vui lòng tham khảo [CONTRIBUTING_vi-VN.md](/CONTRIBUTING_vi-VN.md)
|
||||||
|
|
||||||
### 🏗️ Xây dựng
|
### Xây dựng
|
||||||
|
|
||||||
#### Desktop
|
#### Desktop
|
||||||
|
|
||||||
@@ -241,7 +170,7 @@ command:
|
|||||||
- android
|
- android
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
gomobile bind -tags nosqlite -ldflags="-w -s -checklinkname=0" -o ui/flutter/android/app/libs/libgopeed.aar -target=android -androidapi 21 -javapkg="com.gopeed" github.com/GopeedLab/gopeed/bind/mobile
|
gomobile bind -tags nosqlite -ldflags="-w -s" -o ui/flutter/android/app/libs/libgopeed.aar -target=android -androidapi 21 -javapkg="com.gopeed" github.com/GopeedLab/gopeed/bind/mobile
|
||||||
cd ui/flutter
|
cd ui/flutter
|
||||||
flutter build apk
|
flutter build apk
|
||||||
```
|
```
|
||||||
@@ -267,7 +196,7 @@ cp -r ui/flutter/build/web cmd/web/dist
|
|||||||
go build -tags nosqlite,web -ldflags="-s -w" -o bin/ github.com/GopeedLab/gopeed/cmd/web
|
go build -tags nosqlite,web -ldflags="-s -w" -o bin/ github.com/GopeedLab/gopeed/cmd/web
|
||||||
```
|
```
|
||||||
|
|
||||||
## ❤️ Tín dụng
|
## Tín dụng
|
||||||
|
|
||||||
### Người đóng góp
|
### Người đóng góp
|
||||||
|
|
||||||
|
|||||||
+48
-119
@@ -13,151 +13,80 @@
|
|||||||
|
|
||||||
[English](/README.md) | [中文](/README_zh-CN.md) | [日本語](/README_ja-JP.md) | [正體中文](/README_zh-TW.md) | [Tiếng Việt](/README_vi-VN.md)
|
[English](/README.md) | [中文](/README_zh-CN.md) | [日本語](/README_ja-JP.md) | [正體中文](/README_zh-TW.md) | [Tiếng Việt](/README_vi-VN.md)
|
||||||
|
|
||||||
## 🚀 介绍
|
## 介绍
|
||||||
|
|
||||||
Gopeed(全称 Go Speed),直译过来中文名叫做`够快下载器`(不是狗屁下载器!),是一款由`Golang`+`Flutter`开发的高速下载器,支持(HTTP、BitTorrent、Magnet)协议下载,并且支持全平台使用。除了基本的下载功能外,Gopeed 还是一款高度可定制化的下载器,支持通过对接[APIs](https://docs.gopeed.com/zh/dev-api.html)或者安装和开发[扩展](https://docs.gopeed.com/zh/dev-extension.html)来实现更多的功能。
|
Gopeed(全称 Go Speed),直译过来中文名叫做`够快下载器`(不是狗屁下载器!),是一款由`Golang`+`Flutter`开发的高速下载器,支持(HTTP、BitTorrent、Magnet)协议下载,并且支持全平台使用。除了基本的下载功能外,Gopeed 还是一款高度可定制化的下载器,支持通过对接[APIs](https://docs.gopeed.com/zh/dev-api.html)或者安装和开发[扩展](https://docs.gopeed.com/zh/dev-extension.html)来实现更多的功能。
|
||||||
|
|
||||||
访问 ✈ [官方网站](https://gopeed.com/zh-CN) | 📖 [官方文档](https://docs.gopeed.com/zh/)
|
访问 ✈ [官方网站](https://gopeed.com/zh-CN) | 📖 [官方文档](https://docs.gopeed.com/zh/)
|
||||||
|
|
||||||
## ⬇️ 下载
|
## 下载
|
||||||
|
|
||||||
<table>
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>运行平台</th>
|
||||||
|
<th>安装包类型</th>
|
||||||
|
<th>下载链接</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td rowspan="2">🪟 Windows</td>
|
<td rowspan=2>Windows</td>
|
||||||
<td><code>EXE</code></td>
|
<td><code>EXE Installer</code></td>
|
||||||
<td>amd64</td>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64.zip">前往</a></td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64.zip">📥</a></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><code>Portable</code></td>
|
<td><code>Portable ZIP</code></td>
|
||||||
<td>amd64</td>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64-portable.zip">前往</a></td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64-portable.zip">📥</a></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td rowspan="3">🍎 MacOS</td>
|
<td>MacOS</td>
|
||||||
<td rowspan="3"><code>DMG</code></td>
|
<td><code>DMG Installer</code></td>
|
||||||
<td>universal</td>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-macos.dmg">前往</a></td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-macos.dmg">📥</a></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>amd64</td>
|
<td rowspan=4>Linux</td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-macos-amd64.dmg">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>arm64</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-macos-arm64.dmg">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td rowspan="6">🐧 Linux</td>
|
|
||||||
<td><code>Flathub</code></td>
|
<td><code>Flathub</code></td>
|
||||||
<td>amd64</td>
|
<td><a href="https://flathub.org/apps/com.gopeed.Gopeed">前往</a></td>
|
||||||
<td><a href="https://flathub.org/apps/com.gopeed.Gopeed">📥</a></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><code>SNAP</code></td>
|
<td><code>SNAP</code></td>
|
||||||
<td>amd64</td>
|
<td><a href="https://snapcraft.io/gopeed">前往</a></td>
|
||||||
<td><a href="https://snapcraft.io/gopeed">📥</a></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td rowspan="2"><code>DEB</code></td>
|
<td><code>DEB</code></td>
|
||||||
<td>amd64</td>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-amd64.deb">前往</a></td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-amd64.deb">📥</a></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>arm64</td>
|
<td><code>AppImage</code></td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-arm64.deb">📥</a></td>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-x86_64.AppImage">前往</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td rowspan="2"><code>AppImage</code></td>
|
<td>Android</td>
|
||||||
<td>amd64</td>
|
<td><code>APK</code></td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-amd64.AppImage">📥</a></td>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android.apk">前往</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>arm64</td>
|
<td>iOS</td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-arm64.AppImage">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td rowspan="4">🤖 Android</td>
|
|
||||||
<td rowspan="4"><code>APK</code></td>
|
|
||||||
<td>universal</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android.apk">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>armeabi-v7a</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android-armeabi-v7a.apk">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>arm64-v8a</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android-arm64-v8a.apk">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>x86_64</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android-x86_64.apk">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>📱 iOS</td>
|
|
||||||
<td><code>IPA</code></td>
|
<td><code>IPA</code></td>
|
||||||
<td>universal</td>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-ios.ipa">前往</a></td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-ios.ipa">📥</a></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>🐳 Docker</td>
|
<td>Web</td>
|
||||||
<td>-</td>
|
<td></td>
|
||||||
<td>universal</td>
|
<td><a href="https://github.com/GopeedLab/gopeed/releases/latest">前往</a></td>
|
||||||
<td><a href="https://hub.docker.com/r/liwei2633/gopeed">📥</a></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td rowspan="2">💾 Qnap</td>
|
<td>Docker</td>
|
||||||
<td rowspan="2"><code>QPKG</code></td>
|
<td></td>
|
||||||
<td>amd64</td>
|
<td><a href="https://hub.docker.com/r/liwei2633/gopeed">前往</a></td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=gopeed-$version-qnap-amd64.qpkg">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>arm64</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=gopeed-$version-qnap-arm64.qpkg">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td rowspan="8">🌐 Web</td>
|
|
||||||
<td rowspan="3"><code>Windows</code></td>
|
|
||||||
<td>amd64</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-windows-amd64.zip">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>arm64</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-windows-arm64.zip">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>386</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-windows-386.zip">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td rowspan="2"><code>MacOS</code></td>
|
|
||||||
<td>amd64</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-macos-amd64.zip">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>arm64</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-macos-arm64.zip">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td rowspan="3"><code>Linux</code></td>
|
|
||||||
<td>amd64</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-linux-amd64.zip">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>arm64</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-linux-arm64.zip">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>386</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-linux-386.zip">📥</a></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
更多关于安装的内容请参考[安装文档](https://docs.gopeed.com/zh/install.html)
|
更多关于安装的内容请参考[安装文档](https://docs.gopeed.com/zh/install.html)
|
||||||
|
|
||||||
### 🛠️ 命令行工具
|
### 命令行工具
|
||||||
|
|
||||||
使用`go install`安装:
|
使用`go install`安装:
|
||||||
|
|
||||||
@@ -165,40 +94,40 @@ Gopeed(全称 Go Speed),直译过来中文名叫做`够快下载器`(不
|
|||||||
go install github.com/GopeedLab/gopeed/cmd/gopeed@latest
|
go install github.com/GopeedLab/gopeed/cmd/gopeed@latest
|
||||||
```
|
```
|
||||||
|
|
||||||
## 🔌 浏览器扩展
|
## 浏览器扩展
|
||||||
|
|
||||||
Gopeed 还提供了浏览器扩展用于接管浏览器下载,支持 Chrome、Edge、Firefox 等浏览器,具体请参考:[https://github.com/GopeedLab/browser-extension](https://github.com/GopeedLab/browser-extension)
|
Gopeed 还提供了浏览器扩展用于接管浏览器下载,支持 Chrome、Edge、Firefox 等浏览器,具体请参考:[https://github.com/GopeedLab/browser-extension](https://github.com/GopeedLab/browser-extension)
|
||||||
|
|
||||||
## 💝 赞助
|
## 赞助
|
||||||
|
|
||||||
如果觉得项目对你有帮助,请考虑[赞助](https://docs.gopeed.com/zh/donate)以支持这个项目的发展,非常感谢!
|
如果觉得项目对你有帮助,请考虑[赞助](https://docs.gopeed.com/zh/donate)以支持这个项目的发展,非常感谢!
|
||||||
|
|
||||||
## 🖼️ 界面展示
|
## 界面展示
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## 👨💻 开发
|
## 开发
|
||||||
|
|
||||||
本项目分为前端和后端两个部分,前端使用`flutter`,后端使用`Golang`,两边通过`http`协议进行通讯,在 unix 系统下,使用的是`unix socket`,在 windows 系统下,使用的是`tcp`协议。
|
本项目分为前端和后端两个部分,前端使用`flutter`,后端使用`Golang`,两边通过`http`协议进行通讯,在 unix 系统下,使用的是`unix socket`,在 windows 系统下,使用的是`tcp`协议。
|
||||||
|
|
||||||
> 前端代码位于`ui/flutter`目录下。
|
> 前端代码位于`ui/flutter`目录下。
|
||||||
|
|
||||||
### 🌍 环境要求
|
### 环境要求
|
||||||
|
|
||||||
1. Golang 1.23+
|
1. Golang 1.23+
|
||||||
2. Flutter 3.24+
|
2. Flutter 3.16+
|
||||||
|
|
||||||
### 📋 克隆项目
|
### 克隆项目
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone [email protected]:GopeedLab/gopeed.git
|
git clone [email protected]:GopeedLab/gopeed.git
|
||||||
```
|
```
|
||||||
|
|
||||||
### 🤝 贡献代码
|
### 贡献代码
|
||||||
|
|
||||||
请参考[贡献指南](CONTRIBUTING_zh-CN.md)
|
请参考[贡献指南](CONTRIBUTING_zh-CN.md)
|
||||||
|
|
||||||
### 🏗️ 编译
|
### 编译
|
||||||
|
|
||||||
#### 桌面端
|
#### 桌面端
|
||||||
|
|
||||||
@@ -245,7 +174,7 @@ gomobile init
|
|||||||
- android
|
- android
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
gomobile bind -tags nosqlite -ldflags="-w -s -checklinkname=0" -o ui/flutter/android/app/libs/libgopeed.aar -target=android -androidapi 21 -javapkg="com.gopeed" github.com/GopeedLab/gopeed/bind/mobile
|
gomobile bind -tags nosqlite -ldflags="-w -s" -o ui/flutter/android/app/libs/libgopeed.aar -target=android -androidapi 21 -javapkg="com.gopeed" github.com/GopeedLab/gopeed/bind/mobile
|
||||||
cd ui/flutter
|
cd ui/flutter
|
||||||
flutter build apk
|
flutter build apk
|
||||||
```
|
```
|
||||||
@@ -271,7 +200,7 @@ cp -r ui/flutter/build/web cmd/web/dist
|
|||||||
go build -tags nosqlite,web -ldflags="-s -w" -o bin/ github.com/GopeedLab/gopeed/cmd/web
|
go build -tags nosqlite,web -ldflags="-s -w" -o bin/ github.com/GopeedLab/gopeed/cmd/web
|
||||||
```
|
```
|
||||||
|
|
||||||
## ❤️ 感谢
|
## 感谢
|
||||||
|
|
||||||
### 贡献者
|
### 贡献者
|
||||||
|
|
||||||
|
|||||||
+46
-118
@@ -13,152 +13,80 @@
|
|||||||
|
|
||||||
[English](/README.md) | [中文](/README_zh-CN.md) | [日本語](/README_ja-JP.md) | [正體中文](/README_zh-TW.md) | [Tiếng Việt](/README_vi-VN.md)
|
[English](/README.md) | [中文](/README_zh-CN.md) | [日本語](/README_ja-JP.md) | [正體中文](/README_zh-TW.md) | [Tiếng Việt](/README_vi-VN.md)
|
||||||
|
|
||||||
## 🚀 簡介
|
## 簡介
|
||||||
|
|
||||||
Gopeed(全稱 Go Speed),是一款使用`Golang`+`Flutter`編寫的高速下載軟體,支援(HTTP、BitTorrent、Magnet)協定,同時支援所有的平台。
|
Gopeed(全稱 Go Speed),是一款使用`Golang`+`Flutter`編寫的高速下載軟體,支援(HTTP、BitTorrent、Magnet)協定,同時支援所有的平台。
|
||||||
|
|
||||||
前往 ✈ [主頁](https://gopeed.com/zh-CN) | 📖 [文檔](https://docs.gopeed.com/zh/)
|
前往 ✈ [主頁](https://gopeed.com/zh-CN) | 📖 [文檔](https://docs.gopeed.com/zh/)
|
||||||
|
|
||||||
## ⬇️ 下載
|
## 下載
|
||||||
|
|
||||||
<table>
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>運行平台</th>
|
||||||
|
<th>安裝包類型</th>
|
||||||
|
<th>下載鏈接</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td rowspan="2">🪟 Windows</td>
|
<td rowspan=2>Windows</td>
|
||||||
<td><code>EXE</code></td>
|
<td><code>EXE Installer</code></td>
|
||||||
<td>amd64</td>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64.zip">前往</a></td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64.zip">📥</a></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><code>Portable</code></td>
|
<td><code>Portable ZIP</code></td>
|
||||||
<td>amd64</td>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64-portable.zip">前往</a></td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64-portable.zip">📥</a></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td rowspan="3">🍎 MacOS</td>
|
<td>MacOS</td>
|
||||||
<td rowspan="3"><code>DMG</code></td>
|
<td><code>DMG Installer</code></td>
|
||||||
<td>universal</td>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-macos.dmg">前往</a></td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-macos.dmg">📥</a></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>amd64</td>
|
<td rowspan=4>Linux</td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-macos-amd64.dmg">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>arm64</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-macos-arm64.dmg">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td rowspan="6">🐧 Linux</td>
|
|
||||||
<td><code>Flathub</code></td>
|
<td><code>Flathub</code></td>
|
||||||
<td>amd64</td>
|
<td><a href="https://flathub.org/apps/com.gopeed.Gopeed">前往</a></td>
|
||||||
<td><a href="https://flathub.org/apps/com.gopeed.Gopeed">📥</a></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><code>SNAP</code></td>
|
<td><code>SNAP</code></td>
|
||||||
<td>amd64</td>
|
<td><a href="https://snapcraft.io/gopeed">前往</a></td>
|
||||||
<td><a href="https://snapcraft.io/gopeed">📥</a></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td rowspan="2"><code>DEB</code></td>
|
<td><code>DEB</code></td>
|
||||||
<td>amd64</td>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-amd64.deb">前往</a></td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-amd64.deb">📥</a></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>arm64</td>
|
<td><code>AppImage</code></td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-arm64.deb">📥</a></td>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-x86_64.AppImage">前往</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td rowspan="2"><code>AppImage</code></td>
|
<td>Android</td>
|
||||||
<td>amd64</td>
|
<td><code>APK</code></td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-amd64.AppImage">📥</a></td>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android.apk">前往</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>arm64</td>
|
<td>iOS</td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-arm64.AppImage">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td rowspan="4">🤖 Android</td>
|
|
||||||
<td rowspan="4"><code>APK</code></td>
|
|
||||||
<td>universal</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android.apk">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>armeabi-v7a</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android-armeabi-v7a.apk">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>arm64-v8a</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android-arm64-v8a.apk">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>x86_64</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android-x86_64.apk">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>📱 iOS</td>
|
|
||||||
<td><code>IPA</code></td>
|
<td><code>IPA</code></td>
|
||||||
<td>universal</td>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-ios.ipa">前往</a></td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-ios.ipa">📥</a></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>🐳 Docker</td>
|
<td>Web</td>
|
||||||
<td>-</td>
|
<td></td>
|
||||||
<td>universal</td>
|
<td><a href="https://github.com/GopeedLab/gopeed/releases/latest">前往</a></td>
|
||||||
<td><a href="https://hub.docker.com/r/liwei2633/gopeed">📥</a></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td rowspan="2">💾 Qnap</td>
|
<td>Docker</td>
|
||||||
<td rowspan="2"><code>QPKG</code></td>
|
<td></td>
|
||||||
<td>amd64</td>
|
<td><a href="https://hub.docker.com/r/liwei2633/gopeed">前往</a></td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=gopeed-$version-qnap-amd64.qpkg">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>arm64</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=gopeed-$version-qnap-arm64.qpkg">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td rowspan="8">🌐 Web</td>
|
|
||||||
<td rowspan="3"><code>Windows</code></td>
|
|
||||||
<td>amd64</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-windows-amd64.zip">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>arm64</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-windows-arm64.zip">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>386</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-windows-386.zip">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td rowspan="2"><code>MacOS</code></td>
|
|
||||||
<td>amd64</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-macos-amd64.zip">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>arm64</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-macos-arm64.zip">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td rowspan="3"><code>Linux</code></td>
|
|
||||||
<td>amd64</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-linux-amd64.zip">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>arm64</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-linux-arm64.zip">📥</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>386</td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-linux-386.zip">📥</a></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
更多關於安裝的內容請參考[安裝文檔](https://docs.gopeed.com/zh/install.html)
|
更多關於安裝的內容請參考[安裝文檔](https://docs.gopeed.com/zh/install.html)
|
||||||
|
|
||||||
### 🛠️ 使用 CLI 安裝
|
### 使用CLI安裝
|
||||||
|
|
||||||
使用`go install`安裝:
|
使用`go install`安裝:
|
||||||
|
|
||||||
@@ -166,36 +94,36 @@ Gopeed(全稱 Go Speed),是一款使用`Golang`+`Flutter`編寫的高速
|
|||||||
go install github.com/GopeedLab/gopeed/cmd/gopeed@latest
|
go install github.com/GopeedLab/gopeed/cmd/gopeed@latest
|
||||||
```
|
```
|
||||||
|
|
||||||
## 💝 贊助
|
## 贊助
|
||||||
|
|
||||||
如果你認為該項目對你有所幫助,請考慮[贊助](https://docs.gopeed.com/zh/donate)以支持該項目的持續發展,謝謝!
|
如果你認為該項目對你有所幫助,請考慮[贊助](https://docs.gopeed.com/zh/donate)以支持該項目的持續發展,謝謝!
|
||||||
|
|
||||||
## 🖼️ 軟體介面
|
## 軟體介面
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## 👨💻 開發
|
## 開發
|
||||||
|
|
||||||
該項目分為前端與後端,前端使用`flutter`編寫,後端使用`Golang`編寫,兩邊通過`http`協定進行通訊,在 unix 系統下,則使用`unix socket`,在 windows 系統下,則使用`tcp`協定。
|
該項目分為前端與後端,前端使用`flutter`編寫,後端使用`Golang`編寫,兩邊通過`http`協定進行通訊,在 unix 系統下,則使用`unix socket`,在 windows 系統下,則使用`tcp`協定。
|
||||||
|
|
||||||
> 前端代碼位於`ui/flutter`目錄內。
|
> 前端代碼位於`ui/flutter`目錄內。
|
||||||
|
|
||||||
### 🌍 開發環境
|
### 開發環境
|
||||||
|
|
||||||
1. Golang 1.23+
|
1. Golang 1.23+
|
||||||
2. Flutter 3.24+
|
2. Flutter 3.16+
|
||||||
|
|
||||||
### 📋 克隆項目
|
### 克隆項目
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone [email protected]:GopeedLab/gopeed.git
|
git clone [email protected]:GopeedLab/gopeed.git
|
||||||
```
|
```
|
||||||
|
|
||||||
### 🤝 協助開發
|
### 協助開發
|
||||||
|
|
||||||
請參考[協助指南](CONTRIBUTING_zh-TW.md)
|
請參考[協助指南](CONTRIBUTING_zh-TW.md)
|
||||||
|
|
||||||
### 🏗️ 編譯
|
### 編譯
|
||||||
|
|
||||||
#### 桌面端
|
#### 桌面端
|
||||||
|
|
||||||
@@ -242,7 +170,7 @@ gomobile init
|
|||||||
- android
|
- android
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
gomobile bind -tags nosqlite -ldflags="-w -s -checklinkname=0" -o ui/flutter/android/app/libs/libgopeed.aar -target=android -androidapi 21 -javapkg="com.gopeed" github.com/GopeedLab/gopeed/bind/mobile
|
gomobile bind -tags nosqlite -ldflags="-w -s" -o ui/flutter/android/app/libs/libgopeed.aar -target=android -androidapi 21 -javapkg="com.gopeed" github.com/GopeedLab/gopeed/bind/mobile
|
||||||
cd ui/flutter
|
cd ui/flutter
|
||||||
flutter build apk
|
flutter build apk
|
||||||
```
|
```
|
||||||
@@ -268,7 +196,7 @@ cp -r ui/flutter/build/web cmd/web/dist
|
|||||||
go build -tags nosqlite,web -ldflags="-s -w" -o bin/ github.com/GopeedLab/gopeed/cmd/web
|
go build -tags nosqlite,web -ldflags="-s -w" -o bin/ github.com/GopeedLab/gopeed/cmd/web
|
||||||
```
|
```
|
||||||
|
|
||||||
## ❤️ 感謝
|
## 感謝
|
||||||
|
|
||||||
### 貢獻者
|
### 貢獻者
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/GopeedLab/gopeed/pkg/base"
|
"github.com/GopeedLab/gopeed/pkg/base"
|
||||||
"github.com/GopeedLab/gopeed/pkg/download"
|
"github.com/GopeedLab/gopeed/pkg/download"
|
||||||
"github.com/GopeedLab/gopeed/pkg/protocol/http"
|
"github.com/GopeedLab/gopeed/pkg/protocol/http"
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package bind
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"github.com/GopeedLab/gopeed/pkg/api"
|
||||||
|
"github.com/GopeedLab/gopeed/pkg/api/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Global singleton instance
|
||||||
|
var instance *api.Instance
|
||||||
|
|
||||||
|
func Init(config *model.StartConfig) (err error) {
|
||||||
|
if instance != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
config.ProductionMode = true
|
||||||
|
instance, err = api.Create(config)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func Invoke(request *api.Request) string {
|
||||||
|
if instance == nil {
|
||||||
|
return BuildResult(errors.New("instance not initialized"))
|
||||||
|
}
|
||||||
|
return jsonEncode(api.Invoke(instance, request))
|
||||||
|
}
|
||||||
|
|
||||||
|
func BuildResult(data any) string {
|
||||||
|
if err, ok := data.(error); ok {
|
||||||
|
buf, _ := json.Marshal(model.NewErrorResult[any](err.Error()))
|
||||||
|
return string(buf)
|
||||||
|
}
|
||||||
|
return jsonEncode(model.NewOkResult(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
func jsonEncode(data any) string {
|
||||||
|
buf, _ := json.Marshal(data)
|
||||||
|
return string(buf)
|
||||||
|
}
|
||||||
+19
-14
@@ -3,27 +3,32 @@ package main
|
|||||||
import "C"
|
import "C"
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"github.com/GopeedLab/gopeed/pkg/rest"
|
"github.com/GopeedLab/gopeed/bind"
|
||||||
"github.com/GopeedLab/gopeed/pkg/rest/model"
|
"github.com/GopeedLab/gopeed/pkg/api"
|
||||||
|
"github.com/GopeedLab/gopeed/pkg/api/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {}
|
func main() {}
|
||||||
|
|
||||||
//export Start
|
//export Init
|
||||||
func Start(cfg *C.char) (int, *C.char) {
|
func Init(cfg *C.char) *C.char {
|
||||||
var config model.StartConfig
|
var config model.StartConfig
|
||||||
if err := json.Unmarshal([]byte(C.GoString(cfg)), &config); err != nil {
|
if err := json.Unmarshal([]byte(C.GoString(cfg)), &config); err != nil {
|
||||||
return 0, C.CString(err.Error())
|
return C.CString(err.Error())
|
||||||
}
|
}
|
||||||
config.ProductionMode = true
|
if err := bind.Init(&config); err != nil {
|
||||||
realPort, err := rest.Start(&config)
|
return C.CString(err.Error())
|
||||||
if err != nil {
|
|
||||||
return 0, C.CString(err.Error())
|
|
||||||
}
|
|
||||||
return realPort, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//export Stop
|
return nil
|
||||||
func Stop() {
|
}
|
||||||
rest.Stop()
|
|
||||||
|
//export Invoke
|
||||||
|
func Invoke(req *C.char) *C.char {
|
||||||
|
var request api.Request
|
||||||
|
if err := json.Unmarshal([]byte(C.GoString(req)), &request); err != nil {
|
||||||
|
return C.CString(bind.BuildResult(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return C.CString(bind.Invoke(&request))
|
||||||
}
|
}
|
||||||
+7
-12
@@ -3,20 +3,15 @@ package libgopeed
|
|||||||
// #cgo LDFLAGS: -static-libstdc++
|
// #cgo LDFLAGS: -static-libstdc++
|
||||||
import "C"
|
import "C"
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"github.com/GopeedLab/gopeed/bind"
|
||||||
"github.com/GopeedLab/gopeed/pkg/rest"
|
"github.com/GopeedLab/gopeed/pkg/api"
|
||||||
"github.com/GopeedLab/gopeed/pkg/rest/model"
|
"github.com/GopeedLab/gopeed/pkg/api/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Start(cfg string) (int, error) {
|
func Init(cfg *model.StartConfig) error {
|
||||||
var config model.StartConfig
|
return bind.Init(cfg)
|
||||||
if err := json.Unmarshal([]byte(cfg), &config); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
config.ProductionMode = true
|
|
||||||
return rest.Start(&config)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Stop() {
|
func Invoke(request *api.Request) string {
|
||||||
rest.Stop()
|
return bind.Invoke(request)
|
||||||
}
|
}
|
||||||
+9
-3
@@ -2,15 +2,21 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/GopeedLab/gopeed/cmd"
|
"github.com/GopeedLab/gopeed/cmd"
|
||||||
"github.com/GopeedLab/gopeed/pkg/rest/model"
|
"github.com/GopeedLab/gopeed/pkg/api/model"
|
||||||
|
"github.com/GopeedLab/gopeed/pkg/base"
|
||||||
)
|
)
|
||||||
|
|
||||||
// only for local development
|
// only for local development
|
||||||
func main() {
|
func main() {
|
||||||
cfg := &model.StartConfig{
|
cfg := &model.StartConfig{
|
||||||
Network: "tcp",
|
|
||||||
Address: "127.0.0.1:9999",
|
|
||||||
Storage: model.StorageBolt,
|
Storage: model.StorageBolt,
|
||||||
|
DownloadConfig: &base.DownloaderStoreConfig{
|
||||||
|
Http: &base.DownloaderHttpConfig{
|
||||||
|
Enable: true,
|
||||||
|
Host: "127.0.0.1",
|
||||||
|
Port: 9999,
|
||||||
|
},
|
||||||
|
},
|
||||||
WebEnable: true,
|
WebEnable: true,
|
||||||
}
|
}
|
||||||
cmd.Start(cfg)
|
cmd.Start(cfg)
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
//go:build !windows
|
|
||||||
// +build !windows
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Dial() (net.Conn, error) {
|
|
||||||
// Get binary path
|
|
||||||
exe, err := os.Executable()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return net.Dial("unix", filepath.Join(filepath.Dir(exe), "gopeed_host.sock"))
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/Microsoft/go-winio"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Dial() (net.Conn, error) {
|
|
||||||
return winio.DialPipe(`\\.\pipe\gopeed_host`, nil)
|
|
||||||
}
|
|
||||||
@@ -1,170 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"encoding/binary"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/pkg/browser"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Message struct {
|
|
||||||
Method string `json:"method"`
|
|
||||||
Meta map[string]any `json:"meta"`
|
|
||||||
Params json.RawMessage `json:"params"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Response struct {
|
|
||||||
Code int `json:"code"`
|
|
||||||
Data any `json:"data,omitempty"`
|
|
||||||
Message string `json:"message,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func check() (data bool, err error) {
|
|
||||||
conn, err := Dial()
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
defer conn.Close()
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func wakeup(hidden bool) error {
|
|
||||||
running, _ := check()
|
|
||||||
if running {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
uri := "gopeed:"
|
|
||||||
if hidden {
|
|
||||||
uri = uri + "?hidden=true"
|
|
||||||
}
|
|
||||||
if err := browser.OpenURL(uri); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < 10; i++ {
|
|
||||||
if ok, _ := check(); ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
time.Sleep(1 * time.Second)
|
|
||||||
}
|
|
||||||
return fmt.Errorf("start gopeed failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
var apiMap = map[string]func(message *Message) (data any, err error){
|
|
||||||
"ping": func(message *Message) (data any, err error) {
|
|
||||||
return check()
|
|
||||||
},
|
|
||||||
"create": func(message *Message) (data any, err error) {
|
|
||||||
buf, err := message.Params.MarshalJSON()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
silent := false
|
|
||||||
if v, ok := message.Meta["silent"]; ok {
|
|
||||||
silent, _ = v.(bool)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := wakeup(silent); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
client := &http.Client{
|
|
||||||
Transport: &http.Transport{
|
|
||||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
|
||||||
return Dial()
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Timeout: 10 * time.Second,
|
|
||||||
}
|
|
||||||
req, err := http.NewRequest("POST", "http://127.0.0.1/create", bytes.NewBuffer(buf))
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if message.Meta != nil {
|
|
||||||
metaJson, _ := json.Marshal(message.Meta)
|
|
||||||
req.Header.Set("X-Gopeed-Host-Meta", string(metaJson))
|
|
||||||
}
|
|
||||||
_, err = client.Do(req)
|
|
||||||
return
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// go build -ldflags="-s -w" -o ui/flutter/assets/exec/ github.com/GopeedLab/gopeed/cmd/host
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
for {
|
|
||||||
// Read message length (first 4 bytes)
|
|
||||||
var length uint32
|
|
||||||
if err := binary.Read(os.Stdin, binary.NativeEndian, &length); err != nil {
|
|
||||||
if err == io.EOF {
|
|
||||||
// Connection closed by Chrome
|
|
||||||
return
|
|
||||||
}
|
|
||||||
sendError("Failed to read message length: " + err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read the message
|
|
||||||
input := make([]byte, length)
|
|
||||||
if _, err := io.ReadFull(os.Stdin, input); err != nil {
|
|
||||||
sendError("Failed to read message: " + err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse message
|
|
||||||
var message Message
|
|
||||||
if err := json.Unmarshal(input, &message); err != nil {
|
|
||||||
sendError("Failed to parse message: " + err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle request
|
|
||||||
var data any
|
|
||||||
var err error
|
|
||||||
if handler, ok := apiMap[message.Method]; ok {
|
|
||||||
data, err = handler(&message)
|
|
||||||
} else {
|
|
||||||
err = errors.New("Unknown method: " + message.Method)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
sendError(err.Error())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
sendResponse(0, data, "")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func sendResponse(code int, data interface{}, message string) {
|
|
||||||
response := Response{
|
|
||||||
Code: code,
|
|
||||||
Data: data,
|
|
||||||
Message: message,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Encode response
|
|
||||||
responseBytes, err := json.Marshal(response)
|
|
||||||
if err != nil {
|
|
||||||
sendError("Failed to encode response: " + err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write message length
|
|
||||||
binary.Write(os.Stdout, binary.NativeEndian, uint32(len(responseBytes)))
|
|
||||||
// Write message
|
|
||||||
os.Stdout.Write(responseBytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
func sendError(msg string) {
|
|
||||||
sendResponse(1, nil, msg)
|
|
||||||
}
|
|
||||||
+18
-14
@@ -3,10 +3,9 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
_ "embed"
|
_ "embed"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/GopeedLab/gopeed/pkg/api"
|
||||||
|
"github.com/GopeedLab/gopeed/pkg/api/model"
|
||||||
"github.com/GopeedLab/gopeed/pkg/base"
|
"github.com/GopeedLab/gopeed/pkg/base"
|
||||||
"github.com/GopeedLab/gopeed/pkg/rest"
|
|
||||||
"github.com/GopeedLab/gopeed/pkg/rest/model"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -18,20 +17,22 @@ var banner string
|
|||||||
|
|
||||||
func Start(cfg *model.StartConfig) {
|
func Start(cfg *model.StartConfig) {
|
||||||
fmt.Println(banner)
|
fmt.Println(banner)
|
||||||
srv, listener, err := rest.BuildServer(cfg)
|
instance, err := api.Create(cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
downloadCfg, err := rest.Downloader.GetConfig()
|
|
||||||
if err != nil {
|
downloadCfgResult := instance.GetConfig()
|
||||||
panic(err)
|
if downloadCfgResult.HasError() {
|
||||||
|
panic(downloadCfgResult.Msg)
|
||||||
}
|
}
|
||||||
|
downloadCfg := downloadCfgResult.Data
|
||||||
if downloadCfg.FirstLoad {
|
if downloadCfg.FirstLoad {
|
||||||
// Set default download config
|
// Set default download config
|
||||||
if cfg.DownloadConfig != nil {
|
if cfg.DownloadConfig != nil {
|
||||||
cfg.DownloadConfig.Merge(downloadCfg)
|
cfg.DownloadConfig.Merge(downloadCfg)
|
||||||
// TODO Use PatchConfig
|
// TODO Use PatchConfig
|
||||||
rest.Downloader.PutConfig(cfg.DownloadConfig)
|
instance.PutConfig(cfg.DownloadConfig)
|
||||||
downloadCfg = cfg.DownloadConfig
|
downloadCfg = cfg.DownloadConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,25 +49,28 @@ func Start(cfg *model.StartConfig) {
|
|||||||
}
|
}
|
||||||
if downloadDir != "" {
|
if downloadDir != "" {
|
||||||
downloadCfg.DownloadDir = downloadDir
|
downloadCfg.DownloadDir = downloadDir
|
||||||
rest.Downloader.PutConfig(downloadCfg)
|
instance.PutConfig(downloadCfg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
watchExit()
|
|
||||||
|
|
||||||
fmt.Printf("Server start success on http://%s\n", listener.Addr().String())
|
watchExit(instance)
|
||||||
if err := srv.Serve(listener); err != nil && err != http.ErrServerClosed {
|
|
||||||
|
port, start, err := api.ListenHttp(cfg.DownloadConfig.Http, instance)
|
||||||
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
fmt.Printf("Server start success on http://%s:%d\n", cfg.DownloadConfig.Http.Host, port)
|
||||||
|
start()
|
||||||
}
|
}
|
||||||
|
|
||||||
func watchExit() {
|
func watchExit(instance *api.Instance) {
|
||||||
sigs := make(chan os.Signal, 1)
|
sigs := make(chan os.Signal, 1)
|
||||||
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
|
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
|
||||||
go func() {
|
go func() {
|
||||||
sig := <-sigs
|
sig := <-sigs
|
||||||
fmt.Printf("Server is shutting down due to signal: %s\n", sig)
|
fmt.Printf("Server is shutting down due to signal: %s\n", sig)
|
||||||
rest.Downloader.Close()
|
instance.Close()
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
@@ -1,115 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/pkg/browser"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
// go build -ldflags="-s -w" -o ui/flutter/assets/exec/ github.com/GopeedLab/gopeed/cmd/updater
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
pid := flag.Int("pid", 0, "PID of the process to update")
|
|
||||||
updateChannel := flag.String("channel", "", "Update channel")
|
|
||||||
packagePath := flag.String("asset", "", "Path to the package asset")
|
|
||||||
exeDir := flag.String("exeDir", "", "Directory of the entry executable")
|
|
||||||
logPath := flag.String("log", "", "Log file path")
|
|
||||||
flag.Parse()
|
|
||||||
|
|
||||||
if *pid == 0 {
|
|
||||||
log.Println("Invalid PID")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
if *updateChannel == "" {
|
|
||||||
log.Println("Invalid update channel")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if *logPath != "" {
|
|
||||||
logFile, err := os.OpenFile(*logPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0666)
|
|
||||||
if err == nil {
|
|
||||||
defer logFile.Close()
|
|
||||||
log.SetOutput(logFile)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
restart bool
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
if restart, err = update(*pid, *updateChannel, *packagePath, *exeDir); err != nil {
|
|
||||||
log.Printf("Update failed: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Restart the application
|
|
||||||
if restart {
|
|
||||||
browser.OpenURL("gopeed:///")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete package asset
|
|
||||||
if *packagePath != "" {
|
|
||||||
os.Remove(*packagePath)
|
|
||||||
}
|
|
||||||
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func update(pid int, updateChannel, packagePath, exeDir string) (restart bool, err error) {
|
|
||||||
killSignalChan := make(chan any, 1)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
<-killSignalChan
|
|
||||||
|
|
||||||
if err = killProcess(pid); err != nil {
|
|
||||||
log.Printf("Failed to kill process: %v\n", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = waitForProcessExit(pid); err != nil {
|
|
||||||
log.Printf("Failed to wait for process exit: %v\n", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
log.Printf("Updating process updateChannel=%s packagePath=%s exeDir=%s\n", updateChannel, packagePath, exeDir)
|
|
||||||
if restart, err = install(killSignalChan, updateChannel, packagePath, exeDir); err != nil {
|
|
||||||
return false, errors.Wrap(err, "failed to install package")
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func waitForProcessExit(pid int) error {
|
|
||||||
deadline := time.Now().Add(10 * time.Second)
|
|
||||||
for time.Now().Before(deadline) {
|
|
||||||
process, err := os.FindProcess(pid)
|
|
||||||
if err != nil {
|
|
||||||
// On some systems, error is returned if process doesn't exist
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send null signal to test if process exists
|
|
||||||
err = process.Signal(syscall.Signal(0))
|
|
||||||
if err != nil {
|
|
||||||
// If error occurs, the process no longer exists
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(100 * time.Millisecond)
|
|
||||||
}
|
|
||||||
return fmt.Errorf("process %d still running after timeout", pid)
|
|
||||||
}
|
|
||||||
|
|
||||||
func killProcess(pid int) error {
|
|
||||||
process, err := os.FindProcess(pid)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return process.Kill()
|
|
||||||
}
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
//go:build darwin
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
func install(killSignalChan chan<- any, updateChannel, packagePath, destDir string) (bool, error) {
|
|
||||||
return true, installByDmg(killSignalChan, packagePath, destDir)
|
|
||||||
}
|
|
||||||
|
|
||||||
// installByDmg handles macOS dmg package installation
|
|
||||||
func installByDmg(killSignalChan chan<- any, packagePath, destDir string) error {
|
|
||||||
// /Applications/Gopeed.app/Contents/MacOS -> /Applications
|
|
||||||
appPath := getParentDir(getParentDir(getParentDir(destDir)))
|
|
||||||
output, err := exec.Command("hdiutil", "attach", packagePath, "-nobrowse").Output()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
mountPoint := ""
|
|
||||||
for _, line := range strings.Split(string(output), "\n") {
|
|
||||||
if strings.Contains(line, "/Volumes/") {
|
|
||||||
// Find the /Volumes/ path in the line
|
|
||||||
// hdiutil output format: /dev/disk4s1 Apple_HFS /Volumes/Gopeed
|
|
||||||
// or with sequence number: /dev/disk4s1 Apple_HFS /Volumes/Gopeed 1
|
|
||||||
idx := strings.Index(line, "/Volumes/")
|
|
||||||
if idx != -1 {
|
|
||||||
// Extract everything from /Volumes/ onwards and trim whitespace
|
|
||||||
mountPoint = strings.TrimSpace(line[idx:])
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if mountPoint == "" {
|
|
||||||
return fmt.Errorf("failed to get mount point from hdiutil output: %s", string(output))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Detach the mounted DMG
|
|
||||||
defer exec.Command("hdiutil", "detach", mountPoint, "-quiet").Run()
|
|
||||||
|
|
||||||
matches, err := filepath.Glob(filepath.Join(mountPoint, "*.app"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if len(matches) == 0 {
|
|
||||||
return fmt.Errorf("no .app found in dmg, mountPoint: %s", mountPoint)
|
|
||||||
}
|
|
||||||
|
|
||||||
killSignalChan <- nil
|
|
||||||
|
|
||||||
// Copy the new app to the destination
|
|
||||||
// cp -Rf /Volumes/GoPeed/GoPeed.app /Applications
|
|
||||||
if err := exec.Command("cp", "-Rf", matches[0], appPath).Run(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get parent directory safely, handling trailing separators
|
|
||||||
func getParentDir(path string) string {
|
|
||||||
// Remove trailing separators if they exist
|
|
||||||
path = strings.TrimRight(path, string(filepath.Separator))
|
|
||||||
// Now get the parent directory
|
|
||||||
return filepath.Dir(path)
|
|
||||||
}
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
//go:build linux
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os/exec"
|
|
||||||
)
|
|
||||||
|
|
||||||
func install(killSignalChan chan<- any, updateChannel, packagePath, destDir string) (bool, error) {
|
|
||||||
killSignalChan <- nil
|
|
||||||
switch updateChannel {
|
|
||||||
case "linuxDeb":
|
|
||||||
return true, installByDeb(packagePath)
|
|
||||||
case "linuxFlathub":
|
|
||||||
return true, installByFlathub()
|
|
||||||
case "linuxSnap":
|
|
||||||
return true, installBySnap()
|
|
||||||
default:
|
|
||||||
return false, fmt.Errorf("unsupported update channel for Linux: %s", updateChannel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// executeInTerminal tries to execute a command in one of several common terminal emulators
|
|
||||||
func executeInTerminal(command string) error {
|
|
||||||
terminals := []string{
|
|
||||||
"gnome-terminal", // GNOME
|
|
||||||
"konsole", // KDE
|
|
||||||
"xfce4-terminal", // XFCE
|
|
||||||
"xterm", // X11
|
|
||||||
}
|
|
||||||
|
|
||||||
command = fmt.Sprintf(`echo "Starting update..." && echo "[CMD] %s" && %s`, command, command)
|
|
||||||
|
|
||||||
for _, term := range terminals {
|
|
||||||
if _, err := exec.LookPath(term); err == nil {
|
|
||||||
var cmd *exec.Cmd
|
|
||||||
switch term {
|
|
||||||
case "gnome-terminal", "xfce4-terminal":
|
|
||||||
cmd = exec.Command(term, "--", "bash", "-c", command)
|
|
||||||
case "konsole":
|
|
||||||
cmd = exec.Command(term, "-e", "bash", "-c", command)
|
|
||||||
case "xterm":
|
|
||||||
cmd = exec.Command(term, "-e", command)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := cmd.Start(); err == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf("no suitable terminal emulator found. Please install gnome-terminal, konsole, xfce4-terminal or xterm")
|
|
||||||
}
|
|
||||||
|
|
||||||
// installByDeb installs the .deb package
|
|
||||||
func installByDeb(packagePath string) error {
|
|
||||||
command := fmt.Sprintf(`sudo dpkg -i "%s"`, packagePath)
|
|
||||||
return executeInTerminal(command)
|
|
||||||
}
|
|
||||||
|
|
||||||
// installByFlathub updates the application via Flathub
|
|
||||||
func installByFlathub() error {
|
|
||||||
command := "flatpak update com.gopeed.Gopeed -y"
|
|
||||||
return executeInTerminal(command)
|
|
||||||
}
|
|
||||||
|
|
||||||
// installBySnap updates the application via Snap
|
|
||||||
func installBySnap() error {
|
|
||||||
command := "sudo snap refresh gopeed"
|
|
||||||
return executeInTerminal(command)
|
|
||||||
}
|
|
||||||
@@ -1,134 +0,0 @@
|
|||||||
//go:build windows
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"archive/zip"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
func install(killSignalChan chan<- any, updateChannel, packagePath, destDir string) (bool, error) {
|
|
||||||
switch updateChannel {
|
|
||||||
case "windowsInstaller":
|
|
||||||
return false, installByInstaller(killSignalChan, packagePath, destDir)
|
|
||||||
default:
|
|
||||||
return true, installByPortable(killSignalChan, packagePath, destDir)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// installByInstaller extracts the installer from the zip file and runs it
|
|
||||||
func installByInstaller(killSignalChan chan<- any, packagePath, destDir string) error {
|
|
||||||
// Create a temp directory for extraction
|
|
||||||
tempDir, err := os.MkdirTemp("", "gopeed_update")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(tempDir)
|
|
||||||
|
|
||||||
// Extract the zip file
|
|
||||||
reader, err := zip.OpenReader(packagePath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer reader.Close()
|
|
||||||
|
|
||||||
// Find the installer file
|
|
||||||
var installerPath string
|
|
||||||
for _, file := range reader.File {
|
|
||||||
if file.FileInfo().IsDir() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract file
|
|
||||||
path := filepath.Join(tempDir, file.Name)
|
|
||||||
|
|
||||||
if err := os.MkdirAll(filepath.Dir(path), os.ModePerm); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
dstFile, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, file.Mode())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
srcFile, err := file.Open()
|
|
||||||
if err != nil {
|
|
||||||
dstFile.Close()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = io.Copy(dstFile, srcFile)
|
|
||||||
srcFile.Close()
|
|
||||||
dstFile.Close()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// If this is likely an installer (.exe, .msi), save its path
|
|
||||||
ext := strings.ToLower(filepath.Ext(file.Name))
|
|
||||||
if ext == ".exe" || ext == ".msi" {
|
|
||||||
installerPath = path
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if installerPath == "" {
|
|
||||||
return fmt.Errorf("no installer found in the update package")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run the installer
|
|
||||||
cmd := exec.Command(installerPath)
|
|
||||||
if err := cmd.Start(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
killSignalChan <- nil
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// installByPortable extracts the portable version to the destination directory
|
|
||||||
func installByPortable(killSignalChan chan<- any, packagePath, destDir string) error {
|
|
||||||
killSignalChan <- nil
|
|
||||||
|
|
||||||
reader, err := zip.OpenReader(packagePath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer reader.Close()
|
|
||||||
|
|
||||||
for _, file := range reader.File {
|
|
||||||
path := filepath.Join(destDir, file.Name)
|
|
||||||
|
|
||||||
if file.FileInfo().IsDir() {
|
|
||||||
os.MkdirAll(path, file.Mode())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := os.MkdirAll(filepath.Dir(path), os.ModePerm); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
dstFile, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, file.Mode())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
srcFile, err := file.Open()
|
|
||||||
if err != nil {
|
|
||||||
dstFile.Close()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = io.Copy(dstFile, srcFile)
|
|
||||||
srcFile.Close()
|
|
||||||
dstFile.Close()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
+46
-161
@@ -3,23 +3,19 @@ package main
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"flag"
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"github.com/GopeedLab/gopeed/pkg/base"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/GopeedLab/gopeed/pkg/base"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type args struct {
|
type args struct {
|
||||||
Address *string `json:"address"`
|
Host *string `json:"host"`
|
||||||
Port *int `json:"port"`
|
Port *int `json:"port"`
|
||||||
Username *string `json:"username"`
|
Username *string `json:"username"`
|
||||||
Password *string `json:"password"`
|
Password *string `json:"password"`
|
||||||
ApiToken *string `json:"apiToken"`
|
ApiToken *string `json:"apiToken"`
|
||||||
StorageDir *string `json:"storageDir"`
|
StorageDir *string `json:"storageDir"`
|
||||||
WhiteDownloadDirs []string `json:"whiteDownloadDirs"`
|
|
||||||
// DownloadConfig when the first time to start the server, it will be configured as initial value
|
// DownloadConfig when the first time to start the server, it will be configured as initial value
|
||||||
DownloadConfig *base.DownloaderStoreConfig `json:"downloadConfig"`
|
DownloadConfig *base.DownloaderStoreConfig `json:"downloadConfig"`
|
||||||
|
|
||||||
@@ -27,173 +23,62 @@ type args struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func parse() *args {
|
func parse() *args {
|
||||||
cfg := &args{}
|
var cliArgs args
|
||||||
|
cliArgs.Host = flag.String("H", "0.0.0.0", "Bind Host")
|
||||||
cliConfig := loadCliArgs()
|
cliArgs.Port = flag.Int("P", 9999, "Bind Port")
|
||||||
loadConfigFile(cfg, *cliConfig.configPath)
|
cliArgs.Username = flag.String("u", "gopeed", "HTTP Basic Auth Username")
|
||||||
loadEnvVars(cfg)
|
cliArgs.Password = flag.String("p", "", "HTTP Basic Auth Pwd")
|
||||||
// override with non-default command line arguments
|
cliArgs.ApiToken = flag.String("T", "", "API token, it must be configured when using HTTP API in the case of enabling basic authentication")
|
||||||
overrideWithCliArgs(cfg, cliConfig)
|
cliArgs.StorageDir = flag.String("d", "", "Storage directory")
|
||||||
// set default values
|
cliArgs.configPath = flag.String("c", "./config.json", "Config file path")
|
||||||
setDefaults(cfg, cliConfig)
|
|
||||||
return cfg
|
|
||||||
}
|
|
||||||
|
|
||||||
// loadCliArgs parses command line arguments and returns initial config
|
|
||||||
func loadCliArgs() *args {
|
|
||||||
cfg := &args{}
|
|
||||||
cfg.Address = flag.String("A", "0.0.0.0", "Bind Address")
|
|
||||||
cfg.Port = flag.Int("P", 9999, "Bind Port")
|
|
||||||
cfg.Username = flag.String("u", "gopeed", "Web Authentication Username")
|
|
||||||
cfg.Password = flag.String("p", "", "Web Authentication Password, if no password is set, web authentication will not be enabled")
|
|
||||||
cfg.ApiToken = flag.String("T", "", "API token, it must be configured when using HTTP API in the case of enabling web authentication")
|
|
||||||
cfg.StorageDir = flag.String("d", "", "Storage directory")
|
|
||||||
whiteDownloadDirs := flag.String("w", "", "White download directories, comma-separated")
|
|
||||||
cfg.configPath = flag.String("c", "./config.json", "Config file path")
|
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
// Parse white download directories from comma-separated string
|
// args priority: config file > cli args
|
||||||
if whiteDownloadDirs != nil && *whiteDownloadDirs != "" {
|
cfgArgs := loadConfig(*cliArgs.configPath)
|
||||||
dirs := strings.Split(*whiteDownloadDirs, ",")
|
if cfgArgs.Host == nil {
|
||||||
for i := range dirs {
|
cfgArgs.Host = cliArgs.Host
|
||||||
dirs[i] = strings.TrimSpace(dirs[i])
|
|
||||||
}
|
}
|
||||||
cfg.WhiteDownloadDirs = dirs
|
if cfgArgs.Port == nil {
|
||||||
|
cfgArgs.Port = cliArgs.Port
|
||||||
|
}
|
||||||
|
if cfgArgs.Username == nil {
|
||||||
|
cfgArgs.Username = cliArgs.Username
|
||||||
|
}
|
||||||
|
if cfgArgs.Password == nil {
|
||||||
|
cfgArgs.Password = cliArgs.Password
|
||||||
|
}
|
||||||
|
if cfgArgs.ApiToken == nil {
|
||||||
|
cfgArgs.ApiToken = cliArgs.ApiToken
|
||||||
|
}
|
||||||
|
if cfgArgs.StorageDir == nil {
|
||||||
|
cfgArgs.StorageDir = cliArgs.StorageDir
|
||||||
|
}
|
||||||
|
return cfgArgs
|
||||||
}
|
}
|
||||||
|
|
||||||
return cfg
|
func loadConfig(path string) *args {
|
||||||
}
|
var args args
|
||||||
|
|
||||||
// overrideWithCliArgs overrides config with non-empty command line arguments
|
if !filepath.IsAbs(path) {
|
||||||
func overrideWithCliArgs(cfg *args, cliConfig *args) {
|
|
||||||
flag.Visit(func(f *flag.Flag) {
|
|
||||||
switch f.Name {
|
|
||||||
case "A":
|
|
||||||
cfg.Address = cliConfig.Address
|
|
||||||
case "P":
|
|
||||||
cfg.Port = cliConfig.Port
|
|
||||||
case "u":
|
|
||||||
cfg.Username = cliConfig.Username
|
|
||||||
case "p":
|
|
||||||
cfg.Password = cliConfig.Password
|
|
||||||
case "T":
|
|
||||||
cfg.ApiToken = cliConfig.ApiToken
|
|
||||||
case "d":
|
|
||||||
cfg.StorageDir = cliConfig.StorageDir
|
|
||||||
case "w":
|
|
||||||
cfg.WhiteDownloadDirs = cliConfig.WhiteDownloadDirs
|
|
||||||
case "c":
|
|
||||||
cfg.configPath = cliConfig.configPath
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// setDefaults sets default values for any unset configuration fields
|
|
||||||
func setDefaults(cfg *args, cliConfig *args) {
|
|
||||||
if cfg.Address == nil {
|
|
||||||
cfg.Address = cliConfig.Address
|
|
||||||
}
|
|
||||||
if cfg.Port == nil {
|
|
||||||
cfg.Port = cliConfig.Port
|
|
||||||
}
|
|
||||||
if cfg.Username == nil {
|
|
||||||
cfg.Username = cliConfig.Username
|
|
||||||
}
|
|
||||||
if cfg.Password == nil {
|
|
||||||
cfg.Password = cliConfig.Password
|
|
||||||
}
|
|
||||||
if cfg.ApiToken == nil {
|
|
||||||
cfg.ApiToken = cliConfig.ApiToken
|
|
||||||
}
|
|
||||||
if cfg.StorageDir == nil {
|
|
||||||
cfg.StorageDir = cliConfig.StorageDir
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// loadConfigFile loads configuration from file
|
|
||||||
func loadConfigFile(cfg *args, configPath string) {
|
|
||||||
if !filepath.IsAbs(configPath) {
|
|
||||||
dir, err := os.Getwd()
|
dir, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
fmt.Println("config dir get failed, reason:" + err.Error())
|
||||||
|
return &args
|
||||||
}
|
}
|
||||||
configPath = filepath.Join(dir, configPath)
|
path = filepath.Join(dir, path)
|
||||||
}
|
}
|
||||||
|
file, err := os.ReadFile(path)
|
||||||
file, err := os.ReadFile(configPath)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
return
|
return &args
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = json.Unmarshal(file, cfg); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// loadEnvVars loads configuration from environment variables with prefix GOPEED_
|
|
||||||
func loadEnvVars(cfg *args) {
|
|
||||||
v := reflect.ValueOf(cfg).Elem()
|
|
||||||
t := reflect.TypeOf(cfg).Elem()
|
|
||||||
|
|
||||||
for i := 0; i < v.NumField(); i++ {
|
|
||||||
field := v.Field(i)
|
|
||||||
fieldType := t.Field(i)
|
|
||||||
|
|
||||||
// Get json tag as environment variable suffix
|
|
||||||
jsonTag := fieldType.Tag.Get("json")
|
|
||||||
if jsonTag == "" || jsonTag == "-" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove options like omitempty
|
|
||||||
if commaIdx := strings.Index(jsonTag, ","); commaIdx != -1 {
|
|
||||||
jsonTag = jsonTag[:commaIdx]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert to uppercase and add GOPEED_ prefix
|
|
||||||
envKey := "GOPEED_" + strings.ToUpper(jsonTag)
|
|
||||||
envValue := os.Getenv(envKey)
|
|
||||||
|
|
||||||
if envValue == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set value based on field type
|
|
||||||
if field.Kind() == reflect.Ptr {
|
|
||||||
if field.IsNil() {
|
|
||||||
// Create new pointer instance
|
|
||||||
newVal := reflect.New(field.Type().Elem())
|
|
||||||
field.Set(newVal)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch field.Type().Elem().Kind() {
|
|
||||||
case reflect.String:
|
|
||||||
field.Elem().SetString(envValue)
|
|
||||||
case reflect.Int:
|
|
||||||
if intVal, err := strconv.Atoi(envValue); err == nil {
|
|
||||||
field.Elem().SetInt(int64(intVal))
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
// For complex types like DownloadConfig, try JSON unmarshaling
|
|
||||||
if field.Type().Elem() == reflect.TypeOf(base.DownloaderStoreConfig{}) {
|
|
||||||
var config base.DownloaderStoreConfig
|
|
||||||
if err := json.Unmarshal([]byte(envValue), &config); err == nil {
|
|
||||||
field.Set(reflect.ValueOf(&config))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if field.Kind() == reflect.Slice {
|
|
||||||
// Handle non-pointer slice types (like []string for WhiteDownloadDirs)
|
|
||||||
if field.Type().Elem().Kind() == reflect.String {
|
|
||||||
dirs := strings.Split(envValue, ",")
|
|
||||||
for i := range dirs {
|
|
||||||
dirs[i] = strings.TrimSpace(dirs[i])
|
|
||||||
}
|
|
||||||
field.Set(reflect.ValueOf(dirs))
|
|
||||||
}
|
}
|
||||||
|
fmt.Println("config file read failed, reason:" + err.Error())
|
||||||
|
return &args
|
||||||
}
|
}
|
||||||
|
if err = json.Unmarshal(file, &args); err != nil {
|
||||||
|
fmt.Println("config file parse failed, reason:" + err.Error())
|
||||||
|
return &args
|
||||||
}
|
}
|
||||||
|
fmt.Printf("config file loaded: %s\n", path)
|
||||||
|
return &args
|
||||||
}
|
}
|
||||||
File diff suppressed because it is too large.
Load diff
+20
-15
@@ -5,13 +5,12 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"embed"
|
"embed"
|
||||||
"fmt"
|
"github.com/GopeedLab/gopeed/cmd"
|
||||||
|
"github.com/GopeedLab/gopeed/pkg/api/model"
|
||||||
|
"github.com/GopeedLab/gopeed/pkg/base"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/GopeedLab/gopeed/cmd"
|
|
||||||
"github.com/GopeedLab/gopeed/pkg/rest/model"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed dist/*
|
//go:embed dist/*
|
||||||
@@ -24,37 +23,43 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
args := parse()
|
args := parse()
|
||||||
var webAuth *model.WebAuth
|
var webBasicAuth *model.WebBasicAuth
|
||||||
if isNotBlank(args.Username) && isNotBlank(args.Password) {
|
if isNotBlank(args.Username) && isNotBlank(args.Password) {
|
||||||
webAuth = &model.WebAuth{
|
webBasicAuth = &model.WebBasicAuth{
|
||||||
Username: *args.Username,
|
Username: *args.Username,
|
||||||
Password: *args.Password,
|
Password: *args.Password,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var storageDir string
|
var dir string
|
||||||
if args.StorageDir != nil && *args.StorageDir != "" {
|
if args.StorageDir != nil && *args.StorageDir != "" {
|
||||||
storageDir = *args.StorageDir
|
dir = *args.StorageDir
|
||||||
} else {
|
} else {
|
||||||
exe, err := os.Executable()
|
exe, err := os.Executable()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
storageDir = filepath.Join(filepath.Dir(exe), "storage")
|
dir = filepath.Dir(exe)
|
||||||
|
}
|
||||||
|
|
||||||
|
if args.DownloadConfig == nil {
|
||||||
|
args.DownloadConfig = &base.DownloaderStoreConfig{}
|
||||||
|
}
|
||||||
|
args.DownloadConfig.Http = &base.DownloaderHttpConfig{
|
||||||
|
Enable: true,
|
||||||
|
Host: *args.Host,
|
||||||
|
Port: *args.Port,
|
||||||
|
ApiToken: *args.ApiToken,
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg := &model.StartConfig{
|
cfg := &model.StartConfig{
|
||||||
Network: "tcp",
|
|
||||||
Address: fmt.Sprintf("%s:%d", *args.Address, *args.Port),
|
|
||||||
Storage: model.StorageBolt,
|
Storage: model.StorageBolt,
|
||||||
StorageDir: storageDir,
|
StorageDir: filepath.Join(dir, "storage"),
|
||||||
WhiteDownloadDirs: args.WhiteDownloadDirs,
|
|
||||||
ApiToken: *args.ApiToken,
|
|
||||||
DownloadConfig: args.DownloadConfig,
|
DownloadConfig: args.DownloadConfig,
|
||||||
ProductionMode: true,
|
ProductionMode: true,
|
||||||
WebEnable: true,
|
WebEnable: true,
|
||||||
WebFS: sub,
|
WebFS: sub,
|
||||||
WebAuth: webAuth,
|
WebBasicAuth: webBasicAuth,
|
||||||
}
|
}
|
||||||
cmd.Start(cfg)
|
cmd.Start(cfg)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ require (
|
|||||||
github.com/go-git/go-git/v5 v5.8.1
|
github.com/go-git/go-git/v5 v5.8.1
|
||||||
github.com/gorilla/handlers v1.5.1
|
github.com/gorilla/handlers v1.5.1
|
||||||
github.com/gorilla/mux v1.8.0
|
github.com/gorilla/mux v1.8.0
|
||||||
github.com/imroc/req/v3 v3.52.2
|
|
||||||
github.com/matoous/go-nanoid/v2 v2.0.0
|
github.com/matoous/go-nanoid/v2 v2.0.0
|
||||||
github.com/mattn/go-ieproxy v0.0.12
|
github.com/mattn/go-ieproxy v0.0.12
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
@@ -22,29 +21,7 @@ require (
|
|||||||
github.com/xiaoqidun/setft v0.0.0-20220310121541-be86327699ad
|
github.com/xiaoqidun/setft v0.0.0-20220310121541-be86327699ad
|
||||||
go.etcd.io/bbolt v1.3.11
|
go.etcd.io/bbolt v1.3.11
|
||||||
golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f
|
golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f
|
||||||
golang.org/x/sync v0.13.0
|
golang.org/x/sync v0.9.0
|
||||||
)
|
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/andybalholm/brotli v1.1.1 // indirect
|
|
||||||
github.com/ebitengine/purego v0.8.1 // indirect
|
|
||||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
|
||||||
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
|
||||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
|
||||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
|
||||||
github.com/icholy/digest v1.1.0 // indirect
|
|
||||||
github.com/klauspost/compress v1.18.0 // indirect
|
|
||||||
github.com/lufia/plan9stats v0.0.0-20220913051719-115f729f3c8c // indirect
|
|
||||||
github.com/onsi/ginkgo/v2 v2.23.4 // indirect
|
|
||||||
github.com/power-devops/perfstat v0.0.0-20220216144756-c35f1ee13d7c // indirect
|
|
||||||
github.com/quic-go/qpack v0.5.1 // indirect
|
|
||||||
github.com/quic-go/quic-go v0.51.0 // indirect
|
|
||||||
github.com/refraction-networking/utls v1.6.7 // indirect
|
|
||||||
github.com/tklauser/go-sysconf v0.3.12 // indirect
|
|
||||||
github.com/tklauser/numcpus v0.6.1 // indirect
|
|
||||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
|
||||||
go.uber.org/automaxprocs v1.6.0 // indirect
|
|
||||||
go.uber.org/mock v0.5.1 // indirect
|
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@@ -74,7 +51,7 @@ require (
|
|||||||
github.com/bits-and-blooms/bitset v1.15.0 // indirect
|
github.com/bits-and-blooms/bitset v1.15.0 // indirect
|
||||||
github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 // indirect
|
github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 // indirect
|
||||||
github.com/cespare/xxhash v1.1.0 // indirect
|
github.com/cespare/xxhash v1.1.0 // indirect
|
||||||
github.com/cloudflare/circl v1.6.1 // indirect
|
github.com/cloudflare/circl v1.3.3 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/dlclark/regexp2 v1.11.4 // indirect
|
github.com/dlclark/regexp2 v1.11.4 // indirect
|
||||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
@@ -90,7 +67,7 @@ require (
|
|||||||
github.com/go-sourcemap/sourcemap v2.1.4+incompatible // indirect
|
github.com/go-sourcemap/sourcemap v2.1.4+incompatible // indirect
|
||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||||
github.com/google/btree v1.1.3 // indirect
|
github.com/google/btree v1.1.3 // indirect
|
||||||
github.com/google/pprof v0.0.0-20250423184734-337e5dd93bb4 // indirect
|
github.com/google/pprof v0.0.0-20240910150728-a0b0bb1d4134 // indirect
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
github.com/gorilla/websocket v1.5.3 // indirect
|
github.com/gorilla/websocket v1.5.3 // indirect
|
||||||
github.com/huandu/xstrings v1.5.0 // indirect
|
github.com/huandu/xstrings v1.5.0 // indirect
|
||||||
@@ -122,11 +99,9 @@ require (
|
|||||||
github.com/pion/turn/v4 v4.0.0 // indirect
|
github.com/pion/turn/v4 v4.0.0 // indirect
|
||||||
github.com/pion/webrtc/v4 v4.0.2 // indirect
|
github.com/pion/webrtc/v4 v4.0.2 // indirect
|
||||||
github.com/pjbgf/sha1cd v0.3.0 // indirect
|
github.com/pjbgf/sha1cd v0.3.0 // indirect
|
||||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c
|
|
||||||
github.com/protolambda/ctxlock v0.1.0 // indirect
|
github.com/protolambda/ctxlock v0.1.0 // indirect
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||||
github.com/sergi/go-diff v1.1.0 // indirect
|
github.com/sergi/go-diff v1.1.0 // indirect
|
||||||
github.com/shirou/gopsutil/v4 v4.24.12
|
|
||||||
github.com/skeema/knownhosts v1.2.0 // indirect
|
github.com/skeema/knownhosts v1.2.0 // indirect
|
||||||
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
||||||
github.com/tidwall/btree v1.7.0 // indirect
|
github.com/tidwall/btree v1.7.0 // indirect
|
||||||
@@ -135,13 +110,13 @@ require (
|
|||||||
go.opentelemetry.io/otel v1.32.0 // indirect
|
go.opentelemetry.io/otel v1.32.0 // indirect
|
||||||
go.opentelemetry.io/otel/metric v1.32.0 // indirect
|
go.opentelemetry.io/otel/metric v1.32.0 // indirect
|
||||||
go.opentelemetry.io/otel/trace v1.32.0 // indirect
|
go.opentelemetry.io/otel/trace v1.32.0 // indirect
|
||||||
golang.org/x/crypto v0.37.0 // indirect
|
golang.org/x/crypto v0.29.0 // indirect
|
||||||
golang.org/x/mod v0.24.0 // indirect
|
golang.org/x/mod v0.22.0 // indirect
|
||||||
golang.org/x/net v0.39.0 // indirect
|
golang.org/x/net v0.31.0 // indirect
|
||||||
golang.org/x/sys v0.32.0 // indirect
|
golang.org/x/sys v0.27.0 // indirect
|
||||||
golang.org/x/text v0.24.0 // indirect
|
golang.org/x/text v0.20.0 // indirect
|
||||||
golang.org/x/time v0.8.0 // indirect
|
golang.org/x/time v0.8.0 // indirect
|
||||||
golang.org/x/tools v0.32.0 // indirect
|
golang.org/x/tools v0.27.0 // indirect
|
||||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||||
lukechampine.com/blake3 v1.3.0 // indirect
|
lukechampine.com/blake3 v1.3.0 // indirect
|
||||||
modernc.org/libc v1.61.0 // indirect
|
modernc.org/libc v1.61.0 // indirect
|
||||||
|
|||||||
@@ -94,8 +94,6 @@ github.com/anacrolix/upnp v0.1.4 h1:+2t2KA6QOhm/49zeNyeVwDu1ZYS9dB9wfxyVvh/wk7U=
|
|||||||
github.com/anacrolix/upnp v0.1.4/go.mod h1:Qyhbqo69gwNWvEk1xNTXsS5j7hMHef9hdr984+9fIic=
|
github.com/anacrolix/upnp v0.1.4/go.mod h1:Qyhbqo69gwNWvEk1xNTXsS5j7hMHef9hdr984+9fIic=
|
||||||
github.com/anacrolix/utp v0.2.0 h1:65Cdmr6q9WSw2KsM+rtJFu7rqDzLl2bdysf4KlNPcFI=
|
github.com/anacrolix/utp v0.2.0 h1:65Cdmr6q9WSw2KsM+rtJFu7rqDzLl2bdysf4KlNPcFI=
|
||||||
github.com/anacrolix/utp v0.2.0/go.mod h1:HGk4GYQw1O/3T1+yhqT/F6EcBd+AAwlo9dYErNy7mj8=
|
github.com/anacrolix/utp v0.2.0/go.mod h1:HGk4GYQw1O/3T1+yhqT/F6EcBd+AAwlo9dYErNy7mj8=
|
||||||
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
|
|
||||||
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
|
|
||||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
||||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
|
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
|
||||||
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
||||||
@@ -124,9 +122,8 @@ github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4M
|
|||||||
github.com/chzyer/readline v1.5.0/go.mod h1:x22KAscuvRqlLoK9CsoYsmxoXZMMFVyOl86cAH8qUic=
|
github.com/chzyer/readline v1.5.0/go.mod h1:x22KAscuvRqlLoK9CsoYsmxoXZMMFVyOl86cAH8qUic=
|
||||||
github.com/chzyer/test v0.0.0-20210722231415-061457976a23/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
github.com/chzyer/test v0.0.0-20210722231415-061457976a23/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
|
github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs=
|
||||||
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
|
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
|
||||||
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
|
|
||||||
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
|
||||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
@@ -146,8 +143,6 @@ github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+m
|
|||||||
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
|
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
|
||||||
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
|
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
|
||||||
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
|
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
|
||||||
github.com/ebitengine/purego v0.8.1 h1:sdRKd6plj7KYW33EH5As6YKfe8m9zbN9JMrOjNVF/BE=
|
|
||||||
github.com/ebitengine/purego v0.8.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
|
||||||
github.com/edsrzf/mmap-go v1.2.0 h1:hXLYlkbaPzt1SaQk+anYwKSRNhufIDCchSPkUD6dD84=
|
github.com/edsrzf/mmap-go v1.2.0 h1:hXLYlkbaPzt1SaQk+anYwKSRNhufIDCchSPkUD6dD84=
|
||||||
github.com/edsrzf/mmap-go v1.2.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q=
|
github.com/edsrzf/mmap-go v1.2.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q=
|
||||||
github.com/elazarl/goproxy v0.0.0-20221015165544-a0805db90819 h1:RIB4cRk+lBqKK3Oy0r2gRX4ui7tuhiZq2SuTtTCi0/0=
|
github.com/elazarl/goproxy v0.0.0-20221015165544-a0805db90819 h1:RIB4cRk+lBqKK3Oy0r2gRX4ui7tuhiZq2SuTtTCi0/0=
|
||||||
@@ -192,15 +187,11 @@ github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
|||||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
|
||||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
|
||||||
github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
|
github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
|
||||||
github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
|
github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
|
||||||
github.com/go-sourcemap/sourcemap v2.1.4+incompatible h1:a+iTbH5auLKxaNwQFg0B+TCYl6lbukKPc7b5x0n1s6Q=
|
github.com/go-sourcemap/sourcemap v2.1.4+incompatible h1:a+iTbH5auLKxaNwQFg0B+TCYl6lbukKPc7b5x0n1s6Q=
|
||||||
github.com/go-sourcemap/sourcemap v2.1.4+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
|
github.com/go-sourcemap/sourcemap v2.1.4+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
|
||||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||||
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
|
||||||
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
|
||||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
@@ -234,12 +225,12 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
|||||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
|
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
|
||||||
github.com/google/pprof v0.0.0-20250423184734-337e5dd93bb4 h1:gD0vax+4I+mAj+jEChEf25Ia07Jq7kYOFO5PPhAxFl4=
|
github.com/google/pprof v0.0.0-20240910150728-a0b0bb1d4134 h1:c5FlPPgxOn7kJz3VoPLkQYQXGBS3EklQ4Zfi57uOuqQ=
|
||||||
github.com/google/pprof v0.0.0-20250423184734-337e5dd93bb4/go.mod h1:5hDyRhoBCxViHszMt12TnOpEI4VVi+U8Gm9iphldiMA=
|
github.com/google/pprof v0.0.0-20240910150728-a0b0bb1d4134/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
|
||||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
@@ -255,11 +246,6 @@ github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
|||||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
|
||||||
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
|
||||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
|
||||||
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
|
||||||
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
|
||||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
||||||
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
||||||
@@ -272,10 +258,6 @@ github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq
|
|||||||
github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI=
|
github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI=
|
||||||
github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
|
github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
|
||||||
github.com/icholy/digest v1.1.0 h1:HfGg9Irj7i+IX1o1QAmPfIBNu/Q5A5Tu3n/MED9k9H4=
|
|
||||||
github.com/icholy/digest v1.1.0/go.mod h1:QNrsSGQ5v7v9cReDI0+eyjsXGUoRSUZQHeQ5C4XLa0Y=
|
|
||||||
github.com/imroc/req/v3 v3.52.2 h1:xJocr1aIv0a2K9knfBQ4JnZHk+kWTITdjf0mgDg229I=
|
|
||||||
github.com/imroc/req/v3 v3.52.2/go.mod h1:dBGsDloOSZJcFs6PnTjZXYBJK70OXbZpizHBLNqcH2k=
|
|
||||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
||||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
||||||
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
||||||
@@ -291,8 +273,6 @@ github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:C
|
|||||||
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
|
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
|
||||||
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
|
||||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
|
||||||
github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
|
github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
|
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
@@ -308,8 +288,6 @@ 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.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 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/lufia/plan9stats v0.0.0-20220913051719-115f729f3c8c h1:VtwQ41oftZwlMnOEbMWQtSEUgU64U4s+GHk7hZK+jtY=
|
|
||||||
github.com/lufia/plan9stats v0.0.0-20220913051719-115f729f3c8c/go.mod h1:JKx41uQRwqlTZabZc+kILPrO/3jlKnQ2Z8b7YiVw5cE=
|
|
||||||
github.com/matoous/go-nanoid v1.5.0/go.mod h1:zyD2a71IubI24efhpvkJz+ZwfwagzgSO6UNiFsZKN7U=
|
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 h1:d19kur2QuLeHmJBkvYkFdhFBzLoo1XVm2GgTpL+9Tj0=
|
||||||
github.com/matoous/go-nanoid/v2 v2.0.0/go.mod h1:FtS4aGPVfEkxKxhdWPAspZpZSh1cOjtM7Ej/So3hR0g=
|
github.com/matoous/go-nanoid/v2 v2.0.0/go.mod h1:FtS4aGPVfEkxKxhdWPAspZpZSh1cOjtM7Ej/So3hR0g=
|
||||||
@@ -347,11 +325,7 @@ github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJm
|
|||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
github.com/onsi/ginkgo/v2 v2.23.4 h1:ktYTpKJAVZnDT4VjxSbiBenUjmlL/5QkBEocaWXiQus=
|
|
||||||
github.com/onsi/ginkgo/v2 v2.23.4/go.mod h1:Bt66ApGPBFzHyR+JO10Zbt0Gsp4uWxu5mIOTusL46e8=
|
|
||||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||||
github.com/onsi/gomega v1.36.3 h1:hID7cr8t3Wp26+cYnfcjR6HpJ00fdogN6dqZ1t6IylU=
|
|
||||||
github.com/onsi/gomega v1.36.3/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0=
|
|
||||||
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
|
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
|
||||||
github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
|
github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
|
||||||
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||||
@@ -389,8 +363,6 @@ github.com/pion/webrtc/v4 v4.0.2 h1:fBwm5/hqSUybrCWl0DDBSTDrpbkcgkqpeLmXw9CsBQA=
|
|||||||
github.com/pion/webrtc/v4 v4.0.2/go.mod h1:moylBT2A4dNoEaYBCdV1nThM3TLwRHzWszIG+eSPaqQ=
|
github.com/pion/webrtc/v4 v4.0.2/go.mod h1:moylBT2A4dNoEaYBCdV1nThM3TLwRHzWszIG+eSPaqQ=
|
||||||
github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4=
|
github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4=
|
||||||
github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI=
|
github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI=
|
||||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
|
||||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
|
||||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
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.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
@@ -398,10 +370,6 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
|||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/power-devops/perfstat v0.0.0-20220216144756-c35f1ee13d7c h1:NRoLoZvkBTKvR5gQLgA3e0hqjkY9u1wm+iOL45VN/qI=
|
|
||||||
github.com/power-devops/perfstat v0.0.0-20220216144756-c35f1ee13d7c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
|
||||||
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
|
|
||||||
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
|
|
||||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
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 v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
|
||||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||||
@@ -426,13 +394,7 @@ github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O
|
|||||||
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||||
github.com/protolambda/ctxlock v0.1.0 h1:rCUY3+vRdcdZXqT07iXgyr744J2DU2LCBIXowYAjBCE=
|
github.com/protolambda/ctxlock v0.1.0 h1:rCUY3+vRdcdZXqT07iXgyr744J2DU2LCBIXowYAjBCE=
|
||||||
github.com/protolambda/ctxlock v0.1.0/go.mod h1:vefhX6rIZH8rsg5ZpOJfEDYQOppZi19SfPiGOFrNnwM=
|
github.com/protolambda/ctxlock v0.1.0/go.mod h1:vefhX6rIZH8rsg5ZpOJfEDYQOppZi19SfPiGOFrNnwM=
|
||||||
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
|
|
||||||
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
|
|
||||||
github.com/quic-go/quic-go v0.51.0 h1:K8exxe9zXxeRKxaXxi/GpUqYiTrtdiWP8bo1KFya6Wc=
|
|
||||||
github.com/quic-go/quic-go v0.51.0/go.mod h1:MFlGGpcpJqRAfmYi6NC2cptDPSxRWTOGNuP4wqrWmzQ=
|
|
||||||
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||||
github.com/refraction-networking/utls v1.6.7 h1:zVJ7sP1dJx/WtVuITug3qYUq034cDq9B2MR1K67ULZM=
|
|
||||||
github.com/refraction-networking/utls v1.6.7/go.mod h1:BC3O4vQzye5hqpmDTWUqi4P5DDhzJfkV1tdqtawQIH0=
|
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||||
@@ -450,8 +412,6 @@ github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46 h1:GHRpF1pTW19a
|
|||||||
github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46/go.mod h1:uAQ5PCi+MFsC7HjREoAz1BU+Mq60+05gifQSsHSDG/8=
|
github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46/go.mod h1:uAQ5PCi+MFsC7HjREoAz1BU+Mq60+05gifQSsHSDG/8=
|
||||||
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
|
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
|
||||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||||
github.com/shirou/gopsutil/v4 v4.24.12 h1:qvePBOk20e0IKA1QXrIIU+jmk+zEiYVVx06WjBRlZo4=
|
|
||||||
github.com/shirou/gopsutil/v4 v4.24.12/go.mod h1:DCtMPAad2XceTeIAbGyVfycbYQNBGk2P8cvDi7/VN9o=
|
|
||||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||||
@@ -480,18 +440,13 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
|||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
|
||||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
|
||||||
github.com/tidwall/btree v1.7.0 h1:L1fkJH/AuEh5zBnnBbmTwQ5Lt+bRJ5A8EWecslvo9iI=
|
github.com/tidwall/btree v1.7.0 h1:L1fkJH/AuEh5zBnnBbmTwQ5Lt+bRJ5A8EWecslvo9iI=
|
||||||
github.com/tidwall/btree v1.7.0/go.mod h1:twD9XRA5jj9VUQGELzDO4HPQTNJsoWWfYEL+EUQ2cKY=
|
github.com/tidwall/btree v1.7.0/go.mod h1:twD9XRA5jj9VUQGELzDO4HPQTNJsoWWfYEL+EUQ2cKY=
|
||||||
github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
|
github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
|
||||||
github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
|
github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
|
||||||
github.com/tinylib/msgp v1.1.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
|
github.com/tinylib/msgp v1.1.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
|
||||||
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
|
|
||||||
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
|
||||||
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
|
|
||||||
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
|
|
||||||
github.com/willf/bitset v1.1.9/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
|
github.com/willf/bitset v1.1.9/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
|
||||||
github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
|
github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
|
||||||
github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU=
|
github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU=
|
||||||
@@ -500,13 +455,9 @@ github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM
|
|||||||
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
|
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
|
||||||
github.com/xiaoqidun/setft v0.0.0-20220310121541-be86327699ad h1:QtuWk6dsrNXc/ugwCBEN0jG52q/4tXRzMvZm4fH6T9g=
|
github.com/xiaoqidun/setft v0.0.0-20220310121541-be86327699ad h1:QtuWk6dsrNXc/ugwCBEN0jG52q/4tXRzMvZm4fH6T9g=
|
||||||
github.com/xiaoqidun/setft v0.0.0-20220310121541-be86327699ad/go.mod h1:Jj8p9bgKGTPQ+M8CdUMS9p7Mmdoxa3OAcAjJQBu0CcI=
|
github.com/xiaoqidun/setft v0.0.0-20220310121541-be86327699ad/go.mod h1:Jj8p9bgKGTPQ+M8CdUMS9p7Mmdoxa3OAcAjJQBu0CcI=
|
||||||
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
|
||||||
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
|
||||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
|
||||||
go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0=
|
go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0=
|
||||||
go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I=
|
go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I=
|
||||||
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
||||||
@@ -518,12 +469,8 @@ go.opentelemetry.io/otel/metric v1.32.0 h1:xV2umtmNcThh2/a/aCP+h64Xx5wsj8qqnkYZk
|
|||||||
go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzauciCRLoc/SyMv8=
|
go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzauciCRLoc/SyMv8=
|
||||||
go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM=
|
go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM=
|
||||||
go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8=
|
go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8=
|
||||||
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
|
|
||||||
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
|
|
||||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||||
go.uber.org/mock v0.5.1 h1:ASgazW/qBmR+A32MYFDB6E2POoTgOwT509VP0CT/fjs=
|
|
||||||
go.uber.org/mock v0.5.1/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
|
|
||||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
@@ -532,8 +479,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
|
|||||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||||
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
|
golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ=
|
||||||
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
|
golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20220428152302-39d4317da171/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE=
|
golang.org/x/exp v0.0.0-20220428152302-39d4317da171/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE=
|
||||||
golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f h1:XdNn9LlyWAhLVp6P/i8QYBW+hlyhrhei9uErw2B5GJo=
|
golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f h1:XdNn9LlyWAhLVp6P/i8QYBW+hlyhrhei9uErw2B5GJo=
|
||||||
@@ -547,8 +494,8 @@ golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
|||||||
golang.org/x/mod v0.6.0-dev.0.20211013180041-c96bc1413d57/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
|
golang.org/x/mod v0.6.0-dev.0.20211013180041-c96bc1413d57/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
|
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
|
||||||
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
@@ -570,8 +517,8 @@ golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
|||||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||||
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
|
golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo=
|
||||||
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
|
golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
@@ -585,8 +532,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ
|
|||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
|
golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ=
|
||||||
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
@@ -596,7 +543,6 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
|
|||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@@ -606,7 +552,6 @@ golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
@@ -617,23 +562,20 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
|
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
|
||||||
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||||
golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o=
|
golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU=
|
||||||
golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw=
|
golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
@@ -642,8 +584,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
|||||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
|
golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=
|
||||||
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
|
golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
|
||||||
golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
|
golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
|
||||||
golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
@@ -657,8 +599,8 @@ golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4f
|
|||||||
golang.org/x/tools v0.1.8-0.20211029000441-d6a9af8af023/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
|
golang.org/x/tools v0.1.8-0.20211029000441-d6a9af8af023/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU=
|
golang.org/x/tools v0.27.0 h1:qEKojBykQkQ4EynWy4S8Weg69NumxKdn40Fce3uc/8o=
|
||||||
golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s=
|
golang.org/x/tools v0.27.0/go.mod h1:sUi0ZgbwW9ZPAq26Ekut+weQPR5eIM6GQLQ1Yjm1H0Q=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
@@ -679,8 +621,6 @@ google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miE
|
|||||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
|
|
||||||
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
|
||||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
@@ -702,8 +642,6 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
|||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
|
|
||||||
gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
|
|
||||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
lukechampine.com/blake3 v1.3.0 h1:sJ3XhFINmHSrYCgl958hscfIa3bw8x4DqMP3u1YvoYE=
|
lukechampine.com/blake3 v1.3.0 h1:sJ3XhFINmHSrYCgl958hscfIa3bw8x4DqMP3u1YvoYE=
|
||||||
|
|||||||
@@ -25,9 +25,7 @@ type DefaultFileController struct {
|
|||||||
func NewController() *Controller {
|
func NewController() *Controller {
|
||||||
return &Controller{
|
return &Controller{
|
||||||
GetConfig: func(v any) {},
|
GetConfig: func(v any) {},
|
||||||
GetProxy: func(requestProxy *base.RequestProxy) func(*http.Request) (*url.URL, error) {
|
GetProxy: func(requestProxy *base.RequestProxy) func(*http.Request) (*url.URL, error) { return nil },
|
||||||
return requestProxy.ToHandler()
|
|
||||||
},
|
|
||||||
FileController: &DefaultFileController{},
|
FileController: &DefaultFileController{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ type config struct {
|
|||||||
ListenPort int `json:"listenPort"`
|
ListenPort int `json:"listenPort"`
|
||||||
Trackers []string `json:"trackers"`
|
Trackers []string `json:"trackers"`
|
||||||
// SeedKeep is always keep seeding after downloading is complete, unless manually stopped.
|
// SeedKeep is always keep seeding after downloading is complete, unless manually stopped.
|
||||||
SeedKeep bool `json:"seedKeep"`
|
SeedKeep bool `json:"seedKeep`
|
||||||
// SeedRatio is the ratio of uploaded data to downloaded data to seed.
|
// SeedRatio is the ratio of uploaded data to downloaded data to seed.
|
||||||
SeedRatio float64 `json:"seedRatio"`
|
SeedRatio float64 `json:"seedRatio"`
|
||||||
// SeedTime is the time in seconds to seed after downloading is complete.
|
// SeedTime is the time in seconds to seed after downloading is complete.
|
||||||
|
|||||||
@@ -4,15 +4,6 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/GopeedLab/gopeed/internal/controller"
|
"github.com/GopeedLab/gopeed/internal/controller"
|
||||||
"github.com/GopeedLab/gopeed/internal/fetcher"
|
"github.com/GopeedLab/gopeed/internal/fetcher"
|
||||||
"github.com/GopeedLab/gopeed/pkg/base"
|
"github.com/GopeedLab/gopeed/pkg/base"
|
||||||
@@ -20,6 +11,12 @@ import (
|
|||||||
"github.com/GopeedLab/gopeed/pkg/util"
|
"github.com/GopeedLab/gopeed/pkg/util"
|
||||||
"github.com/anacrolix/torrent"
|
"github.com/anacrolix/torrent"
|
||||||
"github.com/anacrolix/torrent/metainfo"
|
"github.com/anacrolix/torrent/metainfo"
|
||||||
|
"net/url"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -101,6 +98,9 @@ func (f *Fetcher) initClient() (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f *Fetcher) Resolve(req *base.Request) error {
|
func (f *Fetcher) Resolve(req *base.Request) error {
|
||||||
|
if err := base.ParseReqExtra[bt.ReqExtra](req); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
f.meta.Req = req
|
f.meta.Req = req
|
||||||
if err := f.addTorrent(req, false); err != nil {
|
if err := f.addTorrent(req, false); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -332,7 +332,7 @@ func (f *Fetcher) doUpload(fromUpload bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get torrent stats maybe panic, see https://github.com/anacrolix/torrent/issues/972
|
// get torrent stats maybe panic, see https://github.com/anacrolix/torrent/issues/972
|
||||||
func (f *Fetcher) torrentStats() torrent.TorrentStats {
|
func (f *Fetcher) torrentStats() torrent.TorrentStats {
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
@@ -353,60 +353,28 @@ func (f *Fetcher) WaitUpload() (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f *Fetcher) addTorrent(req *base.Request, fromUpload bool) (err error) {
|
func (f *Fetcher) addTorrent(req *base.Request, fromUpload bool) (err error) {
|
||||||
if err = base.ParseReqExtra[bt.ReqExtra](req); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err = f.initClient(); err != nil {
|
if err = f.initClient(); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
schema := util.ParseSchema(req.URL)
|
schema := util.ParseSchema(req.URL)
|
||||||
privateTorrent := false
|
|
||||||
if schema == "MAGNET" {
|
if schema == "MAGNET" {
|
||||||
f.torrent, err = client.AddMagnet(req.URL)
|
f.torrent, err = client.AddMagnet(req.URL)
|
||||||
} else {
|
|
||||||
var reader io.Reader
|
|
||||||
if schema == "FILE" {
|
|
||||||
fileUrl, _ := url.Parse(req.URL)
|
|
||||||
filePath := fileUrl.Path[1:]
|
|
||||||
reader, err = os.Open(filePath)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else if schema == "DATA" {
|
} else if schema == "DATA" {
|
||||||
_, data := util.ParseDataUri(req.URL)
|
_, data := util.ParseDataUri(req.URL)
|
||||||
reader = bytes.NewBuffer(data)
|
buf := bytes.NewBuffer(data)
|
||||||
} else {
|
|
||||||
reader, err = os.Open(req.URL)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer reader.(io.Closer).Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
var metaInfo *metainfo.MetaInfo
|
var metaInfo *metainfo.MetaInfo
|
||||||
metaInfo, err = metainfo.Load(reader)
|
metaInfo, err = metainfo.Load(buf)
|
||||||
// Hotfix for https://github.com/anacrolix/torrent/issues/992, ignore "expected EOF" error
|
if err != nil {
|
||||||
// TODO remove this after the issue is fixed
|
|
||||||
if err != nil && !strings.Contains(err.Error(), "expected EOF") {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
info, er := metaInfo.UnmarshalInfo()
|
|
||||||
if er != nil {
|
|
||||||
return er
|
|
||||||
}
|
|
||||||
|
|
||||||
if info.Private != nil && *info.Private {
|
|
||||||
privateTorrent = true
|
|
||||||
}
|
|
||||||
f.torrent, err = client.AddTorrent(metaInfo)
|
f.torrent, err = client.AddTorrent(metaInfo)
|
||||||
|
} else {
|
||||||
|
f.torrent, err = client.AddTorrentFromFile(req.URL)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do not add external tracker to a private torrent.
|
|
||||||
if !privateTorrent {
|
|
||||||
// use map to deduplicate
|
// use map to deduplicate
|
||||||
trackers := make(map[string]bool)
|
trackers := make(map[string]bool)
|
||||||
if req.Extra != nil {
|
if req.Extra != nil {
|
||||||
@@ -429,7 +397,6 @@ func (f *Fetcher) addTorrent(req *base.Request, fromUpload bool) (err error) {
|
|||||||
}
|
}
|
||||||
f.torrent.AddTrackers(announceList)
|
f.torrent.AddTrackers(announceList)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
<-f.torrent.GotInfo()
|
<-f.torrent.GotInfo()
|
||||||
f.torrentReady.Store(true)
|
f.torrentReady.Store(true)
|
||||||
|
|
||||||
|
|||||||
@@ -3,18 +3,16 @@ package bt
|
|||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
gohttp "net/http"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/GopeedLab/gopeed/internal/controller"
|
"github.com/GopeedLab/gopeed/internal/controller"
|
||||||
"github.com/GopeedLab/gopeed/internal/fetcher"
|
"github.com/GopeedLab/gopeed/internal/fetcher"
|
||||||
"github.com/GopeedLab/gopeed/internal/test"
|
"github.com/GopeedLab/gopeed/internal/test"
|
||||||
"github.com/GopeedLab/gopeed/pkg/base"
|
"github.com/GopeedLab/gopeed/pkg/base"
|
||||||
"github.com/GopeedLab/gopeed/pkg/protocol/bt"
|
"github.com/GopeedLab/gopeed/pkg/protocol/bt"
|
||||||
|
gohttp "net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestFetcher_Resolve_Torrent(t *testing.T) {
|
func TestFetcher_Resolve_Torrent(t *testing.T) {
|
||||||
@@ -98,7 +96,7 @@ func doResolve(t *testing.T, fetcher fetcher.Fetcher) {
|
|||||||
Hash: "8a55cfbd5ca5d11507364765936c4f9e55b253ed",
|
Hash: "8a55cfbd5ca5d11507364765936c4f9e55b253ed",
|
||||||
}
|
}
|
||||||
if !reflect.DeepEqual(want, fetcher.Meta().Res) {
|
if !reflect.DeepEqual(want, fetcher.Meta().Res) {
|
||||||
t.Errorf("Resolve Single File Resolve() got = %v, want %v", fetcher.Meta().Res, want)
|
t.Errorf("Resolve() got = %v, want %v", fetcher.Meta().Res, want)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -138,29 +136,10 @@ func doResolve(t *testing.T, fetcher fetcher.Fetcher) {
|
|||||||
Hash: "ccbc92b0cd8deec16a2ef4be242a8c9243b1cedb",
|
Hash: "ccbc92b0cd8deec16a2ef4be242a8c9243b1cedb",
|
||||||
}
|
}
|
||||||
if !reflect.DeepEqual(want, fetcher.Meta().Res) {
|
if !reflect.DeepEqual(want, fetcher.Meta().Res) {
|
||||||
t.Errorf("Resolve Multi Files Resolve() got = %v, want %v", fetcher.Meta().Res, want)
|
t.Errorf("Resolve() got = %v, want %v", fetcher.Meta().Res, want)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Resolve Unclean Torrent", func(t *testing.T) {
|
|
||||||
err := fetcher.Resolve(&base.Request{
|
|
||||||
URL: "./testdata/test.unclean.torrent",
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Resolve Unclean Torrent Resolve() got = %v, want nil", err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Resolve file scheme Torrent", func(t *testing.T) {
|
|
||||||
file, _ := filepath.Abs("./testdata/test.unclean.torrent")
|
|
||||||
uri := "file:///" + file
|
|
||||||
err := fetcher.Resolve(&base.Request{
|
|
||||||
URL: uri,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Resolve file scheme Torrent Resolve() got = %v, want nil", err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFetcherManager_ParseName(t *testing.T) {
|
func TestFetcherManager_ParseName(t *testing.T) {
|
||||||
|
|||||||
@@ -5,12 +5,11 @@ package bt
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/anacrolix/torrent/storage"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/anacrolix/torrent/storage"
|
|
||||||
|
|
||||||
"github.com/anacrolix/missinggo/v2"
|
"github.com/anacrolix/missinggo/v2"
|
||||||
|
|
||||||
"github.com/anacrolix/torrent/common"
|
"github.com/anacrolix/torrent/common"
|
||||||
@@ -35,7 +34,7 @@ func newFileOpts(opts newFileClientOpts) storage.ClientImplCloser {
|
|||||||
if opts.FilePathMaker == nil {
|
if opts.FilePathMaker == nil {
|
||||||
opts.FilePathMaker = func(opts storage.FilePathMakerOpts) string {
|
opts.FilePathMaker = func(opts storage.FilePathMakerOpts) string {
|
||||||
var parts []string
|
var parts []string
|
||||||
if opts.Info.Length == 0 || opts.File.Path == nil {
|
if opts.Info.Name != metainfo.NoName {
|
||||||
parts = append(parts, opts.Info.Name)
|
parts = append(parts, opts.Info.Name)
|
||||||
}
|
}
|
||||||
return filepath.Join(append(parts, opts.File.Path...)...)
|
return filepath.Join(append(parts, opts.File.Path...)...)
|
||||||
|
|||||||
Binary file not shown.
File diff suppressed because it is too large.
Load diff
@@ -11,7 +11,6 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
gohttp "net/http"
|
gohttp "net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -37,36 +36,6 @@ func TestFetcher_Resolve(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}, t)
|
}, t)
|
||||||
testResolve(test.StartTestCustomServer, "encoded-word", &base.Resource{
|
|
||||||
Size: test.BuildSize,
|
|
||||||
Range: false,
|
|
||||||
Files: []*base.FileInfo{
|
|
||||||
{
|
|
||||||
Name: "测试.zip",
|
|
||||||
Size: test.BuildSize,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}, t)
|
|
||||||
testResolve(test.StartTestCustomServer, "no-encode", &base.Resource{
|
|
||||||
Size: test.BuildSize,
|
|
||||||
Range: false,
|
|
||||||
Files: []*base.FileInfo{
|
|
||||||
{
|
|
||||||
Name: "测试.zip",
|
|
||||||
Size: test.BuildSize,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}, t)
|
|
||||||
testResolve(test.StartTestCustomServer, "%E6%B5%8B%E8%AF%95.zip", &base.Resource{
|
|
||||||
Size: 0,
|
|
||||||
Range: false,
|
|
||||||
Files: []*base.FileInfo{
|
|
||||||
{
|
|
||||||
Name: "测试.zip",
|
|
||||||
Size: 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}, t)
|
|
||||||
testResolve(test.StartTestCustomServer, test.BuildName, &base.Resource{
|
testResolve(test.StartTestCustomServer, test.BuildName, &base.Resource{
|
||||||
Size: 0,
|
Size: 0,
|
||||||
Range: false,
|
Range: false,
|
||||||
@@ -90,36 +59,6 @@ func TestFetcher_Resolve(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFetcher_ResolveWithHostHeader(t *testing.T) {
|
|
||||||
fetcher := buildFetcher()
|
|
||||||
err := fetcher.Resolve(&base.Request{
|
|
||||||
URL: "https://bing.com",
|
|
||||||
Extra: &http.ReqExtra{
|
|
||||||
Header: map[string]string{
|
|
||||||
"Host": "test",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err == nil || !strings.Contains(err.Error(), "400") {
|
|
||||||
t.Errorf("Resolve() got = %v, want %v", err, "400")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFetcher_ResolveWithInvalidHeader(t *testing.T) {
|
|
||||||
fetcher := buildFetcher()
|
|
||||||
err := fetcher.Resolve(&base.Request{
|
|
||||||
URL: "https://bing.com",
|
|
||||||
Extra: &http.ReqExtra{
|
|
||||||
Header: map[string]string{
|
|
||||||
"Referer": "\rtest",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Resolve() got = %v, want nil", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func testResolve(startTestServer func() net.Listener, path string, want *base.Resource, t *testing.T) {
|
func testResolve(startTestServer func() net.Listener, path string, want *base.Resource, t *testing.T) {
|
||||||
listener := startTestServer()
|
listener := startTestServer()
|
||||||
defer listener.Close()
|
defer listener.Close()
|
||||||
@@ -193,22 +132,6 @@ func TestFetcher_DownloadLimit(t *testing.T) {
|
|||||||
downloadNormal(listener, 8, t)
|
downloadNormal(listener, 8, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFetcher_DownloadResponseBodyReadTimeout(t *testing.T) {
|
|
||||||
listener := test.StartTestLimitServer(16, readTimeout.Milliseconds()+5000)
|
|
||||||
defer listener.Close()
|
|
||||||
|
|
||||||
downloadError(listener, 1, t)
|
|
||||||
downloadError(listener, 4, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFetcher_DownloadOnBugFileServer(t *testing.T) {
|
|
||||||
listener := test.StartTestRangeBugServer()
|
|
||||||
defer listener.Close()
|
|
||||||
|
|
||||||
downloadNormal(listener, 1, t)
|
|
||||||
downloadNormal(listener, 4, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFetcher_DownloadResume(t *testing.T) {
|
func TestFetcher_DownloadResume(t *testing.T) {
|
||||||
listener := test.StartTestFileServer()
|
listener := test.StartTestFileServer()
|
||||||
defer listener.Close()
|
defer listener.Close()
|
||||||
@@ -271,33 +194,6 @@ func TestFetcher_ConfigUseServerCtime(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFetcher_Stats(t *testing.T) {
|
|
||||||
listener := test.StartTestFileServer()
|
|
||||||
defer listener.Close()
|
|
||||||
fetcher := doDownloadReady(buildConfigFetcher(config{
|
|
||||||
Connections: 16,
|
|
||||||
}), listener, 0, t)
|
|
||||||
err := fetcher.Start()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
err = fetcher.Wait()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
stats := fetcher.Stats().(*http.Stats)
|
|
||||||
if len(stats.Connections) != 16 {
|
|
||||||
t.Errorf("Stats() got = %v, want %v", len(stats.Connections), 16)
|
|
||||||
}
|
|
||||||
totalDownloaded := int64(0)
|
|
||||||
for _, conn := range stats.Connections {
|
|
||||||
totalDownloaded += conn.Downloaded
|
|
||||||
}
|
|
||||||
if totalDownloaded != test.BuildSize {
|
|
||||||
t.Errorf("Stats() got = %v, want %v", totalDownloaded, test.BuildSize)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFetcherManager_ParseName(t *testing.T) {
|
func TestFetcherManager_ParseName(t *testing.T) {
|
||||||
type args struct {
|
type args struct {
|
||||||
u string
|
u string
|
||||||
|
|||||||
@@ -1,40 +0,0 @@
|
|||||||
package http
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"io"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type TimeoutReader struct {
|
|
||||||
reader io.Reader
|
|
||||||
timeout time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewTimeoutReader(r io.Reader, timeout time.Duration) *TimeoutReader {
|
|
||||||
return &TimeoutReader{
|
|
||||||
reader: r,
|
|
||||||
timeout: timeout,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tr *TimeoutReader) Read(p []byte) (n int, err error) {
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), tr.timeout)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
done := make(chan struct{})
|
|
||||||
var readErr error
|
|
||||||
var bytesRead int
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
bytesRead, readErr = tr.reader.Read(p)
|
|
||||||
close(done)
|
|
||||||
}()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-done:
|
|
||||||
return bytesRead, readErr
|
|
||||||
case <-ctx.Done():
|
|
||||||
return 0, ctx.Err()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
package http
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestTimeoutReader_Read(t *testing.T) {
|
|
||||||
data := []byte("Hello, World!")
|
|
||||||
reader := bytes.NewReader(data)
|
|
||||||
timeoutReader := NewTimeoutReader(reader, 1*time.Second)
|
|
||||||
|
|
||||||
buf := make([]byte, len(data))
|
|
||||||
n, err := timeoutReader.Read(buf)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("expected no error, got %v", err)
|
|
||||||
}
|
|
||||||
if n != len(data) {
|
|
||||||
t.Fatalf("expected to read %d bytes, read %d", len(data), n)
|
|
||||||
}
|
|
||||||
if !bytes.Equal(buf, data) {
|
|
||||||
t.Fatalf("expected %s, got %s", data, buf)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTimeoutReader_ReadTimeout(t *testing.T) {
|
|
||||||
reader := &slowReader{delay: 2 * time.Second}
|
|
||||||
timeoutReader := NewTimeoutReader(reader, 1*time.Second)
|
|
||||||
|
|
||||||
buf := make([]byte, 8192)
|
|
||||||
_, err := timeoutReader.Read(buf)
|
|
||||||
if err == nil {
|
|
||||||
t.Fatal("expected timeout error, got nil")
|
|
||||||
}
|
|
||||||
if !errors.Is(err, context.DeadlineExceeded) {
|
|
||||||
t.Fatalf("expected %v, got %v", context.DeadlineExceeded, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type slowReader struct {
|
|
||||||
delay time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sr *slowReader) Read(p []byte) (n int, err error) {
|
|
||||||
time.Sleep(sr.delay)
|
|
||||||
return 0, io.EOF
|
|
||||||
}
|
|
||||||
+31
-102
@@ -4,7 +4,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/GopeedLab/gopeed/pkg/base"
|
"github.com/GopeedLab/gopeed/pkg/base"
|
||||||
"github.com/armon/go-socks5"
|
"github.com/armon/go-socks5"
|
||||||
@@ -37,7 +36,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func StartTestFileServer() net.Listener {
|
func StartTestFileServer() net.Listener {
|
||||||
return startTestServer(func(sl *shutdownListener) http.Handler {
|
return startTestServer(func() http.Handler {
|
||||||
return http.FileServer(http.Dir(Dir))
|
return http.FileServer(http.Dir(Dir))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -53,7 +52,7 @@ func (s *SlowFileServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func StartTestSlowFileServer(delay time.Duration) net.Listener {
|
func StartTestSlowFileServer(delay time.Duration) net.Listener {
|
||||||
return startTestServer(func(sl *shutdownListener) http.Handler {
|
return startTestServer(func() http.Handler {
|
||||||
return &SlowFileServer{
|
return &SlowFileServer{
|
||||||
delay: delay,
|
delay: delay,
|
||||||
handler: http.FileServer(http.Dir(Dir)),
|
handler: http.FileServer(http.Dir(Dir)),
|
||||||
@@ -62,7 +61,7 @@ func StartTestSlowFileServer(delay time.Duration) net.Listener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func StartTestCustomServer() net.Listener {
|
func StartTestCustomServer() net.Listener {
|
||||||
return startTestServer(func(sl *shutdownListener) http.Handler {
|
return startTestServer(func() http.Handler {
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
mux.HandleFunc("/"+BuildName, func(writer http.ResponseWriter, request *http.Request) {
|
mux.HandleFunc("/"+BuildName, func(writer http.ResponseWriter, request *http.Request) {
|
||||||
file, err := os.Open(BuildFile)
|
file, err := os.Open(BuildFile)
|
||||||
@@ -83,43 +82,13 @@ func StartTestCustomServer() net.Listener {
|
|||||||
defer file.Close()
|
defer file.Close()
|
||||||
io.Copy(writer, file)
|
io.Copy(writer, file)
|
||||||
})
|
})
|
||||||
mux.HandleFunc("/encoded-word", func(writer http.ResponseWriter, request *http.Request) {
|
|
||||||
writer.Header().Set("Content-Disposition", "attachment; filename=\"=?UTF8?B?5rWL6K+VLnppcA==?=\"")
|
|
||||||
writer.Header().Set("Content-Type", "application/octet-stream")
|
|
||||||
writer.Header().Set("Content-Length", fmt.Sprintf("%d", BuildSize))
|
|
||||||
file, err := os.Open(BuildFile)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
io.Copy(writer, file)
|
|
||||||
})
|
|
||||||
mux.HandleFunc("/%E6%B5%8B%E8%AF%95.zip", func(writer http.ResponseWriter, request *http.Request) {
|
|
||||||
file, err := os.Open(BuildFile)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
io.Copy(writer, file)
|
|
||||||
})
|
|
||||||
mux.HandleFunc("/no-encode", func(writer http.ResponseWriter, request *http.Request) {
|
|
||||||
writer.Header().Set("Content-Disposition", "attachment; filename=测试.zip")
|
|
||||||
writer.Header().Set("Content-Type", "application/octet-stream")
|
|
||||||
writer.Header().Set("Content-Length", fmt.Sprintf("%d", BuildSize))
|
|
||||||
file, err := os.Open(BuildFile)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
io.Copy(writer, file)
|
|
||||||
})
|
|
||||||
return mux
|
return mux
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func StartTestRetryServer() net.Listener {
|
func StartTestRetryServer() net.Listener {
|
||||||
counter := 0
|
counter := 0
|
||||||
return startTestServer(func(sl *shutdownListener) http.Handler {
|
return startTestServer(func() http.Handler {
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
mux.HandleFunc("/"+BuildName, func(writer http.ResponseWriter, request *http.Request) {
|
mux.HandleFunc("/"+BuildName, func(writer http.ResponseWriter, request *http.Request) {
|
||||||
counter++
|
counter++
|
||||||
@@ -139,7 +108,7 @@ func StartTestRetryServer() net.Listener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func StartTestPostServer() net.Listener {
|
func StartTestPostServer() net.Listener {
|
||||||
return startTestServer(func(sl *shutdownListener) http.Handler {
|
return startTestServer(func() http.Handler {
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
mux.HandleFunc("/"+BuildName, func(writer http.ResponseWriter, request *http.Request) {
|
mux.HandleFunc("/"+BuildName, func(writer http.ResponseWriter, request *http.Request) {
|
||||||
if request.Method == "POST" && request.Header.Get("Authorization") != "" {
|
if request.Method == "POST" && request.Header.Get("Authorization") != "" {
|
||||||
@@ -163,7 +132,7 @@ func StartTestPostServer() net.Listener {
|
|||||||
|
|
||||||
func StartTestErrorServer() net.Listener {
|
func StartTestErrorServer() net.Listener {
|
||||||
counter := 0
|
counter := 0
|
||||||
return startTestServer(func(sl *shutdownListener) http.Handler {
|
return startTestServer(func() http.Handler {
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
mux.HandleFunc("/"+BuildName, func(writer http.ResponseWriter, request *http.Request) {
|
mux.HandleFunc("/"+BuildName, func(writer http.ResponseWriter, request *http.Request) {
|
||||||
counter++
|
counter++
|
||||||
@@ -180,7 +149,7 @@ func StartTestErrorServer() net.Listener {
|
|||||||
func StartTestLimitServer(maxConnections int32, delay int64) net.Listener {
|
func StartTestLimitServer(maxConnections int32, delay int64) net.Listener {
|
||||||
var connections atomic.Int32
|
var connections atomic.Int32
|
||||||
|
|
||||||
return startTestServer(func(sl *shutdownListener) http.Handler {
|
return startTestServer(func() http.Handler {
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
mux.HandleFunc("/"+BuildName, func(writer http.ResponseWriter, request *http.Request) {
|
mux.HandleFunc("/"+BuildName, func(writer http.ResponseWriter, request *http.Request) {
|
||||||
defer func() {
|
defer func() {
|
||||||
@@ -191,49 +160,18 @@ func StartTestLimitServer(maxConnections int32, delay int64) net.Listener {
|
|||||||
writer.WriteHeader(403)
|
writer.WriteHeader(403)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
rangeFileHandle(
|
|
||||||
writer,
|
|
||||||
request,
|
|
||||||
nil,
|
|
||||||
func(file *os.File, n int64) {
|
|
||||||
slowCopyN(sl, writer, file, n, delay)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
})
|
|
||||||
return mux
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// StartTestRangeBugServer simulate bug server:
|
|
||||||
// Don't follow Range request rules, always return more data than range, e.g. Range: bytes=0-100, return 150 bytes
|
|
||||||
func StartTestRangeBugServer() net.Listener {
|
|
||||||
return startTestServer(func(sl *shutdownListener) http.Handler {
|
|
||||||
mux := http.NewServeMux()
|
|
||||||
mux.HandleFunc("/"+BuildName, func(writer http.ResponseWriter, request *http.Request) {
|
|
||||||
rangeFileHandle(
|
|
||||||
writer,
|
|
||||||
request,
|
|
||||||
func(end int64) int64 {
|
|
||||||
var bugEnd = end
|
|
||||||
if end != 0 {
|
|
||||||
bugEnd = end + 50
|
|
||||||
if bugEnd >= BuildSize {
|
|
||||||
bugEnd = end
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return bugEnd
|
|
||||||
},
|
|
||||||
func(file *os.File, n int64) {
|
|
||||||
io.CopyN(writer, file, n)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
})
|
|
||||||
return mux
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func rangeFileHandle(writer http.ResponseWriter, request *http.Request, modifyEnd func(end int64) int64, iocpN func(file *os.File, n int64)) {
|
|
||||||
r := request.Header.Get("Range")
|
r := request.Header.Get("Range")
|
||||||
|
if r == "" {
|
||||||
|
writer.Header().Set("Content-Length", fmt.Sprintf("%d", BuildSize))
|
||||||
|
writer.WriteHeader(200)
|
||||||
|
file, err := os.Open(BuildFile)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
slowCopy(writer, file, delay)
|
||||||
|
} else {
|
||||||
// split range
|
// split range
|
||||||
s := strings.Split(r, "=")
|
s := strings.Split(r, "=")
|
||||||
if len(s) != 2 {
|
if len(s) != 2 {
|
||||||
@@ -262,17 +200,10 @@ func rangeFileHandle(writer http.ResponseWriter, request *http.Request, modifyEn
|
|||||||
if end >= BuildSize {
|
if end >= BuildSize {
|
||||||
end = BuildSize - 1
|
end = BuildSize - 1
|
||||||
}
|
}
|
||||||
|
|
||||||
if modifyEnd != nil {
|
|
||||||
end = modifyEnd(end)
|
|
||||||
}
|
|
||||||
|
|
||||||
writer.Header().Set("Content-Length", fmt.Sprintf("%d", end-start+1))
|
writer.Header().Set("Content-Length", fmt.Sprintf("%d", end-start+1))
|
||||||
writer.Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", start, end, BuildSize))
|
writer.Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", start, end, BuildSize))
|
||||||
writer.Header().Set("Accept-Ranges", "bytes")
|
writer.Header().Set("Accept-Ranges", "bytes")
|
||||||
writer.WriteHeader(206)
|
writer.WriteHeader(206)
|
||||||
(writer.(http.Flusher)).Flush()
|
|
||||||
|
|
||||||
file, err := os.Open(BuildFile)
|
file, err := os.Open(BuildFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writer.WriteHeader(500)
|
writer.WriteHeader(500)
|
||||||
@@ -280,16 +211,17 @@ func rangeFileHandle(writer http.ResponseWriter, request *http.Request, modifyEn
|
|||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
file.Seek(start, 0)
|
file.Seek(start, 0)
|
||||||
iocpN(file, end-start+1)
|
slowCopyN(writer, file, end-start+1, delay)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return mux
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// slowCopyN copies n bytes from src to dst, speed limit is bytes per second
|
// slowCopyN copies n bytes from src to dst, speed limit is bytes per second
|
||||||
func slowCopy(sl *shutdownListener, dst io.Writer, src io.Reader, delay int64) (written int64, err error) {
|
func slowCopy(dst io.Writer, src io.Reader, delay int64) (written int64, err error) {
|
||||||
buf := make([]byte, 32*1024)
|
buf := make([]byte, 32*1024)
|
||||||
for {
|
for {
|
||||||
if sl.isShutdown {
|
|
||||||
return 0, errors.New("server shutdown")
|
|
||||||
}
|
|
||||||
nr, er := src.Read(buf)
|
nr, er := src.Read(buf)
|
||||||
if nr > 0 {
|
if nr > 0 {
|
||||||
nw, ew := dst.Write(buf[0:nr])
|
nw, ew := dst.Write(buf[0:nr])
|
||||||
@@ -318,8 +250,8 @@ func slowCopy(sl *shutdownListener, dst io.Writer, src io.Reader, delay int64) (
|
|||||||
return written, err
|
return written, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func slowCopyN(sl *shutdownListener, dst io.Writer, src io.Reader, n int64, delay int64) (written int64, err error) {
|
func slowCopyN(dst io.Writer, src io.Reader, n int64, delay int64) (written int64, err error) {
|
||||||
written, err = slowCopy(sl, dst, io.LimitReader(src, n), delay)
|
written, err = slowCopy(dst, io.LimitReader(src, n), delay)
|
||||||
if written == n {
|
if written == n {
|
||||||
return n, nil
|
return n, nil
|
||||||
}
|
}
|
||||||
@@ -330,7 +262,7 @@ func slowCopyN(sl *shutdownListener, dst io.Writer, src io.Reader, n int64, dela
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func startTestServer(serverHandle func(sl *shutdownListener) http.Handler) net.Listener {
|
func startTestServer(serverHandle func() http.Handler) net.Listener {
|
||||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@@ -340,7 +272,7 @@ func startTestServer(serverHandle func(sl *shutdownListener) http.Handler) net.L
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
// Write random data
|
// 随机生成一个文件
|
||||||
l := int64(8192)
|
l := int64(8192)
|
||||||
buf := make([]byte, l)
|
buf := make([]byte, l)
|
||||||
size := int64(0)
|
size := int64(0)
|
||||||
@@ -357,24 +289,21 @@ func startTestServer(serverHandle func(sl *shutdownListener) http.Handler) net.L
|
|||||||
size += l
|
size += l
|
||||||
}
|
}
|
||||||
server := &http.Server{}
|
server := &http.Server{}
|
||||||
sl := &shutdownListener{
|
server.Handler = serverHandle()
|
||||||
|
go server.Serve(listener)
|
||||||
|
|
||||||
|
return &shutdownListener{
|
||||||
server: server,
|
server: server,
|
||||||
Listener: listener,
|
Listener: listener,
|
||||||
}
|
}
|
||||||
server.Handler = serverHandle(sl)
|
|
||||||
go server.Serve(listener)
|
|
||||||
|
|
||||||
return sl
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type shutdownListener struct {
|
type shutdownListener struct {
|
||||||
server *http.Server
|
server *http.Server
|
||||||
isShutdown bool
|
|
||||||
net.Listener
|
net.Listener
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *shutdownListener) Close() error {
|
func (c *shutdownListener) Close() error {
|
||||||
c.isShutdown = true
|
|
||||||
closeErr := c.server.Shutdown(context.Background())
|
closeErr := c.server.Shutdown(context.Background())
|
||||||
if err := ifExistAndRemove(BuildFile); err != nil {
|
if err := ifExistAndRemove(BuildFile); err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
|
|||||||
+400
@@ -0,0 +1,400 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"github.com/GopeedLab/gopeed/pkg/api/model"
|
||||||
|
"github.com/GopeedLab/gopeed/pkg/base"
|
||||||
|
"github.com/GopeedLab/gopeed/pkg/download"
|
||||||
|
"github.com/GopeedLab/gopeed/pkg/util"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"reflect"
|
||||||
|
"runtime"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Instance struct {
|
||||||
|
startCfg *model.StartConfig
|
||||||
|
downloader *download.Downloader
|
||||||
|
|
||||||
|
httpLock sync.Mutex
|
||||||
|
srv *http.Server
|
||||||
|
listener net.Listener
|
||||||
|
}
|
||||||
|
|
||||||
|
func Create(startCfg *model.StartConfig) (*Instance, error) {
|
||||||
|
if startCfg == nil {
|
||||||
|
startCfg = &model.StartConfig{}
|
||||||
|
}
|
||||||
|
startCfg.Init()
|
||||||
|
|
||||||
|
i := &Instance{
|
||||||
|
startCfg: startCfg,
|
||||||
|
}
|
||||||
|
|
||||||
|
downloadCfg := &download.DownloaderConfig{
|
||||||
|
ProductionMode: startCfg.ProductionMode,
|
||||||
|
RefreshInterval: startCfg.RefreshInterval,
|
||||||
|
}
|
||||||
|
if startCfg.Storage == model.StorageBolt {
|
||||||
|
downloadCfg.Storage = download.NewBoltStorage(startCfg.StorageDir)
|
||||||
|
} else {
|
||||||
|
downloadCfg.Storage = download.NewMemStorage()
|
||||||
|
}
|
||||||
|
downloadCfg.StorageDir = startCfg.StorageDir
|
||||||
|
downloadCfg.Init()
|
||||||
|
downloader := download.NewDownloader(downloadCfg)
|
||||||
|
if err := downloader.Setup(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
i.downloader = downloader
|
||||||
|
i.httpLock = sync.Mutex{}
|
||||||
|
|
||||||
|
return i, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Instance) Listener(listener download.Listener) *model.Result[any] {
|
||||||
|
i.downloader.Listener(listener)
|
||||||
|
return model.NewNilResult()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Instance) StartHttp() *model.Result[int] {
|
||||||
|
i.httpLock.Lock()
|
||||||
|
defer i.httpLock.Unlock()
|
||||||
|
|
||||||
|
return i.startHttp()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Instance) startHttp() *model.Result[int] {
|
||||||
|
httpCfg := i.getHttpConfig()
|
||||||
|
if httpCfg == nil || !httpCfg.Enable {
|
||||||
|
return model.NewErrorResult[int]("HTTP API server not enabled")
|
||||||
|
}
|
||||||
|
if i.srv != nil {
|
||||||
|
return model.NewErrorResult[int]("HTTP API server already started")
|
||||||
|
}
|
||||||
|
|
||||||
|
port, start, err := ListenHttp(httpCfg, i)
|
||||||
|
if err != nil {
|
||||||
|
return model.NewErrorResult[int](err.Error())
|
||||||
|
}
|
||||||
|
httpCfg.RunningPort = port
|
||||||
|
go start()
|
||||||
|
return model.NewOkResult(port)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Instance) getHttpConfig() *base.DownloaderHttpConfig {
|
||||||
|
var httpCfg *base.DownloaderHttpConfig
|
||||||
|
if i.startCfg.DownloadConfig != nil && i.startCfg.DownloadConfig.Http != nil {
|
||||||
|
httpCfg = i.startCfg.DownloadConfig.Http
|
||||||
|
} else {
|
||||||
|
cfg, _ := i.downloader.GetConfig()
|
||||||
|
httpCfg = cfg.Http
|
||||||
|
}
|
||||||
|
return httpCfg
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Instance) StopHttp() *model.Result[any] {
|
||||||
|
i.httpLock.Lock()
|
||||||
|
defer i.httpLock.Unlock()
|
||||||
|
|
||||||
|
return i.stopHttp()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Instance) stopHttp() *model.Result[any] {
|
||||||
|
if i.srv != nil {
|
||||||
|
if err := i.srv.Shutdown(context.Background()); err != nil {
|
||||||
|
i.downloader.Logger.Warn().Err(err).Msg("shutdown http server failed")
|
||||||
|
}
|
||||||
|
i.srv = nil
|
||||||
|
i.listener = nil
|
||||||
|
}
|
||||||
|
httpCfg := i.getHttpConfig()
|
||||||
|
if httpCfg != nil {
|
||||||
|
httpCfg.RunningPort = 0
|
||||||
|
}
|
||||||
|
return model.NewNilResult()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Instance) RestartHttp() *model.Result[int] {
|
||||||
|
i.httpLock.Lock()
|
||||||
|
defer i.httpLock.Unlock()
|
||||||
|
|
||||||
|
i.stopHttp()
|
||||||
|
return i.startHttp()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Instance) Info() *model.Result[map[string]any] {
|
||||||
|
return model.NewOkResult(map[string]any{
|
||||||
|
"version": base.Version,
|
||||||
|
"runtime": runtime.Version(),
|
||||||
|
"os": runtime.GOOS,
|
||||||
|
"arch": runtime.GOARCH,
|
||||||
|
"inDocker": base.InDocker == "true",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Instance) Resolve(req *base.Request) *model.Result[*download.ResolveResult] {
|
||||||
|
rr, err := i.downloader.Resolve(req)
|
||||||
|
if err != nil {
|
||||||
|
return model.NewErrorResult[*download.ResolveResult](err.Error())
|
||||||
|
}
|
||||||
|
return model.NewOkResult(rr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Instance) CreateTask(req *model.CreateTask) *model.Result[string] {
|
||||||
|
var (
|
||||||
|
taskId string
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if req.Rid != "" {
|
||||||
|
taskId, err = i.downloader.Create(req.Rid, req.Opt)
|
||||||
|
} else if req.Req != nil {
|
||||||
|
taskId, err = i.downloader.CreateDirect(req.Req, req.Opt)
|
||||||
|
} else {
|
||||||
|
return model.NewErrorResult[string]("param invalid: rid or req", model.CodeInvalidParam)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return model.NewErrorResult[string](err.Error())
|
||||||
|
}
|
||||||
|
return model.NewOkResult(taskId)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Instance) CreateTaskBatch(req *model.CreateTaskBatch) *model.Result[[]string] {
|
||||||
|
if len(req.Reqs) == 0 {
|
||||||
|
return model.NewErrorResult[[]string]("param invalid: reqs", model.CodeInvalidParam)
|
||||||
|
}
|
||||||
|
taskIds, err := i.downloader.CreateDirectBatch(req.Reqs, req.Opt)
|
||||||
|
if err != nil {
|
||||||
|
return model.NewErrorResult[[]string](err.Error())
|
||||||
|
}
|
||||||
|
return model.NewOkResult(taskIds)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Instance) PauseTask(taskId string) *model.Result[any] {
|
||||||
|
err := i.downloader.Pause(&download.TaskFilter{
|
||||||
|
ID: []string{taskId},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return model.NewErrorResult[any](err.Error())
|
||||||
|
}
|
||||||
|
return model.NewNilResult()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Instance) PauseTasks(filter *download.TaskFilter) *model.Result[any] {
|
||||||
|
err := i.downloader.Pause(filter)
|
||||||
|
if err != nil {
|
||||||
|
return model.NewErrorResult[any](err.Error())
|
||||||
|
}
|
||||||
|
return model.NewNilResult()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Instance) ContinueTask(taskId string) *model.Result[any] {
|
||||||
|
err := i.downloader.Continue(&download.TaskFilter{
|
||||||
|
ID: []string{taskId},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return model.NewErrorResult[any](err.Error())
|
||||||
|
}
|
||||||
|
return model.NewNilResult()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Instance) ContinueTasks(filter *download.TaskFilter) *model.Result[any] {
|
||||||
|
err := i.downloader.Continue(filter)
|
||||||
|
if err != nil {
|
||||||
|
return model.NewErrorResult[any](err.Error())
|
||||||
|
}
|
||||||
|
return model.NewNilResult()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Instance) DeleteTask(taskId string, force bool) *model.Result[any] {
|
||||||
|
err := i.downloader.Delete(&download.TaskFilter{
|
||||||
|
ID: []string{taskId},
|
||||||
|
}, force)
|
||||||
|
if err != nil {
|
||||||
|
return model.NewErrorResult[any](err.Error())
|
||||||
|
}
|
||||||
|
return model.NewNilResult()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Instance) DeleteTasks(filter *download.TaskFilter, force bool) *model.Result[any] {
|
||||||
|
err := i.downloader.Delete(filter, force)
|
||||||
|
if err != nil {
|
||||||
|
return model.NewErrorResult[any](err.Error())
|
||||||
|
}
|
||||||
|
return model.NewNilResult()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Instance) GetTask(taskId string) *model.Result[*download.Task] {
|
||||||
|
if taskId == "" {
|
||||||
|
return model.NewErrorResult[*download.Task]("param invalid: id", model.CodeInvalidParam)
|
||||||
|
}
|
||||||
|
task := i.downloader.GetTask(taskId)
|
||||||
|
if task == nil {
|
||||||
|
return model.NewErrorResult[*download.Task]("task not found", model.CodeTaskNotFound)
|
||||||
|
}
|
||||||
|
return model.NewOkResult(task)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Instance) GetTasks(filter *download.TaskFilter) *model.Result[[]*download.Task] {
|
||||||
|
return model.NewOkResult(i.downloader.GetTasksByFilter(filter))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Instance) GetTaskStats(taskId string) *model.Result[any] {
|
||||||
|
stats, err := i.downloader.GetTaskStats(taskId)
|
||||||
|
if err != nil {
|
||||||
|
return model.NewErrorResult[any](err.Error())
|
||||||
|
}
|
||||||
|
return model.NewOkResult(stats)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Instance) GetConfig() *model.Result[*base.DownloaderStoreConfig] {
|
||||||
|
config, err := i.downloader.GetConfig()
|
||||||
|
if err != nil {
|
||||||
|
return model.NewErrorResult[*base.DownloaderStoreConfig](err.Error())
|
||||||
|
}
|
||||||
|
return model.NewOkResult(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Instance) PutConfig(cfg *base.DownloaderStoreConfig) *model.Result[any] {
|
||||||
|
err := i.downloader.PutConfig(cfg)
|
||||||
|
if err != nil {
|
||||||
|
return model.NewErrorResult[any](err.Error())
|
||||||
|
}
|
||||||
|
return model.NewNilResult()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Instance) InstallExtension(req *model.InstallExtension) *model.Result[string] {
|
||||||
|
var (
|
||||||
|
installedExt *download.Extension
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if req.DevMode {
|
||||||
|
installedExt, err = i.downloader.InstallExtensionByFolder(req.URL, true)
|
||||||
|
} else {
|
||||||
|
installedExt, err = i.downloader.InstallExtensionByGit(req.URL)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return model.NewErrorResult[string](err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return model.NewOkResult(installedExt.Identity)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Instance) GetExtension(identity string) *model.Result[*download.Extension] {
|
||||||
|
extension, err := i.downloader.GetExtension(identity)
|
||||||
|
if err != nil {
|
||||||
|
return model.NewErrorResult[*download.Extension](err.Error())
|
||||||
|
}
|
||||||
|
return model.NewOkResult(extension)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Instance) GetExtensions() *model.Result[[]*download.Extension] {
|
||||||
|
return model.NewOkResult(i.downloader.GetExtensions())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Instance) UpdateExtensionSettings(identity string, req *model.UpdateExtensionSettings) *model.Result[any] {
|
||||||
|
err := i.downloader.UpdateExtensionSettings(identity, req.Settings)
|
||||||
|
if err != nil {
|
||||||
|
return model.NewErrorResult[any](err.Error())
|
||||||
|
}
|
||||||
|
return model.NewNilResult()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Instance) SwitchExtension(identity string, req *model.SwitchExtension) *model.Result[any] {
|
||||||
|
err := i.downloader.SwitchExtension(identity, req.Status)
|
||||||
|
if err != nil {
|
||||||
|
return model.NewErrorResult[any](err.Error())
|
||||||
|
}
|
||||||
|
return model.NewNilResult()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Instance) DeleteExtension(identity string) *model.Result[any] {
|
||||||
|
err := i.downloader.DeleteExtension(identity)
|
||||||
|
if err != nil {
|
||||||
|
return model.NewErrorResult[any](err.Error())
|
||||||
|
}
|
||||||
|
return model.NewNilResult()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Instance) UpgradeCheckExtension(identity string) *model.Result[*model.UpgradeCheckExtensionResp] {
|
||||||
|
newVersion, err := i.downloader.UpgradeCheckExtension(identity)
|
||||||
|
if err != nil {
|
||||||
|
return model.NewErrorResult[*model.UpgradeCheckExtensionResp](err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return model.NewOkResult(&model.UpgradeCheckExtensionResp{
|
||||||
|
NewVersion: newVersion,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Instance) UpgradeExtension(identity string) *model.Result[any] {
|
||||||
|
err := i.downloader.UpgradeExtension(identity)
|
||||||
|
if err != nil {
|
||||||
|
return model.NewErrorResult[any](err.Error())
|
||||||
|
}
|
||||||
|
return model.NewNilResult()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Instance) Close() *model.Result[any] {
|
||||||
|
i.StopHttp()
|
||||||
|
if i.downloader != nil {
|
||||||
|
if err := i.downloader.Close(); err != nil {
|
||||||
|
i.downloader.Logger.Warn().Err(err).Msg("close downloader failed")
|
||||||
|
}
|
||||||
|
i.downloader = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return model.NewNilResult()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Instance) Clear() *model.Result[any] {
|
||||||
|
if err := i.downloader.Clear(); err != nil {
|
||||||
|
return model.NewErrorResult[any](err.Error())
|
||||||
|
}
|
||||||
|
return model.NewNilResult()
|
||||||
|
}
|
||||||
|
|
||||||
|
type Request struct {
|
||||||
|
Method string `json:"method"`
|
||||||
|
Params []any `json:"params"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invoke support dynamic call method
|
||||||
|
func Invoke(instance *Instance, request *Request) (ret any) {
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
ret = model.NewErrorResult[any](fmt.Sprintf("%v", err))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
method, args := request.Method, request.Params
|
||||||
|
dsType := reflect.ValueOf(instance)
|
||||||
|
fn := dsType.MethodByName(method)
|
||||||
|
numIn := fn.Type().NumIn()
|
||||||
|
in := make([]reflect.Value, numIn)
|
||||||
|
for i := 0; i < numIn; i++ {
|
||||||
|
paramType := fn.Type().In(i)
|
||||||
|
arg := args[i]
|
||||||
|
if arg == nil {
|
||||||
|
in[i] = reflect.Zero(fn.Type().In(i))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var param reflect.Value
|
||||||
|
var paramPtr any
|
||||||
|
if paramType.Kind() == reflect.Ptr {
|
||||||
|
param = reflect.New(paramType.Elem())
|
||||||
|
paramPtr = param.Interface()
|
||||||
|
} else {
|
||||||
|
param = reflect.New(paramType).Elem()
|
||||||
|
paramPtr = param.Addr().Interface()
|
||||||
|
}
|
||||||
|
if err := util.MapToStruct(arg, paramPtr); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
in[i] = param
|
||||||
|
}
|
||||||
|
retVals := fn.Call(in)
|
||||||
|
return retVals[0].Interface()
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"github.com/GopeedLab/gopeed/pkg/api/model"
|
||||||
|
"github.com/GopeedLab/gopeed/pkg/download"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestInvoke(t *testing.T) {
|
||||||
|
doTestInvoke(t, "Info", []any{}, model.CodeOk)
|
||||||
|
doTestInvoke(t, "Info1", []any{}, model.CodeError)
|
||||||
|
doTestInvoke(t, "CreateTask", []any{nil}, model.CodeError)
|
||||||
|
doTestInvoke(t, "GetTasks", nil, model.CodeError)
|
||||||
|
doTestInvoke(t, "GetTasks", []any{}, model.CodeError)
|
||||||
|
doTestInvoke(t, "GetTasks", []any{"{abc:123"}, model.CodeError)
|
||||||
|
doTestInvoke(t, "GetTasks", []any{&download.TaskFilter{}}, model.CodeOk)
|
||||||
|
doTestInvoke(t, "SwitchExtension", []any{"test", &model.SwitchExtension{Status: true}}, model.CodeError)
|
||||||
|
}
|
||||||
|
|
||||||
|
func doTestInvoke(t *testing.T, method string, params []any, expectCode model.RespCode) {
|
||||||
|
instance, _ := Create(nil)
|
||||||
|
defer instance.Close()
|
||||||
|
|
||||||
|
result := Invoke(instance, &Request{
|
||||||
|
Method: method,
|
||||||
|
Params: params,
|
||||||
|
})
|
||||||
|
buf, err := json.Marshal(result)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var res model.Result[any]
|
||||||
|
if err := json.Unmarshal(buf, &res); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if res.Code != expectCode {
|
||||||
|
t.Fatalf("Invoke method [%s] failed, expect code [%d], got [%d], msg: %s", method, expectCode, res.Code, res.Msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
+451
@@ -0,0 +1,451 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"github.com/GopeedLab/gopeed/pkg/api/model"
|
||||||
|
"github.com/GopeedLab/gopeed/pkg/base"
|
||||||
|
"github.com/GopeedLab/gopeed/pkg/download"
|
||||||
|
"github.com/gorilla/handlers"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const instanceKey string = "apiInstance"
|
||||||
|
|
||||||
|
func ListenHttp(httpCfg *base.DownloaderHttpConfig, ai *Instance) (int, func(), error) {
|
||||||
|
var r = mux.NewRouter()
|
||||||
|
r.Methods(http.MethodGet).Path("/api/v1/info").HandlerFunc(Info)
|
||||||
|
r.Methods(http.MethodPost).Path("/api/v1/resolve").HandlerFunc(Resolve)
|
||||||
|
r.Methods(http.MethodPost).Path("/api/v1/tasks").HandlerFunc(CreateTask)
|
||||||
|
r.Methods(http.MethodPost).Path("/api/v1/tasks/batch").HandlerFunc(CreateTaskBatch)
|
||||||
|
r.Methods(http.MethodPut).Path("/api/v1/tasks/{id}/pause").HandlerFunc(PauseTask)
|
||||||
|
r.Methods(http.MethodPut).Path("/api/v1/tasks/pause").HandlerFunc(PauseTasks)
|
||||||
|
r.Methods(http.MethodPut).Path("/api/v1/tasks/{id}/continue").HandlerFunc(ContinueTask)
|
||||||
|
r.Methods(http.MethodPut).Path("/api/v1/tasks/continue").HandlerFunc(ContinueTasks)
|
||||||
|
r.Methods(http.MethodDelete).Path("/api/v1/tasks/{id}").HandlerFunc(DeleteTask)
|
||||||
|
r.Methods(http.MethodDelete).Path("/api/v1/tasks").HandlerFunc(DeleteTasks)
|
||||||
|
r.Methods(http.MethodGet).Path("/api/v1/tasks/{id}").HandlerFunc(GetTask)
|
||||||
|
r.Methods(http.MethodGet).Path("/api/v1/tasks").HandlerFunc(GetTasks)
|
||||||
|
r.Methods(http.MethodGet).Path("/api/v1/tasks/{id}/stats").HandlerFunc(GetTaskStats)
|
||||||
|
r.Methods(http.MethodGet).Path("/api/v1/config").HandlerFunc(GetConfig)
|
||||||
|
r.Methods(http.MethodPut).Path("/api/v1/config").HandlerFunc(PutConfig)
|
||||||
|
r.Methods(http.MethodPost).Path("/api/v1/extensions").HandlerFunc(InstallExtension)
|
||||||
|
r.Methods(http.MethodGet).Path("/api/v1/extensions").HandlerFunc(GetExtensions)
|
||||||
|
r.Methods(http.MethodGet).Path("/api/v1/extensions/{identity}").HandlerFunc(GetExtension)
|
||||||
|
r.Methods(http.MethodPut).Path("/api/v1/extensions/{identity}/settings").HandlerFunc(UpdateExtensionSettings)
|
||||||
|
r.Methods(http.MethodPut).Path("/api/v1/extensions/{identity}/switch").HandlerFunc(SwitchExtension)
|
||||||
|
r.Methods(http.MethodDelete).Path("/api/v1/extensions/{identity}").HandlerFunc(DeleteExtension)
|
||||||
|
r.Methods(http.MethodGet).Path("/api/v1/extensions/{identity}/upgrade").HandlerFunc(UpgradeCheckExtension)
|
||||||
|
r.Methods(http.MethodPost).Path("/api/v1/extensions/{identity}/upgrade").HandlerFunc(UpgradeExtension)
|
||||||
|
if ai.startCfg.WebEnable {
|
||||||
|
r.Path("/api/v1/proxy").HandlerFunc(DoProxy)
|
||||||
|
r.PathPrefix("/fs/tasks").Handler(http.FileServer(&taskFileSystem{
|
||||||
|
downloader: ai.downloader,
|
||||||
|
}))
|
||||||
|
r.PathPrefix("/fs/extensions").Handler(http.FileServer(&extensionFileSystem{
|
||||||
|
downloader: ai.downloader,
|
||||||
|
}))
|
||||||
|
r.PathPrefix("/").Handler(http.FileServer(http.FS(ai.startCfg.WebFS)))
|
||||||
|
}
|
||||||
|
|
||||||
|
r.Use(func(h http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := context.WithValue(r.Context(), instanceKey, ai)
|
||||||
|
h.ServeHTTP(w, r.WithContext(ctx))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
if httpCfg.ApiToken != "" || (ai.startCfg.WebEnable && ai.startCfg.WebBasicAuth != nil) {
|
||||||
|
r.Use(func(h http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if httpCfg.ApiToken != "" && r.Header.Get("X-Api-Token") == httpCfg.ApiToken {
|
||||||
|
h.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if ai.startCfg.WebEnable && ai.startCfg.WebBasicAuth != nil {
|
||||||
|
if r.Header.Get("Authorization") == ai.startCfg.WebBasicAuth.Authorization() {
|
||||||
|
h.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Header().Set("WWW-Authenticate", "Basic realm=\"gopeed web\"")
|
||||||
|
}
|
||||||
|
WriteStatusJson(w, http.StatusUnauthorized, model.NewErrorResult[any]("unauthorized", model.CodeUnauthorized))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// recover panic
|
||||||
|
r.Use(func(h http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
defer func() {
|
||||||
|
if v := recover(); v != nil {
|
||||||
|
err := errors.WithStack(fmt.Errorf("%v", v))
|
||||||
|
ai.downloader.Logger.Error().Stack().Err(err).Msgf("http server panic: %s %s", r.Method, r.RequestURI)
|
||||||
|
WriteJson(w, model.NewErrorResult[any](err.Error(), model.CodeError))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
h.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
srv := &http.Server{Handler: handlers.CORS(
|
||||||
|
handlers.AllowedHeaders([]string{"Content-Type", "X-Api-Token", "X-Target-Uri"}),
|
||||||
|
handlers.AllowedMethods([]string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"}),
|
||||||
|
handlers.AllowedOrigins([]string{"*"}),
|
||||||
|
)(r)}
|
||||||
|
|
||||||
|
listener, err := net.Listen("tcp", net.JoinHostPort(httpCfg.Host, fmt.Sprintf("%d", httpCfg.Port)))
|
||||||
|
if err != nil {
|
||||||
|
return 0, nil, err
|
||||||
|
}
|
||||||
|
ai.srv = srv
|
||||||
|
ai.listener = listener
|
||||||
|
|
||||||
|
return listener.Addr().(*net.TCPAddr).Port, func() {
|
||||||
|
if err := srv.Serve(listener); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||||
|
ai.downloader.Logger.Error().Err(err).Msg("http server listen error")
|
||||||
|
}
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getInstance(r *http.Request) *Instance {
|
||||||
|
return r.Context().Value(instanceKey).(*Instance)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Info(w http.ResponseWriter, r *http.Request) {
|
||||||
|
WriteJson(w, getInstance(r).Info())
|
||||||
|
}
|
||||||
|
|
||||||
|
func Resolve(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req base.Request
|
||||||
|
if ReadJson(r, w, &req) {
|
||||||
|
WriteJson(w, getInstance(r).Resolve(&req))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateTask(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req model.CreateTask
|
||||||
|
if ReadJson(r, w, &req) {
|
||||||
|
WriteJson(w, getInstance(r).CreateTask(&req))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateTaskBatch(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req model.CreateTaskBatch
|
||||||
|
if ReadJson(r, w, &req) {
|
||||||
|
WriteJson(w, getInstance(r).CreateTaskBatch(&req))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func PauseTask(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var taskId string
|
||||||
|
if !parseTaskId(r, w, &taskId) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
WriteJson(w, getInstance(r).PauseTask(taskId))
|
||||||
|
}
|
||||||
|
|
||||||
|
func PauseTasks(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var filter download.TaskFilter
|
||||||
|
if !parseFilter(r, w, &filter) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
WriteJson(w, getInstance(r).PauseTasks(&filter))
|
||||||
|
}
|
||||||
|
|
||||||
|
func ContinueTask(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var taskId string
|
||||||
|
if !parseTaskId(r, w, &taskId) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
WriteJson(w, getInstance(r).ContinueTask(taskId))
|
||||||
|
}
|
||||||
|
|
||||||
|
func ContinueTasks(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var filter download.TaskFilter
|
||||||
|
if !parseFilter(r, w, &filter) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
WriteJson(w, getInstance(r).ContinueTasks(&filter))
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteTask(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var taskId string
|
||||||
|
if !parseTaskId(r, w, &taskId) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
force := r.FormValue("force")
|
||||||
|
WriteJson(w, getInstance(r).DeleteTask(taskId, force == "true"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteTasks(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var filter download.TaskFilter
|
||||||
|
if !parseFilter(r, w, &filter) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
force := r.FormValue("force")
|
||||||
|
WriteJson(w, getInstance(r).DeleteTasks(&filter, force == "true"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetTask(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var taskId string
|
||||||
|
if !parseTaskId(r, w, &taskId) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
WriteJson(w, getInstance(r).GetTask(taskId))
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetTasks(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var filter download.TaskFilter
|
||||||
|
if !parseFilter(r, w, &filter) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
WriteJson(w, getInstance(r).GetTasks(&filter))
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetTaskStats(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var taskId string
|
||||||
|
if !parseTaskId(r, w, &taskId) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
WriteJson(w, getInstance(r).GetTaskStats(taskId))
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetConfig(w http.ResponseWriter, r *http.Request) {
|
||||||
|
WriteJson(w, getInstance(r).GetConfig())
|
||||||
|
}
|
||||||
|
|
||||||
|
func PutConfig(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var cfg base.DownloaderStoreConfig
|
||||||
|
if !ReadJson(r, w, &cfg) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
WriteJson(w, getInstance(r).PutConfig(&cfg))
|
||||||
|
}
|
||||||
|
|
||||||
|
func InstallExtension(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req model.InstallExtension
|
||||||
|
if !ReadJson(r, w, &req) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
WriteJson(w, getInstance(r).InstallExtension(&req))
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetExtensions(w http.ResponseWriter, r *http.Request) {
|
||||||
|
WriteJson(w, getInstance(r).GetExtensions())
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetExtension(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var identity string
|
||||||
|
if !parseIdentity(r, w, &identity) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
WriteJson(w, getInstance(r).GetExtension(identity))
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateExtensionSettings(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var identity string
|
||||||
|
if !parseIdentity(r, w, &identity) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var req model.UpdateExtensionSettings
|
||||||
|
if !ReadJson(r, w, &req) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
WriteJson(w, getInstance(r).UpdateExtensionSettings(identity, &req))
|
||||||
|
}
|
||||||
|
|
||||||
|
func SwitchExtension(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var identity string
|
||||||
|
if !parseIdentity(r, w, &identity) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var switchExtension model.SwitchExtension
|
||||||
|
if !ReadJson(r, w, &switchExtension) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
WriteJson(w, getInstance(r).SwitchExtension(identity, &switchExtension))
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteExtension(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var identity string
|
||||||
|
if !parseIdentity(r, w, &identity) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
WriteJson(w, getInstance(r).DeleteExtension(identity))
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpgradeCheckExtension(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var identity string
|
||||||
|
if !parseIdentity(r, w, &identity) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
WriteJson(w, getInstance(r).UpgradeCheckExtension(identity))
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpgradeExtension(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var identity string
|
||||||
|
if !parseIdentity(r, w, &identity) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
WriteJson(w, getInstance(r).UpgradeExtension(identity))
|
||||||
|
}
|
||||||
|
|
||||||
|
func DoProxy(w http.ResponseWriter, r *http.Request) {
|
||||||
|
target := r.Header.Get("X-Target-Uri")
|
||||||
|
if target == "" {
|
||||||
|
writeError(w, "param invalid: X-Target-Uri")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
targetUrl, err := url.Parse(target)
|
||||||
|
if err != nil {
|
||||||
|
writeError(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r.RequestURI = ""
|
||||||
|
r.URL = targetUrl
|
||||||
|
r.Host = targetUrl.Host
|
||||||
|
resp, err := http.DefaultClient.Do(r)
|
||||||
|
if err != nil {
|
||||||
|
writeError(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
for k, vv := range resp.Header {
|
||||||
|
for _, v := range vv {
|
||||||
|
w.Header().Set(k, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.WriteHeader(resp.StatusCode)
|
||||||
|
buf, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
writeError(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Write(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parsePathParam(r *http.Request, w http.ResponseWriter, name string, value *string) bool {
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
*value = vars[name]
|
||||||
|
if *value == "" {
|
||||||
|
WriteJson(w, model.NewErrorResult[any]("param invalid: id", model.CodeInvalidParam))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseTaskId(r *http.Request, w http.ResponseWriter, taskId *string) bool {
|
||||||
|
return parsePathParam(r, w, "id", taskId)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseIdentity(r *http.Request, w http.ResponseWriter, identity *string) bool {
|
||||||
|
return parsePathParam(r, w, "identity", identity)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseFilter(r *http.Request, w http.ResponseWriter, filter *download.TaskFilter) bool {
|
||||||
|
if err := r.ParseForm(); err != nil {
|
||||||
|
WriteJson(w, model.NewErrorResult[any](err.Error()))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
filter.ID = r.Form["id"]
|
||||||
|
filter.Status = convertStatues(r.Form["status"])
|
||||||
|
filter.NotStatus = convertStatues(r.Form["notStatus"])
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertStatues(statues []string) []base.Status {
|
||||||
|
result := make([]base.Status, 0)
|
||||||
|
for _, status := range statues {
|
||||||
|
result = append(result, base.Status(status))
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeError(w http.ResponseWriter, msg string) {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
w.Write([]byte(msg))
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadJson(r *http.Request, w http.ResponseWriter, v any) bool {
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(v); err != nil {
|
||||||
|
WriteJson(w, model.NewErrorResult[any](err.Error()))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func WriteJson(w http.ResponseWriter, v any) {
|
||||||
|
WriteStatusJson(w, http.StatusOK, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func WriteStatusJson(w http.ResponseWriter, statusCode int, v any) {
|
||||||
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
|
w.WriteHeader(statusCode)
|
||||||
|
json.NewEncoder(w).Encode(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolvePath(urlPath string, prefix string) (identity string, path string, err error) {
|
||||||
|
// remove prefix
|
||||||
|
clearPath := strings.TrimPrefix(urlPath, prefix)
|
||||||
|
// match extension identity, eg: /fs/extensions/identity/xxx
|
||||||
|
reg := regexp.MustCompile(`^/([^/]+)/(.*)$`)
|
||||||
|
if !reg.MatchString(clearPath) {
|
||||||
|
err = os.ErrNotExist
|
||||||
|
return
|
||||||
|
}
|
||||||
|
matched := reg.FindStringSubmatch(clearPath)
|
||||||
|
if len(matched) != 3 {
|
||||||
|
err = os.ErrNotExist
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return matched[1], matched[2], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle task file resource
|
||||||
|
type taskFileSystem struct {
|
||||||
|
downloader *download.Downloader
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *taskFileSystem) Open(name string) (http.File, error) {
|
||||||
|
// get extension identity
|
||||||
|
identity, path, err := resolvePath(name, "/fs/tasks")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
task := e.downloader.GetTask(identity)
|
||||||
|
if task == nil {
|
||||||
|
return nil, os.ErrNotExist
|
||||||
|
}
|
||||||
|
return os.Open(filepath.Join(task.Meta.RootDirPath(), path))
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle extension file resource
|
||||||
|
type extensionFileSystem struct {
|
||||||
|
downloader *download.Downloader
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *extensionFileSystem) Open(name string) (http.File, error) {
|
||||||
|
// get extension identity
|
||||||
|
identity, path, err := resolvePath(name, "/fs/extensions")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
extension, err := e.downloader.GetExtension(identity)
|
||||||
|
if err != nil {
|
||||||
|
return nil, os.ErrNotExist
|
||||||
|
}
|
||||||
|
extensionPath := e.downloader.ExtensionPath(extension)
|
||||||
|
return os.Open(filepath.Join(extensionPath, path))
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package rest
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@@ -7,22 +7,21 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/GopeedLab/gopeed/internal/test"
|
"github.com/GopeedLab/gopeed/internal/test"
|
||||||
|
"github.com/GopeedLab/gopeed/pkg/api/model"
|
||||||
"github.com/GopeedLab/gopeed/pkg/base"
|
"github.com/GopeedLab/gopeed/pkg/base"
|
||||||
"github.com/GopeedLab/gopeed/pkg/download"
|
"github.com/GopeedLab/gopeed/pkg/download"
|
||||||
"github.com/GopeedLab/gopeed/pkg/rest/model"
|
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
httpInstance *Instance
|
||||||
restPort int
|
restPort int
|
||||||
|
|
||||||
taskReq = &base.Request{
|
taskReq = &base.Request{
|
||||||
@@ -92,7 +91,7 @@ func TestCreateTask(t *testing.T) {
|
|||||||
|
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
Downloader.Listener(func(event *download.Event) {
|
httpInstance.Listener(func(event *download.Event) {
|
||||||
if event.Key == download.EventKeyFinally {
|
if event.Key == download.EventKeyFinally {
|
||||||
wg.Done()
|
wg.Done()
|
||||||
}
|
}
|
||||||
@@ -119,7 +118,7 @@ func TestCreateDirectTask(t *testing.T) {
|
|||||||
doTest(func() {
|
doTest(func() {
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
Downloader.Listener(func(event *download.Event) {
|
httpInstance.Listener(func(event *download.Event) {
|
||||||
if event.Key == download.EventKeyFinally {
|
if event.Key == download.EventKeyFinally {
|
||||||
wg.Done()
|
wg.Done()
|
||||||
}
|
}
|
||||||
@@ -141,13 +140,11 @@ func TestCreateDirectTask(t *testing.T) {
|
|||||||
|
|
||||||
func TestCreateDirectTaskBatch(t *testing.T) {
|
func TestCreateDirectTaskBatch(t *testing.T) {
|
||||||
doTest(func() {
|
doTest(func() {
|
||||||
reqs := make([]*base.CreateTaskBatchItem, 0)
|
reqs := make([]*base.Request, 0)
|
||||||
for i := 0; i < 5; i++ {
|
for i := 0; i < 5; i++ {
|
||||||
reqs = append(reqs, &base.CreateTaskBatchItem{
|
reqs = append(reqs, createReq.Req)
|
||||||
Req: createReq.Req,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
taskIds := httpRequestCheckOk[[]string](http.MethodPost, "/api/v1/tasks/batch", &base.CreateTaskBatch{
|
taskIds := httpRequestCheckOk[[]string](http.MethodPost, "/api/v1/tasks/batch", &model.CreateTaskBatch{
|
||||||
Reqs: reqs,
|
Reqs: reqs,
|
||||||
})
|
})
|
||||||
if len(taskIds) != len(reqs) {
|
if len(taskIds) != len(reqs) {
|
||||||
@@ -156,50 +153,11 @@ func TestCreateDirectTaskBatch(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCreateDirectTaskBatchWithOpt(t *testing.T) {
|
|
||||||
doTest(func() {
|
|
||||||
reqs := make([]*base.CreateTaskBatchItem, 0)
|
|
||||||
for i := 0; i < 5; i++ {
|
|
||||||
item := &base.CreateTaskBatchItem{
|
|
||||||
Req: createReq.Req,
|
|
||||||
}
|
|
||||||
if i == 0 {
|
|
||||||
item.Opts = &base.Options{
|
|
||||||
Name: "spe_opt.data",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
reqs = append(reqs, item)
|
|
||||||
}
|
|
||||||
taskIds := httpRequestCheckOk[[]string](http.MethodPost, "/api/v1/tasks/batch", &base.CreateTaskBatch{
|
|
||||||
Reqs: reqs,
|
|
||||||
Opts: &base.Options{
|
|
||||||
Name: "default_opt.data",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if len(taskIds) != len(reqs) {
|
|
||||||
t.Errorf("CreateDirectTaskBatch() got = %v, want %v", len(taskIds), len(reqs))
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, taskId := range taskIds {
|
|
||||||
task := httpRequestCheckOk[*download.Task](http.MethodGet, "/api/v1/tasks/"+taskId, nil)
|
|
||||||
if i == 0 {
|
|
||||||
if !strings.Contains(task.Name(), "spe_opt") {
|
|
||||||
t.Errorf("CreateDirectTaskBatch() got = %v, want %v", task.Name(), "spe_opt.data")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if !strings.Contains(task.Name(), "default_opt") {
|
|
||||||
t.Errorf("CreateDirectTaskBatch() got = %v, want %v", task.Name(), "default_opt.data")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCreateDirectTaskWithResoled(t *testing.T) {
|
func TestCreateDirectTaskWithResoled(t *testing.T) {
|
||||||
doTest(func() {
|
doTest(func() {
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
Downloader.Listener(func(event *download.Event) {
|
httpInstance.Listener(func(event *download.Event) {
|
||||||
if event.Key == download.EventKeyFinally {
|
if event.Key == download.EventKeyFinally {
|
||||||
wg.Done()
|
wg.Done()
|
||||||
}
|
}
|
||||||
@@ -223,7 +181,7 @@ func TestPauseAndContinueTask(t *testing.T) {
|
|||||||
doTest(func() {
|
doTest(func() {
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
Downloader.Listener(func(event *download.Event) {
|
httpInstance.Listener(func(event *download.Event) {
|
||||||
switch event.Key {
|
switch event.Key {
|
||||||
case download.EventKeyFinally:
|
case download.EventKeyFinally:
|
||||||
wg.Done()
|
wg.Done()
|
||||||
@@ -257,10 +215,7 @@ func TestPauseAndContinueTask(t *testing.T) {
|
|||||||
|
|
||||||
func TestPauseAllAndContinueALLTasks(t *testing.T) {
|
func TestPauseAllAndContinueALLTasks(t *testing.T) {
|
||||||
doTest(func() {
|
doTest(func() {
|
||||||
cfg, err := Downloader.GetConfig()
|
cfg := UnwrapResult(httpInstance.GetConfig())
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
createAndPause := func() {
|
createAndPause := func() {
|
||||||
taskId := httpRequestCheckOk[string](http.MethodPost, "/api/v1/tasks", createReq)
|
taskId := httpRequestCheckOk[string](http.MethodPost, "/api/v1/tasks", createReq)
|
||||||
@@ -318,7 +273,7 @@ func TestDeleteAllTasks(t *testing.T) {
|
|||||||
|
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
wg.Add(taskCount)
|
wg.Add(taskCount)
|
||||||
Downloader.Listener(func(event *download.Event) {
|
httpInstance.Listener(func(event *download.Event) {
|
||||||
if event.Key == download.EventKeyFinally {
|
if event.Key == download.EventKeyFinally {
|
||||||
wg.Done()
|
wg.Done()
|
||||||
}
|
}
|
||||||
@@ -344,7 +299,7 @@ func TestDeleteTasksByStatues(t *testing.T) {
|
|||||||
|
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
wg.Add(taskCount)
|
wg.Add(taskCount)
|
||||||
Downloader.Listener(func(event *download.Event) {
|
httpInstance.Listener(func(event *download.Event) {
|
||||||
if event.Key == download.EventKeyFinally {
|
if event.Key == download.EventKeyFinally {
|
||||||
wg.Done()
|
wg.Done()
|
||||||
}
|
}
|
||||||
@@ -368,7 +323,7 @@ func TestGetTasks(t *testing.T) {
|
|||||||
doTest(func() {
|
doTest(func() {
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
Downloader.Listener(func(event *download.Event) {
|
httpInstance.Listener(func(event *download.Event) {
|
||||||
if event.Key == download.EventKeyFinally {
|
if event.Key == download.EventKeyFinally {
|
||||||
wg.Done()
|
wg.Done()
|
||||||
}
|
}
|
||||||
@@ -397,7 +352,10 @@ func TestGetAndPutConfig(t *testing.T) {
|
|||||||
cfg := httpRequestCheckOk[*base.DownloaderStoreConfig](http.MethodGet, "/api/v1/config", nil)
|
cfg := httpRequestCheckOk[*base.DownloaderStoreConfig](http.MethodGet, "/api/v1/config", nil)
|
||||||
cfg.DownloadDir = "./download"
|
cfg.DownloadDir = "./download"
|
||||||
cfg.Extra = map[string]any{
|
cfg.Extra = map[string]any{
|
||||||
"serverConfig": &Config{
|
"serverConfig": &struct {
|
||||||
|
Host string `json:"host"`
|
||||||
|
Port int `json:"port"`
|
||||||
|
}{
|
||||||
Host: "127.0.0.1",
|
Host: "127.0.0.1",
|
||||||
Port: 8080,
|
Port: 8080,
|
||||||
},
|
},
|
||||||
@@ -489,16 +447,16 @@ func TestDeleteExtension(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUpdateCheckExtension(t *testing.T) {
|
func TestUpgradeCheckExtension(t *testing.T) {
|
||||||
doTest(func() {
|
doTest(func() {
|
||||||
identity := httpRequestCheckOk[string](http.MethodPost, "/api/v1/extensions", installExtensionReq)
|
identity := httpRequestCheckOk[string](http.MethodPost, "/api/v1/extensions", installExtensionReq)
|
||||||
resp := httpRequestCheckOk[*model.UpdateCheckExtensionResp](http.MethodGet, "/api/v1/extensions/"+identity+"/update", nil)
|
resp := httpRequestCheckOk[*model.UpgradeCheckExtensionResp](http.MethodGet, "/api/v1/extensions/"+identity+"/upgrade", nil)
|
||||||
// no new version
|
// no new version
|
||||||
if resp.NewVersion != "" {
|
if resp.NewVersion != "" {
|
||||||
t.Errorf("UpdateCheckExtension() got = %v, want %v", resp.NewVersion, "")
|
t.Errorf("UpgradeCheckExtension() got = %v, want %v", resp.NewVersion, "")
|
||||||
}
|
}
|
||||||
// force update
|
// force update
|
||||||
httpRequestCheckOk[any](http.MethodPost, "/api/v1/extensions/"+identity+"/update", nil)
|
httpRequestCheckOk[any](http.MethodPost, "/api/v1/extensions/"+identity+"/upgrade", nil)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -521,66 +479,6 @@ func TestFsExtensionFail(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWebFsEnhance(t *testing.T) {
|
|
||||||
indexHtml := `
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>index</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>index</h1>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
`
|
|
||||||
webDistPath := "dist"
|
|
||||||
os.MkdirAll("dist", os.ModePerm)
|
|
||||||
if err := os.WriteFile(filepath.Join(webDistPath, "index.html"), []byte(indexHtml), os.ModePerm); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(webDistPath)
|
|
||||||
|
|
||||||
doTest0(func(cfg *model.StartConfig) {
|
|
||||||
cfg.WebFS = os.DirFS(webDistPath)
|
|
||||||
}, func() {
|
|
||||||
// First request no cache
|
|
||||||
code, header, _ := doHttpRequest1(http.MethodGet, "/index.html", map[string]string{
|
|
||||||
"Accept-Encoding": "gzip",
|
|
||||||
}, nil)
|
|
||||||
if code != http.StatusOK {
|
|
||||||
t.Errorf("TestWebFsEnhance() got = %v, want %v", code, http.StatusOK)
|
|
||||||
}
|
|
||||||
// Check header last-modified
|
|
||||||
if _, ok := header["Last-Modified"]; !ok {
|
|
||||||
t.Errorf("TestWebFsEnhance() missing key = %v", "Last-Modified")
|
|
||||||
}
|
|
||||||
// Check gzip compress
|
|
||||||
if _, ok := header["Content-Encoding"]; !ok || header["Content-Encoding"] != "gzip" {
|
|
||||||
t.Errorf("TestWebFsEnhance() no gzip compress")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Request with If-Modified-Since
|
|
||||||
ifModifiedSince := header["Last-Modified"]
|
|
||||||
code, _, _ = doHttpRequest1(http.MethodGet, "/index.html", map[string]string{
|
|
||||||
"If-Modified-Since": ifModifiedSince,
|
|
||||||
}, nil)
|
|
||||||
if code != http.StatusNotModified {
|
|
||||||
t.Errorf("TestWebFsEnhance() got = %v, want %v", code, http.StatusNotModified)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Request with un gzip
|
|
||||||
code, header, _ = doHttpRequest1(http.MethodGet, "/index.html?t=123", nil, nil)
|
|
||||||
if code != http.StatusOK {
|
|
||||||
t.Errorf("TestWebFsEnhance() got = %v, want %v", code, http.StatusOK)
|
|
||||||
}
|
|
||||||
// Check no gzip compress
|
|
||||||
if _, ok := header["Content-Encoding"]; ok && header["Content-Encoding"] == "gzip" {
|
|
||||||
t.Errorf("TestWebFsEnhance() has gzip compress")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDoProxy(t *testing.T) {
|
func TestDoProxy(t *testing.T) {
|
||||||
doTest(func() {
|
doTest(func() {
|
||||||
code, respBody := doHttpRequest0(http.MethodGet, "/api/v1/proxy", map[string]string{
|
code, respBody := doHttpRequest0(http.MethodGet, "/api/v1/proxy", map[string]string{
|
||||||
@@ -609,13 +507,19 @@ func TestDoProxy(t *testing.T) {
|
|||||||
func TestApiToken(t *testing.T) {
|
func TestApiToken(t *testing.T) {
|
||||||
var cfg = &model.StartConfig{}
|
var cfg = &model.StartConfig{}
|
||||||
cfg.Init()
|
cfg.Init()
|
||||||
cfg.ApiToken = "123456"
|
cfg.DownloadConfig = &base.DownloaderStoreConfig{
|
||||||
fileListener := doStart(cfg)
|
Http: &base.DownloaderHttpConfig{
|
||||||
|
Enable: true,
|
||||||
|
ApiToken: "123456",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
httpInstance, _ = Create(cfg)
|
||||||
|
fileListener := doStart()
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := fileListener.Close(); err != nil {
|
if err := fileListener.Close(); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
Stop()
|
httpInstance.Close()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
status, _ := doHttpRequest0(http.MethodGet, "/api/v1/config", nil, nil)
|
status, _ := doHttpRequest0(http.MethodGet, "/api/v1/config", nil, nil)
|
||||||
@@ -624,7 +528,7 @@ func TestApiToken(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
status, _ = doHttpRequest0(http.MethodGet, "/api/v1/config", map[string]string{
|
status, _ = doHttpRequest0(http.MethodGet, "/api/v1/config", map[string]string{
|
||||||
"X-Api-Token": cfg.ApiToken,
|
"X-Api-Token": cfg.DownloadConfig.Http.ApiToken,
|
||||||
}, nil)
|
}, nil)
|
||||||
if status != http.StatusOK {
|
if status != http.StatusOK {
|
||||||
t.Errorf("TestApiToken() got = %v, want %v", status, http.StatusOK)
|
t.Errorf("TestApiToken() got = %v, want %v", status, http.StatusOK)
|
||||||
@@ -635,129 +539,70 @@ func TestApiToken(t *testing.T) {
|
|||||||
func TestAuthorization(t *testing.T) {
|
func TestAuthorization(t *testing.T) {
|
||||||
var cfg = &model.StartConfig{}
|
var cfg = &model.StartConfig{}
|
||||||
cfg.Init()
|
cfg.Init()
|
||||||
cfg.ApiToken = "123456"
|
cfg.DownloadConfig = &base.DownloaderStoreConfig{
|
||||||
|
Http: &base.DownloaderHttpConfig{
|
||||||
|
Enable: true,
|
||||||
|
ApiToken: "123456",
|
||||||
|
},
|
||||||
|
}
|
||||||
cfg.WebEnable = true
|
cfg.WebEnable = true
|
||||||
cfg.WebAuth = &model.WebAuth{
|
cfg.WebBasicAuth = &model.WebBasicAuth{
|
||||||
Username: "admin",
|
Username: "admin",
|
||||||
Password: "123456",
|
Password: "123456",
|
||||||
}
|
}
|
||||||
fileListener := doStart(cfg)
|
httpInstance, _ = Create(cfg)
|
||||||
|
fileListener := doStart()
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := fileListener.Close(); err != nil {
|
if err := fileListener.Close(); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
Stop()
|
httpInstance.Close()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
status, _ := doHttpRequest0(http.MethodPost, "/api/web/login", nil, &model.WebAuth{
|
status, _ := doHttpRequest0(http.MethodGet, "/api/v1/config", nil, nil)
|
||||||
Username: "xxx",
|
|
||||||
Password: "xxx",
|
|
||||||
})
|
|
||||||
if status != http.StatusUnauthorized {
|
|
||||||
t.Errorf("TestAuthorization() got = %v, want %v", status, http.StatusUnauthorized)
|
|
||||||
}
|
|
||||||
|
|
||||||
token := httpRequestCheckOk[string](http.MethodPost, "/api/web/login", cfg.WebAuth)
|
|
||||||
authToken := fmt.Sprintf("Bearer %s", token)
|
|
||||||
authHeaders := map[string]string{
|
|
||||||
"Authorization": authToken,
|
|
||||||
}
|
|
||||||
|
|
||||||
status, _ = doHttpRequest0(http.MethodGet, "/api/v1/config", nil, nil)
|
|
||||||
if status != http.StatusUnauthorized {
|
if status != http.StatusUnauthorized {
|
||||||
t.Errorf("TestAuthorization() got = %v, want %v", status, http.StatusUnauthorized)
|
t.Errorf("TestAuthorization() got = %v, want %v", status, http.StatusUnauthorized)
|
||||||
}
|
}
|
||||||
|
|
||||||
status, _ = doHttpRequest0(http.MethodGet, "/api/v1/config", map[string]string{
|
status, _ = doHttpRequest0(http.MethodGet, "/api/v1/config", map[string]string{
|
||||||
"Authorization": "xxx",
|
"Authorization": cfg.WebBasicAuth.Authorization(),
|
||||||
}, nil)
|
|
||||||
if status != http.StatusUnauthorized {
|
|
||||||
t.Errorf("TestAuthorization() got = %v, want %v", status, http.StatusUnauthorized)
|
|
||||||
}
|
|
||||||
|
|
||||||
status, _ = doHttpRequest0(http.MethodGet, "/api/v1/config", map[string]string{
|
|
||||||
"Authorization": "xxx",
|
|
||||||
}, nil)
|
|
||||||
if status != http.StatusUnauthorized {
|
|
||||||
t.Errorf("TestAuthorization() got = %v, want %v", status, http.StatusUnauthorized)
|
|
||||||
}
|
|
||||||
|
|
||||||
buildToken := func(username, password string, ts int64) string {
|
|
||||||
token, _ := aesEncrypt(aesKey, []byte(fmt.Sprintf("%s:%s:%d", username, password, ts)))
|
|
||||||
return token
|
|
||||||
}
|
|
||||||
|
|
||||||
fakeToken := buildToken("fake", "fake", time.Now().Unix())
|
|
||||||
status, _ = doHttpRequest0(http.MethodGet, "/api/v1/config", map[string]string{
|
|
||||||
"Authorization": fmt.Sprintf("Bearer %s", fakeToken),
|
|
||||||
}, nil)
|
|
||||||
if status != http.StatusUnauthorized {
|
|
||||||
t.Errorf("TestAuthorization() got = %v, want %v", status, http.StatusUnauthorized)
|
|
||||||
}
|
|
||||||
|
|
||||||
expireToken := buildToken(cfg.WebAuth.Username, cfg.WebAuth.Password, time.Now().Add(-time.Hour*8*24).Unix())
|
|
||||||
status, _ = doHttpRequest0(http.MethodGet, "/api/v1/config", map[string]string{
|
|
||||||
"Authorization": fmt.Sprintf("Bearer %s", expireToken),
|
|
||||||
}, nil)
|
|
||||||
if status != http.StatusUnauthorized {
|
|
||||||
t.Errorf("TestAuthorization() got = %v, want %v", status, http.StatusUnauthorized)
|
|
||||||
}
|
|
||||||
|
|
||||||
status, _ = doHttpRequest0(http.MethodGet, "/api/v1/config", authHeaders, nil)
|
|
||||||
if status != http.StatusOK {
|
|
||||||
t.Errorf("TestAuthorization() got = %v, want %v", status, http.StatusOK)
|
|
||||||
}
|
|
||||||
|
|
||||||
status, _ = doHttpRequest0(http.MethodGet, "/api/v1/config", map[string]string{
|
|
||||||
"X-Api-Token": cfg.ApiToken,
|
|
||||||
}, nil)
|
}, nil)
|
||||||
if status != http.StatusOK {
|
if status != http.StatusOK {
|
||||||
t.Errorf("TestAuthorization() got = %v, want %v", status, http.StatusOK)
|
t.Errorf("TestAuthorization() got = %v, want %v", status, http.StatusOK)
|
||||||
}
|
}
|
||||||
|
|
||||||
status, _ = doHttpRequest0(http.MethodGet, "/api/v1/config", map[string]string{
|
status, _ = doHttpRequest0(http.MethodGet, "/api/v1/config", map[string]string{
|
||||||
"Authorization": authToken,
|
"X-Api-Token": cfg.DownloadConfig.Http.ApiToken,
|
||||||
"X-Api-Token": cfg.ApiToken,
|
|
||||||
}, nil)
|
}, nil)
|
||||||
if status != http.StatusOK {
|
if status != http.StatusOK {
|
||||||
t.Errorf("TestAuthorization() got = %v, want %v", status, http.StatusOK)
|
t.Errorf("TestAuthorization() got = %v, want %v", status, http.StatusOK)
|
||||||
}
|
}
|
||||||
|
|
||||||
status, _ = doHttpRequest0(http.MethodGet, "/api/v1/config", map[string]string{
|
|
||||||
"Authorization": authToken,
|
|
||||||
"X-Api-Token": "",
|
|
||||||
}, nil)
|
|
||||||
if status != http.StatusUnauthorized {
|
|
||||||
t.Errorf("TestAuthorization() got = %v, want %v", status, http.StatusUnauthorized)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func doTest(handler func()) {
|
func doTest(handler func()) {
|
||||||
doTest0(nil, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
func doTest0(onStart func(cfg *model.StartConfig), handler func()) {
|
|
||||||
testFunc := func(storage model.Storage) {
|
testFunc := func(storage model.Storage) {
|
||||||
var cfg = &model.StartConfig{}
|
var cfg = &model.StartConfig{}
|
||||||
cfg.Init()
|
cfg.Init()
|
||||||
cfg.Storage = storage
|
cfg.Storage = storage
|
||||||
cfg.StorageDir = ".test_storage"
|
cfg.StorageDir = ".test_storage"
|
||||||
cfg.WebEnable = true
|
cfg.WebEnable = true
|
||||||
if onStart != nil {
|
cfg.DownloadConfig = &base.DownloaderStoreConfig{
|
||||||
onStart(cfg)
|
Http: &base.DownloaderHttpConfig{
|
||||||
|
Enable: true,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
fileListener := doStart(cfg)
|
httpInstance, _ = Create(cfg)
|
||||||
|
fileListener := doStart()
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := fileListener.Close(); err != nil {
|
if err := fileListener.Close(); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
Stop()
|
httpInstance.StopHttp()
|
||||||
Downloader.Clear()
|
httpInstance.Clear()
|
||||||
}()
|
}()
|
||||||
defer func() {
|
defer func() {
|
||||||
time.Sleep(500 * time.Millisecond)
|
httpInstance.PauseTasks(nil)
|
||||||
Downloader.Pause(nil)
|
httpInstance.DeleteTasks(nil, true)
|
||||||
Downloader.Delete(nil, true)
|
|
||||||
os.RemoveAll(cfg.StorageDir)
|
os.RemoveAll(cfg.StorageDir)
|
||||||
}()
|
}()
|
||||||
taskReq.URL = "http://" + fileListener.Addr().String() + "/" + test.BuildName
|
taskReq.URL = "http://" + fileListener.Addr().String() + "/" + test.BuildName
|
||||||
@@ -767,21 +612,20 @@ func doTest0(onStart func(cfg *model.StartConfig), handler func()) {
|
|||||||
testFunc(model.StorageBolt)
|
testFunc(model.StorageBolt)
|
||||||
}
|
}
|
||||||
|
|
||||||
func doStart(cfg *model.StartConfig) net.Listener {
|
func doStart() net.Listener {
|
||||||
port, err := Start(cfg)
|
port := UnwrapResult(httpInstance.StartHttp())
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
restPort = port
|
restPort = port
|
||||||
return test.StartTestFileServer()
|
return test.StartTestFileServer()
|
||||||
}
|
}
|
||||||
|
|
||||||
func doHttpRequest0(method string, path string, headers map[string]string, body any) (int, []byte) {
|
func UnwrapResult[T any](result *model.Result[T]) T {
|
||||||
r1, _, r3 := doHttpRequest1(method, path, headers, body)
|
if result.HasError() {
|
||||||
return r1, r3
|
panic(result.Msg)
|
||||||
|
}
|
||||||
|
return result.Data
|
||||||
}
|
}
|
||||||
|
|
||||||
func doHttpRequest1(method string, path string, headers map[string]string, body any) (int, map[string]string, []byte) {
|
func doHttpRequest0(method string, path string, headers map[string]string, body any) (int, []byte) {
|
||||||
var reader io.Reader
|
var reader io.Reader
|
||||||
if body != nil {
|
if body != nil {
|
||||||
buf, _ := json.Marshal(body)
|
buf, _ := json.Marshal(body)
|
||||||
@@ -802,18 +646,12 @@ func doHttpRequest1(method string, path string, headers map[string]string, body
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
defer response.Body.Close()
|
defer response.Body.Close()
|
||||||
|
|
||||||
respHeader := make(map[string]string)
|
|
||||||
for k, vv := range response.Header {
|
|
||||||
respHeader[k] = vv[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
respBody, err := io.ReadAll(response.Body)
|
respBody, err := io.ReadAll(response.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.StatusCode, respHeader, respBody
|
return response.StatusCode, respBody
|
||||||
}
|
}
|
||||||
|
|
||||||
func doHttpRequest[T any](method string, path string, headers map[string]string, body any) (int, *model.Result[T]) {
|
func doHttpRequest[T any](method string, path string, headers map[string]string, body any) (int, *model.Result[T]) {
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package model
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/base64"
|
||||||
"github.com/GopeedLab/gopeed/pkg/base"
|
"github.com/GopeedLab/gopeed/pkg/base"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
)
|
)
|
||||||
@@ -13,29 +14,20 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type StartConfig struct {
|
type StartConfig struct {
|
||||||
Network string `json:"network"`
|
|
||||||
Address string `json:"address"`
|
|
||||||
RefreshInterval int `json:"refreshInterval"`
|
RefreshInterval int `json:"refreshInterval"`
|
||||||
Storage Storage `json:"storage"`
|
Storage Storage `json:"storage"`
|
||||||
StorageDir string `json:"storageDir"`
|
StorageDir string `json:"storageDir"`
|
||||||
WhiteDownloadDirs []string `json:"whiteDownloadDirs"`
|
WhiteDownloadDirs []string `json:"whiteDownloadDirs"`
|
||||||
ApiToken string `json:"apiToken"`
|
|
||||||
DownloadConfig *base.DownloaderStoreConfig `json:"downloadConfig"`
|
DownloadConfig *base.DownloaderStoreConfig `json:"downloadConfig"`
|
||||||
|
|
||||||
ProductionMode bool
|
ProductionMode bool
|
||||||
|
|
||||||
WebEnable bool
|
WebEnable bool
|
||||||
WebFS fs.FS
|
WebFS fs.FS
|
||||||
WebAuth *WebAuth
|
WebBasicAuth *WebBasicAuth
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *StartConfig) Init() *StartConfig {
|
func (cfg *StartConfig) Init() *StartConfig {
|
||||||
if cfg.Network == "" {
|
|
||||||
cfg.Network = "tcp"
|
|
||||||
}
|
|
||||||
if cfg.Address == "" {
|
|
||||||
cfg.Address = "127.0.0.1:0"
|
|
||||||
}
|
|
||||||
if cfg.RefreshInterval == 0 {
|
if cfg.RefreshInterval == 0 {
|
||||||
cfg.RefreshInterval = 350
|
cfg.RefreshInterval = 350
|
||||||
}
|
}
|
||||||
@@ -48,7 +40,19 @@ func (cfg *StartConfig) Init() *StartConfig {
|
|||||||
return cfg
|
return cfg
|
||||||
}
|
}
|
||||||
|
|
||||||
type WebAuth struct {
|
type WebBasicAuth struct {
|
||||||
Username string
|
Username string
|
||||||
Password string
|
Password string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Authorization returns the value of the Authorization header to be used in HTTP requests.
|
||||||
|
func (cfg *WebBasicAuth) Authorization() string {
|
||||||
|
userId := cfg.Username + ":" + cfg.Password
|
||||||
|
return "Basic " + base64.StdEncoding.EncodeToString([]byte(userId))
|
||||||
|
}
|
||||||
|
|
||||||
|
type HttpListenResult struct {
|
||||||
|
Host string `json:"host"`
|
||||||
|
// Port is the http server real listen port.
|
||||||
|
Port int `json:"port"`
|
||||||
|
}
|
||||||
@@ -13,6 +13,6 @@ type SwitchExtension struct {
|
|||||||
Status bool `json:"status"`
|
Status bool `json:"status"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UpdateCheckExtensionResp struct {
|
type UpgradeCheckExtensionResp struct {
|
||||||
NewVersion string `json:"newVersion"`
|
NewVersion string `json:"newVersion"`
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
package model
|
package model
|
||||||
|
|
||||||
|
import "encoding/json"
|
||||||
|
|
||||||
type RespCode int
|
type RespCode int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -20,6 +22,15 @@ type Result[T any] struct {
|
|||||||
Data T `json:"data"`
|
Data T `json:"data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Result[T]) Json() string {
|
||||||
|
buf, _ := json.Marshal(r)
|
||||||
|
return string(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Result[T]) HasError() bool {
|
||||||
|
return r.Code != CodeOk
|
||||||
|
}
|
||||||
|
|
||||||
func NewOkResult[T any](data T) *Result[T] {
|
func NewOkResult[T any](data T) *Result[T] {
|
||||||
return &Result[T]{
|
return &Result[T]{
|
||||||
Code: CodeOk,
|
Code: CodeOk,
|
||||||
@@ -33,14 +44,14 @@ func NewNilResult() *Result[any] {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewErrorResult(msg string, code ...RespCode) *Result[any] {
|
func NewErrorResult[T any](msg string, code ...RespCode) *Result[T] {
|
||||||
// if code is not provided, the default code is CodeError
|
// if code is not provided, the default code is CodeError
|
||||||
c := CodeError
|
c := CodeError
|
||||||
if len(code) > 0 {
|
if len(code) > 0 {
|
||||||
c = code[0]
|
c = code[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Result[any]{
|
return &Result[T]{
|
||||||
Code: c,
|
Code: c,
|
||||||
Msg: msg,
|
Msg: msg,
|
||||||
}
|
}
|
||||||
@@ -7,3 +7,8 @@ type CreateTask struct {
|
|||||||
Req *base.Request `json:"req"`
|
Req *base.Request `json:"req"`
|
||||||
Opt *base.Options `json:"opt"`
|
Opt *base.Options `json:"opt"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CreateTaskBatch struct {
|
||||||
|
Reqs []*base.Request `json:"reqs"`
|
||||||
|
Opt *base.Options `json:"opt"`
|
||||||
|
}
|
||||||
@@ -15,7 +15,6 @@ const (
|
|||||||
HttpCodeOK = 200
|
HttpCodeOK = 200
|
||||||
HttpCodePartialContent = 206
|
HttpCodePartialContent = 206
|
||||||
|
|
||||||
HttpHeaderHost = "Host"
|
|
||||||
HttpHeaderRange = "Range"
|
HttpHeaderRange = "Range"
|
||||||
HttpHeaderAcceptRanges = "Accept-Ranges"
|
HttpHeaderAcceptRanges = "Accept-Ranges"
|
||||||
HttpHeaderContentLength = "Content-Length"
|
HttpHeaderContentLength = "Content-Length"
|
||||||
|
|||||||
+23
-10
@@ -163,16 +163,6 @@ func ParseOptsExtra[E any](opts *Options) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type CreateTaskBatch struct {
|
|
||||||
Reqs []*CreateTaskBatchItem `json:"reqs"`
|
|
||||||
Opts *Options `json:"opts"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CreateTaskBatchItem struct {
|
|
||||||
Req *Request `json:"req"`
|
|
||||||
Opts *Options `json:"opts"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// DownloaderStoreConfig is the config that can restore the downloader.
|
// DownloaderStoreConfig is the config that can restore the downloader.
|
||||||
type DownloaderStoreConfig struct {
|
type DownloaderStoreConfig struct {
|
||||||
FirstLoad bool `json:"-"` // FirstLoad is the flag that the config is first time init and not from store
|
FirstLoad bool `json:"-"` // FirstLoad is the flag that the config is first time init and not from store
|
||||||
@@ -181,6 +171,7 @@ type DownloaderStoreConfig struct {
|
|||||||
MaxRunning int `json:"maxRunning"` // MaxRunning is the max running download count
|
MaxRunning int `json:"maxRunning"` // MaxRunning is the max running download count
|
||||||
ProtocolConfig map[string]any `json:"protocolConfig"` // ProtocolConfig is special config for each protocol
|
ProtocolConfig map[string]any `json:"protocolConfig"` // ProtocolConfig is special config for each protocol
|
||||||
Extra map[string]any `json:"extra"`
|
Extra map[string]any `json:"extra"`
|
||||||
|
Http *DownloaderHttpConfig `json:"http"`
|
||||||
Proxy *DownloaderProxyConfig `json:"proxy"`
|
Proxy *DownloaderProxyConfig `json:"proxy"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,6 +182,12 @@ func (cfg *DownloaderStoreConfig) Init() *DownloaderStoreConfig {
|
|||||||
if cfg.ProtocolConfig == nil {
|
if cfg.ProtocolConfig == nil {
|
||||||
cfg.ProtocolConfig = make(map[string]any)
|
cfg.ProtocolConfig = make(map[string]any)
|
||||||
}
|
}
|
||||||
|
if cfg.Extra == nil {
|
||||||
|
cfg.Extra = make(map[string]any)
|
||||||
|
}
|
||||||
|
if cfg.Http == nil {
|
||||||
|
cfg.Http = &DownloaderHttpConfig{}
|
||||||
|
}
|
||||||
if cfg.Proxy == nil {
|
if cfg.Proxy == nil {
|
||||||
cfg.Proxy = &DownloaderProxyConfig{}
|
cfg.Proxy = &DownloaderProxyConfig{}
|
||||||
}
|
}
|
||||||
@@ -213,12 +210,28 @@ func (cfg *DownloaderStoreConfig) Merge(beforeCfg *DownloaderStoreConfig) *Downl
|
|||||||
if cfg.Extra == nil {
|
if cfg.Extra == nil {
|
||||||
cfg.Extra = beforeCfg.Extra
|
cfg.Extra = beforeCfg.Extra
|
||||||
}
|
}
|
||||||
|
if cfg.Http == nil {
|
||||||
|
cfg.Http = beforeCfg.Http
|
||||||
|
}
|
||||||
if cfg.Proxy == nil {
|
if cfg.Proxy == nil {
|
||||||
cfg.Proxy = beforeCfg.Proxy
|
cfg.Proxy = beforeCfg.Proxy
|
||||||
}
|
}
|
||||||
return cfg
|
return cfg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DownloaderHttpConfig struct {
|
||||||
|
// Enable is the flag that enable http server
|
||||||
|
Enable bool `json:"Enable"`
|
||||||
|
// Host is the http server listen address.
|
||||||
|
Host string `json:"host"`
|
||||||
|
// Port is the http server listen port.
|
||||||
|
Port int `json:"port"`
|
||||||
|
// ApiToken is the auth token for http API
|
||||||
|
ApiToken string `json:"apiToken"`
|
||||||
|
// RunningPort is the http server real listen port.
|
||||||
|
RunningPort int `json:"runningPort"`
|
||||||
|
}
|
||||||
|
|
||||||
type DownloaderProxyConfig struct {
|
type DownloaderProxyConfig struct {
|
||||||
Enable bool `json:"enable"`
|
Enable bool `json:"enable"`
|
||||||
// System is the flag that use system proxy
|
// System is the flag that use system proxy
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ func TestDownloaderStoreConfig_Init(t *testing.T) {
|
|||||||
&DownloaderStoreConfig{
|
&DownloaderStoreConfig{
|
||||||
MaxRunning: 5,
|
MaxRunning: 5,
|
||||||
ProtocolConfig: map[string]any{},
|
ProtocolConfig: map[string]any{},
|
||||||
|
Extra: make(map[string]any),
|
||||||
|
Http: &DownloaderHttpConfig{},
|
||||||
Proxy: &DownloaderProxyConfig{},
|
Proxy: &DownloaderProxyConfig{},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -28,6 +30,8 @@ func TestDownloaderStoreConfig_Init(t *testing.T) {
|
|||||||
&DownloaderStoreConfig{
|
&DownloaderStoreConfig{
|
||||||
MaxRunning: 10,
|
MaxRunning: 10,
|
||||||
ProtocolConfig: map[string]any{},
|
ProtocolConfig: map[string]any{},
|
||||||
|
Extra: make(map[string]any),
|
||||||
|
Http: &DownloaderHttpConfig{},
|
||||||
Proxy: &DownloaderProxyConfig{},
|
Proxy: &DownloaderProxyConfig{},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -43,6 +47,8 @@ func TestDownloaderStoreConfig_Init(t *testing.T) {
|
|||||||
ProtocolConfig: map[string]any{
|
ProtocolConfig: map[string]any{
|
||||||
"key": "value",
|
"key": "value",
|
||||||
},
|
},
|
||||||
|
Extra: make(map[string]any),
|
||||||
|
Http: &DownloaderHttpConfig{},
|
||||||
Proxy: &DownloaderProxyConfig{},
|
Proxy: &DownloaderProxyConfig{},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -56,6 +62,8 @@ func TestDownloaderStoreConfig_Init(t *testing.T) {
|
|||||||
&DownloaderStoreConfig{
|
&DownloaderStoreConfig{
|
||||||
MaxRunning: 5,
|
MaxRunning: 5,
|
||||||
ProtocolConfig: map[string]any{},
|
ProtocolConfig: map[string]any{},
|
||||||
|
Extra: make(map[string]any),
|
||||||
|
Http: &DownloaderHttpConfig{},
|
||||||
Proxy: &DownloaderProxyConfig{
|
Proxy: &DownloaderProxyConfig{
|
||||||
Enable: true,
|
Enable: true,
|
||||||
},
|
},
|
||||||
|
|||||||
+43
-48
@@ -2,6 +2,15 @@ package download
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"github.com/GopeedLab/gopeed/internal/controller"
|
||||||
|
"github.com/GopeedLab/gopeed/internal/fetcher"
|
||||||
|
"github.com/GopeedLab/gopeed/internal/logger"
|
||||||
|
"github.com/GopeedLab/gopeed/pkg/base"
|
||||||
|
"github.com/GopeedLab/gopeed/pkg/protocol/http"
|
||||||
|
"github.com/GopeedLab/gopeed/pkg/util"
|
||||||
|
gonanoid "github.com/matoous/go-nanoid/v2"
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
"github.com/rs/zerolog/pkgerrors"
|
||||||
"math"
|
"math"
|
||||||
gohttp "net/http"
|
gohttp "net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
@@ -13,16 +22,6 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/GopeedLab/gopeed/internal/controller"
|
|
||||||
"github.com/GopeedLab/gopeed/internal/fetcher"
|
|
||||||
"github.com/GopeedLab/gopeed/internal/logger"
|
|
||||||
"github.com/GopeedLab/gopeed/pkg/base"
|
|
||||||
"github.com/GopeedLab/gopeed/pkg/protocol/http"
|
|
||||||
"github.com/GopeedLab/gopeed/pkg/util"
|
|
||||||
gonanoid "github.com/matoous/go-nanoid/v2"
|
|
||||||
"github.com/rs/zerolog"
|
|
||||||
"github.com/rs/zerolog/pkgerrors"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -149,7 +148,6 @@ func (d *Downloader) Setup() error {
|
|||||||
tasks = append(tasks[:i], tasks[i+1:]...)
|
tasks = append(tasks[:i], tasks[i+1:]...)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
d.assignFetcherManager(task)
|
|
||||||
initTask(task)
|
initTask(task)
|
||||||
if task.Status != base.DownloadStatusDone && task.Status != base.DownloadStatusError {
|
if task.Status != base.DownloadStatusDone && task.Status != base.DownloadStatusError {
|
||||||
task.Status = base.DownloadStatusPause
|
task.Status = base.DownloadStatusPause
|
||||||
@@ -210,7 +208,7 @@ func (d *Downloader) Setup() error {
|
|||||||
if task.Status == base.DownloadStatusRunning {
|
if task.Status == base.DownloadStatusRunning {
|
||||||
downloadDataChanged = current != task.Progress.Downloaded
|
downloadDataChanged = current != task.Progress.Downloaded
|
||||||
task.Progress.Used = task.timer.Used()
|
task.Progress.Used = task.timer.Used()
|
||||||
task.Progress.Speed = task.updateSpeed(current-task.Progress.Downloaded, tick)
|
task.Progress.Speed = task.calcSpeed(task.speedArr, current-task.Progress.Downloaded, tick)
|
||||||
task.Progress.Downloaded = current
|
task.Progress.Downloaded = current
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -219,7 +217,7 @@ func (d *Downloader) Setup() error {
|
|||||||
uploader := task.fetcher.(fetcher.Uploader)
|
uploader := task.fetcher.(fetcher.Uploader)
|
||||||
currentUploaded := uploader.UploadedBytes()
|
currentUploaded := uploader.UploadedBytes()
|
||||||
uploadDataChanged = currentUploaded != task.Progress.Uploaded
|
uploadDataChanged = currentUploaded != task.Progress.Uploaded
|
||||||
task.Progress.UploadSpeed = task.updateUploadSpeed(currentUploaded-task.Progress.Uploaded, tick)
|
task.Progress.UploadSpeed = task.calcSpeed(task.uploadSpeedArr, currentUploaded-task.Progress.Uploaded, tick)
|
||||||
task.Progress.Uploaded = currentUploaded
|
task.Progress.Uploaded = currentUploaded
|
||||||
}
|
}
|
||||||
d.emit(EventKeyProgress, task)
|
d.emit(EventKeyProgress, task)
|
||||||
@@ -254,7 +252,7 @@ func (d *Downloader) setupFetcher(fm fetcher.FetcherManager, fetcher fetcher.Fet
|
|||||||
ctl.GetConfig = func(v any) {
|
ctl.GetConfig = func(v any) {
|
||||||
d.getProtocolConfig(fm.Name(), v)
|
d.getProtocolConfig(fm.Name(), v)
|
||||||
}
|
}
|
||||||
// Get proxy config, task request proxy config has higher priority, then use global proxy config
|
// get proxy config, task request proxy config has higher priority, then use global proxy config
|
||||||
ctl.GetProxy = func(requestProxy *base.RequestProxy) func(*gohttp.Request) (*url.URL, error) {
|
ctl.GetProxy = func(requestProxy *base.RequestProxy) func(*gohttp.Request) (*url.URL, error) {
|
||||||
if requestProxy == nil {
|
if requestProxy == nil {
|
||||||
return d.cfg.Proxy.ToHandler()
|
return d.cfg.Proxy.ToHandler()
|
||||||
@@ -360,14 +358,10 @@ func (d *Downloader) CreateDirect(req *base.Request, opts *base.Options) (taskId
|
|||||||
return d.doCreate(fetcher, opts)
|
return d.doCreate(fetcher, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Downloader) CreateDirectBatch(req *base.CreateTaskBatch) (taskId []string, err error) {
|
func (d *Downloader) CreateDirectBatch(reqs []*base.Request, opts *base.Options) (taskId []string, err error) {
|
||||||
taskIds := make([]string, 0)
|
taskIds := make([]string, 0)
|
||||||
for _, ir := range req.Reqs {
|
for _, req := range reqs {
|
||||||
opts := ir.Opts
|
taskId, err := d.CreateDirect(req, opts.Clone())
|
||||||
if opts == nil {
|
|
||||||
opts = req.Opts
|
|
||||||
}
|
|
||||||
taskId, err := d.CreateDirect(ir.Req, opts.Clone())
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -396,7 +390,7 @@ func (d *Downloader) Pause(filter *TaskFilter) (err error) {
|
|||||||
return d.pauseAll()
|
return d.pauseAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
filter.NotStatuses = []base.Status{base.DownloadStatusPause, base.DownloadStatusError, base.DownloadStatusDone}
|
filter.NotStatus = []base.Status{base.DownloadStatusPause, base.DownloadStatusError, base.DownloadStatusDone}
|
||||||
pauseTasks := d.GetTasksByFilter(filter)
|
pauseTasks := d.GetTasksByFilter(filter)
|
||||||
if len(pauseTasks) == 0 {
|
if len(pauseTasks) == 0 {
|
||||||
return ErrTaskNotFound
|
return ErrTaskNotFound
|
||||||
@@ -436,7 +430,7 @@ func (d *Downloader) Continue(filter *TaskFilter) (err error) {
|
|||||||
return d.continueAll()
|
return d.continueAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
filter.NotStatuses = []base.Status{base.DownloadStatusRunning, base.DownloadStatusDone}
|
filter.NotStatus = []base.Status{base.DownloadStatusRunning, base.DownloadStatusDone}
|
||||||
continueTasks := d.GetTasksByFilter(filter)
|
continueTasks := d.GetTasksByFilter(filter)
|
||||||
if len(continueTasks) == 0 {
|
if len(continueTasks) == 0 {
|
||||||
return ErrTaskNotFound
|
return ErrTaskNotFound
|
||||||
@@ -579,19 +573,18 @@ func (d *Downloader) Delete(filter *TaskFilter, force bool) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *Downloader) deleteAll(force bool) (err error) {
|
func (d *Downloader) deleteAll(force bool) (err error) {
|
||||||
var deleteTasksTemp []*Task
|
deleteTasksPtr := make([]*Task, 0)
|
||||||
|
|
||||||
func() {
|
func() {
|
||||||
d.lock.Lock()
|
d.lock.Lock()
|
||||||
defer d.lock.Unlock()
|
defer d.lock.Unlock()
|
||||||
|
|
||||||
for _, task := range d.tasks {
|
deleteTasksPtr = append(deleteTasksPtr, d.tasks...)
|
||||||
deleteTasksTemp = append(deleteTasksTemp, task)
|
|
||||||
}
|
|
||||||
d.tasks = make([]*Task, 0)
|
d.tasks = make([]*Task, 0)
|
||||||
d.waitTasks = make([]*Task, 0)
|
d.waitTasks = make([]*Task, 0)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
for _, task := range deleteTasksTemp {
|
for _, task := range deleteTasksPtr {
|
||||||
if err = d.doDelete(task, force); err != nil {
|
if err = d.doDelete(task, force); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -599,7 +592,7 @@ func (d *Downloader) deleteAll(force bool) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Downloader) Stats(id string) (sr any, err error) {
|
func (d *Downloader) GetTaskStats(id string) (sr any, err error) {
|
||||||
task := d.GetTask(id)
|
task := d.GetTask(id)
|
||||||
if task == nil {
|
if task == nil {
|
||||||
return sr, ErrTaskNotFound
|
return sr, ErrTaskNotFound
|
||||||
@@ -729,10 +722,10 @@ func (d *Downloader) GetTasksByFilter(filter *TaskFilter) []*Task {
|
|||||||
}
|
}
|
||||||
|
|
||||||
idMatch := func(task *Task) bool {
|
idMatch := func(task *Task) bool {
|
||||||
if len(filter.IDs) == 0 {
|
if len(filter.ID) == 0 {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
for _, id := range filter.IDs {
|
for _, id := range filter.ID {
|
||||||
if task.ID == id {
|
if task.ID == id {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -740,10 +733,10 @@ func (d *Downloader) GetTasksByFilter(filter *TaskFilter) []*Task {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
statusMatch := func(task *Task) bool {
|
statusMatch := func(task *Task) bool {
|
||||||
if len(filter.Statuses) == 0 {
|
if len(filter.Status) == 0 {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
for _, status := range filter.Statuses {
|
for _, status := range filter.Status {
|
||||||
if task.Status == status {
|
if task.Status == status {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -751,10 +744,10 @@ func (d *Downloader) GetTasksByFilter(filter *TaskFilter) []*Task {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
notStatusMatch := func(task *Task) bool {
|
notStatusMatch := func(task *Task) bool {
|
||||||
if len(filter.NotStatuses) == 0 {
|
if len(filter.NotStatus) == 0 {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
for _, status := range filter.NotStatuses {
|
for _, status := range filter.NotStatus {
|
||||||
if task.Status == status {
|
if task.Status == status {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -836,7 +829,7 @@ func (d *Downloader) watch(task *Task) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// When delete a not resolved task, need check if the task resource is nil
|
// When delete a not resolved task, need check if the task resource is nil
|
||||||
if task.Meta.Res == nil || d.GetTask(task.ID) == nil {
|
if task.Meta.Res == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -902,7 +895,18 @@ func (d *Downloader) restoreTask(task *Task) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *Downloader) restoreFetcher(task *Task) error {
|
func (d *Downloader) restoreFetcher(task *Task) error {
|
||||||
v, f := task.fetcherManager.Restore()
|
var fm fetcher.FetcherManager
|
||||||
|
for _, f := range d.cfg.FetchManagers {
|
||||||
|
if f.Name() == task.Protocol {
|
||||||
|
fm = f
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if fm == nil {
|
||||||
|
return ErrUnSupportedProtocol
|
||||||
|
}
|
||||||
|
task.fetcherManager = fm
|
||||||
|
v, f := fm.Restore()
|
||||||
if v != nil {
|
if v != nil {
|
||||||
err := d.storage.Pop(bucketSave, task.ID, v)
|
err := d.storage.Pop(bucketSave, task.ID, v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -942,9 +946,9 @@ func (d *Downloader) doCreate(f fetcher.Fetcher, opts *base.Options) (taskId str
|
|||||||
}
|
}
|
||||||
|
|
||||||
// if enable white download directory, check if the download directory is in the white list
|
// if enable white download directory, check if the download directory is in the white list
|
||||||
if len(d.cfg.WhiteDownloadDirs) > 0 {
|
if len(d.cfg.DownloadDirWhiteList) > 0 {
|
||||||
inWhiteList := false
|
inWhiteList := false
|
||||||
for _, dir := range d.cfg.WhiteDownloadDirs {
|
for _, dir := range d.cfg.DownloadDirWhiteList {
|
||||||
if match, err := filepath.Match(dir, opts.Path); match && err == nil {
|
if match, err := filepath.Match(dir, opts.Path); match && err == nil {
|
||||||
inWhiteList = true
|
inWhiteList = true
|
||||||
break
|
break
|
||||||
@@ -1144,15 +1148,6 @@ func logPanic(logDir string) {
|
|||||||
debug.SetCrashOutput(f, debug.CrashOptions{})
|
debug.SetCrashOutput(f, debug.CrashOptions{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Downloader) assignFetcherManager(task *Task) error {
|
|
||||||
fm, err := d.parseFm(task.Meta.Req.URL)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
task.fetcherManager = fm
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Downloader) buildFetcher(url string) (fetcher.Fetcher, error) {
|
func (d *Downloader) buildFetcher(url string) (fetcher.Fetcher, error) {
|
||||||
fm, err := d.parseFm(url)
|
fm, err := d.parseFm(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
Loaded 50 of 207 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user