Compare commits

...
Author SHA1 Message Date
liwei c7fefd2b9b fix: test 2024-05-01 18:39:24 +08:00
liwei b8e36b483e fix: test 2024-05-01 18:38:13 +08:00
liwei 91ac841b9d setting ui 2024-05-01 18:35:32 +08:00
liwei 5134446c3f fix: test 2024-05-01 18:35:32 +08:00
liwei 82b6ebd6f1 feat: support proxy from system env 2024-05-01 18:35:32 +08:00
liwei f8ea009fe9 temp 2024-05-01 18:35:32 +08:00
Levi 070db64ca9 fix: http request cookie incremented indefinitely on retries (#514) 2024-05-01 17:45:59 +08:00
Levi 6a8956b04d docs: fix linux download link 2024-05-01 04:16:56 +00:00
Deepansh Singh Arya 057504bb81 feat: updated iOS icons (#512) 2024-05-01 10:48:35 +08:00
madoka773 470b488824 ci: build gopeed with libayatana-appindicator (#508) 2024-04-26 15:41:47 +08:00
madoka773 c8c9df1ae8 ci: build linux with ubuntu latest (#501) 2024-04-23 08:57:23 +08:00
invictus1032 33d787c74e docs: fix Linux download links (#465)
Fix filename discrepancies for .deb and .AppImage
2024-04-12 14:45:09 +08:00
liwei 67338a5e83 docs: add browser extension link 2024-04-03 17:52:37 +08:00
Levi 723697192d fix: command panic before resolve resource (#451) 2024-04-03 16:56:20 +08:00
Zack 4294d07d6a fix: Fix the issue of installing apk files(Android) (#448) (#449) 2024-04-03 09:23:05 +08:00
WINZORT 3936fd786f feat: Update tr_tr.dart (#445) 2024-04-01 09:07:44 +08:00
ilkeiii 45197e9a3b fix: docker entrypoint_sh (#434) 2024-03-30 12:35:00 +08:00
liwei c9692e76e5 fix: build flags type 2024-03-30 11:10:34 +08:00
liwei 0a9e39ed75 ci: fix Dockerfile InDocker flag 2024-03-30 11:00:14 +08:00
liwei 1971f582b5 ci: fix Dockerfile build 2024-03-30 10:50:53 +08:00
liwei c62d6be3da ci: fix Dockerfile build args 2024-03-30 10:39:46 +08:00
liwei 7d5e503894 fix: bump rounded_loading_button 2024-03-30 10:21:19 +08:00
liwei 6c4066fde4 ci: fix Dockerfile 2024-03-30 10:05:45 +08:00
liwei ffdf3e717f ci: fix docker build args format 2024-03-30 09:57:21 +08:00
liwei f66814b4ae ci: fix docker build args 2024-03-30 09:50:41 +08:00
Levi 0c3e201a51 release: v1.5.6 2024-03-30 09:35:20 +08:00
liwei c32a5d750f ci: update change log template 2024-03-30 09:32:43 +08:00
Levi 207c287c97 feat: add browser extension link (#433) 2024-03-30 09:12:24 +08:00
Levi 56ef25065e fix: task lock by unreachable torrent (#431) 2024-03-29 22:11:21 +08:00
Levi bd6d6be71f feat: web config add stroage dir item (#428) 2024-03-29 17:10:25 +08:00
Zack 1d3a028bae docs: Optimize compile commands (#420) 2024-03-27 22:18:52 +08:00
Zack 27011fb96b feat: Support Via Download task, require via version 5.5.0 or above(Android) (#412) (#419) 2024-03-27 21:58:16 +08:00
ilkeiii c696b59c4b feat: add docker permission related settings (#380) 2024-03-25 08:51:07 +08:00
WINZORT 78b5b8a78b feat: Create tr_tr.dart (#402) 2024-03-12 13:57:11 +08:00
Levi c81ca7f5cd feat: docker download dir move to /app/Download (#398) 2024-03-12 13:39:56 +08:00
liwei e39d50fe09 docs: add Flathub link 2024-02-23 08:53:20 +08:00
David Nguyen 086269f8bd feat: add Vietnamese translate (#389) 2024-02-22 15:54:35 +08:00
liwei 00d2183961 docs: fix download link 2024-02-18 20:45:07 +08:00
liwei 5f914dc4c4 docs: fix mac download link 2024-02-18 12:47:27 +08:00
MonoYan 9bf204cf04 Update README_zh-CN.md (#381)
Fixed MacOS download link
2024-02-18 01:14:56 +08:00
Levi a816b9ccc8 fix: create task with default select files (#374) 2024-02-02 09:44:00 +08:00
liwei d60d370463 docs: remove linux portable link 2024-02-01 15:11:41 +08:00
Levi 84d054b494 fix: start duplicate server on mobile when the app is closed by swiping (#370) 2024-01-31 18:45:09 +08:00
Elliot 1aa96e147f feat: improve zh-TW translation (#369) 2024-01-31 18:23:25 +08:00
Levi 8c01e45780 release: v1.5.5 2024-01-30 14:50:29 +08:00
Levi 8ad571aa08 fix: config init fail (#365) 2024-01-30 14:49:26 +08:00
Levi 39316e9b8e fix: windows default install dir (#366) 2024-01-30 14:41:22 +08:00
liwei f778596390 release: v1.5.4 2024-01-30 10:57:08 +08:00
liwei c2cbc14492 docs: improve download guide 2024-01-30 10:37:27 +08:00
Levi 62c22bc6b4 feat: support use server time for file creation (#359) 2024-01-30 09:08:01 +08:00
Levi b89c7bc608 feat: migrate shared_preferences to hive (#343) 2024-01-24 15:49:51 +08:00
Tracey Lesley f2cd686563 fix: missing pb size format (#342) 2024-01-24 14:33:18 +08:00
Levi ef5ee99f04 feat: custom torrent client version (#341) 2024-01-23 17:44:44 +08:00
Tracey Lesley 6d136604db feat: downloader task stats (#339) 2024-01-22 11:43:12 +08:00
Levi 38ff711ea9 feat: add batch delete tasks api (#338) 2024-01-19 18:04:54 +08:00
liwei a1a8abc25f fix: ios download path 2024-01-19 15:45:24 +08:00
madoka773 abccbabc78 fix: wrong debian installed size (#335) 2024-01-19 11:17:50 +08:00
Levi c26eb26b8f fix: default download path on mobile (#337) 2024-01-19 11:12:27 +08:00
Levi 74beea6044 feat: auto create torrent task for http download (#334) 2024-01-18 18:38:44 +08:00
Levi e548d7e0c9 feat: save window size and resize on startup (#332) 2024-01-18 15:00:05 +08:00
madoka773 a996d57914 ci: fix debian 12 depends (#329) 2024-01-18 11:16:50 +08:00
Levi f7dc60ad50 fix: proxy setting only numeric input (#328) 2024-01-18 09:17:06 +08:00
Levi 8425a96ac9 feat: add mirror.ghproxy.com trackers subscribe cdn (#330) 2024-01-17 22:09:37 +08:00
Tracey Lesley 99f4e8c895 feat: add download stats info (#323) 2024-01-17 21:59:01 +08:00
Elliot cc0f61b527 feat: improve zh-TW translation (#327) 2024-01-17 18:16:40 +08:00
Levi 922ef969a6 feat: add thanks item for contributors (#325) 2024-01-16 19:02:20 +08:00
Tracey Lesley 3a542054fc fix: compatible with not follow the http range response standard 2024-01-14 09:58:14 +08:00
Levi 895a8d2ee4 fix: foreground task scope (#314) 2024-01-05 21:49:21 +08:00
madoka773 29d0f5ebcf ci: use flutter distributor (#313) 2024-01-04 12:46:16 +08:00
Levi 31228e3edf release: v1.5.3 2023-12-15 19:23:16 +08:00
Levi 31a32d7e14 fix: handle api panic (#302) 2023-12-15 19:20:56 +08:00
Levi 65fd6fb489 fix: http pause handle (#303) 2023-12-15 19:15:38 +08:00
Levi 953b878a89 fix: adapt mobile proxy settings ui (#301) 2023-12-15 19:09:48 +08:00
Elliot ae4ca3da58 feat: improve zh-TW translation (#300) 2023-12-14 18:06:49 +08:00
Levi d09bd180af release: v1.5.2 2023-12-13 09:38:34 +08:00
liwei 3d24e353c5 docs: docker data path mounted 2023-12-13 08:57:57 +08:00
Levi ce7489b0c0 feat: support volume storage dir on docker (#291) 2023-12-13 08:51:22 +08:00
Levi 2b5091ba21 feat: improve extension engine (#298)
1. support onError event
2. add built-in MessageError
2023-12-12 19:00:42 +08:00
Levi ff32971aca fix: repair invalid filename (#296) 2023-12-12 18:40:36 +08:00
Levi f2af61f9aa fix: sort of completed tasks (#297) 2023-12-12 18:36:40 +08:00
Elliot d935b9d631 feat: improve zh_TW translate (#287) 2023-12-04 23:01:49 +08:00
liwei ae66d1d83d docs: fix contributing 2023-12-04 13:41:27 +08:00
madoka773 aa91ca1d50 ci: linux build use libappindicator (#286) 2023-12-04 13:36:27 +08:00
128 changed files with 3345 additions and 1545 deletions

No files matched your search

+4 -2
View File
@@ -16,9 +16,11 @@ categories:
- title: '🧰 Maintenance' - title: '🧰 Maintenance'
label: 'chore' label: 'chore'
- title: '📖 Document' - title: '📖 Document'
label: 'doc' label: 'docs'
- title: '🚀 CI/CD' - title: '🚀 CI/CD'
label: 'ci' label: 'ci'
- title: '🌎 Internationalization'
label: 'translation'
change-template: '- $TITLE @$AUTHOR (#$NUMBER)' change-template: '- $TITLE @$AUTHOR (#$NUMBER)'
version-resolver: version-resolver:
major: major:
@@ -32,7 +34,7 @@ version-resolver:
- 'patch' - 'patch'
default: patch default: patch
template: | template: |
# Changes # Release notes
$CHANGES $CHANGES
+61 -150
View File
@@ -7,14 +7,14 @@ on:
description: 'Build platform' description: 'Build platform'
required: true required: true
default: 'all' default: 'all'
channel: test:
description: 'Snap channel' description: 'Test mode'
required: true required: true
default: 'stable' default: "false"
env: env:
GO_VERSION: "1.19" GO_VERSION: "1.21"
FLUTTER_VERSION: "3.16.1" FLUTTER_VERSION: "3.16.8"
permissions: permissions:
contents: write contents: write
@@ -23,11 +23,11 @@ jobs:
get-release: get-release:
runs-on: ubuntu-latest runs-on: ubuntu-latest
outputs: outputs:
tag_name: ${{ steps.get-release.outputs.tag_name }} version: ${{ steps.get-release.outputs.version }}
upload_url: ${{ steps.get-release.outputs.upload_url }} upload_url: ${{ steps.get-release.outputs.upload_url }}
steps: steps:
- uses: monkeyWie/get-latest-release@v1 - uses: monkeyWie/get-latest-release@v2.1
id: get-release id: get-release
with: with:
myToken: ${{ github.token }} myToken: ${{ github.token }}
@@ -40,19 +40,20 @@ jobs:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: actions/setup-go@v4 - uses: actions/setup-go@v4
with: with:
go-version: "1.20" go-version: ${{ env.GO_VERSION }}
- uses: subosito/flutter-action@v2 - uses: subosito/flutter-action@v2
with: with:
flutter-version: ${{ env.FLUTTER_VERSION }} flutter-version: ${{ env.FLUTTER_VERSION }}
- name: Build - name: Build
env: env:
VERSION: ${{ needs.get-release.outputs.tag_name }} VERSION: ${{ needs.get-release.outputs.version }}
run: | run: |
Invoke-WebRequest -Uri "https://github.com/jrsoftware/issrc/raw/main/Files/Languages/Unofficial/ChineseSimplified.isl" -OutFile "ChineseSimplified.isl" Invoke-WebRequest -Uri "https://github.com/jrsoftware/issrc/raw/main/Files/Languages/Unofficial/ChineseSimplified.isl" -OutFile "ChineseSimplified.isl"
mv ChineseSimplified.isl "C:\Program Files (x86)\Inno Setup 6\Languages\" mv ChineseSimplified.isl "C:\Program Files (x86)\Inno Setup 6\Languages\"
go build -tags nosqlite -ldflags="-w -s" -buildmode=c-shared -o ui/flutter/windows/libgopeed.dll github.com/GopeedLab/gopeed/bind/desktop go build -tags nosqlite -ldflags="-w -s -X github.com/GopeedLab/gopeed/pkg/base.Version=$VERSION" -buildmode=c-shared -o ui/flutter/windows/libgopeed.dll github.com/GopeedLab/gopeed/bind/desktop
cd ui/flutter cd ui/flutter
$TAG = "v$env:VERSION"
flutter build windows 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"
@@ -65,7 +66,7 @@ jobs:
cp $system\vcruntime140_1.dll $release cp $system\vcruntime140_1.dll $release
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-$env:VERSION-windows-amd64-portable.zip" Compress-Archive -Path "$release*" -DestinationPath "build\windows\Output\Gopeed-$TAG-windows-amd64-portable.zip"
cd build/windows cd build/windows
echo @" echo @"
@@ -73,7 +74,7 @@ jobs:
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
#define MyAppName "Gopeed" #define MyAppName "Gopeed"
#define MyAppVersion "$($env:VERSION.substring(1))" #define MyAppVersion "$env:VERSION"
#define MyAppPublisher "monkeyWie" #define MyAppPublisher "monkeyWie"
#define MyAppURL "https://gopeed.com" #define MyAppURL "https://gopeed.com"
#define MyAppExeName "gopeed.exe" #define MyAppExeName "gopeed.exe"
@@ -124,9 +125,9 @@ jobs:
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent
"@ > setup.iss "@ > setup.iss
iscc.exe setup.iss iscc.exe setup.iss
mv "Output\gopeed.exe" "Output\Gopeed-$env:VERSION-windows-amd64.exe" mv "Output\gopeed.exe" "Output\Gopeed-$TAG-windows-amd64.exe"
Compress-Archive -Path "Output\Gopeed-$env:VERSION-windows-amd64.exe" -DestinationPath "Output\Gopeed-$env:VERSION-windows-amd64.zip" Compress-Archive -Path "Output\Gopeed-$TAG-windows-amd64.exe" -DestinationPath "Output\Gopeed-$TAG-windows-amd64.zip"
Remove-Item -Path "Output\Gopeed-$env:VERSION-windows-amd64.exe" Remove-Item -Path "Output\Gopeed-$TAG-windows-amd64.exe"
- name: Upload - name: Upload
uses: shogo82148/actions-upload-release-asset@v1 uses: shogo82148/actions-upload-release-asset@v1
with: with:
@@ -143,9 +144,11 @@ jobs:
with: with:
go-version: ${{ env.GO_VERSION }} go-version: ${{ env.GO_VERSION }}
- name: Build - name: Build
env:
VERSION: ${{ needs.get-release.outputs.version }}
run: | run: |
go install src.techknowlogick.com/xgo@latest go install src.techknowlogick.com/xgo@latest
xgo -go go-$GO_VERSION.x --targets=darwin/arm64 -tags="nosqlite" -ldflags="-w -s" -buildmode=c-shared -pkg=bind/desktop -out=libgopeed . xgo -go go-$GO_VERSION.x --targets=darwin/arm64 -tags="nosqlite" -ldflags="-w -s -X github.com/GopeedLab/gopeed/pkg/base.Version=$VERSION" -buildmode=c-shared -pkg=bind/desktop -out=libgopeed .
mv libgopeed-*.dylib libgopeed.dylib mv libgopeed-*.dylib libgopeed.dylib
- uses: actions/upload-artifact@v3 - uses: actions/upload-artifact@v3
with: with:
@@ -176,12 +179,12 @@ jobs:
path: ui/flutter/lib-arm64 path: ui/flutter/lib-arm64
- name: Build - name: Build
env: env:
VERSION: ${{ needs.get-release.outputs.tag_name }} VERSION: ${{ needs.get-release.outputs.version }}
run: | run: |
PROJECT_DIR=$(pwd) PROJECT_DIR=$(pwd)
# amd64 lib # amd64 lib
go build -tags nosqlite -ldflags="-w -s" -buildmode=c-shared -o ui/flutter/lib-amd64/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 ui/flutter/lib-amd64/libgopeed.dylib github.com/GopeedLab/gopeed/bind/desktop
# universal binary # universal binary
mkdir -p ui/flutter/macos/Frameworks mkdir -p ui/flutter/macos/Frameworks
cp ui/flutter/lib-amd64/libgopeed.dylib ui/flutter/macos/Frameworks/amd64-lib cp ui/flutter/lib-amd64/libgopeed.dylib ui/flutter/macos/Frameworks/amd64-lib
@@ -203,7 +206,7 @@ jobs:
} }
EOF EOF
mkdir dist mkdir dist
appdmg appdmg.json dist/Gopeed-$VERSION-macos.dmg appdmg appdmg.json dist/Gopeed-v$VERSION-macos.dmg
- name: Upload - name: Upload
uses: shogo82148/actions-upload-release-asset@v1 uses: shogo82148/actions-upload-release-asset@v1
with: with:
@@ -212,7 +215,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: ubuntu-20.04 runs-on: ubuntu-latest
needs: [ get-release ] needs: [ get-release ]
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
@@ -223,126 +226,30 @@ jobs:
with: with:
flutter-version: ${{ env.FLUTTER_VERSION }} flutter-version: ${{ env.FLUTTER_VERSION }}
- run: | - run: |
sudo apt-get update -y sudo apt update -y
sudo apt-get install -y ninja-build libgtk-3-dev libayatana-appindicator3-dev # flutter build dependencies
sudo apt install -y ninja-build libgtk-3-dev libayatana-appindicator3-1 libayatana-appindicator3-dev
# appimage build dependencies
sudo apt install -y libfuse2 locate
wget -O appimagetool "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage"
chmod +x appimagetool
sudo mv appimagetool /usr/local/bin/
- name: Build - name: Build
env: env:
VERSION: ${{ needs.get-release.outputs.tag_name }} VERSION: ${{ needs.get-release.outputs.version }}
run: | run: |
go build -tags nosqlite -ldflags="-w -s" -buildmode=c-shared -o ui/flutter/linux/bundle/lib/libgopeed.so github.com/GopeedLab/gopeed/bind/desktop go build -tags nosqlite -ldflags="-w -s -X github.com/GopeedLab/gopeed/pkg/base.Version=$VERSION" -buildmode=c-shared -o ui/flutter/linux/bundle/lib/libgopeed.so github.com/GopeedLab/gopeed/bind/desktop
cd ui/flutter cd ui/flutter
flutter build linux dart pub global activate -sgit https://github.com/GopeedLab/flutter_distributor.git --git-path packages/flutter_distributor
mkdir -p build/dist flutter_distributor package --platform linux --targets appimage,deb
cd dist/*
# Build portable mv gopeed-*-linux.AppImage Gopeed-v${VERSION}-linux-x86_64.AppImage
tar -C build/linux/x64/release/bundle -czaf build/dist/Gopeed-$VERSION-linux-amd64-portable.tar.gz ./ mv gopeed-*-linux.deb Gopeed-v${VERSION}-linux-amd64.deb
# Build deb package
mkdir -p debian/gopeed/opt/gopeed
mkdir -p debian/gopeed/usr/share/applications
mkdir -p debian/gopeed/usr/share/icons/hicolor/scalable/apps
mkdir -p debian/gopeed/DEBIAN
cp -r build/linux/x64/release/bundle/* debian/gopeed/opt/gopeed/
cp assets/icon/icon.svg debian/gopeed/usr/share/icons/hicolor/scalable/apps/com.gopeed.Gopeed.svg
cat>debian/gopeed/DEBIAN/postinst<<EOF
#!/bin/bash
ln -sf /opt/gopeed/gopeed /usr/bin/gopeed
EOF
cat>debian/gopeed/DEBIAN/postrm<<EOF
#!/bin/bash
rm -rf /usr/bin/gopeed
EOF
chmod 0755 debian/gopeed/DEBIAN/postinst
chmod 0755 debian/gopeed/DEBIAN/postrm
cat>debian/gopeed/DEBIAN/control<<EOF
Package: gopeed
Version: ${VERSION:1}
Architecture: amd64
Essential: no
Priority: optional
Depends:
Maintainer: monkeyWie
Description: High speed downloader that supports all platforms.
EOF
cp linux/assets/com.gopeed.Gopeed.desktop debian/gopeed/usr/share/applications/gopeed.desktop
dpkg-deb --build debian/gopeed
mv debian/gopeed.deb build/dist/Gopeed-$VERSION-linux-amd64.deb
# Build AppImage
sudo apt-get -y install libfuse2
wget -O appimage-builder https://github.com/AppImageCrafters/appimage-builder/releases/download/v1.1.0/appimage-builder-1.1.0-x86_64.AppImage
chmod +x appimage-builder
mkdir AppDir
cp -r build/linux/x64/release/bundle/* AppDir
mkdir -p AppDir/usr/share/icons/hicolor/scalable/apps/
cp assets/icon/icon.svg AppDir/usr/share/icons/hicolor/scalable/apps/gopeed.svg
cat>AppImageBuilder.yml<<EOF
version: 1
AppDir:
path: ${PWD}/AppDir
app_info:
id: com.gopeed.gopeed
name: Gopeed
icon: gopeed
version: ${VERSION:1}
exec: gopeed
exec_args: \$@
apt:
arch: amd64
allow_unauthenticated: true
sources:
- sourceline: deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy main restricted
universe multiverse
- sourceline: deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy-updates main
restricted universe multiverse
- sourceline: deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy-backports
main restricted universe multiverse
- sourceline: deb http://security.ubuntu.com/ubuntu/ jammy-security main restricted
universe multiverse
include: []
files:
include: []
exclude:
- usr/share/man
- usr/share/doc/*/README.*
- usr/share/doc/*/changelog.*
- usr/share/doc/*/NEWS.*
- usr/share/doc/*/TODO.*
test:
fedora-30:
image: appimagecrafters/tests-env:fedora-30
command: ./AppRun
debian-stable:
image: appimagecrafters/tests-env:debian-stable
command: ./AppRun
archlinux-latest:
image: appimagecrafters/tests-env:archlinux-latest
command: ./AppRun
centos-7:
image: appimagecrafters/tests-env:centos-7
command: ./AppRun
ubuntu-xenial:
image: appimagecrafters/tests-env:ubuntu-xenial
command: ./AppRun
AppImage:
arch: x86_64
update-information: guess
EOF
./appimage-builder --skip-tests --recipe AppImageBuilder.yml
mv Gopeed-*-x86_64.AppImage build/dist/Gopeed-$VERSION-linux-amd64.AppImage
- 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: ui/flutter/build/dist/* asset_path: ui/flutter/dist/*/*
overwrite: true overwrite: true
build-snap: build-snap:
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'snap' }} if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'snap' }}
@@ -362,9 +269,9 @@ jobs:
channel: latest/stable channel: latest/stable
- name: Build - name: Build
env: env:
VERSION: ${{ needs.get-release.outputs.tag_name }} VERSION: ${{ needs.get-release.outputs.version }}
run: | run: |
go build -tags nosqlite -ldflags="-w -s" -buildmode=c-shared -o ui/flutter/linux/bundle/lib/libgopeed.so github.com/GopeedLab/gopeed/bind/desktop go build -tags nosqlite -ldflags="-w -s -X github.com/GopeedLab/gopeed/pkg/base.Version=$VERSION" -buildmode=c-shared -o ui/flutter/linux/bundle/lib/libgopeed.so github.com/GopeedLab/gopeed/bind/desktop
cd ui/flutter cd ui/flutter
sudo snap install snapcraft --classic sudo snap install snapcraft --classic
@@ -373,7 +280,7 @@ jobs:
cat>snap/snapcraft.yaml<<EOF cat>snap/snapcraft.yaml<<EOF
name: gopeed name: gopeed
version: ${VERSION:1} version: $VERSION
summary: A modern download manager summary: A modern download manager
description: High speed downloader that supports all platforms. description: High speed downloader that supports all platforms.
@@ -405,7 +312,7 @@ jobs:
plugin: flutter plugin: flutter
stage-packages: stage-packages:
- libgtk-3-dev - libgtk-3-dev
- libayatana-appindicator3-dev - libappindicator3-dev
flutter-target: lib/main.dart flutter-target: lib/main.dart
EOF EOF
@@ -416,7 +323,7 @@ jobs:
# Snapcraft login # Snapcraft login
export SNAPCRAFT_STORE_CREDENTIALS=${{ secrets.SNAP_STORE_LOGIN }} export SNAPCRAFT_STORE_CREDENTIALS=${{ secrets.SNAP_STORE_LOGIN }}
snapcraft upload --release=${{ github.event.inputs.channel }} gopeed_${VERSION:1}_amd64.snap snapcraft upload --release=${{ github.event.inputs.test == 'true' && 'edge' || 'stable' }} gopeed_${VERSION}_amd64.snap
build-android: build-android:
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'android' }} if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'android' }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -435,7 +342,7 @@ jobs:
flutter-version: ${{ env.FLUTTER_VERSION }} flutter-version: ${{ env.FLUTTER_VERSION }}
- name: Build - name: Build
env: env:
VERSION: ${{ needs.get-release.outputs.tag_name }} VERSION: ${{ needs.get-release.outputs.version }}
APK_KEYSTORE: ${{ secrets.APK_KEYSTORE }} APK_KEYSTORE: ${{ secrets.APK_KEYSTORE }}
APK_KEY_PASSWORD: ${{ secrets.APK_KEY_PASSWORD }} APK_KEY_PASSWORD: ${{ secrets.APK_KEY_PASSWORD }}
APK_STORE_PASSWORD: ${{ secrets.APK_STORE_PASSWORD }} APK_STORE_PASSWORD: ${{ secrets.APK_STORE_PASSWORD }}
@@ -443,12 +350,12 @@ jobs:
go install golang.org/x/mobile/cmd/gomobile@latest go install golang.org/x/mobile/cmd/gomobile@latest
go get golang.org/x/mobile/bind go get golang.org/x/mobile/bind
gomobile init gomobile init
gomobile bind -tags nosqlite -ldflags="-w -s" -o ui/flutter/android/app/libs/libgopeed.aar -target=android -androidapi 19 -javapkg=com.gopeed github.com/GopeedLab/gopeed/bind/mobile gomobile bind -tags nosqlite -ldflags="-w -s -X github.com/GopeedLab/gopeed/pkg/base.Version=$VERSION" -o ui/flutter/android/app/libs/libgopeed.aar -target=android -androidapi 19 -javapkg=com.gopeed github.com/GopeedLab/gopeed/bind/mobile
cd ui/flutter cd ui/flutter
echo $APK_KEYSTORE | base64 -di > android/app/upload-keystore.jks echo $APK_KEYSTORE | base64 -di > android/app/upload-keystore.jks
flutter build apk flutter build apk
mkdir dist mkdir dist
cp build/app/outputs/flutter-apk/app-release.apk dist/Gopeed-$VERSION-android.apk cp build/app/outputs/flutter-apk/app-release.apk dist/Gopeed-v$VERSION-android.apk
- name: Upload - name: Upload
uses: shogo82148/actions-upload-release-asset@v1 uses: shogo82148/actions-upload-release-asset@v1
with: with:
@@ -469,19 +376,19 @@ jobs:
flutter-version: ${{ env.FLUTTER_VERSION }} flutter-version: ${{ env.FLUTTER_VERSION }}
- name: Build - name: Build
env: env:
VERSION: ${{ needs.get-release.outputs.tag_name }} VERSION: ${{ needs.get-release.outputs.version }}
run: | run: |
go install golang.org/x/mobile/cmd/gomobile@latest go install golang.org/x/mobile/cmd/gomobile@latest
go get golang.org/x/mobile/bind go get golang.org/x/mobile/bind
gomobile init gomobile init
gomobile bind -tags nosqlite -ldflags="-w -s" -o ui/flutter/ios/Frameworks/Libgopeed.xcframework -target=ios github.com/GopeedLab/gopeed/bind/mobile gomobile bind -tags nosqlite,netgo -ldflags="-w -s -X github.com/GopeedLab/gopeed/pkg/base.Version=$VERSION" -o ui/flutter/ios/Frameworks/Libgopeed.xcframework -target=ios github.com/GopeedLab/gopeed/bind/mobile
cd ui/flutter cd ui/flutter
flutter build ios --no-codesign flutter build ios --no-codesign
mkdir Payload mkdir Payload
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
mkdir dist mkdir dist
mv Payload.zip dist/Gopeed-$VERSION-ios.ipa mv Payload.zip dist/Gopeed-v$VERSION-ios.ipa
- name: Upload - name: Upload
uses: shogo82148/actions-upload-release-asset@v1 uses: shogo82148/actions-upload-release-asset@v1
with: with:
@@ -502,13 +409,14 @@ jobs:
flutter-version: ${{ env.FLUTTER_VERSION }} flutter-version: ${{ env.FLUTTER_VERSION }}
- name: Build - name: Build
env: env:
VERSION: ${{ needs.get-release.outputs.tag_name }} VERSION: ${{ needs.get-release.outputs.version }}
run: | run: |
cd ui/flutter cd ui/flutter
flutter build web --web-renderer html flutter build web --web-renderer html
cd ../../ cd ../../
cp -r ui/flutter/build/web cmd/web/dist cp -r ui/flutter/build/web cmd/web/dist
mkdir -p dist/zip mkdir -p dist/zip
goos_arr=(windows darwin linux) goos_arr=(windows darwin linux)
goarch_arr=(386 amd64 arm64) goarch_arr=(386 amd64 arm64)
export CGO_ENABLED=0 export CGO_ENABLED=0
@@ -518,9 +426,9 @@ jobs:
if [ $goos = "darwin" ]; then if [ $goos = "darwin" ]; then
goos_name="macos" goos_name="macos"
fi fi
name=gopeed-web-$VERSION-$goos_name-$goarch name=gopeed-web-v$VERSION-$goos_name-$goarch
dir="dist/$name/" dir="dist/$name/"
(GOOS=$goos GOARCH=$goarch go build -tags nosqlite,web -ldflags="-s -w" -o $dir github.com/GopeedLab/gopeed/cmd/web \ (GOOS=$goos GOARCH=$goarch go build -tags nosqlite,web -ldflags="-s -w -X github.com/GopeedLab/gopeed/pkg/base.Version=$VERSION" -o $dir github.com/GopeedLab/gopeed/cmd/web \
&& cd $dir \ && cd $dir \
&& file=$(ls -AU | head -1) \ && file=$(ls -AU | head -1) \
&& mkdir $name \ && mkdir $name \
@@ -558,15 +466,18 @@ jobs:
- name: Build and push - name: Build and push
uses: docker/build-push-action@v2 uses: docker/build-push-action@v2
env: env:
VERSION: ${{ needs.get-release.outputs.tag_name }} VERSION: ${{ needs.get-release.outputs.version }}
with: with:
push: true push: ${{ github.event.inputs.test == 'true' && 'false' || 'true' }}
build-args: |
VERSION=${{ env.VERSION }}
IN_DOCKER=true
platforms: | platforms: |
linux/386 linux/386
linux/amd64 linux/amd64
linux/arm64 linux/arm64
linux/arm/v6
linux/arm/v7 linux/arm/v7
linux/arm64/v8
tags: | tags: |
liwei2633/gopeed:latest liwei2633/gopeed:latest
liwei2633/gopeed:${{ env.VERSION }} liwei2633/gopeed:v${{ env.VERSION }}
+2 -2
View File
@@ -27,8 +27,8 @@ on:
- "go.sum" - "go.sum"
env: env:
GO_VERSION: "1.19" GO_VERSION: "1.21"
FLUTTER_VERSION: "3.16.1" FLUTTER_VERSION: "3.16.8"
jobs: jobs:
# lint: # lint:
+1 -1
View File
@@ -4,7 +4,7 @@
*.dll *.dll
*.so *.so
*.dylib *.dylib
ui/flutter/dist/
# Test binary, built with `go test -c` # Test binary, built with `go test -c`
*.test *.test
+29
View File
@@ -0,0 +1,29 @@
# Hướng dẫn đóng góp cho Gopeed
Trước tiên, cảm ơn bạn đã quan tâm đến việc đóng góp cho Gopeed. Hướng dẫn này sẽ giúp bạn tham gia
phát triển Gopeed một cách tốt hơn.
## Mô tả nhánh
Dự án này chỉ có một nhánh chính duy nhất, đó là nhánh `main`. Nếu bạn muốn tham gia vào
phát triển Gopeed, hãy fork dự án này trước, sau đó phát triển trong dự án fork của bạn. Sau khi
hoàn thành phát triển, gửi một PR đến dự án này và merge vào nhánh `main`.
## Phát triển cục bộ
Đề nghị phát triển và gỡ lỗi thông qua web. Đầu tiên, khởi động dịch vụ backend bằng cách chạy
lệnh `go run cmd/api/main.go` trong dòng lệnh, cổng mặc định của dịch vụ là `9999`, sau đó
khởi động dự án flutter frontend trong chế độ `debug` để chạy.
## Dịch thuật
Các tệp quốc tế hóa của Gopeed được đặt trong thư mục `ui/flutter/lib/i18n/langs`.
Bạn chỉ cần thêm tệp ngôn ngữ tương ứng trong thư mục này.
Vui lòng tham khảo `en_us.dart` để biết cách dịch thuật.
## Phát triển flutter
Đừng quên chạy `dart format ./ui/flutter` trước khi commit để giữ mã của bạn theo định dạng dart chuẩn.
Bật build_runner watcher nếu bạn muốn chỉnh sửa api/models:
+1 -1
View File
@@ -16,7 +16,7 @@ flutter 项目即可运行。
Gopeed 的国际化文件位于 `ui/flutter/lib/i18n/langs` 目录下,只需要在该目录下添加对应的语言文件即可。 Gopeed 的国际化文件位于 `ui/flutter/lib/i18n/langs` 目录下,只需要在该目录下添加对应的语言文件即可。
请注意以 `en_US.json` 为参照进行翻译。 请注意以 `en_us.dart` 为参照进行翻译。
## flutter开发 ## flutter开发
+1 -1
View File
@@ -15,7 +15,7 @@
Gopeed 的翻譯文件位於 `ui/flutter/lib/i18n/langs` 目錄中,只需要修改或新建翻譯文件即可。 Gopeed 的翻譯文件位於 `ui/flutter/lib/i18n/langs` 目錄中,只需要修改或新建翻譯文件即可。
請以 `en_US.json` 作為參照。 請以 `en_us.dart` 作為參照。
## flutter開發 ## flutter開發
+15 -4
View File
@@ -1,21 +1,32 @@
FROM instrumentisto/flutter:3.16.1 AS flutter FROM instrumentisto/flutter:3.16.8 AS flutter
WORKDIR /app WORKDIR /app
COPY ./ui/flutter/pubspec.yaml ./ui/flutter/pubspec.lock ./ COPY ./ui/flutter/pubspec.yaml ./ui/flutter/pubspec.lock ./
RUN flutter pub get RUN flutter pub get
COPY ./ui/flutter ./ COPY ./ui/flutter ./
RUN flutter build web --web-renderer html RUN flutter build web --web-renderer html
FROM golang:1.19.3 AS go FROM golang:1.21.6 AS go
WORKDIR /app WORKDIR /app
COPY ./go.mod ./go.sum ./ COPY ./go.mod ./go.sum ./
RUN go mod download RUN go mod download
COPY . . COPY . .
COPY --from=flutter /app/build/web ./cmd/web/dist COPY --from=flutter /app/build/web ./cmd/web/dist
RUN CGO_ENABLED=0 go build -tags nosqlite,web -ldflags="-s -w" -o dist/gopeed github.com/GopeedLab/gopeed/cmd/web ARG VERSION=dev
RUN CGO_ENABLED=0 go build -tags nosqlite,web \
-ldflags="-s -w -X github.com/GopeedLab/gopeed/pkg/base.Version=$VERSION -X github.com/GopeedLab/gopeed/pkg/base.InDocker=true" \
-o dist/gopeed github.com/GopeedLab/gopeed/cmd/web
FROM alpine:3.14.2 FROM alpine:3.14.2
LABEL maintainer="monkeyWie" LABEL maintainer="monkeyWie"
WORKDIR /app WORKDIR /app
COPY --from=go /app/dist/gopeed ./ COPY --from=go /app/dist/gopeed ./
COPY entrypoint.sh ./entrypoint.sh
RUN apk update && \
apk upgrade --no-cache && \
apk add --no-cache bash su-exec; \
chmod +x ./entrypoint.sh && \
rm -rf /var/cache/apk/*
VOLUME ["/app/storage"]
ENV PUID=0 PGID=0 UMASK=022
EXPOSE 9999 EXPOSE 9999
ENTRYPOINT ["./gopeed"] ENTRYPOINT ["./entrypoint.sh"]
+71 -36
View File
@@ -9,30 +9,80 @@
[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/R6R6IJGN6) [![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/R6R6IJGN6)
[English](/README.md) | [中文](/README_zh-CN.md) | [日本語](/README_ja-JP.md) | [正體中文](/README_zh-TW.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) | 📖 [Develop Docs](https://docs.gopeed.com) ## Download
## Install <table>
<thead>
<tr>
<th>Platform</th>
<th>Package Type</th>
<th>Download Link</th>
</tr>
</thead>
<tbody>
<tr>
<td rowspan=2>Windows</td>
<td><code>EXE Installer</code></td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64.zip">Link</a></td>
</tr>
<tr>
<td><code>Portable ZIP</code></td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64-portable.zip">Link</a></td>
</tr>
<tr>
<td>MacOS</td>
<td><code>DMG Installer</code></td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-macos.dmg">Link</a></td>
</tr>
<tr>
<td rowspan=4>Linux</td>
<td><code>Flathub</code></td>
<td><a href="https://flathub.org/apps/com.gopeed.Gopeed">Link</a></td>
</tr>
<tr>
<td><code>SNAP</code></td>
<td><a href="https://snapcraft.io/gopeed">Link</a></td>
</tr>
<tr>
<td><code>DEB</code></td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-amd64.deb">Link</a></td>
</tr>
<tr>
<td><code>AppImage</code></td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-x86_64.AppImage">Link</a></td>
</tr>
<tr>
<td>Android</td>
<td><code>APK</code></td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android.apk">Link</a></td>
</tr>
<tr>
<td>iOS</td>
<td><code>IPA</code></td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-ios.ipa">Link</a></td>
</tr>
<tr>
<td>Web</td>
<td></td>
<td><a href="https://github.com/GopeedLab/gopeed/releases/latest">Link</a></td>
</tr>
<tr>
<td>Docker</td>
<td></td>
<td><a href="https://hub.docker.com/r/liwei2633/gopeed">Link</a></td>
</tr>
</tbody>
</table>
**Supported platforms** More about installation, please refer to [Installation](https://docs.gopeed.com/install.html)
- [x] windows
- [x] macos
- [x] linux
- [x] android
- [x] ios
- [x] web
- [x] docker
[Download](https://github.com/GopeedLab/gopeed/releases/latest)
> Tips: If the macos open failed, please execute the `xattr -d com.apple.quarantine /Applications/Gopeed.app` command in the terminal
### Command tool ### Command tool
@@ -42,23 +92,9 @@ use `go install`:
go install github.com/GopeedLab/gopeed/cmd/gopeed@latest go install github.com/GopeedLab/gopeed/cmd/gopeed@latest
``` ```
### Docker ## Browser Extension
#### Start directly 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)
```bash
docker run -d -p 9999:9999 -v /path/to/download:/root/Downloads liwei2633/gopeed
```
#### Using Docker Compose
```bash
docker-compose up -d
```
#### Access Gopeed
When the docker container is running, you can access the web page through `http://localhost:9999`.
## Donate ## Donate
@@ -76,8 +112,8 @@ This project is divided into two parts, the front end uses `flutter`, the back e
### Environment ### Environment
1. Golang 1.19+ 1. Golang 1.21+
2. Flutter 3.10+ 2. Flutter 3.16+
### Clone ### Clone
@@ -136,7 +172,7 @@ command:
- android - android
```bash ```bash
gomobile bind -tags nosqlite -ldflags="-w -s" -o ui/flutter/android/app/libs/libgopeed.aar -target=android -androidapi 19 -javapkg=com.gopeed github.com/GopeedLab/gopeed/bind/mobile gomobile bind -tags nosqlite -ldflags="-w -s" -o ui/flutter/android/app/libs/libgopeed.aar -target=android -androidapi 19 -javapkg="com.gopeed" github.com/GopeedLab/gopeed/bind/mobile
cd ui/flutter cd ui/flutter
flutter build apk flutter build apk
``` ```
@@ -160,7 +196,6 @@ 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
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
go run cmd/web/main.go
``` ```
## Credits ## Credits
+68 -44
View File
@@ -9,7 +9,7 @@
[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/R6R6IJGN6) [![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/R6R6IJGN6)
[English](/README.md) | [中文](/README_zh-CN.md) | [日本語](/README_ja-JP.md) | [正體中文](/README_zh-TW.md) [English](/README.md) | [中文](/README_zh-CN.md) | [日本語](/README_ja-JP.md) | [正體中文](/README_zh-TW.md) | [Tiếng Việt](/README_vi-VN.md)
## はじめに ## はじめに
@@ -19,49 +19,74 @@ Gopeed (正式名 Go Speed) は `Golang` + `Flutter` によって開発された
## インストール ## インストール
**サポートするプラットフォーム** <table>
<thead>
<tr>
<th>Platform</th>
<th>Package Type</th>
<th>Download Link</th>
</tr>
</thead>
<tbody>
<tr>
<td rowspan=2>Windows</td>
<td><code>EXE Installer</code></td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64.zip">Link</a></td>
</tr>
<tr>
<td><code>Portable ZIP</code></td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64-portable.zip">Link</a></td>
</tr>
<tr>
<td>MacOS</td>
<td><code>DMG Installer</code></td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-macos.dmg">Link</a></td>
</tr>
<tr>
<td rowspan=4>Linux</td>
<td><code>Flathub</code></td>
<td><a href="https://flathub.org/apps/com.gopeed.Gopeed">Link</a></td>
</tr>
<tr>
<td><code>SNAP</code></td>
<td><a href="https://snapcraft.io/gopeed">Link</a></td>
</tr>
<tr>
<td><code>DEB</code></td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-amd64.deb">Link</a></td>
</tr>
<tr>
<td><code>AppImage</code></td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-x86_64.AppImage">Link</a></td>
</tr>
<tr>
<td>Android</td>
<td><code>APK</code></td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android.apk">Link</a></td>
</tr>
<tr>
<td>iOS</td>
<td><code>IPA</code></td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-ios.ipa">Link</a></td>
</tr>
<tr>
<td>Web</td>
<td></td>
<td><a href="https://github.com/GopeedLab/gopeed/releases/latest">Link</a></td>
</tr>
<tr>
<td>Docker</td>
<td></td>
<td><a href="https://hub.docker.com/r/liwei2633/gopeed">Link</a></td>
</tr>
</tbody>
</table>
- [x] windows インストールについての詳細は、[インストール](https://docs.gopeed.com/install.html)を参照してください。
- [x] macos
- [x] linux
- [x] android
- [x] ios
- [x] web
- [x] docker
[ダウンロード](https://github.com/GopeedLab/gopeed/releases/latest)
> ヒント: macos を開くのに失敗した場合は、ターミナルで `xattr -d com.apple.quarantine /Applications/Gopeed.app` コマンドを実行してください
### コマンドツール
`go install` を使用:
```bash
go install github.com/GopeedLab/gopeed/cmd/gopeed@latest
```
### Docker
#### 直接始める
```bash
docker run -d -p 9999:9999 -v /path/to/download:/root/Downloads liwei2633/gopeed
```
#### Docker Compose の使用
```bash
docker-compose up -d
```
#### Gopeed へのアクセス
docker コンテナが起動しているときは、`http://localhost:9999` からウェブページにアクセスできます。
## 寄付 ## 寄付
もしこのプロジェクトがお気に召しましたら、このプロジェクトの発展を支援するために[寄付](/.donate/index.md#donate)をご検討ください! もしこのプロジェクトがお気に召しましたら、このプロジェクトの発展を支援するために[寄付](https://docs.gopeed.com/donate.html)をご検討ください!
## ショーケース ## ショーケース
@@ -75,8 +100,8 @@ docker コンテナが起動しているときは、`http://localhost:9999` か
### 環境 ### 環境
1. Go 言語 1.19+ 1. Go 言語 1.21+
2. Flutter 3.10+ 2. Flutter 3.16+
### クローン ### クローン
@@ -135,7 +160,7 @@ gomobile init
- android - android
```bash ```bash
gomobile bind -tags nosqlite -ldflags="-w -s" -o ui/flutter/android/app/libs/libgopeed.aar -target=android -androidapi 19 -javapkg=com.gopeed github.com/GopeedLab/gopeed/bind/mobile gomobile bind -tags nosqlite -ldflags="-w -s" -o ui/flutter/android/app/libs/libgopeed.aar -target=android -androidapi 19 -javapkg="com.gopeed" github.com/GopeedLab/gopeed/bind/mobile
cd ui/flutter cd ui/flutter
flutter build apk flutter build apk
``` ```
@@ -159,7 +184,6 @@ 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
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
go run cmd/web/main.go
``` ```
## 感謝 ## 感謝
+211
View File
@@ -0,0 +1,211 @@
[![Trạng thái kiểm tra](https://github.com/GopeedLab/gopeed/workflows/test/badge.svg)](https://github.com/GopeedLab/gopeed/actions?query=workflow%3Atest)
[![Codecov](https://codecov.io/gh/GopeedLab/gopeed/branch/main/graph/badge.svg)](https://codecov.io/gh/GopeedLab/gopeed)
[![Phiên bản](https://img.shields.io/github/release/GopeedLab/gopeed.svg)](https://github.com/GopeedLab/gopeed/releases)
[![Tải về](https://img.shields.io/github/downloads/GopeedLab/gopeed/total.svg)](https://github.com/GopeedLab/gopeed/releases)
[![Ủng hộ](https://img.shields.io/badge/%24-ủng%20hộ-ff69b4.svg)](https://docs.gopeed.com/donate.html)
[![Discord](https://img.shields.io/discord/1037992631881449472?label=Discord&logo=discord&style=social)](https://discord.gg/ZUJqJrwCGB)
![](_docs/img/banner.png)
[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/R6R6IJGN6)
[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
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)
## Tải về
<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>
<tr>
<td rowspan=2>Windows</td>
<td><code>Bộ cài đặt EXE</code></td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64.zip">Liên kết</a></td>
</tr>
<tr>
<td><code>ZIP Portable</code></td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64-portable.zip">Liên kết</a></td>
</tr>
<tr>
<td>MacOS</td>
<td><code>Bộ cài đặt DMG</code></td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-macos.dmg">Liên kết</a></td>
</tr>
<tr>
<td rowspan=4>Linux</td>
<td><code>Flathub</code></td>
<td><a href="https://flathub.org/apps/com.gopeed.Gopeed">Liên kết</a></td>
</tr>
<tr>
<td><code>SNAP</code></td>
<td><a href="https://snapcraft.io/gopeed">Liên kết</a></td>
</tr>
<tr>
<td><code>DEB</code></td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-amd64.deb">Liên kết</a></td>
</tr>
<tr>
<td><code>AppImage</code></td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-x86_64.AppImage">Liên kết</a></td>
</tr>
<tr>
<td>Android</td>
<td><code>APK</code></td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android.apk">Liên kết</a></td>
</tr>
<tr>
<td>iOS</td>
<td><code>IPA</code></td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-ios.ipa">Liên kết</a></td>
</tr>
<tr>
<td>Web</td>
<td></td>
<td><a href="https://github.com/GopeedLab/gopeed/releases/latest">Liên kết</a></td>
</tr>
<tr>
<td>Docker</td>
<td></td>
<td><a href="https://hub.docker.com/r/liwei2633/gopeed">Liên kết</a></td>
</tr>
</tbody>
</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)
### Command tool
Sử dụng `go install`:
```bash
go install github.com/GopeedLab/gopeed/cmd/gopeed@latest
```
## 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!
## Trưng bày
![](_docs/img/ui-demo.png)
## 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`.
> Mã giao diện nằm trong thư mục `ui/flutter`.
### Environment
1. Golang 1.21+
2. Flutter 3.16+
### Clone
```bash
git clone [email protected]:GopeedLab/gopeed.git
```
### Đóng góp
Vui lòng tham khảo [CONTRIBUTING_vi-VN.md](/CONTRIBUTING_vi-VN.md)
### Xây dựng
#### Desktop
Trước tiên, bạn cần cấu hình môi trường theo tài liệu chính thức của [Tài liệu trang web máy tính để bàn Flutter](https://docs.flutter.dev/development/platform-integration/desktop), sau đó bạn cần đảm bảo môi trường cgo được thiết lập đúng. Để biết hướng dẫn chi tiết về cách thiết lập môi trường cgo, vui lòng tham khảo các tài liệu tương ứng có sẵn trực tuyến.
command:
- windows
```bash
go build -tags nosqlite -ldflags="-w -s" -buildmode=c-shared -o ui/flutter/windows/libgopeed.dll github.com/GopeedLab/gopeed/bind/desktop
cd ui/flutter
flutter build windows
```
- macos
```bash
go build -tags nosqlite -ldflags="-w -s" -buildmode=c-shared -o ui/flutter/macos/Frameworks/libgopeed.dylib github.com/GopeedLab/gopeed/bind/desktop
cd ui/flutter
flutter build macos
```
- linux
```bash
go build -tags nosqlite -ldflags="-w -s" -buildmode=c-shared -o ui/flutter/linux/bundle/lib/libgopeed.so github.com/GopeedLab/gopeed/bind/desktop
cd ui/flutter
flutter build linux
```
#### Mobile
Giống như trước đây, bạn cũng cần chuẩn bị môi trường `cgo` và sau đó cài đặt `gomobile`:
```bash
go install golang.org/x/mobile/cmd/gomobile@latest
go get golang.org/x/mobile/bind
gomobile init
```
command:
- android
```bash
gomobile bind -tags nosqlite -ldflags="-w -s" -o ui/flutter/android/app/libs/libgopeed.aar -target=android -androidapi 19 -javapkg="com.gopeed" github.com/GopeedLab/gopeed/bind/mobile
cd ui/flutter
flutter build apk
```
- ios
```bash
gomobile bind -tags nosqlite -ldflags="-w -s" -o ui/flutter/ios/Frameworks/Libgopeed.xcframework -target=ios github.com/GopeedLab/gopeed/bind/mobile
cd ui/flutter
flutter build ios --no-codesign
```
#### Web
command:
```bash
cd ui/flutter
flutter build web
cd ../../
rm -rf cmd/web/dist
cp -r ui/flutter/build/web cmd/web/dist
go build -tags nosqlite,web -ldflags="-s -w" -o bin/ github.com/GopeedLab/gopeed/cmd/web
```
## Tín dụng
### Người đóng góp
<a href="https://github.com/GopeedLab/gopeed/graphs/contributors">
<img src="https://contrib.rocks/image?repo=GopeedLab/gopeed" />
</a>
### JetBrains
[![goland](_docs/img/goland.svg)](https://www.jetbrains.com/?from=gopeed)
## Giấy phép
[GPLv3](LICENSE)
+71 -35
View File
@@ -9,29 +9,80 @@
[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/R6R6IJGN6) [![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/R6R6IJGN6)
[English](/README.md) | [中文](/README_zh-CN.md) | [日本語](/README_ja-JP.md) | [正體中文](/README_zh-TW.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>
<thead>
<tr>
<th>运行平台</th>
<th>安装包类型</th>
<th>下载链接</th>
</tr>
</thead>
<tbody>
<tr>
<td rowspan=2>Windows</td>
<td><code>EXE Installer</code></td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64.zip">前往</a></td>
</tr>
<tr>
<td><code>Portable ZIP</code></td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64-portable.zip">前往</a></td>
</tr>
<tr>
<td>MacOS</td>
<td><code>DMG Installer</code></td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-macos.dmg">前往</a></td>
</tr>
<tr>
<td rowspan=4>Linux</td>
<td><code>Flathub</code></td>
<td><a href="https://flathub.org/apps/com.gopeed.Gopeed">前往</a></td>
</tr>
<tr>
<td><code>SNAP</code></td>
<td><a href="https://snapcraft.io/gopeed">前往</a></td>
</tr>
<tr>
<td><code>DEB</code></td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-amd64.deb">前往</a></td>
</tr>
<tr>
<td><code>AppImage</code></td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-x86_64.AppImage">前往</a></td>
</tr>
<tr>
<td>Android</td>
<td><code>APK</code></td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android.apk">前往</a></td>
</tr>
<tr>
<td>iOS</td>
<td><code>IPA</code></td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-ios.ipa">前往</a></td>
</tr>
<tr>
<td>Web</td>
<td></td>
<td><a href="https://github.com/GopeedLab/gopeed/releases/latest">前往</a></td>
</tr>
<tr>
<td>Docker</td>
<td></td>
<td><a href="https://hub.docker.com/r/liwei2633/gopeed">前往</a></td>
</tr>
</tbody>
</table>
- [x] windows 更多关于安装的内容请参考[安装文档](https://docs.gopeed.com/zh/install.html)
- [x] macos
- [x] linux
- [x] android
- [x] ios
- [x] web
- [x] docker
[前往下载](https://github.com/GopeedLab/gopeed/releases/latest)
> 注:macos 版本运行如果提示损坏,请在终端执行 `xattr -d com.apple.quarantine /Applications/Gopeed.app` 命令
### 命令行工具 ### 命令行工具
@@ -41,23 +92,9 @@ Gopeed(全称 Go Speed),直译过来中文名叫做`够快下载器`(不
go install github.com/GopeedLab/gopeed/cmd/gopeed@latest go install github.com/GopeedLab/gopeed/cmd/gopeed@latest
``` ```
### Docker ## 浏览器扩展
#### 直接运行 Gopeed 还提供了浏览器扩展用于接管浏览器下载,支持 Chrome、Edge、Firefox 等浏览器,具体请参考:[https://github.com/GopeedLab/browser-extension](https://github.com/GopeedLab/browser-extension)
```bash
docker run -d -p 9999:9999 -v /path/to/download:/root/Downloads liwei2633/gopeed
```
#### 使用 Docker Compose
```bash
docker-compose up -d
```
#### 访问服务
当 docker 容器运行时,可以通过 `http://localhost:9999` 访问 web 页面。
## 赞助 ## 赞助
@@ -75,8 +112,8 @@ docker-compose up -d
### 环境要求 ### 环境要求
1. Golang 1.19+ 1. Golang 1.21+
2. Flutter 3.10+ 2. Flutter 3.16+
### 克隆项目 ### 克隆项目
@@ -135,7 +172,7 @@ gomobile init
- android - android
```bash ```bash
gomobile bind -tags nosqlite -ldflags="-w -s" -o ui/flutter/android/app/libs/libgopeed.aar -target=android -androidapi 19 -javapkg=com.gopeed github.com/GopeedLab/gopeed/bind/mobile gomobile bind -tags nosqlite -ldflags="-w -s" -o ui/flutter/android/app/libs/libgopeed.aar -target=android -androidapi 19 -javapkg="com.gopeed" github.com/GopeedLab/gopeed/bind/mobile
cd ui/flutter cd ui/flutter
flutter build apk flutter build apk
``` ```
@@ -159,7 +196,6 @@ 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
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
go run cmd/web/main.go
``` ```
## 感谢 ## 感谢
+68 -36
View File
@@ -9,7 +9,7 @@
[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/R6R6IJGN6) [![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/R6R6IJGN6)
[English](/README.md) | [中文](/README_zh-CN.md) | [日本語](/README_ja-JP.md) | [正體中文](/README_zh-TW.md) [English](/README.md) | [中文](/README_zh-CN.md) | [日本語](/README_ja-JP.md) | [正體中文](/README_zh-TW.md) | [Tiếng Việt](/README_vi-VN.md)
## 簡介 ## 簡介
@@ -17,21 +17,72 @@ Gopeed(全稱 Go Speed),是一款使用`Golang`+`Flutter`編寫的高速
前往 ✈ [主頁](https://gopeed.com/zh-CN) | 📖 [文檔](https://docs.gopeed.com/zh/) 前往 ✈ [主頁](https://gopeed.com/zh-CN) | 📖 [文檔](https://docs.gopeed.com/zh/)
## 安裝 ## 下載
**已支援的平台** <table>
<thead>
<tr>
<th>運行平台</th>
<th>安裝包類型</th>
<th>下載鏈接</th>
</tr>
</thead>
<tbody>
<tr>
<td rowspan=2>Windows</td>
<td><code>EXE Installer</code></td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64.zip">前往</a></td>
</tr>
<tr>
<td><code>Portable ZIP</code></td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64-portable.zip">前往</a></td>
</tr>
<tr>
<td>MacOS</td>
<td><code>DMG Installer</code></td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-macos.dmg">前往</a></td>
</tr>
<tr>
<td rowspan=4>Linux</td>
<td><code>Flathub</code></td>
<td><a href="https://flathub.org/apps/com.gopeed.Gopeed">前往</a></td>
</tr>
<tr>
<td><code>SNAP</code></td>
<td><a href="https://snapcraft.io/gopeed">前往</a></td>
</tr>
<tr>
<td><code>DEB</code></td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-amd64.deb">前往</a></td>
</tr>
<tr>
<td><code>AppImage</code></td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-x86_64.AppImage">前往</a></td>
</tr>
<tr>
<td>Android</td>
<td><code>APK</code></td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android.apk">前往</a></td>
</tr>
<tr>
<td>iOS</td>
<td><code>IPA</code></td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-ios.ipa">前往</a></td>
</tr>
<tr>
<td>Web</td>
<td></td>
<td><a href="https://github.com/GopeedLab/gopeed/releases/latest">前往</a></td>
</tr>
<tr>
<td>Docker</td>
<td></td>
<td><a href="https://hub.docker.com/r/liwei2633/gopeed">前往</a></td>
</tr>
</tbody>
</table>
- [x] Windows 更多關於安裝的內容請參考[安裝文檔](https://docs.gopeed.com/zh/install.html)
- [x] MacOS
- [x] Linux
- [x] Android
- [x] iOS
- [x] Web
- [x] Docker
[下載](https://github.com/GopeedLab/gopeed/releases/latest)
> 註:MacOS 版運行時若提示損壞,請在終端中執行 `xattr -d com.apple.quarantine /Applications/Gopeed.app`
### 使用CLI安裝 ### 使用CLI安裝
@@ -41,24 +92,6 @@ Gopeed(全稱 Go Speed),是一款使用`Golang`+`Flutter`編寫的高速
go install github.com/GopeedLab/gopeed/cmd/gopeed@latest go install github.com/GopeedLab/gopeed/cmd/gopeed@latest
``` ```
### Docker
#### 直接執行
```bash
docker run -d -p 9999:9999 -v /path/to/download:/root/Downloads liwei2633/gopeed
```
#### 使用 Docker Compose
```bash
docker-compose up -d
```
#### 訪問服務
當 docker 容器運作時,可以通過 `http://localhost:9999` 訪問 web 頁面。
## 贊助 ## 贊助
如果你認為該項目對你有所幫助,請考慮[贊助](https://docs.gopeed.com/zh/donate)以支持該項目的持續發展,謝謝! 如果你認為該項目對你有所幫助,請考慮[贊助](https://docs.gopeed.com/zh/donate)以支持該項目的持續發展,謝謝!
@@ -75,8 +108,8 @@ docker-compose up -d
### 開發環境 ### 開發環境
1. Golang 1.19+ 1. Golang 1.21+
2. Flutter 3.10+ 2. Flutter 3.16+
### 克隆項目 ### 克隆項目
@@ -135,7 +168,7 @@ gomobile init
- android - android
```bash ```bash
gomobile bind -tags nosqlite -ldflags="-w -s" -o ui/flutter/android/app/libs/libgopeed.aar -target=android -androidapi 19 -javapkg=com.gopeed github.com/GopeedLab/gopeed/bind/mobile gomobile bind -tags nosqlite -ldflags="-w -s" -o ui/flutter/android/app/libs/libgopeed.aar -target=android -androidapi 19 -javapkg="com.gopeed" github.com/GopeedLab/gopeed/bind/mobile
cd ui/flutter cd ui/flutter
flutter build apk flutter build apk
``` ```
@@ -159,7 +192,6 @@ 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
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
go run cmd/web/main.go
``` ```
## 感謝 ## 感謝
Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 309 KiB

+3
View File
@@ -65,6 +65,9 @@ var (
func printProgress(task *download.Task, title string) { func printProgress(task *download.Task, title string) {
var rate float64 var rate float64
if task.Meta.Res == nil {
task = emptyTask
}
if task.Meta.Res.Size <= 0 { if task.Meta.Res.Size <= 0 {
rate = 0 rate = 0
} else { } else {
+12 -5
View File
@@ -3,6 +3,7 @@ package cmd
import ( import (
_ "embed" _ "embed"
"fmt" "fmt"
"github.com/GopeedLab/gopeed/pkg/base"
"github.com/GopeedLab/gopeed/pkg/rest" "github.com/GopeedLab/gopeed/pkg/rest"
"github.com/GopeedLab/gopeed/pkg/rest/model" "github.com/GopeedLab/gopeed/pkg/rest/model"
"net/http" "net/http"
@@ -24,14 +25,20 @@ func Start(cfg *model.StartConfig) {
panic(err) panic(err)
} }
if downloadCfg.FirstLoad { if downloadCfg.FirstLoad {
// Set default download dir to user download dir // Set default download dir, in docker, it will be ${exe}/Downloads, else it will be ${user}/Downloads
userDir, err := os.UserHomeDir() var downloadDir string
if err == nil { if base.InDocker == "true" {
downloadDir := filepath.Join(userDir, "Downloads") downloadDir = filepath.Join(filepath.Dir(cfg.StorageDir), "Downloads")
} else {
userDir, err := os.UserHomeDir()
if err == nil {
downloadDir = filepath.Join(userDir, "Downloads")
}
}
if downloadDir != "" {
downloadCfg.DownloadDir = downloadDir downloadCfg.DownloadDir = downloadDir
rest.Downloader.PutConfig(downloadCfg) rest.Downloader.PutConfig(downloadCfg)
} }
} }
fmt.Printf("Server start success on http://%s\n", listener.Addr().String()) fmt.Printf("Server start success on http://%s\n", listener.Addr().String())
if err := srv.Serve(listener); err != nil && err != http.ErrServerClosed { if err := srv.Serve(listener); err != nil && err != http.ErrServerClosed {
+10 -5
View File
@@ -9,11 +9,12 @@ import (
) )
type args struct { type args struct {
Address *string `json:"address"` Address *string `json:"address"`
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"`
configPath *string configPath *string
} }
@@ -25,6 +26,7 @@ func parse() *args {
cliArgs.Username = flag.String("u", "gopeed", "HTTP Basic Auth Username") cliArgs.Username = flag.String("u", "gopeed", "HTTP Basic Auth Username")
cliArgs.Password = flag.String("p", "", "HTTP Basic Auth Pwd") cliArgs.Password = flag.String("p", "", "HTTP Basic Auth Pwd")
cliArgs.ApiToken = flag.String("T", "", "API token, that can only be used when basic authentication is enabled.") cliArgs.ApiToken = flag.String("T", "", "API token, that can only be used when basic authentication is enabled.")
cliArgs.StorageDir = flag.String("d", "", "Storage directory")
cliArgs.configPath = flag.String("c", "./config.json", "Config file path") cliArgs.configPath = flag.String("c", "./config.json", "Config file path")
flag.Parse() flag.Parse()
@@ -45,6 +47,9 @@ func parse() *args {
if cfgArgs.ApiToken == nil { if cfgArgs.ApiToken == nil {
cfgArgs.ApiToken = cliArgs.ApiToken cfgArgs.ApiToken = cliArgs.ApiToken
} }
if cfgArgs.StorageDir == nil {
cfgArgs.StorageDir = cliArgs.StorageDir
}
return cfgArgs return cfgArgs
} }
+14
View File
@@ -9,6 +9,8 @@ import (
"github.com/GopeedLab/gopeed/cmd" "github.com/GopeedLab/gopeed/cmd"
"github.com/GopeedLab/gopeed/pkg/rest/model" "github.com/GopeedLab/gopeed/pkg/rest/model"
"io/fs" "io/fs"
"os"
"path/filepath"
) )
//go:embed dist/* //go:embed dist/*
@@ -29,10 +31,22 @@ func main() {
} }
} }
var dir string
if args.StorageDir != nil && *args.StorageDir != "" {
dir = *args.StorageDir
} else {
exe, err := os.Executable()
if err != nil {
panic(err)
}
dir = filepath.Dir(exe)
}
cfg := &model.StartConfig{ cfg := &model.StartConfig{
Network: "tcp", Network: "tcp",
Address: fmt.Sprintf("%s:%d", *args.Address, *args.Port), Address: fmt.Sprintf("%s:%d", *args.Address, *args.Port),
Storage: model.StorageBolt, Storage: model.StorageBolt,
StorageDir: filepath.Join(dir, "storage"),
ApiToken: *args.ApiToken, ApiToken: *args.ApiToken,
ProductionMode: true, ProductionMode: true,
WebEnable: true, WebEnable: true,
+3 -2
View File
@@ -4,8 +4,9 @@ services:
gopeed: gopeed:
container_name: gopeed container_name: gopeed
ports: ports:
- 9999:9999 # HTTP port (host:container) - "9999:9999" # HTTP port (host:container)
image: liwei2633/gopeed image: liwei2633/gopeed
volumes: volumes:
- ~/Downloads:/root/Downloads # replace with your prefer path - ~/gopeed/Downloads:/root/Downloads # mount download path
#- ~/gopeed/storage:/app/storage # if you need to mount storage path, uncomment this line
restart: unless-stopped restart: unless-stopped
+7
View File
@@ -0,0 +1,7 @@
#!/bin/bash
chown -R ${PUID}:${PGID} /app
umask ${UMASK}
exec su-exec ${PUID}:${PGID} ./gopeed "$@"
+63 -53
View File
@@ -1,10 +1,10 @@
module github.com/GopeedLab/gopeed module github.com/GopeedLab/gopeed
go 1.19 go 1.21
require ( require (
github.com/anacrolix/missinggo/v2 v2.7.2-0.20230527121029-a582b4f397b9 github.com/anacrolix/missinggo/v2 v2.7.3
github.com/anacrolix/torrent v1.52.5 github.com/anacrolix/torrent v1.53.3
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5
github.com/dop251/goja v0.0.0-20231027120936-b396bb4c349d github.com/dop251/goja v0.0.0-20231027120936-b396bb4c349d
github.com/dop251/goja_nodejs v0.0.0-20231122114759-e84d9a924c5c github.com/dop251/goja_nodejs v0.0.0-20231122114759-e84d9a924c5c
@@ -12,97 +12,107 @@ require (
github.com/gorilla/handlers v1.5.1 github.com/gorilla/handlers v1.5.1
github.com/gorilla/mux v1.8.0 github.com/gorilla/mux v1.8.0
github.com/matoous/go-nanoid/v2 v2.0.0 github.com/matoous/go-nanoid/v2 v2.0.0
github.com/pkg/errors v0.9.1
github.com/rs/zerolog v1.31.0 github.com/rs/zerolog v1.31.0
github.com/virtuald/go-paniclog v0.0.0-20190812204905-43a7fa316459 github.com/virtuald/go-paniclog v0.0.0-20190812204905-43a7fa316459
go.etcd.io/bbolt v1.3.8 go.etcd.io/bbolt v1.3.8
golang.org/x/exp v0.0.0-20220613132600-b0d781184e0d golang.org/x/exp v0.0.0-20240119083558-1b970713d09a
golang.org/x/sync v0.3.0 golang.org/x/sync v0.6.0
) )
require ( require (
crawshaw.io/sqlite v0.3.3-0.20220618202545-d1964889ea3c // indirect
dario.cat/mergo v1.0.0 // indirect dario.cat/mergo v1.0.0 // indirect
github.com/Microsoft/go-winio v0.6.1 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect
github.com/ProtonMail/go-crypto v0.0.0-20230717121422-5aa5874ade95 // indirect github.com/ProtonMail/go-crypto v0.0.0-20230717121422-5aa5874ade95 // indirect
github.com/RoaringBitmap/roaring v1.2.3 // indirect github.com/RoaringBitmap/roaring v1.7.0 // indirect
github.com/acomagu/bufpipe v1.0.4 // indirect github.com/acomagu/bufpipe v1.0.4 // indirect
github.com/ajwerner/btree v0.0.0-20211221152037-f427b3e689c0 // indirect github.com/ajwerner/btree v0.0.0-20211221152037-f427b3e689c0 // indirect
github.com/alecthomas/atomic v0.1.0-alpha2 // indirect github.com/alecthomas/atomic v0.1.0-alpha2 // indirect
github.com/anacrolix/chansync v0.3.0 // indirect github.com/anacrolix/chansync v0.4.0 // indirect
github.com/anacrolix/dht/v2 v2.19.2-0.20221121215055-066ad8494444 // indirect github.com/anacrolix/dht/v2 v2.21.0 // indirect
github.com/anacrolix/envpprof v1.2.1 // indirect github.com/anacrolix/envpprof v1.3.0 // indirect
github.com/anacrolix/generics v0.0.0-20230428105757-683593396d68 // indirect github.com/anacrolix/generics v0.0.0-20230911070922-5dd7545c6b13 // indirect
github.com/anacrolix/go-libutp v1.3.1 // indirect github.com/anacrolix/go-libutp v1.3.1 // indirect
github.com/anacrolix/log v0.14.0 // indirect github.com/anacrolix/log v0.14.6-0.20231202035202-ed7a02cad0b4 // indirect
github.com/anacrolix/missinggo v1.3.0 // indirect github.com/anacrolix/missinggo v1.3.0 // indirect
github.com/anacrolix/missinggo/perf v1.0.0 // indirect github.com/anacrolix/missinggo/perf v1.0.0 // indirect
github.com/anacrolix/mmsg v1.0.0 // indirect github.com/anacrolix/mmsg v1.0.0 // indirect
github.com/anacrolix/multiless v0.3.0 // indirect github.com/anacrolix/multiless v0.3.1-0.20221221005021-2d12701f83f7 // indirect
github.com/anacrolix/stm v0.4.0 // indirect github.com/anacrolix/stm v0.5.0 // indirect
github.com/anacrolix/sync v0.4.0 // indirect github.com/anacrolix/sync v0.5.1 // indirect
github.com/anacrolix/upnp v0.1.3-0.20220123035249-922794e51c96 // indirect github.com/anacrolix/upnp v0.1.3 // indirect
github.com/anacrolix/utp v0.1.0 // indirect github.com/anacrolix/utp v0.2.0 // indirect
github.com/bahlo/generic-list-go v0.2.0 // indirect github.com/bahlo/generic-list-go v0.2.0 // indirect
github.com/benbjohnson/immutable v0.3.0 // indirect github.com/benbjohnson/immutable v0.4.3 // indirect
github.com/bits-and-blooms/bitset v1.2.2 // indirect github.com/bits-and-blooms/bitset v1.13.0 // indirect
github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 // indirect github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 // indirect
github.com/cloudflare/circl v1.3.3 // indirect github.com/cloudflare/circl v1.3.3 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dlclark/regexp2 v1.10.0 // indirect github.com/dlclark/regexp2 v1.10.0 // indirect
github.com/dustin/go-humanize v1.0.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect
github.com/edsrzf/mmap-go v1.1.0 // indirect github.com/edsrzf/mmap-go v1.1.0 // indirect
github.com/emirpasic/gods v1.18.1 // indirect github.com/emirpasic/gods v1.18.1 // indirect
github.com/felixge/httpsnoop v1.0.1 // indirect github.com/felixge/httpsnoop v1.0.1 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-git/go-billy/v5 v5.4.1 // indirect github.com/go-git/go-billy/v5 v5.4.1 // indirect
github.com/go-logr/logr v1.2.3 // indirect github.com/go-llsqlite/adapter v0.1.0 // indirect
github.com/go-llsqlite/crawshaw v0.5.0 // indirect
github.com/go-logr/logr v1.4.1 // indirect
github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/google/btree v1.1.2 // indirect github.com/google/btree v1.1.2 // indirect
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a // indirect github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a // indirect
github.com/google/uuid v1.3.0 // indirect github.com/google/uuid v1.5.0 // indirect
github.com/gorilla/websocket v1.5.0 // indirect github.com/gorilla/websocket v1.5.1 // indirect
github.com/huandu/xstrings v1.3.2 // indirect github.com/huandu/xstrings v1.4.0 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/lispad/go-generics-tools v1.1.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect github.com/mattn/go-ieproxy v0.0.11 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mschoch/smat v0.2.0 // indirect github.com/mschoch/smat v0.2.0 // indirect
github.com/pion/datachannel v1.5.2 // indirect github.com/pion/datachannel v1.5.5 // indirect
github.com/pion/dtls/v2 v2.2.4 // indirect github.com/pion/dtls/v2 v2.2.9 // indirect
github.com/pion/ice/v2 v2.2.6 // indirect github.com/pion/ice/v2 v2.3.12 // indirect
github.com/pion/interceptor v0.1.11 // indirect github.com/pion/interceptor v0.1.25 // indirect
github.com/pion/logging v0.2.2 // indirect github.com/pion/logging v0.2.2 // indirect
github.com/pion/mdns v0.0.5 // indirect github.com/pion/mdns v0.0.9 // indirect
github.com/pion/randutil v0.1.0 // indirect github.com/pion/randutil v0.1.0 // indirect
github.com/pion/rtcp v1.2.9 // indirect github.com/pion/rtcp v1.2.13 // indirect
github.com/pion/rtp v1.7.13 // indirect github.com/pion/rtp v1.8.3 // indirect
github.com/pion/sctp v1.8.2 // indirect github.com/pion/sctp v1.8.9 // indirect
github.com/pion/sdp/v3 v3.0.5 // indirect github.com/pion/sdp/v3 v3.0.6 // indirect
github.com/pion/srtp/v2 v2.0.9 // indirect github.com/pion/srtp/v2 v2.0.18 // indirect
github.com/pion/stun v0.3.5 // indirect github.com/pion/stun v0.6.1 // indirect
github.com/pion/transport v0.13.1 // indirect github.com/pion/transport/v2 v2.2.4 // indirect
github.com/pion/transport/v2 v2.0.0 // indirect github.com/pion/turn/v2 v2.1.4 // indirect
github.com/pion/turn/v2 v2.0.8 // indirect github.com/pion/webrtc/v3 v3.2.24 // indirect
github.com/pion/udp v0.1.4 // indirect
github.com/pion/webrtc/v3 v3.1.42 // indirect
github.com/pjbgf/sha1cd v0.3.0 // indirect github.com/pjbgf/sha1cd v0.3.0 // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rs/dnscache v0.0.0-20211102005908-e0241e321417 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rs/dnscache v0.0.0-20230804202142-fc85eb664529 // indirect
github.com/sergi/go-diff v1.1.0 // indirect github.com/sergi/go-diff v1.1.0 // indirect
github.com/skeema/knownhosts v1.2.0 // indirect github.com/skeema/knownhosts v1.2.0 // indirect
github.com/tidwall/btree v1.6.0 // indirect github.com/stretchr/testify v1.8.4 // indirect
github.com/tidwall/btree v1.7.0 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect
go.opentelemetry.io/otel v1.8.0 // indirect github.com/xiaoqidun/setft v0.0.0-20220310121541-be86327699ad // indirect
go.opentelemetry.io/otel/trace v1.8.0 // indirect go.opentelemetry.io/otel v1.22.0 // indirect
golang.org/x/crypto v0.14.0 // indirect go.opentelemetry.io/otel/metric v1.22.0 // indirect
golang.org/x/mod v0.8.0 // indirect go.opentelemetry.io/otel/trace v1.22.0 // indirect
golang.org/x/net v0.17.0 // indirect golang.org/x/crypto v0.18.0 // indirect
golang.org/x/sys v0.14.0 // indirect golang.org/x/mod v0.14.0 // indirect
golang.org/x/net v0.20.0 // indirect
golang.org/x/sys v0.16.0 // indirect
golang.org/x/text v0.14.0 // indirect golang.org/x/text v0.14.0 // indirect
golang.org/x/time v0.0.0-20220609170525-579cf78fd858 // indirect golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.6.0 // indirect golang.org/x/tools v0.17.0 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
modernc.org/libc v1.40.6 // indirect
modernc.org/mathutil v1.6.0 // indirect
modernc.org/memory v1.7.2 // indirect
modernc.org/sqlite v1.28.0 // indirect
zombiezen.com/go/sqlite v1.1.0 // indirect
) )
+181 -130
View File
@@ -1,10 +1,7 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
crawshaw.io/iox v0.0.0-20181124134642-c51c3df30797 h1:yDf7ARQc637HoxDho7xjqdvO5ZA2Yb+xzv/fOnnvZzw=
crawshaw.io/iox v0.0.0-20181124134642-c51c3df30797/go.mod h1:sXBiorCo8c46JlQV3oXPKINnZ8mcqnye1EkVkqsectk= crawshaw.io/iox v0.0.0-20181124134642-c51c3df30797/go.mod h1:sXBiorCo8c46JlQV3oXPKINnZ8mcqnye1EkVkqsectk=
crawshaw.io/sqlite v0.3.2/go.mod h1:igAO5JulrQ1DbdZdtVq48mnZUBAPOeFzer7VhDWNtW4= crawshaw.io/sqlite v0.3.2/go.mod h1:igAO5JulrQ1DbdZdtVq48mnZUBAPOeFzer7VhDWNtW4=
crawshaw.io/sqlite v0.3.3-0.20220618202545-d1964889ea3c h1:wvzox0eLO6CKQAMcOqz7oH3UFqMpMmK7kwmwV+22HIs=
crawshaw.io/sqlite v0.3.3-0.20220618202545-d1964889ea3c/go.mod h1:igAO5JulrQ1DbdZdtVq48mnZUBAPOeFzer7VhDWNtW4=
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
filippo.io/edwards25519 v1.0.0-rc.1 h1:m0VOOB23frXZvAOK44usCgLWvtsxIoMCTBGJZlpmGfU= filippo.io/edwards25519 v1.0.0-rc.1 h1:m0VOOB23frXZvAOK44usCgLWvtsxIoMCTBGJZlpmGfU=
@@ -17,8 +14,8 @@ github.com/ProtonMail/go-crypto v0.0.0-20230717121422-5aa5874ade95/go.mod h1:EjA
github.com/RoaringBitmap/roaring v0.4.7/go.mod h1:8khRDP4HmeXns4xIj9oGrKSz7XTQiJx2zgh7AcNke4w= github.com/RoaringBitmap/roaring v0.4.7/go.mod h1:8khRDP4HmeXns4xIj9oGrKSz7XTQiJx2zgh7AcNke4w=
github.com/RoaringBitmap/roaring v0.4.17/go.mod h1:D3qVegWTmfCaX4Bl5CrBE9hfrSrrXIr8KVNvRsDi1NI= github.com/RoaringBitmap/roaring v0.4.17/go.mod h1:D3qVegWTmfCaX4Bl5CrBE9hfrSrrXIr8KVNvRsDi1NI=
github.com/RoaringBitmap/roaring v0.4.23/go.mod h1:D0gp8kJQgE1A4LQ5wFLggQEyvDi06Mq5mKs52e1TwOo= github.com/RoaringBitmap/roaring v0.4.23/go.mod h1:D0gp8kJQgE1A4LQ5wFLggQEyvDi06Mq5mKs52e1TwOo=
github.com/RoaringBitmap/roaring v1.2.3 h1:yqreLINqIrX22ErkKI0vY47/ivtJr6n+kMhVOVmhWBY= github.com/RoaringBitmap/roaring v1.7.0 h1:OZF303tJCER1Tj3x+aArx/S5X7hrT186ri6JjrGvG68=
github.com/RoaringBitmap/roaring v1.2.3/go.mod h1:plvDsJQpxOC5bw8LRteu/MLWHsHez/3y6cubLI4/1yE= github.com/RoaringBitmap/roaring v1.7.0/go.mod h1:6AXUsoIEzDTFFQCe1RbGA6uFONMhvejWj5rqITANK90=
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/acomagu/bufpipe v1.0.4 h1:e3H4WUzM3npvo5uv95QuJM3cQspFNtFBzvJ2oNjKIDQ= github.com/acomagu/bufpipe v1.0.4 h1:e3H4WUzM3npvo5uv95QuJM3cQspFNtFBzvJ2oNjKIDQ=
@@ -33,25 +30,26 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/anacrolix/chansync v0.3.0 h1:lRu9tbeuw3wl+PhMu/r+JJCRu5ArFXIluOgdF0ao6/U= github.com/anacrolix/chansync v0.4.0 h1:Md0HM7zYCAO4KwNwgcIRgxNsMxiRuk7D1Ha0Uo+2y60=
github.com/anacrolix/chansync v0.3.0/go.mod h1:DZsatdsdXxD0WiwcGl0nJVwyjCKMDv+knl1q2iBjA2k= github.com/anacrolix/chansync v0.4.0/go.mod h1:DZsatdsdXxD0WiwcGl0nJVwyjCKMDv+knl1q2iBjA2k=
github.com/anacrolix/dht/v2 v2.19.2-0.20221121215055-066ad8494444 h1:8V0K09lrGoeT2KRJNOtspA7q+OMxGwQqK/Ug0IiaaRE= github.com/anacrolix/dht/v2 v2.21.0 h1:8nzI+faaynY9jOKmVgdmBZVrTo8B7ZE/LKEgN3Vl/Bs=
github.com/anacrolix/dht/v2 v2.19.2-0.20221121215055-066ad8494444/go.mod h1:MctKM1HS5YYDb3F30NGJxLE+QPuqWoT5ReW/4jt8xew= github.com/anacrolix/dht/v2 v2.21.0/go.mod h1:SDGC+sEs1pnO2sJGYuhvIis7T8749dDHNfcjtdH4e3g=
github.com/anacrolix/envpprof v0.0.0-20180404065416-323002cec2fa/go.mod h1:KgHhUaQMc8cC0+cEflSgCFNFbKwi5h54gqtVn8yhP7c= github.com/anacrolix/envpprof v0.0.0-20180404065416-323002cec2fa/go.mod h1:KgHhUaQMc8cC0+cEflSgCFNFbKwi5h54gqtVn8yhP7c=
github.com/anacrolix/envpprof v1.0.0/go.mod h1:KgHhUaQMc8cC0+cEflSgCFNFbKwi5h54gqtVn8yhP7c= github.com/anacrolix/envpprof v1.0.0/go.mod h1:KgHhUaQMc8cC0+cEflSgCFNFbKwi5h54gqtVn8yhP7c=
github.com/anacrolix/envpprof v1.1.0/go.mod h1:My7T5oSqVfEn4MD4Meczkw/f5lSIndGAKu/0SM/rkf4= github.com/anacrolix/envpprof v1.1.0/go.mod h1:My7T5oSqVfEn4MD4Meczkw/f5lSIndGAKu/0SM/rkf4=
github.com/anacrolix/envpprof v1.2.1 h1:25TJe6t/i0AfzzldiGFKCpD+s+dk8lONBcacJZB2rdE= github.com/anacrolix/envpprof v1.3.0 h1:WJt9bpuT7A/CDCxPOv/eeZqHWlle/Y0keJUvc6tcJDk=
github.com/anacrolix/envpprof v1.2.1/go.mod h1:My7T5oSqVfEn4MD4Meczkw/f5lSIndGAKu/0SM/rkf4= github.com/anacrolix/envpprof v1.3.0/go.mod h1:7QIG4CaX1uexQ3tqd5+BRa/9e2D02Wcertl6Yh0jCB0=
github.com/anacrolix/generics v0.0.0-20230428105757-683593396d68 h1:fyXlBfnlFzZSFckJ8QLb2lfmWfY++4RiUnae7ZMuv0A= github.com/anacrolix/generics v0.0.0-20230113004304-d6428d516633/go.mod h1:ff2rHB/joTV03aMSSn/AZNnaIpUw0h3njetGsaXcMy8=
github.com/anacrolix/generics v0.0.0-20230428105757-683593396d68/go.mod h1:ff2rHB/joTV03aMSSn/AZNnaIpUw0h3njetGsaXcMy8= github.com/anacrolix/generics v0.0.0-20230911070922-5dd7545c6b13 h1:qwOprPTDMM3BASJRf84mmZnTXRsPGGJ8xoHKQS7m3so=
github.com/anacrolix/generics v0.0.0-20230911070922-5dd7545c6b13/go.mod h1:ff2rHB/joTV03aMSSn/AZNnaIpUw0h3njetGsaXcMy8=
github.com/anacrolix/go-libutp v1.3.1 h1:idJzreNLl+hNjGC3ZnUOjujEaryeOGgkwHLqSGoige0= github.com/anacrolix/go-libutp v1.3.1 h1:idJzreNLl+hNjGC3ZnUOjujEaryeOGgkwHLqSGoige0=
github.com/anacrolix/go-libutp v1.3.1/go.mod h1:heF41EC8kN0qCLMokLBVkB8NXiLwx3t8R8810MTNI5o= github.com/anacrolix/go-libutp v1.3.1/go.mod h1:heF41EC8kN0qCLMokLBVkB8NXiLwx3t8R8810MTNI5o=
github.com/anacrolix/log v0.3.0/go.mod h1:lWvLTqzAnCWPJA08T2HCstZi0L1y2Wyvm3FJgwU9jwU= github.com/anacrolix/log v0.3.0/go.mod h1:lWvLTqzAnCWPJA08T2HCstZi0L1y2Wyvm3FJgwU9jwU=
github.com/anacrolix/log v0.6.0/go.mod h1:lWvLTqzAnCWPJA08T2HCstZi0L1y2Wyvm3FJgwU9jwU= github.com/anacrolix/log v0.6.0/go.mod h1:lWvLTqzAnCWPJA08T2HCstZi0L1y2Wyvm3FJgwU9jwU=
github.com/anacrolix/log v0.10.1-0.20220123034749-3920702c17f8/go.mod h1:GmnE2c0nvz8pOIPUSC9Rawgefy1sDXqposC2wgtBZE4=
github.com/anacrolix/log v0.13.1/go.mod h1:D4+CvN8SnruK6zIFS/xPoRJmtvtnxs+CSfDQ+BFxZ68= github.com/anacrolix/log v0.13.1/go.mod h1:D4+CvN8SnruK6zIFS/xPoRJmtvtnxs+CSfDQ+BFxZ68=
github.com/anacrolix/log v0.14.0 h1:mYhTSemILe/Z8tIxbGdTIWWpPspI8W/fhZHpoFbDaL0= github.com/anacrolix/log v0.14.2/go.mod h1:1OmJESOtxQGNMlUO5rcv96Vpp9mfMqXXbe2RdinFLdY=
github.com/anacrolix/log v0.14.0/go.mod h1:1OmJESOtxQGNMlUO5rcv96Vpp9mfMqXXbe2RdinFLdY= github.com/anacrolix/log v0.14.6-0.20231202035202-ed7a02cad0b4 h1:CdVK9IoqoqklXQQ4+L2aew64xsz14KdOD+rnKdTQajg=
github.com/anacrolix/log v0.14.6-0.20231202035202-ed7a02cad0b4/go.mod h1:1OmJESOtxQGNMlUO5rcv96Vpp9mfMqXXbe2RdinFLdY=
github.com/anacrolix/lsan v0.0.0-20211126052245-807000409a62 h1:P04VG6Td13FHMgS5ZBcJX23NPC/fiC4cp9bXwYujdYM= github.com/anacrolix/lsan v0.0.0-20211126052245-807000409a62 h1:P04VG6Td13FHMgS5ZBcJX23NPC/fiC4cp9bXwYujdYM=
github.com/anacrolix/lsan v0.0.0-20211126052245-807000409a62/go.mod h1:66cFKPCO7Sl4vbFnAaSq7e4OXtdMhRSBagJGWgmpJbM= github.com/anacrolix/lsan v0.0.0-20211126052245-807000409a62/go.mod h1:66cFKPCO7Sl4vbFnAaSq7e4OXtdMhRSBagJGWgmpJbM=
github.com/anacrolix/missinggo v0.0.0-20180725070939-60ef2fbf63df/go.mod h1:kwGiTUTZ0+p4vAz3VbAI5a30t2YbvemcmspjKwrAz5s= github.com/anacrolix/missinggo v0.0.0-20180725070939-60ef2fbf63df/go.mod h1:kwGiTUTZ0+p4vAz3VbAI5a30t2YbvemcmspjKwrAz5s=
@@ -64,30 +62,29 @@ github.com/anacrolix/missinggo/perf v1.0.0 h1:7ZOGYziGEBytW49+KmYGTaNfnwUqP1HBsy
github.com/anacrolix/missinggo/perf v1.0.0/go.mod h1:ljAFWkBuzkO12MQclXzZrosP5urunoLS0Cbvb4V0uMQ= github.com/anacrolix/missinggo/perf v1.0.0/go.mod h1:ljAFWkBuzkO12MQclXzZrosP5urunoLS0Cbvb4V0uMQ=
github.com/anacrolix/missinggo/v2 v2.2.0/go.mod h1:o0jgJoYOyaoYQ4E2ZMISVa9c88BbUBVQQW4QeRkNCGY= github.com/anacrolix/missinggo/v2 v2.2.0/go.mod h1:o0jgJoYOyaoYQ4E2ZMISVa9c88BbUBVQQW4QeRkNCGY=
github.com/anacrolix/missinggo/v2 v2.5.1/go.mod h1:WEjqh2rmKECd0t1VhQkLGTdIWXO6f6NLjp5GlMZ+6FA= github.com/anacrolix/missinggo/v2 v2.5.1/go.mod h1:WEjqh2rmKECd0t1VhQkLGTdIWXO6f6NLjp5GlMZ+6FA=
github.com/anacrolix/missinggo/v2 v2.5.2/go.mod h1:yNvsLrtZYRYCOI+KRH/JM8TodHjtIE/bjOGhQaLOWIE= github.com/anacrolix/missinggo/v2 v2.7.3 h1:Ee//CmZBMadeNiYB/hHo9ly2PFOEZ4Fhsbnug3rDAIE=
github.com/anacrolix/missinggo/v2 v2.7.2-0.20230527121029-a582b4f397b9 h1:W/oGeHhYwxueeiDjQfmK9G+X9M2xJgfTtow62v0TWAs= github.com/anacrolix/missinggo/v2 v2.7.3/go.mod h1:mIEtp9pgaXqt8VQ3NQxFOod/eQ1H0D1XsZzKUQfwtac=
github.com/anacrolix/missinggo/v2 v2.7.2-0.20230527121029-a582b4f397b9/go.mod h1:mIEtp9pgaXqt8VQ3NQxFOod/eQ1H0D1XsZzKUQfwtac=
github.com/anacrolix/mmsg v0.0.0-20180515031531-a4a3ba1fc8bb/go.mod h1:x2/ErsYUmT77kezS63+wzZp8E3byYB0gzirM/WMBLfw= github.com/anacrolix/mmsg v0.0.0-20180515031531-a4a3ba1fc8bb/go.mod h1:x2/ErsYUmT77kezS63+wzZp8E3byYB0gzirM/WMBLfw=
github.com/anacrolix/mmsg v1.0.0 h1:btC7YLjOn29aTUAExJiVUhQOuf/8rhm+/nWCMAnL3Hg= github.com/anacrolix/mmsg v1.0.0 h1:btC7YLjOn29aTUAExJiVUhQOuf/8rhm+/nWCMAnL3Hg=
github.com/anacrolix/mmsg v1.0.0/go.mod h1:x8kRaJY/dCrY9Al0PEcj1mb/uFHwP6GCJ9fLl4thEPc= github.com/anacrolix/mmsg v1.0.0/go.mod h1:x8kRaJY/dCrY9Al0PEcj1mb/uFHwP6GCJ9fLl4thEPc=
github.com/anacrolix/multiless v0.3.0 h1:5Bu0DZncjE4e06b9r1Ap2tUY4Au0NToBP5RpuEngSis= github.com/anacrolix/multiless v0.3.1-0.20221221005021-2d12701f83f7 h1:lOtCD+LzoD1g7bowhYJNR++uV+FyY5bTZXKwnPex9S8=
github.com/anacrolix/multiless v0.3.0/go.mod h1:TrCLEZfIDbMVfLoQt5tOoiBS/uq4y8+ojuEVVvTNPX4= github.com/anacrolix/multiless v0.3.1-0.20221221005021-2d12701f83f7/go.mod h1:zJv1JF9AqdZiHwxqPgjuOZDGWER6nyE48WBCi/OOrMM=
github.com/anacrolix/stm v0.2.0/go.mod h1:zoVQRvSiGjGoTmbM0vSLIiaKjWtNPeTvXUSdJQA4hsg= github.com/anacrolix/stm v0.2.0/go.mod h1:zoVQRvSiGjGoTmbM0vSLIiaKjWtNPeTvXUSdJQA4hsg=
github.com/anacrolix/stm v0.4.0 h1:tOGvuFwaBjeu1u9X1eIh9TX8OEedEiEQ1se1FjhFnXY= github.com/anacrolix/stm v0.5.0 h1:9df1KBpttF0TzLgDq51Z+TEabZKMythqgx89f1FQJt8=
github.com/anacrolix/stm v0.4.0/go.mod h1:GCkwqWoAsP7RfLW+jw+Z0ovrt2OO7wRzcTtFYMYY5t8= github.com/anacrolix/stm v0.5.0/go.mod h1:MOwrSy+jCm8Y7HYfMAwPj7qWVu7XoVvjOiYwJmpeB/M=
github.com/anacrolix/sync v0.0.0-20180808010631-44578de4e778/go.mod h1:s735Etp3joe/voe2sdaXLcqDdJSay1O0OPnM0ystjqk= github.com/anacrolix/sync v0.0.0-20180808010631-44578de4e778/go.mod h1:s735Etp3joe/voe2sdaXLcqDdJSay1O0OPnM0ystjqk=
github.com/anacrolix/sync v0.3.0/go.mod h1:BbecHL6jDSExojhNtgTFSBcdGerzNc64tz3DCOj/I0g= github.com/anacrolix/sync v0.3.0/go.mod h1:BbecHL6jDSExojhNtgTFSBcdGerzNc64tz3DCOj/I0g=
github.com/anacrolix/sync v0.4.0 h1:T+MdO/u87ir/ijWsTFsPYw5jVm0SMm4kVpg8t4KF38o= github.com/anacrolix/sync v0.5.1 h1:FbGju6GqSjzVoTgcXTUKkF041lnZkG5P0C3T5RL3SGc=
github.com/anacrolix/sync v0.4.0/go.mod h1:BbecHL6jDSExojhNtgTFSBcdGerzNc64tz3DCOj/I0g= github.com/anacrolix/sync v0.5.1/go.mod h1:BbecHL6jDSExojhNtgTFSBcdGerzNc64tz3DCOj/I0g=
github.com/anacrolix/tagflag v0.0.0-20180109131632-2146c8d41bf0/go.mod h1:1m2U/K6ZT+JZG0+bdMK6qauP49QT4wE5pmhJXOKKCHw= github.com/anacrolix/tagflag v0.0.0-20180109131632-2146c8d41bf0/go.mod h1:1m2U/K6ZT+JZG0+bdMK6qauP49QT4wE5pmhJXOKKCHw=
github.com/anacrolix/tagflag v1.0.0/go.mod h1:1m2U/K6ZT+JZG0+bdMK6qauP49QT4wE5pmhJXOKKCHw= github.com/anacrolix/tagflag v1.0.0/go.mod h1:1m2U/K6ZT+JZG0+bdMK6qauP49QT4wE5pmhJXOKKCHw=
github.com/anacrolix/tagflag v1.1.0/go.mod h1:Scxs9CV10NQatSmbyjqmqmeQNwGzlNe0CMUMIxqHIG8= github.com/anacrolix/tagflag v1.1.0/go.mod h1:Scxs9CV10NQatSmbyjqmqmeQNwGzlNe0CMUMIxqHIG8=
github.com/anacrolix/torrent v1.52.5 h1:jWowdx+EU6zFVfBwmnL0d3H4J6vTFEGOrHI35YdfIT8= github.com/anacrolix/torrent v1.53.3 h1:Abb3J8p/JWUjOzHaii8kixuDOGMXz+Oh8+hwc/zA2lA=
github.com/anacrolix/torrent v1.52.5/go.mod h1:CcM8oPMYye5J42cSqJrmUpqwRFgSsJQ1jCEHwygqnqQ= github.com/anacrolix/torrent v1.53.3/go.mod h1:is8GNob5qDeZ5Kq+pKPiE2xqYUi1ms7IgSB+CftZETk=
github.com/anacrolix/upnp v0.1.3-0.20220123035249-922794e51c96 h1:QAVZ3pN/J4/UziniAhJR2OZ9Ox5kOY2053tBbbqUPYA= github.com/anacrolix/upnp v0.1.3 h1:NlYEhE75adz2npEJKjbqyqnyW9qU4STookvSNXBJ5ao=
github.com/anacrolix/upnp v0.1.3-0.20220123035249-922794e51c96/go.mod h1:Wa6n8cYIdaG35x15aH3Zy6d03f7P728QfdcDeD/IEOs= github.com/anacrolix/upnp v0.1.3/go.mod h1:Qyhbqo69gwNWvEk1xNTXsS5j7hMHef9hdr984+9fIic=
github.com/anacrolix/utp v0.1.0 h1:FOpQOmIwYsnENnz7tAGohA+r6iXpRjrq8ssKSre2Cp4= github.com/anacrolix/utp v0.2.0 h1:65Cdmr6q9WSw2KsM+rtJFu7rqDzLl2bdysf4KlNPcFI=
github.com/anacrolix/utp v0.1.0/go.mod h1:MDwc+vsGEq7RMw6lr2GKOEqjWny5hO5OZXRVNaBJ2Dk= github.com/anacrolix/utp v0.2.0/go.mod h1:HGk4GYQw1O/3T1+yhqT/F6EcBd+AAwlo9dYErNy7mj8=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
@@ -95,14 +92,14 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkY
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
github.com/benbjohnson/immutable v0.2.0/go.mod h1:uc6OHo6PN2++n98KHLxW8ef4W42ylHiQSENghE1ezxI= github.com/benbjohnson/immutable v0.2.0/go.mod h1:uc6OHo6PN2++n98KHLxW8ef4W42ylHiQSENghE1ezxI=
github.com/benbjohnson/immutable v0.3.0 h1:TVRhuZx2wG9SZ0LRdqlbs9S5BZ6Y24hJEHTCgWHZEIw= github.com/benbjohnson/immutable v0.4.3 h1:GYHcksoJ9K6HyAUpGxwZURrbTkXA0Dh4otXGqbhdrjA=
github.com/benbjohnson/immutable v0.3.0/go.mod h1:uc6OHo6PN2++n98KHLxW8ef4W42ylHiQSENghE1ezxI= github.com/benbjohnson/immutable v0.4.3/go.mod h1:qJIKKSmdqz1tVzNtst1DZzvaqOU1onk1rc03IeM3Owk=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= github.com/bits-and-blooms/bitset v1.12.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
github.com/bits-and-blooms/bitset v1.2.2 h1:J5gbX05GpMdBjCvQ9MteIg2KKDExr7DrgK+Yc15FvIk= github.com/bits-and-blooms/bitset v1.13.0 h1:bAQ9OPNFYbGHV6Nez0tmNI0RiEu7/hxlYJRUA0wFAVE=
github.com/bits-and-blooms/bitset v1.2.2/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= github.com/bits-and-blooms/bitset v1.13.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
github.com/bradfitz/iter v0.0.0-20140124041915-454541ec3da2/go.mod h1:PyRFw1Lt2wKX4ZVSQ2mk+PeDa1rxyObEDlApuIsUKuo= github.com/bradfitz/iter v0.0.0-20140124041915-454541ec3da2/go.mod h1:PyRFw1Lt2wKX4ZVSQ2mk+PeDa1rxyObEDlApuIsUKuo=
github.com/bradfitz/iter v0.0.0-20190303215204-33e6a9893b0c/go.mod h1:PyRFw1Lt2wKX4ZVSQ2mk+PeDa1rxyObEDlApuIsUKuo= github.com/bradfitz/iter v0.0.0-20190303215204-33e6a9893b0c/go.mod h1:PyRFw1Lt2wKX4ZVSQ2mk+PeDa1rxyObEDlApuIsUKuo=
github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 h1:GKTyiRCL6zVf5wWaqKnf+7Qs6GbEPfd4iMOitWzXJx8= github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 h1:GKTyiRCL6zVf5wWaqKnf+7Qs6GbEPfd4iMOitWzXJx8=
@@ -133,8 +130,9 @@ github.com/dop251/goja_nodejs v0.0.0-20211022123610-8dd9abb0616d/go.mod h1:DngW8
github.com/dop251/goja_nodejs v0.0.0-20231122114759-e84d9a924c5c h1:hLoodLRD4KLWIH8eyAQCLcH8EqIrjac7fCkp/fHnvuQ= github.com/dop251/goja_nodejs v0.0.0-20231122114759-e84d9a924c5c h1:hLoodLRD4KLWIH8eyAQCLcH8EqIrjac7fCkp/fHnvuQ=
github.com/dop251/goja_nodejs v0.0.0-20231122114759-e84d9a924c5c/go.mod h1:bhGPmCgCCTSRfiMYWjpS46IDo9EUZXlsuUaPXSWGbv0= github.com/dop251/goja_nodejs v0.0.0-20231122114759-e84d9a924c5c/go.mod h1:bhGPmCgCCTSRfiMYWjpS46IDo9EUZXlsuUaPXSWGbv0=
github.com/dustin/go-humanize v0.0.0-20180421182945-02af3965c54e/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v0.0.0-20180421182945-02af3965c54e/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
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=
@@ -146,7 +144,8 @@ github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FM
github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ=
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/frankban/quicktest v1.9.0/go.mod h1:ui7WezCLWMWxVWr1GETZY3smRy0G4KWq9vcPtJmFl7Y= github.com/frankban/quicktest v1.9.0/go.mod h1:ui7WezCLWMWxVWr1GETZY3smRy0G4KWq9vcPtJmFl7Y=
github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY= github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY=
@@ -165,11 +164,15 @@ github.com/go-git/go-git/v5 v5.8.1 h1:Zo79E4p7TRk0xoRgMq0RShiTHGKcKI4+DI6BfJc/Q+
github.com/go-git/go-git/v5 v5.8.1/go.mod h1:FHFuoD6yGz5OSKEBK+aWN9Oah0q54Jxl0abmj6GnqAo= github.com/go-git/go-git/v5 v5.8.1/go.mod h1:FHFuoD6yGz5OSKEBK+aWN9Oah0q54Jxl0abmj6GnqAo=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-llsqlite/adapter v0.1.0 h1:wGSQNsu/rtYeu/lqZNZQMjwUdEF3OW66xTLvsFwJQUw=
github.com/go-llsqlite/adapter v0.1.0/go.mod h1:DADrR88ONKPPeSGjFp5iEN55Arx3fi2qXZeKCYDpbmU=
github.com/go-llsqlite/crawshaw v0.5.0 h1:Olbqkth53vkkh4WvmkYjrtfOBcxXD3rMYBYuk6FNH3E=
github.com/go-llsqlite/crawshaw v0.5.0/go.mod h1:/YJdV7uBQaYDE0fwe4z3wwJIZBJxdYzd38ICggWqtaE=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU= github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU=
@@ -207,13 +210,15 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.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 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20230207041349-798e818bf904/go.mod h1:uglQLonpP8qtYCYyzA+8c/9qtqgA3qsXGYqCPKARAFg= github.com/google/pprof v0.0.0-20230207041349-798e818bf904/go.mod h1:uglQLonpP8qtYCYyzA+8c/9qtqgA3qsXGYqCPKARAFg=
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a h1:fEBsGL/sjAuJrgah5XqmmYsTLzJp/TO9Lhy39gkverk= github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a h1:fEBsGL/sjAuJrgah5XqmmYsTLzJp/TO9Lhy39gkverk=
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.1/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.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20190309154008-847fc94819f9/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20190309154008-847fc94819f9/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
@@ -224,8 +229,8 @@ github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
@@ -233,8 +238,8 @@ github.com/huandu/xstrings v1.0.0/go.mod h1:4qWG/gcEcfX4z/mBDHJ++3ReCw9ibxbsNJbc
github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4= github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4=
github.com/huandu/xstrings v1.3.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/huandu/xstrings v1.3.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw= github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU=
github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w= github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
@@ -253,12 +258,11 @@ github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfn
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 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/lispad/go-generics-tools v1.1.0 h1:mbSgcxdFVmpoyso1X/MJHXbSbSL3dD+qhRryyxk+/XY=
github.com/lispad/go-generics-tools v1.1.0/go.mod h1:2csd1EJljo/gy5qG4khXol7ivCPptNjG5Uv2X8MgK84=
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=
@@ -266,9 +270,12 @@ github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A=
github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-ieproxy v0.0.11 h1:MQ/5BuGSgDAHZOJe6YY80IF2UVCfGkwfo6AeD7HtHYo=
github.com/mattn/go-ieproxy v0.0.11/go.mod h1:/NsJd+kxZBmjMc5hrJCKMbP57B84rvq9BiDRbtO9AS0=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -293,49 +300,54 @@ github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAl
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pion/datachannel v1.5.2 h1:piB93s8LGmbECrpO84DnkIVWasRMk3IimbcXkTQLE6E= github.com/pion/datachannel v1.5.5 h1:10ef4kwdjije+M9d7Xm9im2Y3O6A6ccQb0zcqZcJew8=
github.com/pion/datachannel v1.5.2/go.mod h1:FTGQWaHrdCwIJ1rw6xBIfZVkslikjShim5yr05XFuCQ= github.com/pion/datachannel v1.5.5/go.mod h1:iMz+lECmfdCMqFRhXhcA/219B0SQlbpoR2V118yimL0=
github.com/pion/dtls/v2 v2.1.3/go.mod h1:o6+WvyLDAlXF7YiPB/RlskRoeK+/JtuaZa5emwQcWus= github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s=
github.com/pion/dtls/v2 v2.1.5/go.mod h1:BqCE7xPZbPSubGasRoDFJeTsyJtdD1FanJYL0JGheqY= github.com/pion/dtls/v2 v2.2.9 h1:K+D/aVf9/REahQvqk6G5JavdrD8W1PWDKC11UlwN7ts=
github.com/pion/dtls/v2 v2.2.4 h1:YSfYwDQgrxMYXLBc/m7PFY5BVtWlNm/DN4qoU2CbcWg= github.com/pion/dtls/v2 v2.2.9/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s=
github.com/pion/dtls/v2 v2.2.4/go.mod h1:WGKfxqhrddne4Kg3p11FUMJrynkOY4lb25zHNO49wuw= github.com/pion/ice/v2 v2.3.11/go.mod h1:hPcLC3kxMa+JGRzMHqQzjoSj3xtE9F+eoncmXLlCL4E=
github.com/pion/ice/v2 v2.2.6 h1:R/vaLlI1J2gCx141L5PEwtuGAGcyS6e7E0hDeJFq5Ig= github.com/pion/ice/v2 v2.3.12 h1:NWKW2b3+oSZS3klbQMIEWQ0i52Kuo0KBg505a5kQv4s=
github.com/pion/ice/v2 v2.2.6/go.mod h1:SWuHiOGP17lGromHTFadUe1EuPgFh/oCU6FCMZHooVE= github.com/pion/ice/v2 v2.3.12/go.mod h1:hPcLC3kxMa+JGRzMHqQzjoSj3xtE9F+eoncmXLlCL4E=
github.com/pion/interceptor v0.1.11 h1:00U6OlqxA3FFB50HSg25J/8cWi7P6FbSzw4eFn24Bvs= github.com/pion/interceptor v0.1.25 h1:pwY9r7P6ToQ3+IF0bajN0xmk/fNw/suTgaTdlwTDmhc=
github.com/pion/interceptor v0.1.11/go.mod h1:tbtKjZY14awXd7Bq0mmWvgtHB5MDaRN7HV3OZ/uy7s8= github.com/pion/interceptor v0.1.25/go.mod h1:wkbPYAak5zKsfpVDYMtEfWEy8D4zL+rpxCxPImLOg3Y=
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY= github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms= github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
github.com/pion/mdns v0.0.5 h1:Q2oj/JB3NqfzY9xGZ1fPzZzK7sDSD8rZPOvcIQ10BCw= github.com/pion/mdns v0.0.8/go.mod h1:hYE72WX8WDveIhg7fmXgMKivD3Puklk0Ymzog0lSyaI=
github.com/pion/mdns v0.0.5/go.mod h1:UgssrvdD3mxpi8tMxAXbsppL3vJ4Jipw1mTCW+al01g= github.com/pion/mdns v0.0.9 h1:7Ue5KZsqq8EuqStnpPWV33vYYEH0+skdDN5L7EiEsI4=
github.com/pion/mdns v0.0.9/go.mod h1:2JA5exfxwzXiCihmxpTKgFUpiQws2MnipoPK09vecIc=
github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA= github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8= github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
github.com/pion/rtcp v1.2.9 h1:1ujStwg++IOLIEoOiIQ2s+qBuJ1VN81KW+9pMPsif+U= github.com/pion/rtcp v1.2.10/go.mod h1:ztfEwXZNLGyF1oQDttz/ZKIBaeeg/oWbRYqzBM9TL1I=
github.com/pion/rtcp v1.2.9/go.mod h1:qVPhiCzAm4D/rxb6XzKeyZiQK69yJpbUDJSF7TgrqNo= github.com/pion/rtcp v1.2.12/go.mod h1:sn6qjxvnwyAkkPzPULIbVqSKI5Dv54Rv7VG0kNxh9L4=
github.com/pion/rtp v1.7.13 h1:qcHwlmtiI50t1XivvoawdCGTP4Uiypzfrsap+bijcoA= github.com/pion/rtcp v1.2.13 h1:+EQijuisKwm/8VBs8nWllr0bIndR7Lf7cZG200mpbNo=
github.com/pion/rtp v1.7.13/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko= github.com/pion/rtcp v1.2.13/go.mod h1:sn6qjxvnwyAkkPzPULIbVqSKI5Dv54Rv7VG0kNxh9L4=
github.com/pion/sctp v1.8.0/go.mod h1:xFe9cLMZ5Vj6eOzpyiKjT9SwGM4KpK/8Jbw5//jc+0s= github.com/pion/rtp v1.8.2/go.mod h1:pBGHaFt/yW7bf1jjWAoUjpSNoDnw98KTMg+jWWvziqU=
github.com/pion/sctp v1.8.2 h1:yBBCIrUMJ4yFICL3RIvR4eh/H2BTTvlligmSTy+3kiA= github.com/pion/rtp v1.8.3 h1:VEHxqzSVQxCkKDSHro5/4IUUG1ea+MFdqR2R3xSpNU8=
github.com/pion/sctp v1.8.2/go.mod h1:xFe9cLMZ5Vj6eOzpyiKjT9SwGM4KpK/8Jbw5//jc+0s= github.com/pion/rtp v1.8.3/go.mod h1:pBGHaFt/yW7bf1jjWAoUjpSNoDnw98KTMg+jWWvziqU=
github.com/pion/sdp/v3 v3.0.5 h1:ouvI7IgGl+V4CrqskVtr3AaTrPvPisEOxwgpdktctkU= github.com/pion/sctp v1.8.5/go.mod h1:SUFFfDpViyKejTAdwD1d/HQsCu+V/40cCs2nZIvC3s0=
github.com/pion/sdp/v3 v3.0.5/go.mod h1:iiFWFpQO8Fy3S5ldclBkpXqmWy02ns78NOKoLLL0YQw= github.com/pion/sctp v1.8.8/go.mod h1:igF9nZBrjh5AtmKc7U30jXltsFHicFCXSmWA2GWRaWs=
github.com/pion/srtp/v2 v2.0.9 h1:JJq3jClmDFBPX/F5roEb0U19jSU7eUhyDqR/NZ34EKQ= github.com/pion/sctp v1.8.9 h1:TP5ZVxV5J7rz7uZmbyvnUvsn7EJ2x/5q9uhsTtXbI3g=
github.com/pion/srtp/v2 v2.0.9/go.mod h1:5TtM9yw6lsH0ppNCehB/EjEUli7VkUgKSPJqWVqbhQ4= github.com/pion/sctp v1.8.9/go.mod h1:cMLT45jqw3+jiJCrtHVwfQLnfR0MGZ4rgOJwUOIqLkI=
github.com/pion/stun v0.3.5 h1:uLUCBCkQby4S1cf6CGuR9QrVOKcvUwFeemaC865QHDg= github.com/pion/sdp/v3 v3.0.6 h1:WuDLhtuFUUVpTfus9ILC4HRyHsW6TdugjEX/QY9OiUw=
github.com/pion/stun v0.3.5/go.mod h1:gDMim+47EeEtfWogA37n6qXZS88L5V6LqFcf+DZA2UA= github.com/pion/sdp/v3 v3.0.6/go.mod h1:iiFWFpQO8Fy3S5ldclBkpXqmWy02ns78NOKoLLL0YQw=
github.com/pion/transport v0.12.2/go.mod h1:N3+vZQD9HlDP5GWkZ85LohxNsDcNgofQmyL6ojX5d8Q= github.com/pion/srtp/v2 v2.0.18 h1:vKpAXfawO9RtTRKZJbG4y0v1b11NZxQnxRl85kGuUlo=
github.com/pion/transport v0.12.3/go.mod h1:OViWW9SP2peE/HbwBvARicmAVnesphkNkCVZIWJ6q9A= github.com/pion/srtp/v2 v2.0.18/go.mod h1:0KJQjA99A6/a0DOVTu1PhDSw0CXF2jTkqOoMg3ODqdA=
github.com/pion/transport v0.13.0/go.mod h1:yxm9uXpK9bpBBWkITk13cLo1y5/ur5VQpG22ny6EP7g= github.com/pion/stun v0.6.1 h1:8lp6YejULeHBF8NmV8e2787BogQhduZugh5PdhDyyN4=
github.com/pion/transport v0.13.1 h1:/UH5yLeQtwm2VZIPjxwnNFxjS4DFhyLfS4GlfuKUzfA= github.com/pion/stun v0.6.1/go.mod h1:/hO7APkX4hZKu/D0f2lHzNyvdkTGtIy3NDmLR7kSz/8=
github.com/pion/transport v0.13.1/go.mod h1:EBxbqzyv+ZrmDb82XswEE0BjfQFtuw1Nu6sjnjWCsGg= github.com/pion/transport v0.14.1 h1:XSM6olwW+o8J4SCmOBb/BpwZypkHeyM0PGFCxNQBr40=
github.com/pion/transport/v2 v2.0.0 h1:bsMYyqHCbkvHwj+eNCFBuxtlKndKfyGI2vaQmM3fIE4= github.com/pion/transport v0.14.1/go.mod h1:4tGmbk00NeYA3rUa9+n+dzCCoKkcy3YlYb99Jn2fNnI=
github.com/pion/transport/v2 v2.0.0/go.mod h1:HS2MEBJTwD+1ZI2eSXSvHJx/HnzQqRy2/LXxt6eVMHc= github.com/pion/transport/v2 v2.2.1/go.mod h1:cXXWavvCnFF6McHTft3DWS9iic2Mftcz1Aq29pGcU5g=
github.com/pion/turn/v2 v2.0.8 h1:KEstL92OUN3k5k8qxsXHpr7WWfrdp7iJZHx99ud8muw= github.com/pion/transport/v2 v2.2.2/go.mod h1:OJg3ojoBJopjEeECq2yJdXH9YVrUJ1uQ++NjXLOUorc=
github.com/pion/turn/v2 v2.0.8/go.mod h1:+y7xl719J8bAEVpSXBXvTxStjJv3hbz9YFflvkpcGPw= github.com/pion/transport/v2 v2.2.3/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0=
github.com/pion/udp v0.1.1/go.mod h1:6AFo+CMdKQm7UiA0eUPA8/eVCTx8jBIITLZHc9DWX5M= github.com/pion/transport/v2 v2.2.4 h1:41JJK6DZQYSeVLxILA2+F4ZkKb4Xd/tFJZRFZQ9QAlo=
github.com/pion/udp v0.1.4 h1:OowsTmu1Od3sD6i3fQUJxJn2fEvJO6L1TidgadtbTI8= github.com/pion/transport/v2 v2.2.4/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0=
github.com/pion/udp v0.1.4/go.mod h1:G8LDo56HsFwC24LIcnT4YIDU5qcB6NepqqjP0keL2us= github.com/pion/transport/v3 v3.0.1 h1:gDTlPJwROfSfz6QfSi0ZmeCSkFcnWWiiR9ES0ouANiM=
github.com/pion/webrtc/v3 v3.1.42 h1:wJEQFIXVanptnQcHOLTuIo4AtGB2+mG2x4OhIhnITOA= github.com/pion/transport/v3 v3.0.1/go.mod h1:UY7kiITrlMv7/IKgd5eTUcaahZx5oUN3l9SzK5f5xE0=
github.com/pion/webrtc/v3 v3.1.42/go.mod h1:ffD9DulDrPxyWvDPUIPAOSAWx9GUlOExiJPf7cCcMLA= github.com/pion/turn/v2 v2.1.3/go.mod h1:huEpByKKHix2/b9kmTAM3YoX6MKP+/D//0ClgUYR2fY=
github.com/pion/turn/v2 v2.1.4 h1:2xn8rduI5W6sCZQkEnIUDAkrBQNl2eYIBCHMZ3QMmP8=
github.com/pion/turn/v2 v2.1.4/go.mod h1:huEpByKKHix2/b9kmTAM3YoX6MKP+/D//0ClgUYR2fY=
github.com/pion/webrtc/v3 v3.2.24 h1:MiFL5DMo2bDaaIFWr0DDpwiV/L4EGbLZb+xoRvfEo1Y=
github.com/pion/webrtc/v3 v3.2.24/go.mod h1:1CaT2fcZzZ6VZA+O1i9yK2DU4EOcXVvSbWG9pr5jefs=
github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4=
github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
@@ -362,11 +374,14 @@ github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsT
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
github.com/prometheus/procfs v0.0.11/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.0.11/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rs/dnscache v0.0.0-20211102005908-e0241e321417 h1:Lt9DzQALzHoDwMBGJ6v8ObDPR0dzr2a6sXTB1Fq7IHs= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rs/dnscache v0.0.0-20211102005908-e0241e321417/go.mod h1:qe5TWALJ8/a1Lqznoc5BDHpYX/8HU60Hm2AwRmqzxqA= github.com/rs/dnscache v0.0.0-20230804202142-fc85eb664529 h1:18kd+8ZUlt/ARXhljq+14TwAoKa61q6dX8jtwOf6DH8=
github.com/rs/dnscache v0.0.0-20230804202142-fc85eb664529/go.mod h1:qe5TWALJ8/a1Lqznoc5BDHpYX/8HU60Hm2AwRmqzxqA=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A= github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A=
github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
@@ -397,10 +412,12 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
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/tidwall/btree v1.6.0 h1:LDZfKfQIBHGHWSwckhXI0RPSXzlo+KYdjK7FWSqOzzg= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/tidwall/btree v1.6.0/go.mod h1:twD9XRA5jj9VUQGELzDO4HPQTNJsoWWfYEL+EUQ2cKY= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/tidwall/btree v1.7.0 h1:L1fkJH/AuEh5zBnnBbmTwQ5Lt+bRJ5A8EWecslvo9iI=
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=
@@ -410,42 +427,52 @@ github.com/willf/bitset v1.1.9/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPyS
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/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
github.com/xiaoqidun/setft v0.0.0-20220310121541-be86327699ad h1:QtuWk6dsrNXc/ugwCBEN0jG52q/4tXRzMvZm4fH6T9g=
github.com/xiaoqidun/setft v0.0.0-20220310121541-be86327699ad/go.mod h1:Jj8p9bgKGTPQ+M8CdUMS9p7Mmdoxa3OAcAjJQBu0CcI=
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.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.etcd.io/bbolt v1.3.8 h1:xs88BrvEv273UsB79e0hcVrlUWmS0a8upikMFhSyAtA= go.etcd.io/bbolt v1.3.8 h1:xs88BrvEv273UsB79e0hcVrlUWmS0a8upikMFhSyAtA=
go.etcd.io/bbolt v1.3.8/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= go.etcd.io/bbolt v1.3.8/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw=
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opentelemetry.io/otel v1.8.0 h1:zcvBFizPbpa1q7FehvFiHbQwGzmPILebO0tyqIR5Djg= go.opentelemetry.io/otel v1.22.0 h1:xS7Ku+7yTFvDfDraDIJVpw7XPyuHlB9MCiqqX5mcJ6Y=
go.opentelemetry.io/otel v1.8.0/go.mod h1:2pkj+iMj0o03Y+cW6/m8Y4WkRdYN3AvCXCnzRMp9yvM= go.opentelemetry.io/otel v1.22.0/go.mod h1:eoV4iAi3Ea8LkAEI9+GFT44O6T/D0GWAVFyZVCC6pMI=
go.opentelemetry.io/otel/trace v1.8.0 h1:cSy0DF9eGI5WIfNwZ1q2iUyGj00tGzP24dE1lOlHrfY= go.opentelemetry.io/otel/metric v1.22.0 h1:lypMQnGyJYeuYPhOM/bgjbFM6WE44W1/T45er4d8Hhg=
go.opentelemetry.io/otel/trace v1.8.0/go.mod h1:0Bt3PXY8w+3pheS3hQUt+wow8b1ojPaTBoTCh2zIFI4= go.opentelemetry.io/otel/metric v1.22.0/go.mod h1:evJGjVpZv0mQ5QBRJoBF64yMuOf4xCWdXjK8pzFvliY=
go.opentelemetry.io/otel/trace v1.22.0 h1:Hg6pPujv0XG9QaVbGOBVHunyuLcCC3jN7WEhPx83XD0=
go.opentelemetry.io/otel/trace v1.22.0/go.mod h1:RbbHXVqKES9QhzZq/fE5UnOSILqRt40a21sPw2He1xo=
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=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220516162934-403b01795ae8/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
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.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
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.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20220613132600-b0d781184e0d h1:vtUKgx8dahOomfFzLREU8nSv25YHnTgLBn4rDnWZdU0= golang.org/x/exp v0.0.0-20220428152302-39d4317da171/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE=
golang.org/x/exp v0.0.0-20220613132600-b0d781184e0d/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA= golang.org/x/exp v0.0.0-20240119083558-1b970713d09a h1:Q8/wZp0KX97QFTc2ywcOE0YRjZPVIx+MXInMzdvQqcA=
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
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.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 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
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.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -459,25 +486,24 @@ golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201201195509-5d6afe98e0b7/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211201190559-0a0e4e1bb54c/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220401154927-543a649e0bdd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220531201128-c960675eff93/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
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.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -487,10 +513,11 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-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.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -514,30 +541,38 @@ golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7w
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-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220608164250-635b8c9b7f68/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-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.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.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
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.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo=
golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@@ -545,13 +580,17 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.6.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.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.0.0-20220609170525-579cf78fd858 h1:Dpdu/EMxGMFgq0CeYMh4fazTD2vtlZRYE7wyynxJb9U= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.0.0-20220609170525-579cf78fd858/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -560,9 +599,11 @@ golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.8-0.20211029000441-d6a9af8af023/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
golang.org/x/tools v0.1.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 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
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.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc=
golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -607,3 +648,13 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
modernc.org/libc v1.40.6 h1:141JHq3SjhOOCjECBgD4K8VgTFOy19CnHwroC08DAig=
modernc.org/libc v1.40.6/go.mod h1:YAXkAZ8ktnkCKaN9sw/UDeUVkGYJ/YquGO4FTi5nmHE=
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E=
modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E=
modernc.org/sqlite v1.28.0 h1:Zx+LyDDmXczNnEQdvPuEfcFVA2ZPyaD7UCZDjef3BHQ=
modernc.org/sqlite v1.28.0/go.mod h1:Qxpazz0zH8Z1xCFyi5GSL3FzbtZ3fvbjmywNogldEW0=
zombiezen.com/go/sqlite v1.1.0 h1:2BIABTFCE221++xR90rV0mE6T5gFF8o0MUcKDNUrIVc=
zombiezen.com/go/sqlite v1.1.0/go.mod h1:rZEFEJSoeXeiwuAb6HugOJLhtDabl+EekT92DCEKOb0=
+3 -3
View File
@@ -1,14 +1,14 @@
package controller package controller
import ( import (
"net/url" "github.com/GopeedLab/gopeed/pkg/base"
"os" "os"
"path/filepath" "path/filepath"
) )
type Controller struct { type Controller struct {
GetConfig func(v any) bool GetConfig func(v any) bool
ProxyUrl *url.URL ProxyConfig *base.DownloaderProxyConfig
FileController FileController
//ContextDialer() (proxy.Dialer, error) //ContextDialer() (proxy.Dialer, error)
} }
+2
View File
@@ -21,6 +21,8 @@ type Fetcher interface {
Pause() error Pause() error
Close() error Close() error
// Stats refreshes health statistics and returns the latest information
Stats() any
// Meta returns the meta information of the download. // Meta returns the meta information of the download.
Meta() *FetcherMeta Meta() *FetcherMeta
// Progress returns the progress of the download. // Progress returns the progress of the download.
+31 -4
View File
@@ -2,6 +2,7 @@ package bt
import ( import (
"bytes" "bytes"
"fmt"
"github.com/GopeedLab/gopeed/internal/controller" "github.com/GopeedLab/gopeed/internal/controller"
"github.com/GopeedLab/gopeed/internal/fetcher" "github.com/GopeedLab/gopeed/internal/fetcher"
"github.com/GopeedLab/gopeed/pkg/base" "github.com/GopeedLab/gopeed/pkg/base"
@@ -9,8 +10,8 @@ import (
"github.com/GopeedLab/gopeed/pkg/util" "github.com/GopeedLab/gopeed/pkg/util"
"github.com/anacrolix/torrent" "github.com/anacrolix/torrent"
"github.com/anacrolix/torrent/metainfo" "github.com/anacrolix/torrent/metainfo"
"net/http"
"path/filepath" "path/filepath"
"strings"
"sync" "sync"
"sync/atomic" "sync/atomic"
"time" "time"
@@ -31,6 +32,7 @@ type Fetcher struct {
meta *fetcher.FetcherMeta meta *fetcher.FetcherMeta
torrentReady atomic.Bool torrentReady atomic.Bool
torrentDrop atomic.Bool
create atomic.Bool create atomic.Bool
progress fetcher.Progress progress fetcher.Progress
} }
@@ -63,10 +65,10 @@ func (f *Fetcher) initClient() (err error) {
} }
cfg := torrent.NewDefaultClientConfig() cfg := torrent.NewDefaultClientConfig()
cfg.Bep20 = fmt.Sprintf("-GP%s-", parseBep20())
cfg.ExtendedHandshakeClientVersion = fmt.Sprintf("Gopeed %s", base.Version)
cfg.ListenPort = f.config.ListenPort cfg.ListenPort = f.config.ListenPort
if f.ctl.ProxyUrl != nil { cfg.HTTPProxy = f.ctl.ProxyConfig.ToHandler()
cfg.HTTPProxy = http.ProxyURL(f.ctl.ProxyUrl)
}
cfg.DefaultStorage = newFileOpts(newFileClientOpts{ cfg.DefaultStorage = newFileOpts(newFileClientOpts{
ClientBaseDir: cfg.DataDir, ClientBaseDir: cfg.DataDir,
HandleFileTorrent: func(infoHash metainfo.Hash, ft *fileTorrentImpl) { HandleFileTorrent: func(infoHash metainfo.Hash, ft *fileTorrentImpl) {
@@ -128,6 +130,7 @@ func (f *Fetcher) Start() (err error) {
file.Download() file.Download()
} }
} }
return return
} }
@@ -138,6 +141,7 @@ func (f *Fetcher) Pause() (err error) {
} }
func (f *Fetcher) Close() (err error) { func (f *Fetcher) Close() (err error) {
f.torrentDrop.Store(false)
f.safeDrop() f.safeDrop()
return nil return nil
} }
@@ -153,6 +157,9 @@ func (f *Fetcher) safeDrop() {
func (f *Fetcher) Wait() (err error) { func (f *Fetcher) Wait() (err error) {
for { for {
if f.torrentDrop.Load() {
break
}
if f.torrentReady.Load() && len(f.meta.Opts.SelectFiles) > 0 { if f.torrentReady.Load() && len(f.meta.Opts.SelectFiles) > 0 {
done := true done := true
for _, selectIndex := range f.meta.Opts.SelectFiles { for _, selectIndex := range f.meta.Opts.SelectFiles {
@@ -188,6 +195,16 @@ func (f *Fetcher) Meta() *fetcher.FetcherMeta {
return f.meta return f.meta
} }
func (f *Fetcher) Stats() any {
stats := f.torrent.Stats()
baseStats := &bt.Stats{
TotalPeers: stats.TotalPeers,
ActivePeers: stats.ActivePeers,
ConnectedSeeders: stats.ConnectedSeeders,
}
return baseStats
}
func (f *Fetcher) Progress() fetcher.Progress { func (f *Fetcher) Progress() fetcher.Progress {
if !f.torrentReady.Load() { if !f.torrentReady.Load() {
return f.progress return f.progress
@@ -207,6 +224,7 @@ func (f *Fetcher) updateRes() {
Files: make([]*base.FileInfo, len(f.torrent.Files())), Files: make([]*base.FileInfo, len(f.torrent.Files())),
Hash: f.torrent.InfoHash().String(), Hash: f.torrent.InfoHash().String(),
} }
f.torrent.PeerConns()
for i, file := range f.torrent.Files() { for i, file := range f.torrent.Files() {
res.Files[i] = &base.FileInfo{ res.Files[i] = &base.FileInfo{
Name: filepath.Base(file.DisplayPath()), Name: filepath.Base(file.DisplayPath()),
@@ -295,3 +313,12 @@ func (fb *FetcherBuilder) Restore() (v any, f func(meta *fetcher.FetcherMeta, v
} }
} }
} }
// parse version to bep20 format, fixed length 4, if not enough, fill 0
func parseBep20() string {
s := strings.ReplaceAll(base.Version, ".", "")
if len(s) < 4 {
s += strings.Repeat("0", 4-len(s))
}
return s
}
+10 -5
View File
@@ -8,8 +8,6 @@ import (
"github.com/GopeedLab/gopeed/internal/test" "github.com/GopeedLab/gopeed/internal/test"
"github.com/GopeedLab/gopeed/pkg/base" "github.com/GopeedLab/gopeed/pkg/base"
"github.com/GopeedLab/gopeed/pkg/protocol/bt" "github.com/GopeedLab/gopeed/pkg/protocol/bt"
"github.com/GopeedLab/gopeed/pkg/util"
"net/url"
"os" "os"
"reflect" "reflect"
"testing" "testing"
@@ -60,7 +58,14 @@ func TestFetcher_ResolveWithProxy(t *testing.T) {
proxyListener := test.StartSocks5Server(usr, pwd) proxyListener := test.StartSocks5Server(usr, pwd)
defer proxyListener.Close() defer proxyListener.Close()
doResolve(t, buildConfigFetcher(util.BuildProxyUrl("socks5", proxyListener.Addr().String(), usr, pwd))) doResolve(t, buildConfigFetcher(&base.DownloaderProxyConfig{
Enable: true,
System: false,
Scheme: "socks5",
Host: proxyListener.Addr().String(),
Usr: usr,
Pwd: pwd,
}))
} }
func doResolve(t *testing.T, fetcher fetcher.Fetcher) { func doResolve(t *testing.T, fetcher fetcher.Fetcher) {
@@ -100,7 +105,7 @@ func buildFetcher() fetcher.Fetcher {
return fetcher return fetcher
} }
func buildConfigFetcher(proxyUrl *url.URL) fetcher.Fetcher { func buildConfigFetcher(proxyConfig *base.DownloaderProxyConfig) fetcher.Fetcher {
fetcher := new(FetcherBuilder).Build() fetcher := new(FetcherBuilder).Build()
newController := controller.NewController() newController := controller.NewController()
mockCfg := config{ mockCfg := config{
@@ -114,7 +119,7 @@ func buildConfigFetcher(proxyUrl *url.URL) fetcher.Fetcher {
} }
return true return true
} }
newController.ProxyUrl = proxyUrl newController.ProxyConfig = proxyConfig
fetcher.Setup(newController) fetcher.Setup(newController)
return fetcher return fetcher
} }
+3 -2
View File
@@ -1,6 +1,7 @@
package http package http
type config struct { type config struct {
UserAgent string `json:"userAgent"` UserAgent string `json:"userAgent"`
Connections int `json:"connections"` Connections int `json:"connections"`
UseServerCtime bool `json:"useServerCtime"`
} }
+48 -29
View File
@@ -9,6 +9,7 @@ import (
"github.com/GopeedLab/gopeed/internal/fetcher" "github.com/GopeedLab/gopeed/internal/fetcher"
"github.com/GopeedLab/gopeed/pkg/base" "github.com/GopeedLab/gopeed/pkg/base"
fhttp "github.com/GopeedLab/gopeed/pkg/protocol/http" fhttp "github.com/GopeedLab/gopeed/pkg/protocol/http"
"github.com/xiaoqidun/setft"
"golang.org/x/sync/errgroup" "golang.org/x/sync/errgroup"
"io" "io"
"mime" "mime"
@@ -18,6 +19,7 @@ import (
"os" "os"
"path" "path"
"strconv" "strconv"
"strings"
"time" "time"
) )
@@ -104,8 +106,9 @@ func (f *Fetcher) Resolve(req *base.Request) error {
Range: false, Range: false,
Files: []*base.FileInfo{}, Files: []*base.FileInfo{},
} }
if base.HttpCodePartialContent == httpResp.StatusCode {
// 返回206响应码表示支持断点下载 if base.HttpCodePartialContent == httpResp.StatusCode || (base.HttpCodeOK == httpResp.StatusCode && httpResp.Header.Get(base.HttpHeaderAcceptRanges) == base.HttpHeaderBytes && strings.HasPrefix(httpResp.Header.Get(base.HttpHeaderContentRange), base.HttpHeaderBytes)) {
// 1.返回206响应码表示支持断点下载 2.不返回206但是Accept-Ranges首部并且等于bytes也表示支持断点下载
res.Range = true res.Range = true
// 解析资源大小: bytes 0-1000/1001 => 1001 // 解析资源大小: bytes 0-1000/1001 => 1001
contentTotal := path.Base(httpResp.Header.Get(base.HttpHeaderContentRange)) contentTotal := path.Base(httpResp.Header.Get(base.HttpHeaderContentRange))
@@ -129,8 +132,17 @@ func (f *Fetcher) Resolve(req *base.Request) error {
} else { } else {
return NewRequestError(httpResp.StatusCode, httpResp.Status) return NewRequestError(httpResp.StatusCode, httpResp.Status)
} }
// Parse last modified time
var lastModifiedTime *time.Time
lastModified := httpResp.Header.Get(base.HttpHeaderLastModified)
if lastModified != "" {
// ignore parse error
t, _ := time.Parse(time.RFC1123, lastModified)
lastModifiedTime = &t
}
file := &base.FileInfo{ file := &base.FileInfo{
Size: res.Size, Size: res.Size,
Ctime: lastModifiedTime,
} }
contentDisposition := httpResp.Header.Get(base.HttpHeaderContentDisposition) contentDisposition := httpResp.Header.Get(base.HttpHeaderContentDisposition)
if contentDisposition != "" { if contentDisposition != "" {
@@ -217,6 +229,11 @@ func (f *Fetcher) Meta() *fetcher.FetcherMeta {
return f.meta return f.meta
} }
func (f *Fetcher) Stats() any {
// todo http download health
return &fhttp.Stats{}
}
func (f *Fetcher) Progress() fetcher.Progress { func (f *Fetcher) Progress() fetcher.Progress {
p := make(fetcher.Progress, 0) p := make(fetcher.Progress, 0)
if len(f.chunks) > 0 { if len(f.chunks) > 0 {
@@ -251,6 +268,10 @@ func (f *Fetcher) fetch() {
return return
} }
f.file.Close() f.file.Close()
// Update file last modified time
if f.config.UseServerCtime && f.meta.Res.Files[0].Ctime != nil {
setft.SetFileTime(f.file.Name(), time.Now(), *f.meta.Res.Files[0].Ctime, *f.meta.Res.Files[0].Ctime)
}
f.doneCh <- err f.doneCh <- err
}() }()
} }
@@ -259,17 +280,13 @@ func (f *Fetcher) fetchChunk(index int, ctx context.Context) (err error) {
chunk := f.chunks[index] chunk := f.chunks[index]
chunk.retryTimes = 0 chunk.retryTimes = 0
httpReq, err := f.buildRequest(ctx, f.meta.Req)
if err != nil {
return err
}
var ( var (
client = f.buildClient() client = f.buildClient()
buf = make([]byte, 8192) buf = make([]byte, 8192)
maxRetries = 3 maxRetries = 3
) )
// retry until all remain chunks failed // retry until all remain chunks failed
for true { for {
// if chunk is completed, return // if chunk is completed, return
if f.meta.Res.Range && chunk.Downloaded >= chunk.End-chunk.Begin+1 { if f.meta.Res.Range && chunk.Downloaded >= chunk.End-chunk.Begin+1 {
return return
@@ -293,8 +310,13 @@ func (f *Fetcher) fetchChunk(index int, ctx context.Context) (err error) {
} }
var ( var (
resp *http.Response httpReq *http.Request
resp *http.Response
) )
httpReq, err = f.buildRequest(ctx, f.meta.Req)
if err != nil {
return
}
if f.meta.Res.Range { if f.meta.Res.Range {
httpReq.Header.Set(base.HttpHeaderRange, httpReq.Header.Set(base.HttpHeaderRange,
fmt.Sprintf(base.HttpHeaderRangeFormat, chunk.Begin+chunk.Downloaded, chunk.End)) fmt.Sprintf(base.HttpHeaderRangeFormat, chunk.Begin+chunk.Downloaded, chunk.End))
@@ -306,26 +328,13 @@ func (f *Fetcher) fetchChunk(index int, ctx context.Context) (err error) {
if err != nil { if err != nil {
return err return err
} }
defer resp.Body.Close()
if resp.StatusCode != base.HttpCodeOK && resp.StatusCode != base.HttpCodePartialContent { if resp.StatusCode != base.HttpCodeOK && resp.StatusCode != base.HttpCodePartialContent {
err = NewRequestError(resp.StatusCode, resp.Status) err = NewRequestError(resp.StatusCode, resp.Status)
return err return err
} }
return nil // Http request success, reset retry times
}() chunk.retryTimes = 0
if err != nil {
// If canceled, do not retry
if errors.Is(err, context.Canceled) {
return
}
// retry request after 1 second
chunk.retryTimes = chunk.retryTimes + 1
time.Sleep(time.Second)
continue
}
// Http request success, reset retry times
chunk.retryTimes = 0
err = func() error {
defer resp.Body.Close()
for { for {
n, err := resp.Body.Read(buf) n, err := resp.Body.Read(buf)
if n > 0 { if n > 0 {
@@ -342,7 +351,18 @@ func (f *Fetcher) fetchChunk(index int, ctx context.Context) (err error) {
return err return err
} }
} }
return nil
}() }()
if err != nil {
// If canceled, do not retry
if errors.Is(err, context.Canceled) {
return
}
// retry request after 1 second
chunk.retryTimes = chunk.retryTimes + 1
time.Sleep(time.Second)
continue
}
// if chunk is completed, reset other not complete chunks retry times // if chunk is completed, reset other not complete chunks retry times
if err == nil { if err == nil {
for _, c := range f.chunks { for _, c := range f.chunks {
@@ -431,12 +451,11 @@ func (f *Fetcher) splitChunk() (chunks []*chunk) {
} }
func (f *Fetcher) buildClient() *http.Client { func (f *Fetcher) buildClient() *http.Client {
transport := &http.Transport{} transport := &http.Transport{
Proxy: f.ctl.ProxyConfig.ToHandler(),
}
// Cookie handle // Cookie handle
jar, _ := cookiejar.New(nil) jar, _ := cookiejar.New(nil)
if f.ctl.ProxyUrl != nil {
transport.Proxy = http.ProxyURL(f.ctl.ProxyUrl)
}
return &http.Client{ return &http.Client{
Transport: transport, Transport: transport,
Jar: jar, Jar: jar,
+34 -11
View File
@@ -8,9 +8,7 @@ import (
"github.com/GopeedLab/gopeed/internal/test" "github.com/GopeedLab/gopeed/internal/test"
"github.com/GopeedLab/gopeed/pkg/base" "github.com/GopeedLab/gopeed/pkg/base"
"github.com/GopeedLab/gopeed/pkg/protocol/http" "github.com/GopeedLab/gopeed/pkg/protocol/http"
"github.com/GopeedLab/gopeed/pkg/util"
"net" "net"
"reflect"
"testing" "testing"
"time" "time"
) )
@@ -69,7 +67,7 @@ func testResolve(startTestServer func() net.Listener, path string, want *base.Re
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if !reflect.DeepEqual(want, fetcher.meta.Res) { if !test.AssertResourceEqual(want, fetcher.meta.Res) {
t.Errorf("Resolve() got = %v, want %v", fetcher.meta.Res, want) t.Errorf("Resolve() got = %v, want %v", fetcher.meta.Res, want)
} }
} }
@@ -151,10 +149,34 @@ func TestFetcher_DownloadWithProxy(t *testing.T) {
downloadWithProxy(httpListener, proxyListener, t) downloadWithProxy(httpListener, proxyListener, t)
} }
func TestFetcher_Config(t *testing.T) { func TestFetcher_ConfigConnections(t *testing.T) {
listener := test.StartTestFileServer() listener := test.StartTestFileServer()
defer listener.Close() defer listener.Close()
fetcher := doDownloadReady(buildConfigFetcher(), listener, 0, t) 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)
}
want := test.FileMd5(test.BuildFile)
got := test.FileMd5(test.DownloadFile)
if want != got {
t.Errorf("Download() got = %v, want %v", got, want)
}
}
func TestFetcher_ConfigUseServerCtime(t *testing.T) {
listener := test.StartTestFileServer()
defer listener.Close()
fetcher := doDownloadReady(buildConfigFetcher(config{
Connections: 16,
UseServerCtime: true,
}), listener, 0, t)
err := fetcher.Start() err := fetcher.Start()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@@ -315,7 +337,11 @@ func downloadResume(listener net.Listener, connections int, t *testing.T) {
func downloadWithProxy(httpListener net.Listener, proxyListener net.Listener, t *testing.T) { func downloadWithProxy(httpListener net.Listener, proxyListener net.Listener, t *testing.T) {
fetcher := downloadReady(httpListener, 4, t) fetcher := downloadReady(httpListener, 4, t)
ctl := controller.NewController() ctl := controller.NewController()
ctl.ProxyUrl = util.BuildProxyUrl("socks5", proxyListener.Addr().String(), "", "") ctl.ProxyConfig = &base.DownloaderProxyConfig{
Enable: true,
Scheme: "socks5",
Host: proxyListener.Addr().String(),
}
fetcher.Setup(ctl) fetcher.Setup(ctl)
err := fetcher.Start() err := fetcher.Start()
if err != nil { if err != nil {
@@ -338,14 +364,11 @@ func buildFetcher() *Fetcher {
return fetcher.(*Fetcher) return fetcher.(*Fetcher)
} }
func buildConfigFetcher() fetcher.Fetcher { func buildConfigFetcher(cfg config) fetcher.Fetcher {
fetcher := new(FetcherBuilder).Build() fetcher := new(FetcherBuilder).Build()
newController := controller.NewController() newController := controller.NewController()
mockCfg := config{
Connections: 16,
}
newController.GetConfig = func(v any) bool { newController.GetConfig = func(v any) bool {
if err := json.Unmarshal([]byte(test.ToJson(mockCfg)), v); err != nil { if err := json.Unmarshal([]byte(test.ToJson(cfg)), v); err != nil {
return false return false
} }
return true return true
+15
View File
@@ -4,12 +4,14 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/GopeedLab/gopeed/pkg/base"
"github.com/armon/go-socks5" "github.com/armon/go-socks5"
"io" "io"
"math/rand" "math/rand"
"net" "net"
"net/http" "net/http"
"os" "os"
"reflect"
"strconv" "strconv"
"strings" "strings"
"sync/atomic" "sync/atomic"
@@ -22,6 +24,11 @@ const (
Dir = "./" Dir = "./"
BuildFile = Dir + BuildName BuildFile = Dir + BuildName
ExternalDownloadUrl = "https://raw.githubusercontent.com/GopeedLab/gopeed/v1.5.6/_docs/img/banner.png"
ExternalDownloadName = "banner.png"
ExternalDownloadSize = 26416
//ExternalDownloadMd5 = "c67c6e3cae79a95342485676571e8a5c"
DownloadName = "download.data" DownloadName = "download.data"
DownloadRename = "download (1).data" DownloadRename = "download (1).data"
DownloadFile = Dir + DownloadName DownloadFile = Dir + DownloadName
@@ -337,3 +344,11 @@ func StartSocks5Server(usr, pwd string) net.Listener {
go server.Serve(listener) go server.Serve(listener)
return listener return listener
} }
func AssertResourceEqual(want, got *base.Resource) bool {
// Ignore ctime
if got != nil && len(got.Files) > 0 {
got.Files[0].Ctime = nil
}
return reflect.DeepEqual(want, got)
}
+4 -1
View File
@@ -16,10 +16,13 @@ const (
HttpCodePartialContent = 206 HttpCodePartialContent = 206
HttpHeaderRange = "Range" HttpHeaderRange = "Range"
HttpHeaderAcceptRanges = "Accept-Ranges"
HttpHeaderContentLength = "Content-Length" HttpHeaderContentLength = "Content-Length"
HttpHeaderContentRange = "Content-Range" HttpHeaderContentRange = "Content-Range"
HttpHeaderContentDisposition = "Content-Disposition" HttpHeaderContentDisposition = "Content-Disposition"
HttpHeaderUserAgent = "Usr-Agent" HttpHeaderUserAgent = "User-Agent"
HttpHeaderLastModified = "Last-Modified"
HttpHeaderBytes = "bytes"
HttpHeaderRangeFormat = "bytes=%d-%d" HttpHeaderRangeFormat = "bytes=%d-%d"
) )
+11
View File
@@ -0,0 +1,11 @@
package base
// Version is the build version, set at build time, using `go build -ldflags "-X github.com/GopeedLab/gopeed/pkg/base.Version=1.0.0"`.
var Version string
var InDocker string
func init() {
if Version == "" {
Version = "dev"
}
}
+93 -3
View File
@@ -3,7 +3,11 @@ package base
import ( import (
"fmt" "fmt"
"github.com/GopeedLab/gopeed/pkg/util" "github.com/GopeedLab/gopeed/pkg/util"
"github.com/mattn/go-ieproxy"
"golang.org/x/exp/slices" "golang.org/x/exp/slices"
"net/http"
"net/url"
"time"
) )
// Request download request // Request download request
@@ -59,9 +63,10 @@ func (r *Resource) CalcSize(selectFiles []int) {
} }
type FileInfo struct { type FileInfo struct {
Name string `json:"name"` Name string `json:"name"`
Path string `json:"path"` Path string `json:"path"`
Size int64 `json:"size"` Size int64 `json:"size"`
Ctime *time.Time `json:"ctime"`
Req *Request `json:"req"` Req *Request `json:"req"`
} }
@@ -126,3 +131,88 @@ func ParseOptsExtra[E any](opts *Options) error {
opts.Extra = &t opts.Extra = &t
return nil return nil
} }
// DownloaderStoreConfig is the config that can restore the downloader.
type DownloaderStoreConfig struct {
FirstLoad bool `json:"-"` // FirstLoad is the flag that the config is first time init and not from store
DownloadDir string `json:"downloadDir"` // DownloadDir is the default directory to save the downloaded files
MaxRunning int `json:"maxRunning"` // MaxRunning is the max running download count
ProtocolConfig map[string]any `json:"protocolConfig"` // ProtocolConfig is special config for each protocol
Extra map[string]any `json:"extra"`
Proxy *DownloaderProxyConfig `json:"proxy"`
}
func (cfg *DownloaderStoreConfig) Init() *DownloaderStoreConfig {
if cfg.MaxRunning == 0 {
cfg.MaxRunning = 5
}
if cfg.Proxy == nil {
cfg.Proxy = &DownloaderProxyConfig{}
}
return cfg
}
type DownloaderProxyConfig struct {
Enable bool `json:"enable"`
// System is the flag that use system proxy
System bool `json:"system"`
Scheme string `json:"scheme"`
Host string `json:"host"`
Usr string `json:"usr"`
Pwd string `json:"pwd"`
}
func (cfg *DownloaderProxyConfig) ToHandler() func(r *http.Request) (*url.URL, error) {
if cfg == nil || cfg.Enable == false {
return nil
}
if cfg.System {
ieproxy.ReloadConf()
return ieproxy.GetProxyFunc()
}
if cfg.Scheme == "" || cfg.Host == "" {
return nil
}
return http.ProxyURL(util.BuildProxyUrl(cfg.Scheme, cfg.Host, cfg.Usr, cfg.Pwd))
}
// ToUrl returns the proxy url, just for git clone
func (cfg *DownloaderProxyConfig) ToUrl() *url.URL {
if cfg == nil || cfg.Enable == false {
return nil
}
if cfg.System {
ieproxy.ReloadConf()
static := ieproxy.GetConf().Static
if static.Active && len(static.Protocols) > 0 {
// If only one protocol, use it
if len(static.Protocols) == 1 {
for _, v := range static.Protocols {
return parseUrlSafe(v)
}
}
// Check https
if v, ok := static.Protocols["https"]; ok {
return parseUrlSafe(v)
}
// Check http
if v, ok := static.Protocols["http"]; ok {
return parseUrlSafe(v)
}
}
return nil
}
if cfg.Scheme == "" || cfg.Host == "" {
return nil
}
return util.BuildProxyUrl(cfg.Scheme, cfg.Host, cfg.Usr, cfg.Pwd)
}
func parseUrlSafe(rawUrl string) *url.URL {
u, err := url.Parse(rawUrl)
if err != nil {
return nil
}
return u
}
+231 -104
View File
@@ -6,8 +6,11 @@ import (
"github.com/GopeedLab/gopeed/internal/fetcher" "github.com/GopeedLab/gopeed/internal/fetcher"
"github.com/GopeedLab/gopeed/internal/logger" "github.com/GopeedLab/gopeed/internal/logger"
"github.com/GopeedLab/gopeed/pkg/base" "github.com/GopeedLab/gopeed/pkg/base"
"github.com/GopeedLab/gopeed/pkg/protocol/http"
"github.com/GopeedLab/gopeed/pkg/util" "github.com/GopeedLab/gopeed/pkg/util"
gonanoid "github.com/matoous/go-nanoid/v2" gonanoid "github.com/matoous/go-nanoid/v2"
"github.com/rs/zerolog"
"github.com/rs/zerolog/pkgerrors"
"github.com/virtuald/go-paniclog" "github.com/virtuald/go-paniclog"
"os" "os"
"path/filepath" "path/filepath"
@@ -32,7 +35,8 @@ const (
) )
var ( var (
ErrTaskNotFound = errors.New("task not found") ErrTaskNotFound = errors.New("task not found")
ErrUnSupportedProtocol = errors.New("unsupported protocol")
) )
type Listener func(event *Event) type Listener func(event *Event)
@@ -91,6 +95,7 @@ func NewDownloader(cfg *DownloaderConfig) *Downloader {
} }
} }
zerolog.ErrorStackMarshaler = pkgerrors.MarshalStack
d.Logger = logger.NewLogger(cfg.ProductionMode, filepath.Join(cfg.StorageDir, "logs", "core.log")) d.Logger = logger.NewLogger(cfg.ProductionMode, filepath.Join(cfg.StorageDir, "logs", "core.log"))
d.ExtensionLogger = logger.NewLogger(cfg.ProductionMode, filepath.Join(cfg.StorageDir, "logs", "extension.log")) d.ExtensionLogger = logger.NewLogger(cfg.ProductionMode, filepath.Join(cfg.StorageDir, "logs", "extension.log"))
if cfg.ProductionMode { if cfg.ProductionMode {
@@ -105,7 +110,7 @@ func (d *Downloader) Setup() error {
return err return err
} }
// load config from storage // load config from storage
var cfg DownloaderStoreConfig var cfg base.DownloaderStoreConfig
exist, err := d.storage.Get(bucketConfig, "config", &cfg) exist, err := d.storage.Get(bucketConfig, "config", &cfg)
if err != nil { if err != nil {
return err return err
@@ -113,12 +118,17 @@ func (d *Downloader) Setup() error {
if exist { if exist {
d.cfg.DownloaderStoreConfig = &cfg d.cfg.DownloaderStoreConfig = &cfg
} else { } else {
d.cfg.DownloaderStoreConfig = &DownloaderStoreConfig{ d.cfg.DownloaderStoreConfig = &base.DownloaderStoreConfig{
FirstLoad: true, FirstLoad: true,
} }
} }
// init default config // init default config
d.cfg.DownloaderStoreConfig.Init() d.cfg.DownloaderStoreConfig.Init()
for _, fb := range d.fetcherBuilders {
f := fb.Build()
d.setupFetcher(f)
}
// load tasks from storage // load tasks from storage
var tasks []*Task var tasks []*Task
if err = d.storage.List(bucketTask, &tasks); err != nil { if err = d.storage.List(bucketTask, &tasks); err != nil {
@@ -162,8 +172,8 @@ func (d *Downloader) Setup() error {
if len(d.tasks) > 0 { if len(d.tasks) > 0 {
for _, task := range d.tasks { for _, task := range d.tasks {
func() { func() {
task.lock.Lock() task.statusLock.Lock()
defer task.lock.Unlock() defer task.statusLock.Unlock()
if task.Status != base.DownloadStatusRunning { if task.Status != base.DownloadStatusRunning {
return return
} }
@@ -207,7 +217,7 @@ func (d *Downloader) parseFb(url string) (fetcher.FetcherBuilder, error) {
if ok { if ok {
return fetchBuilder, nil return fetchBuilder, nil
} }
return nil, errors.New("unsupported protocol") return nil, ErrUnSupportedProtocol
} }
func (d *Downloader) setupFetcher(fetcher fetcher.Fetcher) { func (d *Downloader) setupFetcher(fetcher fetcher.Fetcher) {
@@ -215,7 +225,7 @@ func (d *Downloader) setupFetcher(fetcher fetcher.Fetcher) {
ctl.GetConfig = func(v any) bool { ctl.GetConfig = func(v any) bool {
return d.getProtocolConfig(fetcher.Name(), v) return d.getProtocolConfig(fetcher.Name(), v)
} }
ctl.ProxyUrl = d.cfg.ProxyUrl() ctl.ProxyConfig = d.cfg.Proxy
fetcher.Setup(ctl) fetcher.Setup(ctl)
} }
@@ -225,7 +235,10 @@ func (d *Downloader) Resolve(req *base.Request) (rr *ResolveResult, err error) {
return return
} }
res := d.triggerOnResolve(req) res, err := d.triggerOnResolve(req)
if err != nil {
return
}
if res != nil && len(res.Files) > 0 { if res != nil && len(res.Files) > 0 {
rr = &ResolveResult{ rr = &ResolveResult{
Res: res, Res: res,
@@ -289,9 +302,6 @@ func (d *Downloader) CreateDirect(req *base.Request, opts *base.Options) (taskId
} }
func (d *Downloader) Create(rrId string, opts *base.Options) (taskId string, err error) { func (d *Downloader) Create(rrId string, opts *base.Options) (taskId string, err error) {
if opts == nil {
opts = &base.Options{}
}
d.fetcherMapLock.RLock() d.fetcherMapLock.RLock()
fetcher, ok := d.fetcherCache[rrId] fetcher, ok := d.fetcherCache[rrId]
d.fetcherMapLock.RUnlock() d.fetcherMapLock.RUnlock()
@@ -319,8 +329,7 @@ func (d *Downloader) Pause(id string) (err error) {
if task.Status == base.DownloadStatusPause { if task.Status == base.DownloadStatusPause {
return return
} }
err = d.doPause(task) if err = d.doPause(task); err != nil {
if err != nil {
return return
} }
d.notifyRunning() d.notifyRunning()
@@ -353,19 +362,7 @@ func (d *Downloader) Continue(id string) (err error) {
} }
}() }()
func() { return d.doStart(task)
task.lock.Lock()
defer task.lock.Unlock()
if task.Status == base.DownloadStatusRunning {
return
}
if err = d.doStart(task); err != nil {
return
}
}()
return
} }
func (d *Downloader) PauseAll() (err error) { func (d *Downloader) PauseAll() (err error) {
@@ -378,13 +375,7 @@ func (d *Downloader) PauseAll() (err error) {
}() }()
for _, task := range d.tasks { for _, task := range d.tasks {
err = func() error { if err = d.doPause(task); err != nil {
task.lock.Lock()
defer task.lock.Unlock()
return d.doPause(task)
}()
if err != nil {
return return
} }
} }
@@ -414,13 +405,7 @@ func (d *Downloader) ContinueAll() (err error) {
for _, task := range continuedTasks { for _, task := range continuedTasks {
tt := task tt := task
err = func() error { if err = d.doStart(tt); err != nil {
tt.lock.Lock()
defer tt.lock.Unlock()
return d.doStart(tt)
}()
if err != nil {
return return
} }
} }
@@ -441,6 +426,13 @@ func (d *Downloader) Delete(id string, force bool) (err error) {
for i, t := range d.tasks { for i, t := range d.tasks {
if t.ID == id { if t.ID == id {
d.tasks = append(d.tasks[:i], d.tasks[i+1:]...) d.tasks = append(d.tasks[:i], d.tasks[i+1:]...)
break
}
}
for i, t := range d.waitTasks {
if t.ID == id {
d.waitTasks = append(d.waitTasks[:i], d.waitTasks[i+1:]...)
break
} }
} }
}() }()
@@ -453,19 +445,73 @@ func (d *Downloader) Delete(id string, force bool) (err error) {
return return
} }
func (d *Downloader) doDelete(task *Task, force bool) (err error) { func (d *Downloader) DeleteByStatues(statues []base.Status, force bool) (err error) {
err = func() error { deleteTasks := d.GetTasksByStatues(statues)
if task.fetcher != nil { if len(deleteTasks) == 0 {
if err := task.fetcher.Close(); err != nil { return
return err }
deleteIds := make([]string, 0)
for _, task := range deleteTasks {
deleteIds = append(deleteIds, task.ID)
}
func() {
d.lock.Lock()
defer d.lock.Unlock()
for _, id := range deleteIds {
for i, t := range d.tasks {
if t.ID == id {
d.tasks = append(d.tasks[:i], d.tasks[i+1:]...)
break
}
}
for i, t := range d.waitTasks {
if t.ID == id {
d.waitTasks = append(d.waitTasks[:i], d.waitTasks[i+1:]...)
break
}
} }
} }
}()
for _, task := range deleteTasks {
err = d.doDelete(task, force)
if err != nil {
return
}
}
d.notifyRunning()
return
}
func (d *Downloader) Stats(id string) (sr any, err error) {
task := d.GetTask(id)
if task == nil {
return sr, ErrTaskNotFound
}
sr = task.fetcher.Stats()
return
}
func (d *Downloader) doDelete(task *Task, force bool) (err error) {
err = func() error {
d.lock.Lock()
defer d.lock.Unlock()
if err := d.storage.Delete(bucketTask, task.ID); err != nil { if err := d.storage.Delete(bucketTask, task.ID); err != nil {
return err return err
} }
if err := d.storage.Delete(bucketSave, task.ID); err != nil { if err := d.storage.Delete(bucketSave, task.ID); err != nil {
return err return err
} }
if task.fetcher != nil {
if err := task.fetcher.Close(); err != nil {
return err
}
}
if force && task.Meta.Res != nil { if force && task.Meta.Res != nil {
if task.Meta.Res.Name != "" { if task.Meta.Res.Name != "" {
if err := os.RemoveAll(task.Meta.FolderPath()); err != nil { if err := os.RemoveAll(task.Meta.FolderPath()); err != nil {
@@ -525,9 +571,6 @@ func (d *Downloader) emit(eventKey EventKey, task *Task, errs ...error) {
Task: task, Task: task,
Err: err, Err: err,
}) })
if eventKey == EventKeyError {
d.emit(EventKeyFinally, task, err)
}
} }
} }
@@ -544,11 +587,27 @@ func (d *Downloader) GetTasks() []*Task {
return d.tasks return d.tasks
} }
func (d *Downloader) GetConfig() (*DownloaderStoreConfig, error) { func (d *Downloader) GetTasksByStatues(statues []base.Status) []*Task {
if len(statues) == 0 {
return d.tasks
}
tasks := make([]*Task, 0)
for _, task := range d.tasks {
for _, status := range statues {
if task.Status == status {
tasks = append(tasks, task)
break
}
}
}
return tasks
}
func (d *Downloader) GetConfig() (*base.DownloaderStoreConfig, error) {
return d.cfg.DownloaderStoreConfig, nil return d.cfg.DownloaderStoreConfig, nil
} }
func (d *Downloader) PutConfig(v *DownloaderStoreConfig) error { func (d *Downloader) PutConfig(v *base.DownloaderStoreConfig) error {
d.cfg.DownloaderStoreConfig = v d.cfg.DownloaderStoreConfig = v
return d.storage.Put(bucketConfig, "config", v) return d.storage.Put(bucketConfig, "config", v)
} }
@@ -572,26 +631,56 @@ func (d *Downloader) getProtocolConfig(name string, v any) bool {
func (d *Downloader) watch(task *Task) { func (d *Downloader) watch(task *Task) {
err := task.fetcher.Wait() err := task.fetcher.Wait()
if err != nil { if err != nil {
task.Status = base.DownloadStatusError d.doOnError(task, err)
d.emit(EventKeyError, task, err) return
} else {
task.Progress.Used = task.timer.Used()
if task.Meta.Res.Size == 0 {
task.Meta.Res.Size = task.fetcher.Progress().TotalDownloaded()
}
used := task.Progress.Used / int64(time.Second)
if used == 0 {
used = 1
}
totalSize := task.Meta.Res.Size
task.Progress.Speed = totalSize / used
task.Progress.Downloaded = totalSize
task.Status = base.DownloadStatusDone
d.storage.Put(bucketTask, task.ID, task.clone())
d.emit(EventKeyDone, task)
d.emit(EventKeyFinally, task, err)
} }
task.Progress.Used = task.timer.Used()
if task.Meta.Res.Size == 0 {
task.Meta.Res.Size = task.fetcher.Progress().TotalDownloaded()
}
used := task.Progress.Used / int64(time.Second)
if used == 0 {
used = 1
}
totalSize := task.Meta.Res.Size
task.Progress.Speed = totalSize / used
task.Progress.Downloaded = totalSize
task.updateStatus(base.DownloadStatusDone)
d.storage.Put(bucketTask, task.ID, task.clone())
d.emit(EventKeyDone, task)
d.emit(EventKeyFinally, task, err)
d.notifyRunning() d.notifyRunning()
if e, ok := task.Meta.Opts.Extra.(*http.OptsExtra); ok {
downloadFilePath := task.Meta.SingleFilepath()
if e.AutoTorrent && strings.HasSuffix(downloadFilePath, ".torrent") {
go func() {
_, err2 := d.CreateDirect(
&base.Request{
URL: downloadFilePath,
},
&base.Options{
Path: task.Meta.Opts.Path,
SelectFiles: make([]int, 0),
})
if err2 != nil {
d.Logger.Error().Err(err2).Msgf("auto create torrent task failed, task id: %s", task.ID)
}
}()
}
}
}
func (d *Downloader) doOnError(task *Task, err error) {
d.Logger.Warn().Err(err).Msgf("task download failed, task id: %s", task.ID)
task.updateStatus(base.DownloadStatusError)
d.triggerOnError(task, err)
if task.Status == base.DownloadStatusError {
d.emit(EventKeyError, task, err)
d.emit(EventKeyFinally, task, err)
d.notifyRunning()
}
} }
func (d *Downloader) restoreFetcher(task *Task) error { func (d *Downloader) restoreFetcher(task *Task) error {
@@ -637,6 +726,9 @@ func (d *Downloader) doCreate(fetcher fetcher.Fetcher, opts *base.Options) (task
if opts == nil { if opts == nil {
opts = &base.Options{} opts = &base.Options{}
} }
if opts.SelectFiles == nil {
opts.SelectFiles = make([]int, 0)
}
meta := fetcher.Meta() meta := fetcher.Meta()
meta.Opts = opts meta.Opts = opts
@@ -656,9 +748,9 @@ func (d *Downloader) doCreate(fetcher fetcher.Fetcher, opts *base.Options) (task
task := NewTask() task := NewTask()
task.fetcherBuilder = fb task.fetcherBuilder = fb
task.fetcher = fetcher task.fetcher = fetcher
task.Protocol = fetcher.Name()
task.Meta = fetcher.Meta() task.Meta = fetcher.Meta()
task.Progress = &Progress{} task.Progress = &Progress{}
task.Status = base.DownloadStatusReady
initTask(task) initTask(task)
if err = fetcher.Create(opts); err != nil { if err = fetcher.Create(opts); err != nil {
return return
@@ -681,41 +773,55 @@ func (d *Downloader) doCreate(fetcher fetcher.Fetcher, opts *base.Options) (task
return return
} }
d.doStart(task) err = d.doStart(task)
}() }()
go d.watch(task) go d.watch(task)
return return
} }
func (d *Downloader) statusMut(task *Task, fn func() (bool, error)) (bool, error) {
task.statusLock.Lock()
defer task.statusLock.Unlock()
return fn()
}
func (d *Downloader) doStart(task *Task) (err error) { func (d *Downloader) doStart(task *Task) (err error) {
if task.Status != base.DownloadStatusRunning && task.Status != base.DownloadStatusDone { var isCreate bool
err := d.restoreFetcher(task) isReturn, err := d.statusMut(task, func() (isReturn bool, err error) {
if task.Status == base.DownloadStatusRunning || task.Status == base.DownloadStatusDone {
isReturn = true
return
}
err = d.restoreFetcher(task)
if err != nil { if err != nil {
d.Logger.Error().Stack().Err(err).Msgf("restore fetcher failed, task id: %s", task.ID) d.Logger.Error().Stack().Err(err).Msgf("restore fetcher failed, task id: %s", task.ID)
return err return
} }
isCreate = task.Status == base.DownloadStatusReady
d.triggerOnStart(task)
task.updateStatus(base.DownloadStatusRunning)
return
})
if err != nil {
d.Logger.Error().Stack().Err(err).Msgf("start task failed, task id: %s", task.ID)
return
}
if isReturn {
return
} }
cloneTask := task.clone() handler := func() error {
isCreate := task.Status == base.DownloadStatusReady
task.Status = base.DownloadStatusRunning
doStart := func() error {
task.lock.Lock() task.lock.Lock()
defer task.lock.Unlock() defer task.lock.Unlock()
req := d.triggerOnStart(cloneTask)
if req != nil {
task.Meta.Req = req
task.fetcher.Meta().Req = req
}
if task.Meta.Res == nil { if task.Meta.Res == nil {
err := task.fetcher.Resolve(task.Meta.Req) err := task.fetcher.Resolve(task.Meta.Req)
if err != nil { if err != nil {
task.Status = base.DownloadStatusError
d.emit(EventKeyError, task, err)
return err return err
} }
task.Meta.Res = task.fetcher.Meta().Res task.Meta.Res = task.fetcher.Meta().Res
@@ -724,8 +830,10 @@ func (d *Downloader) doStart(task *Task) (err error) {
if isCreate { if isCreate {
d.checkDuplicateLock.Lock() d.checkDuplicateLock.Lock()
defer d.checkDuplicateLock.Unlock() defer d.checkDuplicateLock.Unlock()
task.Meta.Opts.Name = util.ReplaceInvalidFilename(task.Meta.Opts.Name)
// check if the download file is duplicated and rename it automatically. // check if the download file is duplicated and rename it automatically.
if task.Meta.Res.Name != "" { if task.Meta.Res.Name != "" {
task.Meta.Res.Name = util.ReplaceInvalidFilename(task.Meta.Res.Name)
fullDirPath := task.Meta.FolderPath() fullDirPath := task.Meta.FolderPath()
newName, err := util.CheckDuplicateAndRename(fullDirPath) newName, err := util.CheckDuplicateAndRename(fullDirPath)
if err != nil { if err != nil {
@@ -733,6 +841,7 @@ func (d *Downloader) doStart(task *Task) (err error) {
} }
task.Meta.Opts.Name = newName task.Meta.Opts.Name = newName
} else { } else {
task.Meta.Res.Files[0].Name = util.ReplaceInvalidFilename(task.Meta.Res.Files[0].Name)
fullFilePath := task.Meta.SingleFilepath() fullFilePath := task.Meta.SingleFilepath()
newName, err := util.CheckDuplicateAndRename(fullFilePath) newName, err := util.CheckDuplicateAndRename(fullFilePath)
if err != nil { if err != nil {
@@ -756,9 +865,8 @@ func (d *Downloader) doStart(task *Task) (err error) {
return nil return nil
} }
go func() { go func() {
err := doStart() if err := handler(); err != nil {
if err != nil { d.doOnError(task, err)
d.Logger.Error().Stack().Err(err).Msgf("start task failed, task id: %s", task.ID)
} }
}() }()
@@ -766,27 +874,44 @@ func (d *Downloader) doStart(task *Task) (err error) {
} }
func (d *Downloader) doPause(task *Task) (err error) { func (d *Downloader) doPause(task *Task) (err error) {
err = func() error { isReturn, err := d.statusMut(task, func() (isReturn bool, err error) {
if task.Status != base.DownloadStatusDone { if task.Status == base.DownloadStatusPause || task.Status == base.DownloadStatusDone {
task.Status = base.DownloadStatusPause isReturn = true
task.timer.Pause() return
if task.fetcher != nil {
if err := task.fetcher.Pause(); err != nil {
return err
}
}
if err := d.storage.Put(bucketTask, task.ID, task.clone()); err != nil {
return err
}
d.emit(EventKeyPause, task)
} }
return nil
}() task.updateStatus(base.DownloadStatusPause)
task.timer.Pause()
return
})
if err != nil { if err != nil {
d.Logger.Error().Stack().Err(err).Msgf("pause task failed, task id: %s", task.ID) d.Logger.Error().Stack().Err(err).Msgf("pause task failed, task id: %s", task.ID)
return return
} }
if isReturn {
return
}
handler := func() error {
task.lock.Lock()
defer task.lock.Unlock()
if task.fetcher != nil {
if err := task.fetcher.Pause(); err != nil {
return err
}
}
if err := d.storage.Put(bucketTask, task.ID, task.clone()); err != nil {
return err
}
d.emit(EventKeyPause, task)
return nil
}
go func() {
if err := handler(); err != nil {
d.Logger.Error().Stack().Err(err).Msgf("pause task handle failed, task id: %s", task.ID)
}
}()
return return
} }
@@ -814,6 +939,8 @@ func (d *Downloader) buildFetcher(url string) (fetcher.Fetcher, error) {
func initTask(task *Task) { func initTask(task *Task) {
task.timer = util.NewTimer(task.Progress.Used) task.timer = util.NewTimer(task.Progress.Used)
task.statusLock = &sync.Mutex{}
task.lock = &sync.Mutex{} task.lock = &sync.Mutex{}
task.speedArr = make([]int64, 0) task.speedArr = make([]int64, 0)
} }
+49 -24
View File
@@ -4,7 +4,7 @@ import (
"github.com/GopeedLab/gopeed/internal/test" "github.com/GopeedLab/gopeed/internal/test"
"github.com/GopeedLab/gopeed/pkg/base" "github.com/GopeedLab/gopeed/pkg/base"
"github.com/GopeedLab/gopeed/pkg/protocol/http" "github.com/GopeedLab/gopeed/pkg/protocol/http"
"reflect" "os"
"sync" "sync"
"testing" "testing"
"time" "time"
@@ -37,7 +37,7 @@ func TestDownloader_Resolve(t *testing.T) {
}, },
}, },
} }
if !reflect.DeepEqual(want, rr.Res) { if !test.AssertResourceEqual(want, rr.Res) {
t.Errorf("Resolve() got = %v, want %v", rr.Res, want) t.Errorf("Resolve() got = %v, want %v", rr.Res, want)
} }
} }
@@ -86,37 +86,58 @@ func TestDownloader_Create(t *testing.T) {
func TestDownloader_CreateWithProxy(t *testing.T) { func TestDownloader_CreateWithProxy(t *testing.T) {
// No proxy // No proxy
doTestDownloaderCreateWithProxy(t, false, func(proxyCfg *DownloaderProxyConfig) *DownloaderProxyConfig { doTestDownloaderCreateWithProxy(t, false, func(proxyCfg *base.DownloaderProxyConfig) *base.DownloaderProxyConfig {
return nil return nil
}) }, nil)
// Disable proxy // Disable proxy
doTestDownloaderCreateWithProxy(t, false, func(proxyCfg *DownloaderProxyConfig) *DownloaderProxyConfig { doTestDownloaderCreateWithProxy(t, false, func(proxyCfg *base.DownloaderProxyConfig) *base.DownloaderProxyConfig {
proxyCfg.Enable = false proxyCfg.Enable = false
return proxyCfg return proxyCfg
}, nil)
// Enable system proxy but not set proxy environment variable
doTestDownloaderCreateWithProxy(t, false, func(proxyCfg *base.DownloaderProxyConfig) *base.DownloaderProxyConfig {
proxyCfg.System = true
return proxyCfg
}, nil)
// Enable proxy but error proxy environment variable
doTestDownloaderCreateWithProxy(t, false, func(proxyCfg *base.DownloaderProxyConfig) *base.DownloaderProxyConfig {
os.Setenv("HTTP_PROXY", "http://127.0.0.1:1234")
os.Setenv("HTTPS_PROXY", "http://127.0.0.1:1234")
proxyCfg.System = true
return proxyCfg
}, func(err error) {
if err == nil {
t.Fatal("doTestDownloaderCreateWithProxy() got = nil, want error")
}
}) })
// Enable system proxy and set proxy environment variable
doTestDownloaderCreateWithProxy(t, false, func(proxyCfg *base.DownloaderProxyConfig) *base.DownloaderProxyConfig {
os.Setenv("HTTP_PROXY", proxyCfg.ToUrl().String())
os.Setenv("HTTPS_PROXY", proxyCfg.ToUrl().String())
proxyCfg.System = true
return proxyCfg
}, nil)
// Invalid proxy scheme // Invalid proxy scheme
doTestDownloaderCreateWithProxy(t, false, func(proxyCfg *DownloaderProxyConfig) *DownloaderProxyConfig { doTestDownloaderCreateWithProxy(t, false, func(proxyCfg *base.DownloaderProxyConfig) *base.DownloaderProxyConfig {
proxyCfg.Scheme = "" proxyCfg.Scheme = ""
return proxyCfg return proxyCfg
}) }, nil)
// Invalid proxy host // Invalid proxy host
doTestDownloaderCreateWithProxy(t, false, func(proxyCfg *DownloaderProxyConfig) *DownloaderProxyConfig { doTestDownloaderCreateWithProxy(t, false, func(proxyCfg *base.DownloaderProxyConfig) *base.DownloaderProxyConfig {
proxyCfg.Host = "" proxyCfg.Host = ""
return proxyCfg return proxyCfg
}) }, nil)
// Use proxy without auth // Use proxy without auth
doTestDownloaderCreateWithProxy(t, false, func(proxyCfg *DownloaderProxyConfig) *DownloaderProxyConfig { doTestDownloaderCreateWithProxy(t, false, func(proxyCfg *base.DownloaderProxyConfig) *base.DownloaderProxyConfig {
return proxyCfg return proxyCfg
}) }, nil)
// Use proxy with auth // Use proxy with auth
doTestDownloaderCreateWithProxy(t, true, func(proxyCfg *DownloaderProxyConfig) *DownloaderProxyConfig { doTestDownloaderCreateWithProxy(t, true, func(proxyCfg *base.DownloaderProxyConfig) *base.DownloaderProxyConfig {
return proxyCfg return proxyCfg
}) }, nil)
} }
func doTestDownloaderCreateWithProxy(t *testing.T, auth bool, buildProxyConfig func(proxyCfg *DownloaderProxyConfig) *DownloaderProxyConfig) { func doTestDownloaderCreateWithProxy(t *testing.T, auth bool, buildProxyConfig func(proxyCfg *base.DownloaderProxyConfig) *base.DownloaderProxyConfig, errHandler func(err error)) {
httpListener := test.StartTestFileServer()
defer httpListener.Close()
usr, pwd := "", "" usr, pwd := "", ""
if auth { if auth {
usr, pwd = "admin", "123" usr, pwd = "admin", "123"
@@ -129,7 +150,7 @@ func doTestDownloaderCreateWithProxy(t *testing.T, auth bool, buildProxyConfig f
t.Fatal(err) t.Fatal(err)
} }
defer downloader.Clear() defer downloader.Clear()
downloader.cfg.DownloaderStoreConfig.Proxy = buildProxyConfig(&DownloaderProxyConfig{ downloader.cfg.DownloaderStoreConfig.Proxy = buildProxyConfig(&base.DownloaderProxyConfig{
Enable: true, Enable: true,
Scheme: "socks5", Scheme: "socks5",
Host: proxyListener.Addr().String(), Host: proxyListener.Addr().String(),
@@ -138,24 +159,28 @@ func doTestDownloaderCreateWithProxy(t *testing.T, auth bool, buildProxyConfig f
}) })
req := &base.Request{ req := &base.Request{
URL: "http://" + httpListener.Addr().String() + "/" + test.BuildName, URL: test.ExternalDownloadUrl,
} }
rr, err := downloader.Resolve(req) rr, err := downloader.Resolve(req)
if err != nil { if err != nil {
t.Fatal(err) if errHandler == nil {
t.Fatal(err)
}
errHandler(err)
return
} }
want := &base.Resource{ want := &base.Resource{
Size: test.BuildSize, Size: test.ExternalDownloadSize,
Range: true, Range: true,
Files: []*base.FileInfo{ Files: []*base.FileInfo{
{ {
Name: test.BuildName, Name: test.ExternalDownloadName,
Path: "", Path: "",
Size: test.BuildSize, Size: test.ExternalDownloadSize,
}, },
}, },
} }
if !reflect.DeepEqual(want, rr.Res) { if !test.AssertResourceEqual(want, rr.Res) {
t.Errorf("Resolve() got = %v, want %v", rr.Res, want) t.Errorf("Resolve() got = %v, want %v", rr.Res, want)
} }
} }
@@ -286,7 +311,7 @@ func TestDownloader_Protocol_Config(t *testing.T) {
t.Errorf("getProtocolConfig() got = %v, want %v", exits, false) t.Errorf("getProtocolConfig() got = %v, want %v", exits, false)
} }
storeCfg := &DownloaderStoreConfig{ storeCfg := &base.DownloaderStoreConfig{
DownloadDir: "./downloads", DownloadDir: "./downloads",
ProtocolConfig: map[string]any{ ProtocolConfig: map[string]any{
"http": map[string]any{ "http": map[string]any{
+12 -4
View File
@@ -3,6 +3,8 @@ package engine
import ( import (
_ "embed" _ "embed"
"errors" "errors"
"github.com/GopeedLab/gopeed/pkg/base"
gojaerror "github.com/GopeedLab/gopeed/pkg/download/engine/inject/error"
"github.com/GopeedLab/gopeed/pkg/download/engine/inject/file" "github.com/GopeedLab/gopeed/pkg/download/engine/inject/file"
"github.com/GopeedLab/gopeed/pkg/download/engine/inject/formdata" "github.com/GopeedLab/gopeed/pkg/download/engine/inject/formdata"
"github.com/GopeedLab/gopeed/pkg/download/engine/inject/vm" "github.com/GopeedLab/gopeed/pkg/download/engine/inject/vm"
@@ -10,7 +12,6 @@ import (
"github.com/dop251/goja" "github.com/dop251/goja"
"github.com/dop251/goja_nodejs/eventloop" "github.com/dop251/goja_nodejs/eventloop"
gojaurl "github.com/dop251/goja_nodejs/url" gojaurl "github.com/dop251/goja_nodejs/url"
"net/url"
"time" "time"
) )
@@ -109,7 +110,7 @@ func (e *Engine) Close() {
} }
type Config struct { type Config struct {
ProxyURL *url.URL ProxyConfig *base.DownloaderProxyConfig
} }
func NewEngine(cfg *Config) *Engine { func NewEngine(cfg *Config) *Engine {
@@ -125,13 +126,16 @@ func NewEngine(cfg *Config) *Engine {
runtime.SetFieldNameMapper(goja.TagFieldNameMapper("json", true)) runtime.SetFieldNameMapper(goja.TagFieldNameMapper("json", true))
vm.Enable(runtime) vm.Enable(runtime)
gojaurl.Enable(runtime) gojaurl.Enable(runtime)
if err := gojaerror.Enable(runtime); err != nil {
return
}
if err := file.Enable(runtime); err != nil { if err := file.Enable(runtime); err != nil {
return return
} }
if err := formdata.Enable(runtime); err != nil { if err := formdata.Enable(runtime); err != nil {
return return
} }
if err := xhr.Enable(runtime, cfg.ProxyURL); err != nil { if err := xhr.Enable(runtime, cfg.ProxyConfig.ToHandler()); err != nil {
return return
} }
if _, err := runtime.RunString(polyfillScript); err != nil { if _, err := runtime.RunString(polyfillScript); err != nil {
@@ -167,7 +171,11 @@ func resolveResult(value goja.Value) (any, error) {
case goja.PromiseStateFulfilled: case goja.PromiseStateFulfilled:
return p.Result().Export(), nil return p.Result().Export(), nil
case goja.PromiseStateRejected: case goja.PromiseStateRejected:
return nil, errors.New(p.Result().String()) if err, ok := p.Result().Export().(error); ok {
return nil, err
} else {
return nil, errors.New(p.Result().String())
}
} }
} }
return export, nil return export, nil
+24 -3
View File
@@ -7,8 +7,10 @@ import (
"errors" "errors"
"fmt" "fmt"
"github.com/GopeedLab/gopeed/internal/test" "github.com/GopeedLab/gopeed/internal/test"
"github.com/GopeedLab/gopeed/pkg/base"
gojaerror "github.com/GopeedLab/gopeed/pkg/download/engine/inject/error"
"github.com/GopeedLab/gopeed/pkg/download/engine/inject/file" "github.com/GopeedLab/gopeed/pkg/download/engine/inject/file"
"github.com/GopeedLab/gopeed/pkg/util" gojautil "github.com/GopeedLab/gopeed/pkg/download/engine/util"
"github.com/dop251/goja" "github.com/dop251/goja"
"io" "io"
"net" "net"
@@ -20,6 +22,7 @@ import (
) )
func TestPolyfill(t *testing.T) { func TestPolyfill(t *testing.T) {
doTestPolyfill(t, "MessageError")
doTestPolyfill(t, "XMLHttpRequest") doTestPolyfill(t, "XMLHttpRequest")
doTestPolyfill(t, "Blob") doTestPolyfill(t, "Blob")
doTestPolyfill(t, "FormData") doTestPolyfill(t, "FormData")
@@ -27,6 +30,16 @@ func TestPolyfill(t *testing.T) {
doTestPolyfill(t, "__gopeed_create_vm") doTestPolyfill(t, "__gopeed_create_vm")
} }
func TestError(t *testing.T) {
engine := NewEngine(nil)
_, err := engine.RunString(`
throw new MessageError('test');
`)
if me, ok := gojautil.AssertError[*gojaerror.MessageError](err); !ok {
t.Fatalf("expect MessageError, but got %v", me)
}
}
func TestFetch(t *testing.T) { func TestFetch(t *testing.T) {
server := startServer() server := startServer()
defer server.Close() defer server.Close()
@@ -204,8 +217,16 @@ func doTestFetchWithProxy(t *testing.T, usr, pwd string) {
proxyListener := test.StartSocks5Server(usr, pwd) proxyListener := test.StartSocks5Server(usr, pwd)
defer proxyListener.Close() defer proxyListener.Close()
engine := NewEngine(&Config{
engine := NewEngine(&Config{ProxyURL: util.BuildProxyUrl("socks5", proxyListener.Addr().String(), usr, pwd)}) ProxyConfig: &base.DownloaderProxyConfig{
Enable: true,
System: false,
Scheme: "socks5",
Host: proxyListener.Addr().String(),
Usr: usr,
Pwd: pwd,
},
})
if _, err := engine.RunString(fmt.Sprintf("var host = 'http://%s';", httpListener.Addr().String())); err != nil { if _, err := engine.RunString(fmt.Sprintf("var host = 'http://%s';", httpListener.Addr().String())); err != nil {
t.Fatal(err) t.Fatal(err)
@@ -0,0 +1,29 @@
package error
import (
"github.com/dop251/goja"
)
type MessageError struct {
Message string `json:"message"`
}
func (e *MessageError) Error() string {
return e.Message
}
func Enable(runtime *goja.Runtime) error {
messageError := runtime.ToValue(func(call goja.ConstructorCall) *goja.Object {
var message string
if len(call.Arguments) > 0 {
message = call.Arguments[0].String()
}
instance := &MessageError{
Message: message,
}
instanceValue := runtime.ToValue(instance).(*goja.Object)
instanceValue.SetPrototype(call.This.Prototype())
return instanceValue
})
return runtime.Set("MessageError", messageError)
}
-9
View File
@@ -1,9 +0,0 @@
package inject
import (
"github.com/dop251/goja"
)
func ThrowTypeError(vm *goja.Runtime, msg string) {
panic(vm.NewTypeError(msg))
}
+10 -9
View File
@@ -2,9 +2,10 @@ package xhr
import ( import (
"bytes" "bytes"
"github.com/GopeedLab/gopeed/pkg/download/engine/inject" "errors"
"github.com/GopeedLab/gopeed/pkg/download/engine/inject/file" "github.com/GopeedLab/gopeed/pkg/download/engine/inject/file"
"github.com/GopeedLab/gopeed/pkg/download/engine/inject/formdata" "github.com/GopeedLab/gopeed/pkg/download/engine/inject/formdata"
"github.com/GopeedLab/gopeed/pkg/download/engine/util"
"github.com/dop251/goja" "github.com/dop251/goja"
"io" "io"
"mime/multipart" "mime/multipart"
@@ -120,7 +121,7 @@ type XMLHttpRequest struct {
requestHeaders map[string]string requestHeaders map[string]string
responseHeaders map[string]string responseHeaders map[string]string
aborted bool aborted bool
proxyUrl *url.URL proxyHandler func(r *http.Request) (*url.URL, error)
Upload *XMLHttpRequestUpload `json:"upload"` Upload *XMLHttpRequestUpload `json:"upload"`
Timeout int `json:"timeout"` Timeout int `json:"timeout"`
@@ -202,9 +203,8 @@ func (xhr *XMLHttpRequest) Send(data goja.Value) {
for k, v := range xhr.requestHeaders { for k, v := range xhr.requestHeaders {
req.Header.Set(k, v) req.Header.Set(k, v)
} }
transport := &http.Transport{} transport := &http.Transport{
if xhr.proxyUrl != nil { Proxy: xhr.proxyHandler,
transport.Proxy = http.ProxyURL(xhr.proxyUrl)
} }
client := &http.Client{ client := &http.Client{
Transport: transport, Transport: transport,
@@ -213,7 +213,8 @@ func (xhr *XMLHttpRequest) Send(data goja.Value) {
resp, err := client.Do(req) resp, err := client.Do(req)
if err != nil { if err != nil {
// handle timeout error // handle timeout error
if err, ok := err.(net.Error); ok && err.Timeout() { var ne net.Error
if errors.As(err, &ne) && ne.Timeout() {
if xhr.Timeout > 0 { if xhr.Timeout > 0 {
xhr.Upload.callOntimeout() xhr.Upload.callOntimeout()
xhr.callOntimeout() xhr.callOntimeout()
@@ -313,10 +314,10 @@ func (xhr *XMLHttpRequest) parseData(data goja.Value) any {
return data.String() return data.String()
} }
func Enable(runtime *goja.Runtime, proxyUrl *url.URL) error { func Enable(runtime *goja.Runtime, proxyHandler func(r *http.Request) (*url.URL, error)) error {
progressEvent := runtime.ToValue(func(call goja.ConstructorCall) *goja.Object { progressEvent := runtime.ToValue(func(call goja.ConstructorCall) *goja.Object {
if len(call.Arguments) < 1 { if len(call.Arguments) < 1 {
inject.ThrowTypeError(runtime, "Failed to construct 'ProgressEvent': 1 argument required, but only 0 present.") util.ThrowTypeError(runtime, "Failed to construct 'ProgressEvent': 1 argument required, but only 0 present.")
} }
instance := &ProgressEvent{ instance := &ProgressEvent{
Type: call.Argument(0).String(), Type: call.Argument(0).String(),
@@ -327,7 +328,7 @@ func Enable(runtime *goja.Runtime, proxyUrl *url.URL) error {
}) })
xhr := runtime.ToValue(func(call goja.ConstructorCall) *goja.Object { xhr := runtime.ToValue(func(call goja.ConstructorCall) *goja.Object {
instance := &XMLHttpRequest{ instance := &XMLHttpRequest{
proxyUrl: proxyUrl, proxyHandler: proxyHandler,
Upload: &XMLHttpRequestUpload{ Upload: &XMLHttpRequestUpload{
EventProp: &EventProp{ EventProp: &EventProp{
eventListeners: make(map[string]func(event *ProgressEvent)), eventListeners: make(map[string]func(event *ProgressEvent)),
+24
View File
@@ -0,0 +1,24 @@
package util
import (
"github.com/dop251/goja"
)
func ThrowTypeError(vm *goja.Runtime, msg string) {
panic(vm.NewTypeError(msg))
}
func AssertError[T error](err error) (t T, r bool) {
if err == nil {
return
}
if e, ok := err.(T); ok {
return e, true
}
if e, ok := err.(*goja.Exception); ok {
if ee, okk := e.Value().Export().(T); okk {
return ee, true
}
}
return
}
+66 -16
View File
@@ -6,6 +6,8 @@ import (
"github.com/GopeedLab/gopeed/internal/logger" "github.com/GopeedLab/gopeed/internal/logger"
"github.com/GopeedLab/gopeed/pkg/base" "github.com/GopeedLab/gopeed/pkg/base"
"github.com/GopeedLab/gopeed/pkg/download/engine" "github.com/GopeedLab/gopeed/pkg/download/engine"
gojaerror "github.com/GopeedLab/gopeed/pkg/download/engine/inject/error"
gojautil "github.com/GopeedLab/gopeed/pkg/download/engine/util"
"github.com/GopeedLab/gopeed/pkg/util" "github.com/GopeedLab/gopeed/pkg/util"
"github.com/dop251/goja" "github.com/dop251/goja"
"github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5"
@@ -35,7 +37,7 @@ type ActivationEvent string
const ( const (
EventOnResolve ActivationEvent = "onResolve" EventOnResolve ActivationEvent = "onResolve"
EventOnStart ActivationEvent = "onStart" EventOnStart ActivationEvent = "onStart"
//EventOnError ActivationEvent = "onError" EventOnError ActivationEvent = "onError"
//EventOnDone ActivationEvent = "onDone" //EventOnDone ActivationEvent = "onDone"
) )
@@ -197,7 +199,7 @@ func (d *Downloader) fetchExtensionByGit(url string, handler func(tempExtPath st
return nil, err return nil, err
} }
proxyOptions := transport.ProxyOptions{} proxyOptions := transport.ProxyOptions{}
proxyUrl := d.cfg.ProxyUrl() proxyUrl := d.cfg.DownloaderStoreConfig.Proxy.ToUrl()
if proxyUrl != nil { if proxyUrl != nil {
proxyOptions.URL = proxyUrl.Scheme + "://" + proxyUrl.Host proxyOptions.URL = proxyUrl.Scheme + "://" + proxyUrl.Host
proxyOptions.Username = proxyUrl.User.Username() proxyOptions.Username = proxyUrl.User.Username()
@@ -238,8 +240,8 @@ func (d *Downloader) parseExtensionByPath(path string) (*Extension, error) {
return &ext, nil return &ext, nil
} }
func (d *Downloader) triggerOnResolve(req *base.Request) (res *base.Resource) { func (d *Downloader) triggerOnResolve(req *base.Request) (res *base.Resource, err error) {
doTrigger(d, err = doTrigger(d,
EventOnResolve, EventOnResolve,
req, req,
&OnResolveContext{ &OnResolveContext{
@@ -252,6 +254,10 @@ func (d *Downloader) triggerOnResolve(req *base.Request) (res *base.Resource) {
gopeed.Logger.logger.Warn().Err(err).Msgf("[%s] resource invalid", ext.buildIdentity()) gopeed.Logger.logger.Warn().Err(err).Msgf("[%s] resource invalid", ext.buildIdentity())
return return
} }
ctx.Res.Name = util.ReplaceInvalidFilename(ctx.Res.Name)
for _, file := range ctx.Res.Files {
file.Name = util.ReplaceInvalidFilename(file.Name)
}
ctx.Res.CalcSize(nil) ctx.Res.CalcSize(nil)
} }
res = ctx.Res res = ctx.Res
@@ -260,12 +266,12 @@ func (d *Downloader) triggerOnResolve(req *base.Request) (res *base.Resource) {
return return
} }
func (d *Downloader) triggerOnStart(task *Task) (req *base.Request) { func (d *Downloader) triggerOnStart(task *Task) {
doTrigger(d, doTrigger(d,
EventOnStart, EventOnStart,
task.Meta.Req, task.Meta.Req,
&OnStartContext{ &OnStartContext{
Task: task, Task: NewExtensionTask(d, task),
}, },
func(ext *Extension, gopeed *Instance, ctx *OnStartContext) { func(ext *Extension, gopeed *Instance, ctx *OnStartContext) {
// Validate request structure // Validate request structure
@@ -274,14 +280,25 @@ func (d *Downloader) triggerOnStart(task *Task) (req *base.Request) {
gopeed.Logger.logger.Warn().Err(err).Msgf("[%s] request invalid", ext.buildIdentity()) gopeed.Logger.logger.Warn().Err(err).Msgf("[%s] request invalid", ext.buildIdentity())
return return
} }
req = ctx.Task.Meta.Req
} }
}, },
) )
return return
} }
func doTrigger[T any](d *Downloader, event ActivationEvent, req *base.Request, ctx T, handler func(ext *Extension, gopeed *Instance, ctx T)) { func (d *Downloader) triggerOnError(task *Task, err error) {
doTrigger(d,
EventOnError,
task.Meta.Req,
&OnErrorContext{
Task: NewExtensionTask(d, task),
Error: err,
},
nil,
)
}
func doTrigger[T any](d *Downloader, event ActivationEvent, req *base.Request, ctx T, handler func(ext *Extension, gopeed *Instance, ctx T)) error {
// init extension global object // init extension global object
gopeed := &Instance{ gopeed := &Instance{
Events: make(InstanceEvents), Events: make(InstanceEvents),
@@ -324,7 +341,7 @@ func doTrigger[T any](d *Downloader, event ActivationEvent, req *base.Request, c
req.Labels = make(map[string]string) req.Labels = make(map[string]string)
} }
engine := engine.NewEngine(&engine.Config{ engine := engine.NewEngine(&engine.Config{
ProxyURL: d.cfg.ProxyUrl(), ProxyConfig: d.cfg.Proxy,
}) })
defer engine.Close() defer engine.Close()
err = engine.Runtime.Set("gopeed", gopeed) err = engine.Runtime.Set("gopeed", gopeed)
@@ -343,13 +360,20 @@ func doTrigger[T any](d *Downloader, event ActivationEvent, req *base.Request, c
gopeed.Logger.logger.Error().Err(err).Msgf("[%s] call function failed: %s", ext.buildIdentity(), event) gopeed.Logger.logger.Error().Err(err).Msgf("[%s] call function failed: %s", ext.buildIdentity(), event)
return return
} }
handler(ext, gopeed, ctx) if handler != nil {
handler(ext, gopeed, ctx)
}
} }
}() }()
} }
} }
} }
return
// Only return MessageError
if me, ok := gojautil.AssertError[*gojaerror.MessageError](err); ok {
return me
}
return nil
} }
func (d *Downloader) ExtensionPath(ext *Extension) string { func (d *Downloader) ExtensionPath(ext *Extension) string {
@@ -577,10 +601,10 @@ func (h InstanceEvents) OnStart(fn goja.Callable) {
h.register(EventOnStart, fn) h.register(EventOnStart, fn)
} }
//func (h InstanceEvents) OnError(fn goja.Callable) { func (h InstanceEvents) OnError(fn goja.Callable) {
// h.register(HookEventOnError, fn) h.register(EventOnError, fn)
//} }
//
//func (h InstanceEvents) OnDone(fn goja.Callable) { //func (h InstanceEvents) OnDone(fn goja.Callable) {
// h.register(HookEventOnDone, fn) // h.register(HookEventOnDone, fn)
//} //}
@@ -649,7 +673,33 @@ type OnResolveContext struct {
} }
type OnStartContext struct { type OnStartContext struct {
Task *Task `json:"task"` Task *ExtensionTask `json:"task"`
}
type OnErrorContext struct {
Task *ExtensionTask `json:"task"`
Error error `json:"error"`
}
type ExtensionTask struct {
*Task
download *Downloader
}
func NewExtensionTask(download *Downloader, task *Task) *ExtensionTask {
return &ExtensionTask{
Task: task.clone(),
download: download,
}
}
func (t *ExtensionTask) Continue() error {
return t.download.Continue(t.ID)
}
func (t *ExtensionTask) Pause() error {
return t.download.Pause(t.ID)
} }
func parseSettings(settings []*Setting) map[string]any { func parseSettings(settings []*Setting) map[string]any {
+66 -20
View File
@@ -4,6 +4,7 @@ import (
"errors" "errors"
"github.com/GopeedLab/gopeed/internal/logger" "github.com/GopeedLab/gopeed/internal/logger"
"github.com/GopeedLab/gopeed/pkg/base" "github.com/GopeedLab/gopeed/pkg/base"
gojaerror "github.com/GopeedLab/gopeed/pkg/download/engine/inject/error"
"github.com/dop251/goja" "github.com/dop251/goja"
"os" "os"
"testing" "testing"
@@ -165,7 +166,7 @@ func TestDownloader_UpgradeExtension(t *testing.T) {
}) })
} }
func TestDownloader_ExtensionByOnStart(t *testing.T) { func TestDownloader_Extension_OnStart(t *testing.T) {
downloadAndCheck := func(req *base.Request) { downloadAndCheck := func(req *base.Request) {
setupDownloader(func(downloader *Downloader) { setupDownloader(func(downloader *Downloader) {
if _, err := downloader.InstallExtensionByFolder("./testdata/extensions/on_start", false); err != nil { if _, err := downloader.InstallExtensionByFolder("./testdata/extensions/on_start", false); err != nil {
@@ -192,10 +193,10 @@ func TestDownloader_ExtensionByOnStart(t *testing.T) {
} }
task := downloader.GetTask(id) task := downloader.GetTask(id)
if task.Meta.Req.URL != "https://github.com" { if task.Meta.Req.URL != "https://github.com" {
panic("extension on start modify url error") t.Fatalf("except url: https://github.com, actual: %s", task.Meta.Req.URL)
} }
if task.Meta.Req.Labels["modified"] != "true" { if task.Meta.Req.Labels["modified"] != "true" {
panic("extension on start modify label error") t.Fatalf("except label: modified=true, actual: %s", task.Meta.Req.Labels["modified"])
} }
}) })
} }
@@ -207,13 +208,54 @@ func TestDownloader_ExtensionByOnStart(t *testing.T) {
// label match // label match
downloadAndCheck(&base.Request{ downloadAndCheck(&base.Request{
URL: "https://xxx.com", URL: "https://test.com",
Labels: map[string]string{ Labels: map[string]string{
"test": "true", "test": "true",
}, },
}) })
} }
func TestDownloader_Extension_OnError(t *testing.T) {
setupDownloader(func(downloader *Downloader) {
if _, err := downloader.InstallExtensionByFolder("./testdata/extensions/on_error", false); err != nil {
t.Fatal(err)
}
errCh := make(chan error, 1)
downloader.Listener(func(event *Event) {
if event.Key == EventKeyFinally {
errCh <- event.Err
}
})
id, err := downloader.CreateDirect(&base.Request{
URL: "https://github.com/gopeed/test/404",
Labels: map[string]string{
"test": "true",
},
}, nil)
if err != nil {
t.Fatal(err)
}
select {
case err = <-errCh:
break
case <-time.After(time.Second * 10):
err = errors.New("timeout")
}
if err != nil {
panic("extension on error download error: " + err.Error())
}
// extension on error modify url and continue download
task := downloader.GetTask(id)
if task.Meta.Req.URL != "https://github.com" {
t.Fatalf("except url: https://github.com, actual: %s", task.Meta.Req.URL)
}
if task.Status != base.DownloadStatusDone {
t.Fatalf("except status is done, actual: %s", task.Status)
}
})
}
func TestDownloader_Extension_Errors(t *testing.T) { func TestDownloader_Extension_Errors(t *testing.T) {
setupDownloader(func(downloader *Downloader) { setupDownloader(func(downloader *Downloader) {
if _, err := downloader.InstallExtensionByFolder("./testdata/extensions/script_error", false); err != nil { if _, err := downloader.InstallExtensionByFolder("./testdata/extensions/script_error", false); err != nil {
@@ -244,6 +286,26 @@ func TestDownloader_Extension_Errors(t *testing.T) {
t.Fatal("function error catch failed") t.Fatal("function error catch failed")
} }
}) })
setupDownloader(func(downloader *Downloader) {
if _, err := downloader.InstallExtensionByFolder("./testdata/extensions/message_error", false); err != nil {
t.Fatal(err)
}
_, err := downloader.Resolve(&base.Request{
URL: "https://github.com/test",
})
if err == nil {
t.Fatalf("except error, but got nil")
}
me, ok := err.(*gojaerror.MessageError)
if !ok {
t.Fatalf("except MessageError type, but got %s", err)
}
want := "test"
if me.Error() != want {
t.Fatalf("except MessageError message %s, but got %s", want, me.Message)
}
})
} }
func TestDownloader_Extension_Settings(t *testing.T) { func TestDownloader_Extension_Settings(t *testing.T) {
@@ -336,22 +398,6 @@ func TestDownloader_DeleteExtension(t *testing.T) {
}) })
} }
func TestDownloader_Extension_OnResolve(t *testing.T) {
setupDownloader(func(downloader *Downloader) {
installedExt, err := downloader.InstallExtensionByFolder("./testdata/extensions/settings_all", false)
if err != nil {
t.Fatal(err)
}
if err := downloader.DeleteExtension(installedExt.Identity); err != nil {
t.Fatal(err)
}
extensions := downloader.GetExtensions()
if len(extensions) != 0 {
t.Fatal("extension delete fail")
}
})
}
func TestDownloader_Extension_Logger(t *testing.T) { func TestDownloader_Extension_Logger(t *testing.T) {
logger := logger.NewLogger(false, "") logger := logger.NewLogger(false, "")
il := newInstanceLogger(&Extension{ il := newInstanceLogger(&Extension{
+11 -41
View File
@@ -8,7 +8,6 @@ import (
"github.com/GopeedLab/gopeed/pkg/base" "github.com/GopeedLab/gopeed/pkg/base"
"github.com/GopeedLab/gopeed/pkg/util" "github.com/GopeedLab/gopeed/pkg/util"
gonanoid "github.com/matoous/go-nanoid/v2" gonanoid "github.com/matoous/go-nanoid/v2"
"net/url"
"sync" "sync"
"time" "time"
) )
@@ -22,12 +21,15 @@ type Task struct {
ID string `json:"id"` ID string `json:"id"`
Meta *fetcher.FetcherMeta `json:"meta"` Meta *fetcher.FetcherMeta `json:"meta"`
Status base.Status `json:"status"` Status base.Status `json:"status"`
Protocol string `json:"protocol"`
Progress *Progress `json:"progress"` Progress *Progress `json:"progress"`
CreatedAt time.Time `json:"createdAt"` CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
fetcherBuilder fetcher.FetcherBuilder fetcherBuilder fetcher.FetcherBuilder
fetcher fetcher.Fetcher fetcher fetcher.Fetcher
timer *util.Timer timer *util.Timer
statusLock *sync.Mutex
lock *sync.Mutex lock *sync.Mutex
speedArr []int64 speedArr []int64
} }
@@ -41,9 +43,15 @@ func NewTask() *Task {
ID: id, ID: id,
Status: base.DownloadStatusReady, Status: base.DownloadStatusReady,
CreatedAt: time.Now(), CreatedAt: time.Now(),
UpdatedAt: time.Now(),
} }
} }
func (t *Task) updateStatus(status base.Status) {
t.UpdatedAt = time.Now()
t.Status = status
}
func (t *Task) clone() *Task { func (t *Task) clone() *Task {
return &Task{ return &Task{
ID: t.ID, ID: t.ID,
@@ -51,6 +59,7 @@ func (t *Task) clone() *Task {
Status: t.Status, Status: t.Status,
Progress: t.Progress, Progress: t.Progress,
CreatedAt: t.CreatedAt, CreatedAt: t.CreatedAt,
UpdatedAt: t.UpdatedAt,
} }
} }
@@ -76,7 +85,7 @@ type DownloaderConfig struct {
ProductionMode bool ProductionMode bool
*DownloaderStoreConfig *base.DownloaderStoreConfig
} }
func (cfg *DownloaderConfig) Init() *DownloaderConfig { func (cfg *DownloaderConfig) Init() *DownloaderConfig {
@@ -97,42 +106,3 @@ func (cfg *DownloaderConfig) Init() *DownloaderConfig {
} }
return cfg return cfg
} }
// DownloaderStoreConfig is the config that can restore the downloader.
type DownloaderStoreConfig struct {
FirstLoad bool `json:"-"` // FirstLoad is the flag that the config is first time init and not from store
DownloadDir string `json:"downloadDir"` // DownloadDir is the default directory to save the downloaded files
MaxRunning int `json:"maxRunning"` // MaxRunning is the max running download count
ProtocolConfig map[string]any `json:"protocolConfig"` // ProtocolConfig is special config for each protocol
Extra map[string]any `json:"extra"`
Proxy *DownloaderProxyConfig `json:"proxy"`
}
func (cfg *DownloaderStoreConfig) Init() *DownloaderStoreConfig {
if cfg.MaxRunning == 0 {
cfg.MaxRunning = 5
}
if cfg.Proxy == nil {
cfg.Proxy = &DownloaderProxyConfig{}
}
return cfg
}
func (cfg *DownloaderStoreConfig) ProxyUrl() *url.URL {
if cfg.Proxy == nil {
return nil
}
if cfg.Proxy.Enable == false || cfg.Proxy.Scheme == "" || cfg.Proxy.Host == "" {
return nil
}
return util.BuildProxyUrl(cfg.Proxy.Scheme, cfg.Proxy.Host, cfg.Proxy.Usr, cfg.Proxy.Pwd)
}
type DownloaderProxyConfig struct {
Enable bool `json:"enable"`
Scheme string `json:"scheme"`
Host string `json:"host"`
Usr string `json:"usr"`
Pwd string `json:"pwd"`
}
@@ -1,5 +1,6 @@
gopeed.events.onResolve(async function (ctx) { gopeed.events.onResolve(async function (ctx) {
const aaa = {}; const aaa = {};
// access undefined property
gopeed.logger.info(aaa.bbb.ccc); gopeed.logger.info(aaa.bbb.ccc);
ctx.res = { ctx.res = {
@@ -0,0 +1,3 @@
gopeed.events.onResolve(async function (ctx) {
throw new MessageError("test");
});
@@ -0,0 +1,16 @@
{
"name": "message-error",
"title": "gopeed extension message error test",
"version": "0.0.1",
"scripts": [
{
"event": "onResolve",
"match": {
"urls": [
"*://github.com/*"
]
},
"entry": "index.js"
}
]
}
+8
View File
@@ -0,0 +1,8 @@
gopeed.events.onError(async function (ctx) {
gopeed.logger.info("url", ctx.task.meta.req.url);
gopeed.logger.info("error", ctx.error);
ctx.task.meta.req.url = "https://github.com";
ctx.task.pause();
ctx.task.continue();
});
+16
View File
@@ -0,0 +1,16 @@
{
"name": "on-error",
"title": "gopeed extension on error event test",
"version": "0.0.1",
"scripts": [
{
"event": "onError",
"match": {
"labels": [
"test"
]
},
"entry": "index.js"
}
]
}
+1
View File
@@ -1,4 +1,5 @@
gopeed.events.onStart(async function (ctx) { gopeed.events.onStart(async function (ctx) {
gopeed.logger.info("url", ctx.task.meta.req.url);
ctx.task.meta.req.url = "https://github.com"; ctx.task.meta.req.url = "https://github.com";
ctx.task.meta.req.labels['modified'] = 'true'; ctx.task.meta.req.labels['modified'] = 'true';
}); });
+9
View File
@@ -3,3 +3,12 @@ package bt
type ReqExtra struct { type ReqExtra struct {
Trackers []string `json:"trackers"` Trackers []string `json:"trackers"`
} }
// Stats for download
type Stats struct {
// bt stats
// health indicators of torrents, from large to small, ConnectedSeeders are also the key to the health of seed resources
TotalPeers int `json:"totalPeers"`
ActivePeers int `json:"activePeers"`
ConnectedSeeders int `json:"connectedSeeders"`
}
+8
View File
@@ -8,4 +8,12 @@ type ReqExtra struct {
type OptsExtra struct { type OptsExtra struct {
Connections int `json:"connections"` Connections int `json:"connections"`
// AutoTorrent when task download complete, and it is a .torrent file, it will be auto create a new task for the torrent file
AutoTorrent bool `json:"autoTorrent"`
}
// Stats for download
type Stats struct {
// http stats
// health indicators of http
} }
+38 -16
View File
@@ -104,6 +104,17 @@ func DeleteTask(w http.ResponseWriter, r *http.Request) {
WriteJson(w, model.NewNilResult()) WriteJson(w, model.NewNilResult())
} }
func DeleteTasks(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
status := r.Form["status"]
force := r.FormValue("force")
if err := Downloader.DeleteByStatues(convertStatues(status), force == "true"); err != nil {
WriteJson(w, model.NewErrorResult(err.Error()))
return
}
WriteJson(w, model.NewNilResult())
}
func GetTask(w http.ResponseWriter, r *http.Request) { func GetTask(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r) vars := mux.Vars(r)
taskId := vars["id"] taskId := vars["id"]
@@ -122,20 +133,8 @@ func GetTask(w http.ResponseWriter, r *http.Request) {
func GetTasks(w http.ResponseWriter, r *http.Request) { func GetTasks(w http.ResponseWriter, r *http.Request) {
r.ParseForm() r.ParseForm()
status := r.Form["status"] status := r.Form["status"]
tasks := Downloader.GetTasks() tasks := Downloader.GetTasksByStatues(convertStatues(status))
if len(status) == 0 { WriteJson(w, model.NewOkResult(tasks))
WriteJson(w, model.NewOkResult(tasks))
return
}
result := make([]*download.Task, 0)
for _, task := range tasks {
for _, s := range status {
if task.Status == base.Status(s) {
result = append(result, task)
}
}
}
WriteJson(w, model.NewOkResult(result))
} }
func GetConfig(w http.ResponseWriter, r *http.Request) { func GetConfig(w http.ResponseWriter, r *http.Request) {
@@ -143,7 +142,7 @@ func GetConfig(w http.ResponseWriter, r *http.Request) {
} }
func PutConfig(w http.ResponseWriter, r *http.Request) { func PutConfig(w http.ResponseWriter, r *http.Request) {
var cfg download.DownloaderStoreConfig var cfg base.DownloaderStoreConfig
if ReadJson(r, w, &cfg) { if ReadJson(r, w, &cfg) {
if err := Downloader.PutConfig(&cfg); err != nil { if err := Downloader.PutConfig(&cfg); err != nil {
WriteJson(w, model.NewErrorResult(err.Error())) WriteJson(w, model.NewErrorResult(err.Error()))
@@ -282,12 +281,35 @@ func DoProxy(w http.ResponseWriter, r *http.Request) {
w.Write(buf) w.Write(buf)
} }
func GetStats(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
taskId := vars["id"]
if taskId == "" {
WriteJson(w, model.NewErrorResult("param invalid: id", model.CodeInvalidParam))
return
}
statsResult, err := Downloader.Stats(taskId)
if err != nil {
writeError(w, err.Error())
return
}
WriteJson(w, model.NewOkResult(statsResult))
}
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) { func writeError(w http.ResponseWriter, msg string) {
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(msg)) w.Write([]byte(msg))
} }
func getServerConfig() *download.DownloaderStoreConfig { func getServerConfig() *base.DownloaderStoreConfig {
cfg, _ := Downloader.GetConfig() cfg, _ := Downloader.GetConfig()
return cfg return cfg
} }
+18
View File
@@ -3,11 +3,13 @@ package rest
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"fmt"
"github.com/GopeedLab/gopeed/pkg/download" "github.com/GopeedLab/gopeed/pkg/download"
"github.com/GopeedLab/gopeed/pkg/rest/model" "github.com/GopeedLab/gopeed/pkg/rest/model"
"github.com/GopeedLab/gopeed/pkg/util" "github.com/GopeedLab/gopeed/pkg/util"
"github.com/gorilla/handlers" "github.com/gorilla/handlers"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/pkg/errors"
"net" "net"
"net/http" "net/http"
"os" "os"
@@ -93,8 +95,10 @@ func BuildServer(startCfg *model.StartConfig) (*http.Server, net.Listener, error
r.Methods(http.MethodPut).Path("/api/v1/tasks/pause").HandlerFunc(PauseAllTask) r.Methods(http.MethodPut).Path("/api/v1/tasks/pause").HandlerFunc(PauseAllTask)
r.Methods(http.MethodPut).Path("/api/v1/tasks/continue").HandlerFunc(ContinueAllTask) r.Methods(http.MethodPut).Path("/api/v1/tasks/continue").HandlerFunc(ContinueAllTask)
r.Methods(http.MethodDelete).Path("/api/v1/tasks/{id}").HandlerFunc(DeleteTask) r.Methods(http.MethodDelete).Path("/api/v1/tasks/{id}").HandlerFunc(DeleteTask)
r.Methods(http.MethodDelete).Path("/api/v1/tasks").HandlerFunc(DeleteTasks)
r.Methods(http.MethodGet).Path("/api/v1/tasks/{id}").HandlerFunc(GetTask) 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").HandlerFunc(GetTasks)
r.Methods(http.MethodGet).Path("/api/v1/tasks/{id}/stats").HandlerFunc(GetStats)
r.Methods(http.MethodGet).Path("/api/v1/config").HandlerFunc(GetConfig) r.Methods(http.MethodGet).Path("/api/v1/config").HandlerFunc(GetConfig)
r.Methods(http.MethodPut).Path("/api/v1/config").HandlerFunc(PutConfig) r.Methods(http.MethodPut).Path("/api/v1/config").HandlerFunc(PutConfig)
r.Methods(http.MethodPost).Path("/api/v1/extensions").HandlerFunc(InstallExtension) r.Methods(http.MethodPost).Path("/api/v1/extensions").HandlerFunc(InstallExtension)
@@ -131,6 +135,20 @@ func BuildServer(startCfg *model.StartConfig) (*http.Server, net.Listener, error
}) })
} }
// 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))
Downloader.Logger.Error().Stack().Err(err).Msg("http server panic")
WriteJson(w, model.NewErrorResult(err.Error(), model.CodeError))
}
}()
h.ServeHTTP(w, r)
})
})
srv = &http.Server{Handler: handlers.CORS( srv = &http.Server{Handler: handlers.CORS(
handlers.AllowedHeaders([]string{"Content-Type", "X-Api-Token", "X-Target-Uri"}), handlers.AllowedHeaders([]string{"Content-Type", "X-Api-Token", "X-Target-Uri"}),
handlers.AllowedMethods([]string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"}), handlers.AllowedMethods([]string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"}),
+57 -3
View File
@@ -66,7 +66,7 @@ var (
func TestResolve(t *testing.T) { func TestResolve(t *testing.T) {
doTest(func() { doTest(func() {
resp := httpRequestCheckOk[*download.ResolveResult](http.MethodPost, "/api/v1/resolve", taskReq) resp := httpRequestCheckOk[*download.ResolveResult](http.MethodPost, "/api/v1/resolve", taskReq)
if !test.JsonEqual(taskRes, resp.Res) { if !test.AssertResourceEqual(taskRes, resp.Res) {
t.Errorf("Resolve() got = %v, want %v", test.ToJson(resp.Res), test.ToJson(taskRes)) t.Errorf("Resolve() got = %v, want %v", test.ToJson(resp.Res), test.ToJson(taskRes))
} }
}) })
@@ -204,12 +204,14 @@ func TestPauseAllAndContinueALLTasks(t *testing.T) {
// continue all // continue all
httpRequestCheckOk[any](http.MethodPut, "/api/v1/tasks/continue", nil) httpRequestCheckOk[any](http.MethodPut, "/api/v1/tasks/continue", nil)
time.Sleep(time.Millisecond * 100)
tasks := httpRequestCheckOk[[]*download.Task](http.MethodGet, fmt.Sprintf("/api/v1/tasks?status=%s", base.DownloadStatusRunning), nil) tasks := httpRequestCheckOk[[]*download.Task](http.MethodGet, fmt.Sprintf("/api/v1/tasks?status=%s", base.DownloadStatusRunning), nil)
if len(tasks) != cfg.MaxRunning { if len(tasks) != cfg.MaxRunning {
t.Errorf("ContinueAllTasks() got = %v, want %v", len(tasks), cfg.MaxRunning) t.Errorf("ContinueAllTasks() got = %v, want %v", len(tasks), cfg.MaxRunning)
} }
// pause all // pause all
httpRequestCheckOk[any](http.MethodPut, "/api/v1/tasks/pause", nil) httpRequestCheckOk[any](http.MethodPut, "/api/v1/tasks/pause", nil)
time.Sleep(time.Millisecond * 100)
tasks = httpRequestCheckOk[[]*download.Task](http.MethodGet, fmt.Sprintf("/api/v1/tasks?status=%s", base.DownloadStatusPause), nil) tasks = httpRequestCheckOk[[]*download.Task](http.MethodGet, fmt.Sprintf("/api/v1/tasks?status=%s", base.DownloadStatusPause), nil)
if len(tasks) != total { if len(tasks) != total {
t.Errorf("PauseAllTasks() got = %v, want %v", len(tasks), total) t.Errorf("PauseAllTasks() got = %v, want %v", len(tasks), total)
@@ -240,6 +242,58 @@ func TestDeleteTaskForce(t *testing.T) {
}) })
} }
func TestDeleteAllTasks(t *testing.T) {
doTest(func() {
taskCount := 3
var wg sync.WaitGroup
wg.Add(taskCount)
Downloader.Listener(func(event *download.Event) {
if event.Key == download.EventKeyFinally {
wg.Done()
}
})
for i := 0; i < taskCount; i++ {
httpRequestCheckOk[string](http.MethodPost, "/api/v1/tasks", createReq)
}
wg.Wait()
httpRequestCheckOk[any](http.MethodDelete, "/api/v1/tasks?force=true", nil)
tasks := httpRequestCheckOk[[]*download.Task](http.MethodGet, "/api/v1/tasks", nil)
if len(tasks) != 0 {
t.Errorf("DeleteTasks() got = %v, want %v", len(tasks), 0)
}
})
}
func TestDeleteTasksByStatues(t *testing.T) {
doTest(func() {
taskCount := 3
var wg sync.WaitGroup
wg.Add(taskCount)
Downloader.Listener(func(event *download.Event) {
if event.Key == download.EventKeyFinally {
wg.Done()
}
})
for i := 0; i < taskCount; i++ {
httpRequestCheckOk[string](http.MethodPost, "/api/v1/tasks", createReq)
}
wg.Wait()
httpRequestCheckOk[any](http.MethodDelete, fmt.Sprintf("/api/v1/tasks?status=%s&force=true", base.DownloadStatusDone), nil)
tasks := httpRequestCheckOk[[]*download.Task](http.MethodGet, "/api/v1/tasks", nil)
if len(tasks) != 0 {
t.Errorf("DeleteTasks() got = %v, want %v", len(tasks), 0)
}
})
}
func TestGetTasks(t *testing.T) { func TestGetTasks(t *testing.T) {
doTest(func() { doTest(func() {
var wg sync.WaitGroup var wg sync.WaitGroup
@@ -270,7 +324,7 @@ func TestGetTasks(t *testing.T) {
func TestGetAndPutConfig(t *testing.T) { func TestGetAndPutConfig(t *testing.T) {
doTest(func() { doTest(func() {
cfg := httpRequestCheckOk[*download.DownloaderStoreConfig](http.MethodGet, "/api/v1/config", nil) cfg := httpRequestCheckOk[*base.DownloaderStoreConfig](http.MethodGet, "/api/v1/config", nil)
cfg.DownloadDir = "./download" cfg.DownloadDir = "./download"
cfg.Extra = map[string]any{ cfg.Extra = map[string]any{
"serverConfig": &Config{ "serverConfig": &Config{
@@ -281,7 +335,7 @@ func TestGetAndPutConfig(t *testing.T) {
} }
httpRequestCheckOk[any](http.MethodPut, "/api/v1/config", cfg) httpRequestCheckOk[any](http.MethodPut, "/api/v1/config", cfg)
newCfg := httpRequestCheckOk[*download.DownloaderStoreConfig](http.MethodGet, "/api/v1/config", nil) newCfg := httpRequestCheckOk[*base.DownloaderStoreConfig](http.MethodGet, "/api/v1/config", nil)
if !test.JsonEqual(cfg, newCfg) { if !test.JsonEqual(cfg, newCfg) {
t.Errorf("GetAndPutConfig() got = %v, want %v", test.ToJson(newCfg), test.ToJson(cfg)) t.Errorf("GetAndPutConfig() got = %v, want %v", test.ToJson(newCfg), test.ToJson(cfg))
} }
+1 -1
View File
@@ -5,7 +5,7 @@ import (
"math" "math"
) )
var unitArr = []string{"B", "KB", "MB", "GB", "TB", "EB"} var unitArr = []string{"B", "KB", "MB", "GB", "TB", "PB", "EB"}
func ByteFmt(size int64) string { func ByteFmt(size int64) string {
if size == 0 { if size == 0 {
+11
View File
@@ -183,3 +183,14 @@ func IsExistsFile(path string) bool {
} }
return false return false
} }
// ReplaceInvalidFilename replace invalid path characters
func ReplaceInvalidFilename(path string) string {
if path == "" {
return ""
}
for _, char := range invalidPathChars {
path = strings.ReplaceAll(path, char, "_")
}
return path
}
+6
View File
@@ -0,0 +1,6 @@
//go:build !windows
// +build !windows
package util
var invalidPathChars = []string{`/`, `:`}
+47
View File
@@ -178,3 +178,50 @@ func TestIsExistsFile(t *testing.T) {
}) })
} }
} }
func TestReplaceInvalidFilename(t *testing.T) {
type args struct {
path string
}
tests := []struct {
name string
args args
want string
}{
{
name: "blank",
args: args{
path: "",
},
want: "",
},
{
name: "normal",
args: args{
path: "test.txt",
},
want: "test.txt",
},
{
name: "case1",
args: args{
path: "te/st.txt",
},
want: "te_st.txt",
},
{
name: "case2",
args: args{
path: "te/st:.txt",
},
want: "te_st_.txt",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := ReplaceInvalidFilename(tt.args.path); got != tt.want {
t.Errorf("ReplaceInvalidFilename() = %v, want %v", got, tt.want)
}
})
}
}
+3
View File
@@ -0,0 +1,3 @@
package util
var invalidPathChars = []string{`\`, `/`, `:`, `*`, `?`, `"`, `<`, `>`, `|`}
+12
View File
@@ -2,6 +2,7 @@ package util
import ( import (
"encoding/base64" "encoding/base64"
"net/http"
"net/url" "net/url"
"regexp" "regexp"
"strings" "strings"
@@ -51,3 +52,14 @@ func BuildProxyUrl(scheme, host, usr, pwd string) *url.URL {
Host: host, Host: host,
} }
} }
// ProxyUrlToHandler gets the proxy handler from the proxy url.
func ProxyUrlToHandler(proxyUrl *url.URL) func(*http.Request) (*url.URL, error) {
if proxyUrl == nil {
return nil
}
if proxyUrl.Scheme == "system" {
return http.ProxyFromEnvironment
}
return http.ProxyURL(proxyUrl)
}
+5 -1
View File
@@ -63,4 +63,8 @@ windows/flutter/generated_plugin_registrant.cc
windows/flutter/generated_plugin_registrant.h windows/flutter/generated_plugin_registrant.h
windows/flutter/generated_plugins.cmake windows/flutter/generated_plugins.cmake
/extensions /extensions
# Hive database files
database.hive
database.lock
@@ -2,6 +2,7 @@
package="com.gopeed"> package="com.gopeed">
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<application <application
android:name="${applicationName}" android:name="${applicationName}"
@@ -71,6 +72,13 @@
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<action android:name="android.intent.action.SEND_MULTIPLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="*/*" />
</intent-filter>
</activity> </activity>
<!-- Add android:stopWithTask option only when necessary. --> <!-- Add android:stopWithTask option only when necessary. -->
+1
View File
@@ -0,0 +1 @@
output: dist/
Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 714 B

After

Width:  |  Height:  |  Size: 890 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 KiB

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

After

Width:  |  Height:  |  Size: 7.7 KiB

+30 -13
View File
@@ -4,13 +4,16 @@ part 'downloader_config.g.dart';
@JsonSerializable(explicitToJson: true) @JsonSerializable(explicitToJson: true)
class DownloaderConfig { class DownloaderConfig {
String downloadDir = ''; String downloadDir;
int maxRunning = 0; int maxRunning;
ProtocolConfig protocolConfig = ProtocolConfig(); ProtocolConfig protocolConfig = ProtocolConfig();
ExtraConfig extra = ExtraConfig(); ExtraConfig extra = ExtraConfig();
ProxyConfig proxy = ProxyConfig(); ProxyConfig proxy = ProxyConfig();
DownloaderConfig(); DownloaderConfig({
this.downloadDir = '',
this.maxRunning = 0,
});
factory DownloaderConfig.fromJson(Map<String, dynamic> json) => factory DownloaderConfig.fromJson(Map<String, dynamic> json) =>
_$DownloaderConfigFromJson(json); _$DownloaderConfigFromJson(json);
@@ -31,11 +34,16 @@ class ProtocolConfig {
@JsonSerializable() @JsonSerializable()
class HttpConfig { class HttpConfig {
String userAgent = String userAgent;
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36'; int connections;
int connections = 0; bool useServerCtime;
HttpConfig(); HttpConfig({
this.userAgent =
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36',
this.connections = 16,
this.useServerCtime = false,
});
factory HttpConfig.fromJson(Map<String, dynamic> json) => factory HttpConfig.fromJson(Map<String, dynamic> json) =>
_$HttpConfigFromJson(json); _$HttpConfigFromJson(json);
@@ -69,13 +77,21 @@ class ExtraConfig {
@JsonSerializable() @JsonSerializable()
class ProxyConfig { class ProxyConfig {
bool enable = false; bool enable;
String scheme = ''; bool system;
String host = ''; String scheme;
String usr = ''; String host;
String pwd = ''; String usr;
String pwd;
ProxyConfig(); ProxyConfig({
this.enable = false,
this.system = false,
this.scheme = '',
this.host = '',
this.usr = '',
this.pwd = '',
});
factory ProxyConfig.fromJson(Map<String, dynamic> json) => factory ProxyConfig.fromJson(Map<String, dynamic> json) =>
_$ProxyConfigFromJson(json); _$ProxyConfigFromJson(json);
@@ -87,6 +103,7 @@ class ProxyConfig {
class ExtraConfigBt { class ExtraConfigBt {
List<String> trackerSubscribeUrls = []; List<String> trackerSubscribeUrls = [];
List<String> subscribeTrackers = []; List<String> subscribeTrackers = [];
bool autoUpdateTrackers = true;
DateTime? lastTrackerUpdateTime; DateTime? lastTrackerUpdateTime;
List<String> customTrackers = []; List<String> customTrackers = [];
@@ -7,9 +7,10 @@ part of 'downloader_config.dart';
// ************************************************************************** // **************************************************************************
DownloaderConfig _$DownloaderConfigFromJson(Map<String, dynamic> json) => DownloaderConfig _$DownloaderConfigFromJson(Map<String, dynamic> json) =>
DownloaderConfig() DownloaderConfig(
..downloadDir = json['downloadDir'] as String downloadDir: json['downloadDir'] as String? ?? '',
..maxRunning = json['maxRunning'] as int maxRunning: (json['maxRunning'] as num?)?.toInt() ?? 0,
)
..protocolConfig = ProtocolConfig.fromJson( ..protocolConfig = ProtocolConfig.fromJson(
json['protocolConfig'] as Map<String, dynamic>?) json['protocolConfig'] as Map<String, dynamic>?)
..extra = ExtraConfig.fromJson(json['extra'] as Map<String, dynamic>?) ..extra = ExtraConfig.fromJson(json['extra'] as Map<String, dynamic>?)
@@ -35,18 +36,22 @@ Map<String, dynamic> _$ProtocolConfigToJson(ProtocolConfig instance) =>
'bt': instance.bt.toJson(), 'bt': instance.bt.toJson(),
}; };
HttpConfig _$HttpConfigFromJson(Map<String, dynamic> json) => HttpConfig() HttpConfig _$HttpConfigFromJson(Map<String, dynamic> json) => HttpConfig(
..userAgent = json['userAgent'] as String userAgent: json['userAgent'] as String? ??
..connections = json['connections'] as int; 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36',
connections: (json['connections'] as num?)?.toInt() ?? 16,
useServerCtime: json['useServerCtime'] as bool? ?? false,
);
Map<String, dynamic> _$HttpConfigToJson(HttpConfig instance) => Map<String, dynamic> _$HttpConfigToJson(HttpConfig instance) =>
<String, dynamic>{ <String, dynamic>{
'userAgent': instance.userAgent, 'userAgent': instance.userAgent,
'connections': instance.connections, 'connections': instance.connections,
'useServerCtime': instance.useServerCtime,
}; };
BtConfig _$BtConfigFromJson(Map<String, dynamic> json) => BtConfig() BtConfig _$BtConfigFromJson(Map<String, dynamic> json) => BtConfig()
..listenPort = json['listenPort'] as int ..listenPort = (json['listenPort'] as num).toInt()
..trackers = ..trackers =
(json['trackers'] as List<dynamic>).map((e) => e as String).toList(); (json['trackers'] as List<dynamic>).map((e) => e as String).toList();
@@ -67,16 +72,19 @@ Map<String, dynamic> _$ExtraConfigToJson(ExtraConfig instance) =>
'bt': instance.bt.toJson(), 'bt': instance.bt.toJson(),
}; };
ProxyConfig _$ProxyConfigFromJson(Map<String, dynamic> json) => ProxyConfig() ProxyConfig _$ProxyConfigFromJson(Map<String, dynamic> json) => ProxyConfig(
..enable = json['enable'] as bool enable: json['enable'] as bool? ?? false,
..scheme = json['scheme'] as String system: json['system'] as bool? ?? false,
..host = json['host'] as String scheme: json['scheme'] as String? ?? '',
..usr = json['usr'] as String host: json['host'] as String? ?? '',
..pwd = json['pwd'] as String; usr: json['usr'] as String? ?? '',
pwd: json['pwd'] as String? ?? '',
);
Map<String, dynamic> _$ProxyConfigToJson(ProxyConfig instance) => Map<String, dynamic> _$ProxyConfigToJson(ProxyConfig instance) =>
<String, dynamic>{ <String, dynamic>{
'enable': instance.enable, 'enable': instance.enable,
'system': instance.system,
'scheme': instance.scheme, 'scheme': instance.scheme,
'host': instance.host, 'host': instance.host,
'usr': instance.usr, 'usr': instance.usr,
@@ -91,6 +99,7 @@ ExtraConfigBt _$ExtraConfigBtFromJson(Map<String, dynamic> json) =>
..subscribeTrackers = (json['subscribeTrackers'] as List<dynamic>) ..subscribeTrackers = (json['subscribeTrackers'] as List<dynamic>)
.map((e) => e as String) .map((e) => e as String)
.toList() .toList()
..autoUpdateTrackers = json['autoUpdateTrackers'] as bool
..lastTrackerUpdateTime = json['lastTrackerUpdateTime'] == null ..lastTrackerUpdateTime = json['lastTrackerUpdateTime'] == null
? null ? null
: DateTime.parse(json['lastTrackerUpdateTime'] as String) : DateTime.parse(json['lastTrackerUpdateTime'] as String)
@@ -102,6 +111,7 @@ Map<String, dynamic> _$ExtraConfigBtToJson(ExtraConfigBt instance) {
final val = <String, dynamic>{ final val = <String, dynamic>{
'trackerSubscribeUrls': instance.trackerSubscribeUrls, 'trackerSubscribeUrls': instance.trackerSubscribeUrls,
'subscribeTrackers': instance.subscribeTrackers, 'subscribeTrackers': instance.subscribeTrackers,
'autoUpdateTrackers': instance.autoUpdateTrackers,
}; };
void writeNotNull(String key, dynamic value) { void writeNotNull(String key, dynamic value) {
+2 -1
View File
@@ -12,7 +12,7 @@ class Options {
Options({ Options({
required this.name, required this.name,
required this.path, required this.path,
required this.selectFiles, this.selectFiles = const [],
this.extra, this.extra,
}); });
@@ -25,6 +25,7 @@ class Options {
@JsonSerializable() @JsonSerializable()
class OptsExtraHttp { class OptsExtraHttp {
int connections = 0; int connections = 0;
bool autoTorrent = false;
OptsExtraHttp(); OptsExtraHttp();
+8 -3
View File
@@ -9,8 +9,10 @@ part of 'options.dart';
Options _$OptionsFromJson(Map<String, dynamic> json) => Options( Options _$OptionsFromJson(Map<String, dynamic> json) => Options(
name: json['name'] as String, name: json['name'] as String,
path: json['path'] as String, path: json['path'] as String,
selectFiles: selectFiles: (json['selectFiles'] as List<dynamic>?)
(json['selectFiles'] as List<dynamic>).map((e) => e as int).toList(), ?.map((e) => (e as num).toInt())
.toList() ??
const [],
extra: json['extra'], extra: json['extra'],
); );
@@ -32,9 +34,12 @@ Map<String, dynamic> _$OptionsToJson(Options instance) {
} }
OptsExtraHttp _$OptsExtraHttpFromJson(Map<String, dynamic> json) => OptsExtraHttp _$OptsExtraHttpFromJson(Map<String, dynamic> json) =>
OptsExtraHttp()..connections = json['connections'] as int; OptsExtraHttp()
..connections = (json['connections'] as num).toInt()
..autoTorrent = json['autoTorrent'] as bool;
Map<String, dynamic> _$OptsExtraHttpToJson(OptsExtraHttp instance) => Map<String, dynamic> _$OptsExtraHttpToJson(OptsExtraHttp instance) =>
<String, dynamic>{ <String, dynamic>{
'connections': instance.connections, 'connections': instance.connections,
'autoTorrent': instance.autoTorrent,
}; };
+2 -2
View File
@@ -8,7 +8,7 @@ part of 'resource.dart';
Resource _$ResourceFromJson(Map<String, dynamic> json) => Resource( Resource _$ResourceFromJson(Map<String, dynamic> json) => Resource(
name: json['name'] as String? ?? "", name: json['name'] as String? ?? "",
size: json['size'] as int? ?? 0, size: (json['size'] as num?)?.toInt() ?? 0,
range: json['range'] as bool? ?? false, range: json['range'] as bool? ?? false,
files: (json['files'] as List<dynamic>) files: (json['files'] as List<dynamic>)
.map((e) => FileInfo.fromJson(e as Map<String, dynamic>)) .map((e) => FileInfo.fromJson(e as Map<String, dynamic>))
@@ -27,7 +27,7 @@ Map<String, dynamic> _$ResourceToJson(Resource instance) => <String, dynamic>{
FileInfo _$FileInfoFromJson(Map<String, dynamic> json) => FileInfo( FileInfo _$FileInfoFromJson(Map<String, dynamic> json) => FileInfo(
path: json['path'] as String? ?? "", path: json['path'] as String? ?? "",
name: json['name'] as String, name: json['name'] as String,
size: json['size'] as int? ?? 0, size: (json['size'] as num?)?.toInt() ?? 0,
req: json['req'] == null req: json['req'] == null
? null ? null
: Request.fromJson(json['req'] as Map<String, dynamic>), : Request.fromJson(json['req'] as Map<String, dynamic>),
+1 -1
View File
@@ -11,7 +11,7 @@ Result<T> _$ResultFromJson<T>(
T Function(Object? json) fromJsonT, T Function(Object? json) fromJsonT,
) => ) =>
Result<T>( Result<T>(
code: json['code'] as int, code: (json['code'] as num).toInt(),
msg: json['msg'] as String?, msg: json['msg'] as String?,
data: _$nullableGenericFromJson(json['data'], fromJsonT), data: _$nullableGenericFromJson(json['data'], fromJsonT),
); );
+2
View File
@@ -13,6 +13,7 @@ class Task {
Status status; Status status;
Progress progress; Progress progress;
DateTime createdAt; DateTime createdAt;
DateTime updatedAt;
Task({ Task({
required this.id, required this.id,
@@ -20,6 +21,7 @@ class Task {
required this.status, required this.status,
required this.progress, required this.progress,
required this.createdAt, required this.createdAt,
required this.updatedAt,
}); });
factory Task.fromJson(Map<String, dynamic> json) => _$TaskFromJson(json); factory Task.fromJson(Map<String, dynamic> json) => _$TaskFromJson(json);
+5 -3
View File
@@ -12,6 +12,7 @@ Task _$TaskFromJson(Map<String, dynamic> json) => Task(
status: $enumDecode(_$StatusEnumMap, json['status']), status: $enumDecode(_$StatusEnumMap, json['status']),
progress: Progress.fromJson(json['progress'] as Map<String, dynamic>), progress: Progress.fromJson(json['progress'] as Map<String, dynamic>),
createdAt: DateTime.parse(json['createdAt'] as String), createdAt: DateTime.parse(json['createdAt'] as String),
updatedAt: DateTime.parse(json['updatedAt'] as String),
); );
Map<String, dynamic> _$TaskToJson(Task instance) => <String, dynamic>{ Map<String, dynamic> _$TaskToJson(Task instance) => <String, dynamic>{
@@ -20,6 +21,7 @@ Map<String, dynamic> _$TaskToJson(Task instance) => <String, dynamic>{
'status': _$StatusEnumMap[instance.status]!, 'status': _$StatusEnumMap[instance.status]!,
'progress': instance.progress.toJson(), 'progress': instance.progress.toJson(),
'createdAt': instance.createdAt.toIso8601String(), 'createdAt': instance.createdAt.toIso8601String(),
'updatedAt': instance.updatedAt.toIso8601String(),
}; };
const _$StatusEnumMap = { const _$StatusEnumMap = {
@@ -32,9 +34,9 @@ const _$StatusEnumMap = {
}; };
Progress _$ProgressFromJson(Map<String, dynamic> json) => Progress( Progress _$ProgressFromJson(Map<String, dynamic> json) => Progress(
used: json['used'] as int, used: (json['used'] as num).toInt(),
speed: json['speed'] as int, speed: (json['speed'] as num).toInt(),
downloaded: json['downloaded'] as int, downloaded: (json['downloaded'] as num).toInt(),
); );
Map<String, dynamic> _$ProgressToJson(Progress instance) => <String, dynamic>{ Map<String, dynamic> _$ProgressToJson(Progress instance) => <String, dynamic>{
@@ -1,8 +0,0 @@
import 'package:get/get.dart';
class ApiProvider extends GetConnect {
@override
void onInit() {
httpClient.baseUrl = 'YOUR-API-URL';
}
}
@@ -6,8 +6,9 @@ import 'package:app_links/app_links.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_foreground_task/flutter_foreground_task.dart'; import 'package:flutter_foreground_task/flutter_foreground_task.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:gopeed/database/entity.dart';
import 'package:lecle_downloads_path_provider/lecle_downloads_path_provider.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:tray_manager/tray_manager.dart'; import 'package:tray_manager/tray_manager.dart';
import 'package:uri_to_file/uri_to_file.dart'; import 'package:uri_to_file/uri_to_file.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
@@ -16,6 +17,7 @@ import 'package:window_manager/window_manager.dart';
import '../../../../api/api.dart'; import '../../../../api/api.dart';
import '../../../../api/model/downloader_config.dart'; import '../../../../api/model/downloader_config.dart';
import '../../../../core/common/start_config.dart'; import '../../../../core/common/start_config.dart';
import '../../../../database/database.dart';
import '../../../../i18n/message.dart'; import '../../../../i18n/message.dart';
import '../../../../util/locale_manager.dart'; import '../../../../util/locale_manager.dart';
import '../../../../util/log_util.dart'; import '../../../../util/log_util.dart';
@@ -23,10 +25,6 @@ import '../../../../util/package_info.dart';
import '../../../../util/util.dart'; import '../../../../util/util.dart';
import '../../../routes/app_pages.dart'; import '../../../routes/app_pages.dart';
const _startConfigNetwork = "start.network";
const _startConfigAddress = "start.address";
const _startConfigApiToken = "start.apiToken";
const unixSocketPath = 'gopeed.sock'; const unixSocketPath = 'gopeed.sock';
const allTrackerSubscribeUrls = [ const allTrackerSubscribeUrls = [
@@ -49,7 +47,12 @@ const allTrackerCdns = [
[ [
"https://ghproxy.com/https://raw.githubusercontent.com", "https://ghproxy.com/https://raw.githubusercontent.com",
r".*github.com(/.*)/raw(/.*)" r".*github.com(/.*)/raw(/.*)"
] ],
// mirror.ghproxy.com: https://mirror.ghproxy.com/https://raw.githubusercontent.com/ngosang/trackerslist/master/trackers_all.txt
[
"https://mirror.ghproxy.com/https://raw.githubusercontent.com",
r".*github.com(/.*)/raw(/.*)"
],
]; ];
final allTrackerSubscribeUrlCdns = Map.fromIterable(allTrackerSubscribeUrls, final allTrackerSubscribeUrlCdns = Map.fromIterable(allTrackerSubscribeUrls,
key: (v) => v as String, key: (v) => v as String,
@@ -92,6 +95,9 @@ class AppController extends GetxController with WindowListener, TrayListener {
_initForegroundTask().onError((error, stackTrace) => _initForegroundTask().onError((error, stackTrace) =>
logger.w("initForegroundTask error", error, stackTrace)); logger.w("initForegroundTask error", error, stackTrace));
_initTrackerUpdate().onError((error, stackTrace) =>
logger.w("initTrackerUpdate error", error, stackTrace));
} }
@override @override
@@ -118,6 +124,27 @@ class AppController extends GetxController with WindowListener, TrayListener {
trayManager.popUpContextMenu(); trayManager.popUpContextMenu();
} }
@override
void onWindowMaximize() {
Database.instance.saveWindowState(WindowStateEntity(isMaximized: true));
}
@override
void onWindowUnmaximize() {
Database.instance.saveWindowState(WindowStateEntity(isMaximized: false));
}
final _windowsResizeSave = Util.debounce(() async {
final size = await windowManager.getSize();
Database.instance.saveWindowState(
WindowStateEntity(width: size.width, height: size.height));
}, 500);
@override
void onWindowResize() {
_windowsResizeSave();
}
Future<void> _initDeepLinks() async { Future<void> _initDeepLinks() async {
// currently only support android // currently only support android
if (!Util.isAndroid()) { if (!Util.isAndroid()) {
@@ -247,7 +274,7 @@ class AppController extends GetxController with WindowListener, TrayListener {
} }
Future<void> _toCreate(Uri uri) async { Future<void> _toCreate(Uri uri) async {
final path = uri.scheme == "magnet" final path = (uri.scheme == "magnet" || uri.scheme == "http" || uri.scheme == "https")
? uri.toString() ? uri.toString()
: (await toFile(uri.toString())).path; : (await toFile(uri.toString())).path;
await Get.rootDelegate.offAndToNamed(Routes.CREATE, arguments: path); await Get.rootDelegate.offAndToNamed(Routes.CREATE, arguments: path);
@@ -280,13 +307,10 @@ class AppController extends GetxController with WindowListener, TrayListener {
Future<void> loadStartConfig() async { Future<void> loadStartConfig() async {
final defaultCfg = await _initDefaultStartConfig(); final defaultCfg = await _initDefaultStartConfig();
final prefs = await SharedPreferences.getInstance(); final saveCfg = Database.instance.getStartConfig();
startConfig.value.network = startConfig.value.network = saveCfg?.network ?? defaultCfg.network;
prefs.getString(_startConfigNetwork) ?? defaultCfg.network; startConfig.value.address = saveCfg?.address ?? defaultCfg.address;
startConfig.value.address = startConfig.value.apiToken = saveCfg?.apiToken ?? defaultCfg.apiToken;
prefs.getString(_startConfigAddress) ?? defaultCfg.address;
startConfig.value.apiToken =
prefs.getString(_startConfigApiToken) ?? defaultCfg.apiToken;
} }
Future<void> loadDownloaderConfig() async { Future<void> loadDownloaderConfig() async {
@@ -336,12 +360,13 @@ class AppController extends GetxController with WindowListener, TrayListener {
btConfig.trackers.toSet().toList(); btConfig.trackers.toSet().toList();
} }
Future<void> trackerUpdateOnStart() async { Future<void> _initTrackerUpdate() async {
final btExtConfig = downloaderConfig.value.extra.bt; final btExtConfig = downloaderConfig.value.extra.bt;
final lastUpdateTime = btExtConfig.lastTrackerUpdateTime; final lastUpdateTime = btExtConfig.lastTrackerUpdateTime;
// if last update time is null or more than 1 day, update trackers // if last update time is null or more than 1 day, update trackers
if (lastUpdateTime == null || if (btExtConfig.autoUpdateTrackers &&
lastUpdateTime.difference(DateTime.now()).inDays < 0) { (lastUpdateTime == null ||
lastUpdateTime.difference(DateTime.now()).inDays < 0)) {
try { try {
await trackerUpdate(); await trackerUpdate();
} catch (e) { } catch (e) {
@@ -365,9 +390,6 @@ class AppController extends GetxController with WindowListener, TrayListener {
_initDownloaderConfig() async { _initDownloaderConfig() async {
final config = downloaderConfig.value; final config = downloaderConfig.value;
if (config.protocolConfig.http.connections == 0) {
config.protocolConfig.http.connections = 16;
}
final extra = config.extra; final extra = config.extra;
if (extra.themeMode.isEmpty) { if (extra.themeMode.isEmpty) {
extra.themeMode = ThemeMode.dark.name; extra.themeMode = ThemeMode.dark.name;
@@ -391,9 +413,12 @@ class AppController extends GetxController with WindowListener, TrayListener {
if (Util.isDesktop()) { if (Util.isDesktop()) {
config.downloadDir = (await getDownloadsDirectory())?.path ?? "./"; config.downloadDir = (await getDownloadsDirectory())?.path ?? "./";
} else if (Util.isAndroid()) { } else if (Util.isAndroid()) {
config.downloadDir = (await getExternalStorageDirectory())?.path ?? final downloadDir = (await DownloadsPath.downloadsDirectory())?.path;
(await getApplicationDocumentsDirectory()).path; if (downloadDir != null) {
return; config.downloadDir = '$downloadDir/Gopeed';
} else {
config.downloadDir = (await getApplicationDocumentsDirectory()).path;
}
} else if (Util.isIOS()) { } else if (Util.isIOS()) {
config.downloadDir = (await getApplicationDocumentsDirectory()).path; config.downloadDir = (await getApplicationDocumentsDirectory()).path;
} else { } else {
@@ -403,10 +428,10 @@ class AppController extends GetxController with WindowListener, TrayListener {
} }
Future<void> saveConfig() async { Future<void> saveConfig() async {
final prefs = await SharedPreferences.getInstance(); Database.instance.saveStartConfig(StartConfigEntity(
await prefs.setString(_startConfigNetwork, startConfig.value.network); network: startConfig.value.network,
await prefs.setString(_startConfigAddress, startConfig.value.address); address: startConfig.value.address,
await prefs.setString(_startConfigApiToken, startConfig.value.apiToken); apiToken: startConfig.value.apiToken));
await putConfig(downloaderConfig.value); await putConfig(downloaderConfig.value);
} }
} }
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_foreground_task/flutter_foreground_task.dart';
import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
@@ -14,22 +15,24 @@ class AppView extends GetView<AppController> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final config = controller.downloaderConfig.value; final config = controller.downloaderConfig.value;
return GetMaterialApp.router( return WithForegroundTask(
useInheritedMediaQuery: true, child: GetMaterialApp.router(
debugShowCheckedModeBanner: false, useInheritedMediaQuery: true,
theme: GopeedTheme.light, debugShowCheckedModeBanner: false,
darkTheme: GopeedTheme.dark, theme: GopeedTheme.light,
themeMode: ThemeMode.values.byName(config.extra.themeMode), darkTheme: GopeedTheme.dark,
translations: messages, themeMode: ThemeMode.values.byName(config.extra.themeMode),
locale: toLocale(config.extra.locale), translations: messages,
fallbackLocale: fallbackLocale, locale: toLocale(config.extra.locale),
localizationsDelegates: const [ fallbackLocale: fallbackLocale,
GlobalMaterialLocalizations.delegate, localizationsDelegates: const [
GlobalWidgetsLocalizations.delegate, GlobalMaterialLocalizations.delegate,
GlobalCupertinoLocalizations.delegate, GlobalWidgetsLocalizations.delegate,
], GlobalCupertinoLocalizations.delegate,
supportedLocales: messages.keys.keys.map((e) => toLocale(e)).toList(), ],
getPages: AppPages.routes, supportedLocales: messages.keys.keys.map((e) => toLocale(e)).toList(),
getPages: AppPages.routes,
),
); );
} }
} }
@@ -4,16 +4,15 @@ import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:gopeed/app/modules/history/controller/history_controller.dart';
import 'package:gopeed/app/modules/history/views/history_view.dart';
import 'package:path/path.dart' as path; import 'package:path/path.dart' as path;
import 'package:rounded_loading_button/rounded_loading_button.dart'; import 'package:rounded_loading_button_plus/rounded_loading_button.dart';
import '../../../../api/api.dart'; import '../../../../api/api.dart';
import '../../../../api/model/create_task.dart'; import '../../../../api/model/create_task.dart';
import '../../../../api/model/options.dart'; import '../../../../api/model/options.dart';
import '../../../../api/model/request.dart'; import '../../../../api/model/request.dart';
import '../../../../api/model/resolve_result.dart'; import '../../../../api/model/resolve_result.dart';
import '../../../../api/model/resource.dart'; import '../../../../database/database.dart';
import '../../../../util/input_formatter.dart'; import '../../../../util/input_formatter.dart';
import '../../../../util/message.dart'; import '../../../../util/message.dart';
import '../../../../util/util.dart'; import '../../../../util/util.dart';
@@ -21,6 +20,7 @@ import '../../../routes/app_pages.dart';
import '../../../views/directory_selector.dart'; import '../../../views/directory_selector.dart';
import '../../../views/file_list_view.dart'; import '../../../views/file_list_view.dart';
import '../../app/controllers/app_controller.dart'; import '../../app/controllers/app_controller.dart';
import '../../history/views/history_view.dart';
import '../controllers/create_controller.dart'; import '../controllers/create_controller.dart';
class CreateView extends GetView<CreateController> { class CreateView extends GetView<CreateController> {
@@ -35,7 +35,6 @@ class CreateView extends GetView<CreateController> {
final _httpCookieController = TextEditingController(); final _httpCookieController = TextEditingController();
final _httpRefererController = TextEditingController(); final _httpRefererController = TextEditingController();
final _btTrackerController = TextEditingController(); final _btTrackerController = TextEditingController();
final _historyController = HistoryController();
final _availableSchemes = ["http:", "https:", "magnet:"]; final _availableSchemes = ["http:", "https:", "magnet:"];
@@ -117,7 +116,7 @@ class CreateView extends GetView<CreateController> {
Row(children: [ Row(children: [
Expanded( Expanded(
child: TextFormField( child: TextFormField(
autofocus: true, autofocus: !Util.isMobile(),
controller: _urlController, controller: _urlController,
minLines: 1, minLines: 1,
maxLines: 5, maxLines: 5,
@@ -168,10 +167,7 @@ class CreateView extends GetView<CreateController> {
icon: const Icon(Icons.history_rounded), icon: const Icon(Icons.history_rounded),
onPressed: () async { onPressed: () async {
List<String> resultOfHistories = List<String> resultOfHistories =
await _historyController.getAllHistory(); Database.instance.getCreateHistory() ?? [];
// reversing: display last entered history first
List<String> reverseResultOfHistories =
resultOfHistories.reversed.toList();
// show dialog box to list history // show dialog box to list history
if (context.mounted) { if (context.mounted) {
showGeneralDialog( showGeneralDialog(
@@ -183,16 +179,14 @@ class CreateView extends GetView<CreateController> {
opacity: a1.value, opacity: a1.value,
child: HistoryView( child: HistoryView(
isHistoryListEmpty: isHistoryListEmpty:
reverseResultOfHistories.isEmpty, resultOfHistories.isEmpty,
historyList: ListView.builder( historyList: ListView.builder(
itemCount: itemCount: resultOfHistories.length,
reverseResultOfHistories.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
return GestureDetector( return GestureDetector(
onTap: () { onTap: () {
_urlController.text = _urlController.text =
reverseResultOfHistories[ resultOfHistories[index];
index];
Navigator.pop(context); Navigator.pop(context);
}, },
child: MouseRegion( child: MouseRegion(
@@ -217,9 +211,7 @@ class CreateView extends GetView<CreateController> {
10.0), 10.0),
), ),
child: Text( child: Text(
reverseResultOfHistories[ resultOfHistories[index],
index]
.toString(),
), ),
), ),
), ),
@@ -409,15 +401,20 @@ class CreateView extends GetView<CreateController> {
try { try {
_confirmController.start(); _confirmController.start();
if (_confirmFormKey.currentState!.validate()) { if (_confirmFormKey.currentState!.validate()) {
final submitUrl = Util.isWeb() && controller.fileDataUri.isNotEmpty final isWebFileChosen =
Util.isWeb() && controller.fileDataUri.isNotEmpty;
final submitUrl = isWebFileChosen
? controller.fileDataUri.value ? controller.fileDataUri.value
: _urlController.text; : _urlController.text;
final urls = Util.textToLines(submitUrl); final urls = Util.textToLines(submitUrl);
// Add url to the history // Add url to the history
for (final url in urls) { if (!isWebFileChosen) {
_historyController.addHistory(url); for (final url in urls) {
Database.instance.saveCreateHistory(url);
}
} }
/* /*
Check if is direct download, there has two ways to direct download Check if is direct download, there has two ways to direct download
1. Direct download option is checked 1. Direct download option is checked
@@ -433,7 +430,7 @@ class CreateView extends GetView<CreateController> {
name: isMultiLine ? "" : _renameController.text, name: isMultiLine ? "" : _renameController.text,
path: _pathController.text, path: _pathController.text,
selectFiles: [], selectFiles: [],
extra: parseReqOpts()))); extra: parseReqOptsExtra())));
})); }));
Get.rootDelegate.offNamed(Routes.TASK); Get.rootDelegate.offNamed(Routes.TASK);
} else { } else {
@@ -471,11 +468,10 @@ class CreateView extends GetView<CreateController> {
return reqExtra; return reqExtra;
} }
Object? parseReqOpts() { Object? parseReqOptsExtra() {
return _connectionsController.text.isEmpty return OptsExtraHttp()
? null ..connections = int.tryParse(_connectionsController.text) ?? 0
: (OptsExtraHttp() ..autoTorrent = true;
..connections = int.parse(_connectionsController.text));
} }
String _hitText() { String _hitText() {
@@ -541,7 +537,7 @@ class CreateView extends GetView<CreateController> {
showMessage('tip'.tr, 'noFileSelected'.tr); showMessage('tip'.tr, 'noFileSelected'.tr);
return; return;
} }
final optExtra = parseReqOpts(); final optExtra = parseReqOptsExtra();
if (createFormKey.currentState!.validate()) { if (createFormKey.currentState!.validate()) {
if (rr.id.isEmpty) { if (rr.id.isEmpty) {
// from extension resolve result // from extension resolve result
@@ -7,21 +7,21 @@ import 'package:flutter/services.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart'; import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:form_builder_validators/form_builder_validators.dart'; import 'package:form_builder_validators/form_builder_validators.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:gopeed/app/views/text_button_loading.dart';
import '../../../../api/model/update_extension_settings.dart';
import '../../../../util/mac_secure_util.dart';
import '../../../../util/message.dart';
import 'package:path/path.dart' as path; import 'package:path/path.dart' as path;
import 'package:rounded_loading_button/rounded_loading_button.dart'; import 'package:rounded_loading_button_plus/rounded_loading_button.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
import '../../../../api/api.dart'; import '../../../../api/api.dart';
import '../../../../api/model/extension.dart'; import '../../../../api/model/extension.dart';
import '../../../../api/model/install_extension.dart'; import '../../../../api/model/install_extension.dart';
import '../../../../api/model/switch_extension.dart'; import '../../../../api/model/switch_extension.dart';
import '../../../../api/model/update_extension_settings.dart';
import '../../../../util/mac_secure_util.dart';
import '../../../../util/message.dart';
import '../../../../util/util.dart'; import '../../../../util/util.dart';
import '../../../views/icon_button_loading.dart'; import '../../../views/icon_button_loading.dart';
import '../../../views/responsive_builder.dart'; import '../../../views/responsive_builder.dart';
import '../../../views/text_button_loading.dart';
import '../controllers/extension_controller.dart'; import '../controllers/extension_controller.dart';
class ExtensionView extends GetView<ExtensionController> { class ExtensionView extends GetView<ExtensionController> {
@@ -1,26 +0,0 @@
import 'package:shared_preferences/shared_preferences.dart';
class HistoryController {
// Get History in List<String>
Future<List<String>> getAllHistory() async {
SharedPreferences preferences = await SharedPreferences.getInstance();
List<String>? historyList = preferences.getStringList('history');
if(historyList == null) {
return [];
}
return historyList;
}
// Get Existing History & Add New History
void addHistory(String historyText) async {
SharedPreferences preferences = await SharedPreferences.getInstance();
List<String> existingHistoryData = await getAllHistory();
existingHistoryData.add(historyText);
preferences.setStringList('history', existingHistoryData);
}
void clearHistory() async {
SharedPreferences preferences = await SharedPreferences.getInstance();
preferences.remove('history');
}
}
@@ -1,6 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:gopeed/app/modules/history/controller/history_controller.dart'; import 'package:gopeed/database/database.dart';
class HistoryView extends StatefulWidget { class HistoryView extends StatefulWidget {
const HistoryView({ const HistoryView({
@@ -62,8 +62,7 @@ class _HistoryViewState extends State<HistoryView> {
children: <Widget>[ children: <Widget>[
IconButton( IconButton(
onPressed: () { onPressed: () {
HistoryController _controller = HistoryController(); Database.instance.clearCreateHistory();
_controller.clearHistory();
Navigator.pop(context); Navigator.pop(context);
}, },
tooltip: "clearHistory".tr, tooltip: "clearHistory".tr,
Loaded 100 of 128 files, more files were not shown because too many files have changed in this diff. Show more