mirror of
https://github.com/GopeedLab/gopeed.git
synced 2026-11-14 12:23:43 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ef97528114 | ||
|
|
b58669bb46 | ||
|
|
51d28c5e53 | ||
|
|
db15650664 | ||
|
|
ee7da179c1 | ||
|
|
6bb32372ab | ||
|
|
7d6428c967 | ||
|
|
bba5107dd5 | ||
|
|
ddfe76b635 | ||
|
|
aad7395dde | ||
|
|
6f541715df | ||
|
|
848797b2fd | ||
|
|
0a05bf41e7 | ||
|
|
485656d1d4 | ||
|
|
c074e0c2bc | ||
|
|
0db0a2c987 | ||
|
|
b06a79b5c7 | ||
|
|
4840c4b8a4 | ||
|
|
e72b097aa2 | ||
|
|
af52c4ce7e | ||
|
|
aae79c2449 | ||
|
|
83838afce9 | ||
|
|
4690107e32 | ||
|
|
74b08b3631 | ||
|
|
953d348c0c | ||
|
|
00cea68b6e | ||
|
|
daa9d4c707 | ||
|
|
6bc587752a | ||
|
|
11befc09bd | ||
|
|
ff6c0da262 | ||
|
|
aab4a37a7e | ||
|
|
74c9491893 | ||
|
|
256faf2cbd | ||
|
|
32e3620f5e |
No files matched your search
@@ -32,6 +32,10 @@ version-resolver:
|
|||||||
- 'patch'
|
- 'patch'
|
||||||
default: patch
|
default: patch
|
||||||
template: |
|
template: |
|
||||||
## Changes
|
# Changes
|
||||||
|
|
||||||
|
$CHANGES
|
||||||
|
|
||||||
|
# 更新日志
|
||||||
|
|
||||||
$CHANGES
|
$CHANGES
|
||||||
+193
-28
@@ -2,10 +2,19 @@ name: build
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
platform:
|
||||||
|
description: 'Build platform'
|
||||||
|
required: true
|
||||||
|
default: 'all'
|
||||||
|
channel:
|
||||||
|
description: 'Snap channel'
|
||||||
|
required: true
|
||||||
|
default: 'stable'
|
||||||
|
|
||||||
env:
|
env:
|
||||||
GO_VERSION: "1.19"
|
GO_VERSION: "1.19"
|
||||||
FLUTTER_VERSION: "3.10.6"
|
FLUTTER_VERSION: "3.16.1"
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
@@ -24,6 +33,7 @@ jobs:
|
|||||||
myToken: ${{ github.token }}
|
myToken: ${{ github.token }}
|
||||||
|
|
||||||
build-windows:
|
build-windows:
|
||||||
|
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'windows' }}
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
needs: [ get-release ]
|
needs: [ get-release ]
|
||||||
steps:
|
steps:
|
||||||
@@ -46,7 +56,7 @@ jobs:
|
|||||||
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"
|
||||||
$release = "build\windows\runner\Release\"
|
$release = "build\windows\x64\runner\Release\"
|
||||||
cp $mingw\libstdc++-6.dll $release
|
cp $mingw\libstdc++-6.dll $release
|
||||||
cp $mingw\libgcc_s_seh-1.dll $release
|
cp $mingw\libgcc_s_seh-1.dll $release
|
||||||
cp $mingw\libwinpthread-1.dll $release
|
cp $mingw\libwinpthread-1.dll $release
|
||||||
@@ -100,7 +110,7 @@ jobs:
|
|||||||
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
|
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
|
||||||
|
|
||||||
[Files]
|
[Files]
|
||||||
Source: ".\runner\Release\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs
|
Source: ".\x64\runner\Release\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs
|
||||||
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
|
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
|
||||||
|
|
||||||
[UninstallDelete]
|
[UninstallDelete]
|
||||||
@@ -124,6 +134,7 @@ jobs:
|
|||||||
asset_path: ui/flutter/build/windows/Output/*
|
asset_path: ui/flutter/build/windows/Output/*
|
||||||
overwrite: true
|
overwrite: true
|
||||||
build-macos-arm64-lib:
|
build-macos-arm64-lib:
|
||||||
|
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'macos' }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [ get-release ]
|
needs: [ get-release ]
|
||||||
steps:
|
steps:
|
||||||
@@ -134,13 +145,14 @@ jobs:
|
|||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
go install src.techknowlogick.com/xgo@latest
|
go install src.techknowlogick.com/xgo@latest
|
||||||
xgo -go go-1.19.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" -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:
|
||||||
name: macos-arm64-lib
|
name: macos-arm64-lib
|
||||||
path: libgopeed.dylib
|
path: libgopeed.dylib
|
||||||
build-macos:
|
build-macos:
|
||||||
|
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'macos' }}
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
needs: [ get-release, build-macos-arm64-lib ]
|
needs: [ get-release, build-macos-arm64-lib ]
|
||||||
steps:
|
steps:
|
||||||
@@ -155,7 +167,9 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||||
- name: Install appdmg
|
- name: Install appdmg
|
||||||
run: npm install -g appdmg
|
run: |
|
||||||
|
python3 -m pip install setuptools
|
||||||
|
npm install -g appdmg
|
||||||
- uses: actions/download-artifact@v3
|
- uses: actions/download-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: macos-arm64-lib
|
name: macos-arm64-lib
|
||||||
@@ -197,7 +211,8 @@ jobs:
|
|||||||
asset_path: ui/flutter/build/macos/Build/Products/Release/dist/*
|
asset_path: ui/flutter/build/macos/Build/Products/Release/dist/*
|
||||||
overwrite: true
|
overwrite: true
|
||||||
build-linux:
|
build-linux:
|
||||||
runs-on: ubuntu-latest
|
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'linux' }}
|
||||||
|
runs-on: ubuntu-20.04
|
||||||
needs: [ get-release ]
|
needs: [ get-release ]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
@@ -217,14 +232,34 @@ jobs:
|
|||||||
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" -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
|
flutter build linux
|
||||||
mkdir -p debian/gui
|
mkdir -p build/dist
|
||||||
cp assets/icon/icon_1024.png debian/gui/gopeed.png
|
|
||||||
cat>debian/debian.yaml<<EOF
|
# Build portable
|
||||||
flutter_app:
|
tar -C build/linux/x64/release/bundle -czaf build/dist/Gopeed-$VERSION-linux-amd64-portable.tar.gz ./
|
||||||
command: gopeed
|
|
||||||
arch: x64
|
# Build deb package
|
||||||
parent: /usr/local/lib
|
mkdir -p debian/gopeed/opt/gopeed
|
||||||
control:
|
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
|
Package: gopeed
|
||||||
Version: ${VERSION:1}
|
Version: ${VERSION:1}
|
||||||
Architecture: amd64
|
Architecture: amd64
|
||||||
@@ -235,28 +270,155 @@ jobs:
|
|||||||
Description: High speed downloader that supports all platforms.
|
Description: High speed downloader that supports all platforms.
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
cat>debian/gui/gopeed.desktop<<EOF
|
cp linux/assets/com.gopeed.Gopeed.desktop debian/gopeed/usr/share/applications/gopeed.desktop
|
||||||
[Desktop Entry]
|
|
||||||
Version=${VERSION:1}
|
dpkg-deb --build debian/gopeed
|
||||||
Name=Gopeed
|
mv debian/gopeed.deb build/dist/Gopeed-$VERSION-linux-amd64.deb
|
||||||
GenericName=Gopeed
|
|
||||||
Comment=High speed downloader that supports all platforms.
|
# Build AppImage
|
||||||
Terminal=false
|
sudo apt-get -y install libfuse2
|
||||||
Type=Application
|
wget -O appimage-builder https://github.com/AppImageCrafters/appimage-builder/releases/download/v1.1.0/appimage-builder-1.1.0-x86_64.AppImage
|
||||||
Categories=Utility
|
chmod +x appimage-builder
|
||||||
Keywords=Flutter;Downloader;
|
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
|
EOF
|
||||||
|
./appimage-builder --skip-tests --recipe AppImageBuilder.yml
|
||||||
|
mv Gopeed-*-x86_64.AppImage build/dist/Gopeed-$VERSION-linux-amd64.AppImage
|
||||||
|
|
||||||
dart pub global activate flutter_to_debian
|
|
||||||
flutter_to_debian
|
|
||||||
mv debian/packages/gopeed*.deb debian/packages/Gopeed-$VERSION-linux-amd64.deb
|
|
||||||
- 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/debian/packages/*
|
asset_path: ui/flutter/build/dist/*
|
||||||
overwrite: true
|
overwrite: true
|
||||||
|
build-snap:
|
||||||
|
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'snap' }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [ get-release ]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: actions/setup-go@v4
|
||||||
|
with:
|
||||||
|
go-version: ${{ env.GO_VERSION }}
|
||||||
|
- uses: subosito/flutter-action@v2
|
||||||
|
with:
|
||||||
|
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||||
|
- name: Setup LXD
|
||||||
|
uses: canonical/[email protected]
|
||||||
|
with:
|
||||||
|
channel: latest/stable
|
||||||
|
- name: Build
|
||||||
|
env:
|
||||||
|
VERSION: ${{ needs.get-release.outputs.tag_name }}
|
||||||
|
run: |
|
||||||
|
go build -tags nosqlite -ldflags="-w -s" -buildmode=c-shared -o ui/flutter/linux/bundle/lib/libgopeed.so github.com/GopeedLab/gopeed/bind/desktop
|
||||||
|
cd ui/flutter
|
||||||
|
|
||||||
|
sudo snap install snapcraft --classic
|
||||||
|
mkdir -p snap/gui
|
||||||
|
cp assets/icon/icon.svg snap/gui/gopeed.svg
|
||||||
|
|
||||||
|
cat>snap/snapcraft.yaml<<EOF
|
||||||
|
name: gopeed
|
||||||
|
version: ${VERSION:1}
|
||||||
|
summary: A modern download manager
|
||||||
|
description: High speed downloader that supports all platforms.
|
||||||
|
|
||||||
|
confinement: strict
|
||||||
|
base: core22
|
||||||
|
grade: stable
|
||||||
|
|
||||||
|
architectures:
|
||||||
|
- build-on: amd64
|
||||||
|
build-for: amd64
|
||||||
|
|
||||||
|
slots:
|
||||||
|
dbus-gopeed:
|
||||||
|
interface: dbus
|
||||||
|
bus: session
|
||||||
|
name: com.gopeed.gopeed
|
||||||
|
|
||||||
|
apps:
|
||||||
|
gopeed:
|
||||||
|
command: gopeed
|
||||||
|
extensions: [gnome]
|
||||||
|
plugs:
|
||||||
|
- network
|
||||||
|
slots:
|
||||||
|
- dbus-gopeed
|
||||||
|
parts:
|
||||||
|
gopeed:
|
||||||
|
source: .
|
||||||
|
plugin: flutter
|
||||||
|
stage-packages:
|
||||||
|
- libgtk-3-dev
|
||||||
|
- libayatana-appindicator3-dev
|
||||||
|
flutter-target: lib/main.dart
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cp linux/assets/com.gopeed.Gopeed.desktop snap/gui/gopeed.desktop
|
||||||
|
sed -i 's/Icon=com.gopeed.Gopeed/Icon=\${SNAP}\/meta\/gui\/gopeed.svg/g' snap/gui/gopeed.desktop
|
||||||
|
|
||||||
|
snapcraft --use-lxd
|
||||||
|
|
||||||
|
# Snapcraft login
|
||||||
|
export SNAPCRAFT_STORE_CREDENTIALS=${{ secrets.SNAP_STORE_LOGIN }}
|
||||||
|
snapcraft upload --release=${{ github.event.inputs.channel }} gopeed_${VERSION:1}_amd64.snap
|
||||||
build-android:
|
build-android:
|
||||||
|
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'android' }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [ get-release ]
|
needs: [ get-release ]
|
||||||
steps:
|
steps:
|
||||||
@@ -294,6 +456,7 @@ jobs:
|
|||||||
asset_path: ui/flutter/dist/*
|
asset_path: ui/flutter/dist/*
|
||||||
overwrite: true
|
overwrite: true
|
||||||
build-ios:
|
build-ios:
|
||||||
|
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'ios' }}
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
needs: [ get-release ]
|
needs: [ get-release ]
|
||||||
steps:
|
steps:
|
||||||
@@ -326,6 +489,7 @@ jobs:
|
|||||||
asset_path: ui/flutter/dist/*
|
asset_path: ui/flutter/dist/*
|
||||||
overwrite: true
|
overwrite: true
|
||||||
build-web:
|
build-web:
|
||||||
|
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'web' }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [ get-release ]
|
needs: [ get-release ]
|
||||||
steps:
|
steps:
|
||||||
@@ -373,6 +537,7 @@ jobs:
|
|||||||
asset_path: dist/zip/*
|
asset_path: dist/zip/*
|
||||||
overwrite: true
|
overwrite: true
|
||||||
build-docker:
|
build-docker:
|
||||||
|
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'docker' }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [ get-release ]
|
needs: [ get-release ]
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ on:
|
|||||||
|
|
||||||
env:
|
env:
|
||||||
GO_VERSION: "1.19"
|
GO_VERSION: "1.19"
|
||||||
FLUTTER_VERSION: "3.10.6"
|
FLUTTER_VERSION: "3.16.1"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
# lint:
|
# lint:
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
# Gopeed 協助指南
|
||||||
|
|
||||||
|
首先感謝您願意幫助我們改進並優化該項目,這份指南將會幫助您更好的參與 Gopeed 的開發。
|
||||||
|
|
||||||
|
## 分支說明
|
||||||
|
|
||||||
|
本項目只有一個分支,即 `main` 分支,如果您想要參與 Gopeed 的開發,請先 fork 該項目,再在您自己的 fork 中進行開發,開發完成後再開啟PR,以合併至 `main` 分支。
|
||||||
|
|
||||||
|
## 離線開發
|
||||||
|
|
||||||
|
建議使用 web 端進行開發與調試,首先啟動服務,使用指令 `go run cmd/api/main.go` 啟動 ,該服務默認連接埠為 `9999`,接著以 `debug` 模式啟動前端 flutter 項目即可。
|
||||||
|
|
||||||
|
## 翻譯
|
||||||
|
|
||||||
|
Gopeed 的翻譯文件位於 `ui/flutter/assets/locales` 目錄中,只需要修改或新建翻譯文件即可。
|
||||||
|
|
||||||
|
修改locales後請執行一下指令:
|
||||||
|
|
||||||
|
```
|
||||||
|
get generate locales
|
||||||
|
```
|
||||||
|
|
||||||
|
請以 `en_US.json` 作為參照。
|
||||||
|
|
||||||
|
## flutter開發
|
||||||
|
|
||||||
|
每次提交PR前請務必執行 `dart format ./ui/flutter`
|
||||||
|
|
||||||
|
如果需要編輯 api/models,請打開build_runner watcher:
|
||||||
|
|
||||||
|
```
|
||||||
|
flutter pub run build_runner watch
|
||||||
|
```
|
||||||
|
|
||||||
|
適用於 get-cli 的指令:
|
||||||
|
|
||||||
|
```
|
||||||
|
create:
|
||||||
|
controller: Generate controller
|
||||||
|
page: Use to generate pages
|
||||||
|
view: Generate view
|
||||||
|
generate:
|
||||||
|
locales: Generate translation file from json files
|
||||||
|
model: generate Class model from json
|
||||||
|
```
|
||||||
|
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
FROM instrumentisto/flutter:3.10.6 AS flutter
|
FROM instrumentisto/flutter:3.16.1 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
|
||||||
|
|||||||
@@ -9,11 +9,12 @@
|
|||||||
|
|
||||||
[](https://ko-fi.com/R6R6IJGN6)
|
[](https://ko-fi.com/R6R6IJGN6)
|
||||||
|
|
||||||
[English](/README.md) | [中文](/README_zh-CN.md) | [日本語](/README_ja-JP.md)
|
[English](/README.md) | [中文](/README_zh-CN.md) | [日本語](/README_ja-JP.md) | [正體中文](/README_zh-TW.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.
|
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) | 📖 [Develop Docs](https://docs.gopeed.com)
|
Visit ✈ [Official Website](https://gopeed.com) | 📖 [Develop Docs](https://docs.gopeed.com)
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -9,11 +9,11 @@
|
|||||||
|
|
||||||
[](https://ko-fi.com/R6R6IJGN6)
|
[](https://ko-fi.com/R6R6IJGN6)
|
||||||
|
|
||||||
[English](/README.md) | [中文](/README_zh-CN.md) | [日本語](/README_ja-JP.md)
|
[English](/README.md) | [中文](/README_zh-CN.md) | [日本語](/README_ja-JP.md) | [正體中文](/README_zh-TW.md)
|
||||||
|
|
||||||
## はじめに
|
## はじめに
|
||||||
|
|
||||||
Gopeed (正式名 Go Speed) は `Golang` + `Flutter` によって開発された高速ダウンローダーで、(HTTP、BitTorrent、Magnet) プロトコルをサポートし、すべてのプラットフォームをサポートします。
|
Gopeed (正式名 Go Speed) は `Golang` + `Flutter` によって開発された高速ダウンローダーで、(HTTP、BitTorrent、Magnet) プロトコルをサポートし、すべてのプラットフォームをサポートします。基本的なダウンロード機能に加え、[APIs](https://docs.gopeed.com/dev-api.html)との連動や[拡張機能](https://docs.gopeed.com/dev-extension.html)のインストール・開発による追加機能にも対応した、カスタマイズ性の高いダウンローダーです。
|
||||||
|
|
||||||
見て下さい ✈ [公式ウェブサイト](https://gopeed.com) | 📖 [開発ドキュメント](https://docs.gopeed.com)
|
見て下さい ✈ [公式ウェブサイト](https://gopeed.com) | 📖 [開発ドキュメント](https://docs.gopeed.com)
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -9,11 +9,11 @@
|
|||||||
|
|
||||||
[](https://ko-fi.com/R6R6IJGN6)
|
[](https://ko-fi.com/R6R6IJGN6)
|
||||||
|
|
||||||
[English](/README.md) | [中文](/README_zh-CN.md) | [日本語](/README_ja-JP.md)
|
[English](/README.md) | [中文](/README_zh-CN.md) | [日本語](/README_ja-JP.md) | [正體中文](/README_zh-TW.md)
|
||||||
|
|
||||||
## 介绍
|
## 介绍
|
||||||
|
|
||||||
Gopeed(全称 Go Speed),是一款由`Golang`+`Flutter`开发的高速下载器,支持(HTTP、BitTorrent、Magnet)协议下载,并且支持全平台使用。
|
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/)
|
||||||
|
|
||||||
|
|||||||
+179
@@ -0,0 +1,179 @@
|
|||||||
|
[](https://github.com/GopeedLab/gopeed/actions?query=workflow%3Atest)
|
||||||
|
[](https://codecov.io/gh/GopeedLab/gopeed)
|
||||||
|
[](https://github.com/GopeedLab/gopeed/releases)
|
||||||
|
[](https://github.com/GopeedLab/gopeed/releases)
|
||||||
|
[](https://docs.gopeed.com/donate.html)
|
||||||
|
[](https://discord.gg/ZUJqJrwCGB)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
[](https://ko-fi.com/R6R6IJGN6)
|
||||||
|
|
||||||
|
[English](/README.md) | [中文](/README_zh-CN.md) | [日本語](/README_ja-JP.md) | [正體中文](/README_zh-TW.md)
|
||||||
|
|
||||||
|
## 簡介
|
||||||
|
|
||||||
|
Gopeed(全稱 Go Speed),是一款使用`Golang`+`Flutter`編寫的高速下載軟體,支援(HTTP、BitTorrent、Magnet)協定,同時支援所有的平台。
|
||||||
|
|
||||||
|
前往 ✈ [主頁](https://gopeed.com/zh-CN) | 📖 [文檔](https://docs.gopeed.com/zh/)
|
||||||
|
|
||||||
|
## 安裝
|
||||||
|
|
||||||
|
**已支援的平台**
|
||||||
|
|
||||||
|
- [x] Windows
|
||||||
|
- [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安裝
|
||||||
|
|
||||||
|
使用`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
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 訪問服務
|
||||||
|
|
||||||
|
當 docker 容器運作時,可以通過 `http://localhost:9999` 訪問 web 頁面。
|
||||||
|
|
||||||
|
## 贊助
|
||||||
|
|
||||||
|
如果你認為該項目對你有所幫助,請考慮[贊助](https://docs.gopeed.com/zh/donate)以支持該項目的持續發展,謝謝!
|
||||||
|
|
||||||
|
## 軟體介面
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## 開發
|
||||||
|
|
||||||
|
該項目分為前端與後端,前端使用`flutter`編寫,後端使用`Golang`編寫,兩邊通過`http`協定進行通訊,在 unix 系統下,則使用`unix socket`,在 windows 系統下,則使用`tcp`協定。
|
||||||
|
|
||||||
|
> 前端代碼位於`ui/flutter`目錄內。
|
||||||
|
|
||||||
|
### 開發環境
|
||||||
|
|
||||||
|
1. Golang 1.19+
|
||||||
|
2. Flutter 3.10+
|
||||||
|
|
||||||
|
### 克隆項目
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone [email protected]:GopeedLab/gopeed.git
|
||||||
|
```
|
||||||
|
|
||||||
|
### 協助開發
|
||||||
|
|
||||||
|
請參考[協助指南](CONTRIBUTING_zh-TW.md)
|
||||||
|
|
||||||
|
### 編譯
|
||||||
|
|
||||||
|
#### 桌面端
|
||||||
|
|
||||||
|
首先需要按照[flutter desktop 官方文檔](https://docs.flutter.dev/development/platform-integration/desktop)配置開發環境,並準備好`cgo`環境,具體方法可以自行搜索。
|
||||||
|
|
||||||
|
組建指令:
|
||||||
|
|
||||||
|
- windows
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go build -tags nosqlite -ldflags="-w -s" -buildmode=c-shared -o ui/flutter/windows/libgopeed.dll github.com/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
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 移動設備
|
||||||
|
|
||||||
|
需要`cgo`環境,並安裝`gomobile`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go install golang.org/x/mobile/cmd/gomobile@latest
|
||||||
|
go get golang.org/x/mobile/bind
|
||||||
|
gomobile init
|
||||||
|
```
|
||||||
|
|
||||||
|
組建指令:
|
||||||
|
|
||||||
|
- android
|
||||||
|
|
||||||
|
```bash
|
||||||
|
gomobile bind -tags nosqlite -ldflags="-w -s" -o ui/flutter/android/app/libs/libgopeed.aar -target=android -androidapi 19 -javapkg=com.gopeed github.com/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
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 網頁端
|
||||||
|
|
||||||
|
組建指令:
|
||||||
|
|
||||||
|
```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
|
||||||
|
go run cmd/web/main.go
|
||||||
|
```
|
||||||
|
|
||||||
|
## 感謝
|
||||||
|
|
||||||
|
### 貢獻者
|
||||||
|
|
||||||
|
<a href="https://github.com/GopeedLab/gopeed/graphs/contributors">
|
||||||
|
<img src="https://contrib.rocks/image?repo=GopeedLab/gopeed" />
|
||||||
|
</a>
|
||||||
|
|
||||||
|
### JetBrains
|
||||||
|
|
||||||
|
[](https://www.jetbrains.com/?from=gopeed)
|
||||||
|
|
||||||
|
## 軟體許可
|
||||||
|
|
||||||
|
該軟體遵循 [GPLv3](LICENSE) 。
|
||||||
@@ -11,6 +11,7 @@ func main() {
|
|||||||
Network: "tcp",
|
Network: "tcp",
|
||||||
Address: "127.0.0.1:9999",
|
Address: "127.0.0.1:9999",
|
||||||
Storage: model.StorageBolt,
|
Storage: model.StorageBolt,
|
||||||
|
WebEnable: true,
|
||||||
}
|
}
|
||||||
cmd.Start(cfg)
|
cmd.Start(cfg)
|
||||||
}
|
}
|
||||||
+14
-1
@@ -2,6 +2,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/GopeedLab/gopeed/internal/fetcher"
|
||||||
"github.com/GopeedLab/gopeed/pkg/base"
|
"github.com/GopeedLab/gopeed/pkg/base"
|
||||||
"github.com/GopeedLab/gopeed/pkg/download"
|
"github.com/GopeedLab/gopeed/pkg/download"
|
||||||
"github.com/GopeedLab/gopeed/pkg/protocol/http"
|
"github.com/GopeedLab/gopeed/pkg/protocol/http"
|
||||||
@@ -47,16 +48,28 @@ func main() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
printProgress(emptyTask, "downloading...")
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
lastLineLen = 0
|
lastLineLen = 0
|
||||||
sb = new(strings.Builder)
|
sb = new(strings.Builder)
|
||||||
|
emptyTask = &download.Task{
|
||||||
|
Progress: &download.Progress{},
|
||||||
|
Meta: &fetcher.FetcherMeta{
|
||||||
|
Res: &base.Resource{},
|
||||||
|
},
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func printProgress(task *download.Task, title string) {
|
func printProgress(task *download.Task, title string) {
|
||||||
rate := float64(task.Progress.Downloaded) / float64(task.Meta.Res.Size)
|
var rate float64
|
||||||
|
if task.Meta.Res.Size <= 0 {
|
||||||
|
rate = 0
|
||||||
|
} else {
|
||||||
|
rate = float64(task.Progress.Downloaded) / float64(task.Meta.Res.Size)
|
||||||
|
}
|
||||||
completeWidth := int(progressWidth * rate)
|
completeWidth := int(progressWidth * rate)
|
||||||
speed := util.ByteFmt(task.Progress.Speed)
|
speed := util.ByteFmt(task.Progress.Speed)
|
||||||
totalSize := util.ByteFmt(task.Meta.Res.Size)
|
totalSize := util.ByteFmt(task.Meta.Res.Size)
|
||||||
|
|||||||
@@ -5,14 +5,16 @@ go 1.19
|
|||||||
require (
|
require (
|
||||||
github.com/anacrolix/missinggo/v2 v2.7.2-0.20230527121029-a582b4f397b9
|
github.com/anacrolix/missinggo/v2 v2.7.2-0.20230527121029-a582b4f397b9
|
||||||
github.com/anacrolix/torrent v1.52.5
|
github.com/anacrolix/torrent v1.52.5
|
||||||
github.com/dop251/goja v0.0.0-20230828202809-3dbe69dd2b8e
|
github.com/dop251/goja v0.0.0-20231027120936-b396bb4c349d
|
||||||
github.com/dop251/goja_nodejs v0.0.0-20230821135201-94e508132562
|
github.com/dop251/goja_nodejs v0.0.0-20231122114759-e84d9a924c5c
|
||||||
github.com/go-git/go-git/v5 v5.8.1
|
github.com/go-git/go-git/v5 v5.8.1
|
||||||
github.com/gorilla/handlers v1.5.1
|
github.com/gorilla/handlers v1.5.1
|
||||||
github.com/gorilla/mux v1.8.0
|
github.com/gorilla/mux v1.8.0
|
||||||
github.com/matoous/go-nanoid/v2 v2.0.0
|
github.com/matoous/go-nanoid/v2 v2.0.0
|
||||||
|
github.com/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.7-0.20231005155708-db45d0d6b1f6
|
go.etcd.io/bbolt v1.3.8
|
||||||
|
golang.org/x/exp v0.0.0-20220613132600-b0d781184e0d
|
||||||
golang.org/x/sync v0.3.0
|
golang.org/x/sync v0.3.0
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -45,7 +47,7 @@ require (
|
|||||||
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.7.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.0 // 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
|
||||||
@@ -57,7 +59,7 @@ require (
|
|||||||
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-20230207041349-798e818bf904 // indirect
|
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a // indirect
|
||||||
github.com/google/uuid v1.3.0 // indirect
|
github.com/google/uuid v1.3.0 // indirect
|
||||||
github.com/gorilla/websocket v1.5.0 // indirect
|
github.com/gorilla/websocket v1.5.0 // indirect
|
||||||
github.com/huandu/xstrings v1.3.2 // indirect
|
github.com/huandu/xstrings v1.3.2 // indirect
|
||||||
@@ -88,19 +90,17 @@ require (
|
|||||||
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/pkg/errors v0.9.1 // indirect
|
||||||
github.com/rs/dnscache v0.0.0-20211102005908-e0241e321417 // indirect
|
github.com/rs/dnscache v0.0.0-20211102005908-e0241e321417 // indirect
|
||||||
github.com/rs/zerolog v1.31.0 // 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/tidwall/btree v1.6.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
|
go.opentelemetry.io/otel v1.8.0 // indirect
|
||||||
go.opentelemetry.io/otel/trace v1.8.0 // indirect
|
go.opentelemetry.io/otel/trace v1.8.0 // indirect
|
||||||
golang.org/x/crypto v0.11.0 // indirect
|
golang.org/x/crypto v0.14.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20220613132600-b0d781184e0d // indirect
|
|
||||||
golang.org/x/mod v0.8.0 // indirect
|
golang.org/x/mod v0.8.0 // indirect
|
||||||
golang.org/x/net v0.12.0 // indirect
|
golang.org/x/net v0.17.0 // indirect
|
||||||
golang.org/x/sys v0.13.0 // indirect
|
golang.org/x/sys v0.14.0 // indirect
|
||||||
golang.org/x/text v0.11.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.0.0-20220609170525-579cf78fd858 // indirect
|
||||||
golang.org/x/tools v0.6.0 // indirect
|
golang.org/x/tools v0.6.0 // indirect
|
||||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||||
|
|||||||
@@ -124,17 +124,17 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
|||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||||
github.com/dlclark/regexp2 v1.7.0 h1:7lJfhqlPssTb1WQx4yvTHN0uElPEv52sbaECrAQxjAo=
|
|
||||||
github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||||
|
github.com/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0=
|
||||||
|
github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||||
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
|
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
|
||||||
github.com/dop251/goja v0.0.0-20211022113120-dc8c55024d06/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk=
|
github.com/dop251/goja v0.0.0-20211022113120-dc8c55024d06/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk=
|
||||||
github.com/dop251/goja v0.0.0-20230531210528-d7324b2d74f7/go.mod h1:QMWlm50DNe14hD7t24KEqZuUdC9sOTy8W6XbCU1mlw4=
|
github.com/dop251/goja v0.0.0-20231027120936-b396bb4c349d h1:wi6jN5LVt/ljaBG4ue79Ekzb12QfJ52L9Q98tl8SWhw=
|
||||||
github.com/dop251/goja v0.0.0-20230828202809-3dbe69dd2b8e h1:UvQD6hTSfeM6hhTQ24Dlw2RppP05W7SWbWb6kubJAog=
|
github.com/dop251/goja v0.0.0-20231027120936-b396bb4c349d/go.mod h1:QMWlm50DNe14hD7t24KEqZuUdC9sOTy8W6XbCU1mlw4=
|
||||||
github.com/dop251/goja v0.0.0-20230828202809-3dbe69dd2b8e/go.mod h1:QMWlm50DNe14hD7t24KEqZuUdC9sOTy8W6XbCU1mlw4=
|
|
||||||
github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y=
|
github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y=
|
||||||
github.com/dop251/goja_nodejs v0.0.0-20211022123610-8dd9abb0616d/go.mod h1:DngW8aVqWbuLRMHItjPUyqdj+HWPvnQe8V8y1nDpIbM=
|
github.com/dop251/goja_nodejs v0.0.0-20211022123610-8dd9abb0616d/go.mod h1:DngW8aVqWbuLRMHItjPUyqdj+HWPvnQe8V8y1nDpIbM=
|
||||||
github.com/dop251/goja_nodejs v0.0.0-20230821135201-94e508132562 h1:ObbB2tzHWWAxzsG5futqeq2Ual2zYlo/+eMkSc5sn8w=
|
github.com/dop251/goja_nodejs v0.0.0-20231122114759-e84d9a924c5c h1:hLoodLRD4KLWIH8eyAQCLcH8EqIrjac7fCkp/fHnvuQ=
|
||||||
github.com/dop251/goja_nodejs v0.0.0-20230821135201-94e508132562/go.mod h1:X2TOTJ+Uamd454RFp7ig2tmP3hQg0Z2Qk8gbVQmU0mk=
|
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 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=
|
||||||
@@ -213,8 +213,9 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
|||||||
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 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||||
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 h1:4/hN5RUoecvl+RmJRE2YxKWtnnQls6rQjjW5oV7qg2U=
|
|
||||||
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/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
|
||||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||||
@@ -420,6 +421,8 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec
|
|||||||
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.7-0.20231005155708-db45d0d6b1f6 h1:NkHVT1GBVV9hUj1iWglFxAuNlvLHELFn71U2H7aOIog=
|
go.etcd.io/bbolt v1.3.7-0.20231005155708-db45d0d6b1f6 h1:NkHVT1GBVV9hUj1iWglFxAuNlvLHELFn71U2H7aOIog=
|
||||||
go.etcd.io/bbolt v1.3.7-0.20231005155708-db45d0d6b1f6/go.mod h1:tDI2vlr15+D9cyN3odhFrZJi3oJj8HlHaOEFPrUiGSY=
|
go.etcd.io/bbolt v1.3.7-0.20231005155708-db45d0d6b1f6/go.mod h1:tDI2vlr15+D9cyN3odhFrZJi3oJj8HlHaOEFPrUiGSY=
|
||||||
|
go.etcd.io/bbolt v1.3.8 h1:xs88BrvEv273UsB79e0hcVrlUWmS0a8upikMFhSyAtA=
|
||||||
|
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=
|
||||||
@@ -439,8 +442,8 @@ golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0
|
|||||||
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.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.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
|
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
|
||||||
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
|
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||||
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-20220613132600-b0d781184e0d h1:vtUKgx8dahOomfFzLREU8nSv25YHnTgLBn4rDnWZdU0=
|
||||||
golang.org/x/exp v0.0.0-20220613132600-b0d781184e0d/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA=
|
golang.org/x/exp v0.0.0-20220613132600-b0d781184e0d/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA=
|
||||||
@@ -482,9 +485,8 @@ 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.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
||||||
golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50=
|
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||||
golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
|
|
||||||
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=
|
||||||
@@ -534,10 +536,11 @@ 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.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.8.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.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
||||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
|
||||||
|
golang.org/x/sys v0.14.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=
|
||||||
@@ -545,8 +548,7 @@ 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.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.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
|
||||||
golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c=
|
|
||||||
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=
|
||||||
@@ -557,9 +559,8 @@ 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.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.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||||
golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
|
||||||
golang.org/x/time v0.0.0-20220609170525-579cf78fd858 h1:Dpdu/EMxGMFgq0CeYMh4fazTD2vtlZRYE7wyynxJb9U=
|
golang.org/x/time v0.0.0-20220609170525-579cf78fd858 h1:Dpdu/EMxGMFgq0CeYMh4fazTD2vtlZRYE7wyynxJb9U=
|
||||||
golang.org/x/time v0.0.0-20220609170525-579cf78fd858/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20220609170525-579cf78fd858/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Controller struct {
|
type Controller struct {
|
||||||
GetConfig func(v any) (bool, error)
|
GetConfig func(v any) bool
|
||||||
FileController
|
FileController
|
||||||
//ContextDialer() (proxy.Dialer, error)
|
//ContextDialer() (proxy.Dialer, error)
|
||||||
}
|
}
|
||||||
@@ -21,8 +21,8 @@ type DefaultFileController struct {
|
|||||||
func NewController() *Controller {
|
func NewController() *Controller {
|
||||||
return &Controller{
|
return &Controller{
|
||||||
FileController: &DefaultFileController{},
|
FileController: &DefaultFileController{},
|
||||||
GetConfig: func(v any) (bool, error) {
|
GetConfig: func(v any) bool {
|
||||||
return false, nil
|
return false
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,6 +57,15 @@ func (m *FetcherMeta) SingleFilepath() string {
|
|||||||
return path.Join(m.Opts.Path, file.Path, fileName)
|
return path.Join(m.Opts.Path, file.Path, fileName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RootDirPath return the root dir path of the task file.
|
||||||
|
func (m *FetcherMeta) RootDirPath() string {
|
||||||
|
if m.Res.Name != "" {
|
||||||
|
return m.FolderPath()
|
||||||
|
} else {
|
||||||
|
return m.Opts.Path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// FetcherBuilder defines the interface for a fetcher builder.
|
// FetcherBuilder defines the interface for a fetcher builder.
|
||||||
type FetcherBuilder interface {
|
type FetcherBuilder interface {
|
||||||
// Schemes returns the schemes supported by the fetcher.
|
// Schemes returns the schemes supported by the fetcher.
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package bt
|
package bt
|
||||||
|
|
||||||
type config struct {
|
type config struct {
|
||||||
|
ListenPort int `json:"listenPort"`
|
||||||
Trackers []string `json:"trackers"`
|
Trackers []string `json:"trackers"`
|
||||||
}
|
}
|
||||||
@@ -24,6 +24,7 @@ var (
|
|||||||
|
|
||||||
type Fetcher struct {
|
type Fetcher struct {
|
||||||
ctl *controller.Controller
|
ctl *controller.Controller
|
||||||
|
config *config
|
||||||
|
|
||||||
torrent *torrent.Torrent
|
torrent *torrent.Torrent
|
||||||
meta *fetcher.FetcherMeta
|
meta *fetcher.FetcherMeta
|
||||||
@@ -42,6 +43,13 @@ func (f *Fetcher) Setup(ctl *controller.Controller) {
|
|||||||
if f.meta == nil {
|
if f.meta == nil {
|
||||||
f.meta = &fetcher.FetcherMeta{}
|
f.meta = &fetcher.FetcherMeta{}
|
||||||
}
|
}
|
||||||
|
exist := f.ctl.GetConfig(&f.config)
|
||||||
|
if !exist {
|
||||||
|
f.config = &config{
|
||||||
|
ListenPort: 0,
|
||||||
|
Trackers: []string{},
|
||||||
|
}
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,7 +62,7 @@ func (f *Fetcher) initClient() (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cfg := torrent.NewDefaultClientConfig()
|
cfg := torrent.NewDefaultClientConfig()
|
||||||
cfg.ListenPort = 0
|
cfg.ListenPort = f.config.ListenPort
|
||||||
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) {
|
||||||
@@ -87,7 +95,6 @@ func (f *Fetcher) Create(opts *base.Options) (err error) {
|
|||||||
if f.meta.Res != nil {
|
if f.meta.Res != nil {
|
||||||
torrentDirMap[f.meta.Res.Hash] = f.meta.FolderPath()
|
torrentDirMap[f.meta.Res.Hash] = f.meta.FolderPath()
|
||||||
}
|
}
|
||||||
f.progress = make(fetcher.Progress, len(f.meta.Opts.SelectFiles))
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,6 +108,14 @@ func (f *Fetcher) Start() (err error) {
|
|||||||
ft.setTorrentDir(f.meta.FolderPath())
|
ft.setTorrentDir(f.meta.FolderPath())
|
||||||
}
|
}
|
||||||
files := f.torrent.Files()
|
files := f.torrent.Files()
|
||||||
|
// If the user does not specify the file to download, all files will be downloaded by default
|
||||||
|
if len(f.meta.Opts.SelectFiles) == 0 {
|
||||||
|
f.meta.Opts.SelectFiles = make([]int, len(files))
|
||||||
|
for i := range files {
|
||||||
|
f.meta.Opts.SelectFiles[i] = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f.progress = make(fetcher.Progress, len(f.meta.Opts.SelectFiles))
|
||||||
if len(f.meta.Opts.SelectFiles) == len(files) {
|
if len(f.meta.Opts.SelectFiles) == len(files) {
|
||||||
f.torrent.DownloadAll()
|
f.torrent.DownloadAll()
|
||||||
} else {
|
} else {
|
||||||
@@ -134,7 +149,7 @@ func (f *Fetcher) safeDrop() {
|
|||||||
|
|
||||||
func (f *Fetcher) Wait() (err error) {
|
func (f *Fetcher) Wait() (err error) {
|
||||||
for {
|
for {
|
||||||
if f.torrentReady.Load() {
|
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 {
|
||||||
file := f.torrent.Files()[selectIndex]
|
file := f.torrent.Files()[selectIndex]
|
||||||
@@ -154,7 +169,7 @@ func (f *Fetcher) Wait() (err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !selected {
|
if !selected {
|
||||||
util.SafeRemove(filepath.Join(f.meta.Opts.Path, file.Path()))
|
util.SafeRemove(filepath.Join(f.meta.Opts.Path, f.meta.Res.Name, file.Path()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
@@ -195,7 +210,7 @@ func (f *Fetcher) updateRes() {
|
|||||||
Size: file.Length(),
|
Size: file.Length(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
res.CalcSize()
|
res.CalcSize(nil)
|
||||||
f.meta.Res = res
|
f.meta.Res = res
|
||||||
if f.meta.Opts != nil {
|
if f.meta.Opts != nil {
|
||||||
f.meta.Opts.InitSelectFiles(len(res.Files))
|
f.meta.Opts.InitSelectFiles(len(res.Files))
|
||||||
@@ -224,11 +239,6 @@ func (f *Fetcher) addTorrent(req *base.Request) (err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var cfg config
|
|
||||||
exist, err := f.ctl.GetConfig(&cfg)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// use map to deduplicate
|
// use map to deduplicate
|
||||||
trackers := make(map[string]bool)
|
trackers := make(map[string]bool)
|
||||||
@@ -240,8 +250,8 @@ func (f *Fetcher) addTorrent(req *base.Request) (err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if exist && len(cfg.Trackers) > 0 {
|
if len(f.config.Trackers) > 0 {
|
||||||
for _, tracker := range cfg.Trackers {
|
for _, tracker := range f.config.Trackers {
|
||||||
trackers[tracker] = true
|
trackers[tracker] = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -98,11 +98,11 @@ func buildConfigFetcher() fetcher.Fetcher {
|
|||||||
"udp://tracker.birkenwald.de:6969/announce",
|
"udp://tracker.birkenwald.de:6969/announce",
|
||||||
"udp://tracker.bitsearch.to:1337/announce",
|
"udp://tracker.bitsearch.to:1337/announce",
|
||||||
}}
|
}}
|
||||||
newController.GetConfig = func(v any) (bool, error) {
|
newController.GetConfig = func(v any) bool {
|
||||||
if err := json.Unmarshal([]byte(test.ToJson(mockCfg)), v); err != nil {
|
if err := json.Unmarshal([]byte(test.ToJson(mockCfg)), v); err != nil {
|
||||||
return false, err
|
return false
|
||||||
}
|
}
|
||||||
return true, nil
|
return true
|
||||||
}
|
}
|
||||||
fetcher.Setup(newController)
|
fetcher.Setup(newController)
|
||||||
return fetcher
|
return fetcher
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package http
|
package http
|
||||||
|
|
||||||
type config struct {
|
type config struct {
|
||||||
|
UserAgent string `json:"userAgent"`
|
||||||
Connections int `json:"connections"`
|
Connections int `json:"connections"`
|
||||||
}
|
}
|
||||||
@@ -51,6 +51,7 @@ func newChunk(begin int64, end int64) *chunk {
|
|||||||
|
|
||||||
type Fetcher struct {
|
type Fetcher struct {
|
||||||
ctl *controller.Controller
|
ctl *controller.Controller
|
||||||
|
config *config
|
||||||
doneCh chan error
|
doneCh chan error
|
||||||
|
|
||||||
meta *fetcher.FetcherMeta
|
meta *fetcher.FetcherMeta
|
||||||
@@ -71,6 +72,13 @@ func (f *Fetcher) Setup(ctl *controller.Controller) {
|
|||||||
if f.meta == nil {
|
if f.meta == nil {
|
||||||
f.meta = &fetcher.FetcherMeta{}
|
f.meta = &fetcher.FetcherMeta{}
|
||||||
}
|
}
|
||||||
|
exist := f.ctl.GetConfig(&f.config)
|
||||||
|
if !exist {
|
||||||
|
f.config = &config{
|
||||||
|
UserAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36",
|
||||||
|
Connections: 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,7 +86,7 @@ func (f *Fetcher) Resolve(req *base.Request) error {
|
|||||||
if err := base.ParseReqExtra[fhttp.ReqExtra](req); err != nil {
|
if err := base.ParseReqExtra[fhttp.ReqExtra](req); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
httpReq, err := buildRequest(nil, req)
|
httpReq, err := f.buildRequest(nil, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -160,16 +168,7 @@ func (f *Fetcher) Create(opts *base.Options) error {
|
|||||||
}
|
}
|
||||||
extra := opts.Extra.(*fhttp.OptsExtra)
|
extra := opts.Extra.(*fhttp.OptsExtra)
|
||||||
if extra.Connections == 0 {
|
if extra.Connections == 0 {
|
||||||
var cfg config
|
extra.Connections = f.config.Connections
|
||||||
exist, err := f.ctl.GetConfig(&cfg)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if exist {
|
|
||||||
extra.Connections = cfg.Connections
|
|
||||||
} else {
|
|
||||||
extra.Connections = 1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -260,7 +259,7 @@ 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 := buildRequest(ctx, f.meta.Req)
|
httpReq, err := f.buildRequest(ctx, f.meta.Req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -314,6 +313,10 @@ func (f *Fetcher) fetchChunk(index int, ctx context.Context) (err error) {
|
|||||||
return nil
|
return nil
|
||||||
}()
|
}()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
// If canceled, do not retry
|
||||||
|
if errors.Is(err, context.Canceled) {
|
||||||
|
return
|
||||||
|
}
|
||||||
// retry request after 1 second
|
// retry request after 1 second
|
||||||
chunk.retryTimes = chunk.retryTimes + 1
|
chunk.retryTimes = chunk.retryTimes + 1
|
||||||
time.Sleep(time.Second)
|
time.Sleep(time.Second)
|
||||||
@@ -353,6 +356,52 @@ func (f *Fetcher) fetchChunk(index int, ctx context.Context) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *Fetcher) buildRequest(ctx context.Context, req *base.Request) (httpReq *http.Request, err error) {
|
||||||
|
url, err := url.Parse(req.URL)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
method string
|
||||||
|
body io.Reader
|
||||||
|
)
|
||||||
|
headers := make(map[string][]string)
|
||||||
|
if req.Extra == nil {
|
||||||
|
method = http.MethodGet
|
||||||
|
} else {
|
||||||
|
extra := req.Extra.(*fhttp.ReqExtra)
|
||||||
|
if extra.Method != "" {
|
||||||
|
method = extra.Method
|
||||||
|
} else {
|
||||||
|
method = http.MethodGet
|
||||||
|
}
|
||||||
|
if len(extra.Header) > 0 {
|
||||||
|
for k, v := range extra.Header {
|
||||||
|
headers[k] = []string{v}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if extra.Body != "" {
|
||||||
|
body = bytes.NewBufferString(extra.Body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, ok := headers[base.HttpHeaderUserAgent]; !ok {
|
||||||
|
// load user agent from config
|
||||||
|
headers[base.HttpHeaderUserAgent] = []string{f.config.UserAgent}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctx != nil {
|
||||||
|
httpReq, err = http.NewRequestWithContext(ctx, method, url.String(), body)
|
||||||
|
} else {
|
||||||
|
httpReq, err = http.NewRequest(method, url.String(), body)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
httpReq.Header = headers
|
||||||
|
return httpReq, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (f *Fetcher) splitChunk() (chunks []*chunk) {
|
func (f *Fetcher) splitChunk() (chunks []*chunk) {
|
||||||
if f.meta.Res.Range {
|
if f.meta.Res.Range {
|
||||||
connections := f.meta.Opts.Extra.(*fhttp.OptsExtra).Connections
|
connections := f.meta.Opts.Extra.(*fhttp.OptsExtra).Connections
|
||||||
@@ -389,51 +438,6 @@ func buildClient() *http.Client {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildRequest(ctx context.Context, req *base.Request) (httpReq *http.Request, err error) {
|
|
||||||
url, err := url.Parse(req.URL)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
method string
|
|
||||||
body io.Reader
|
|
||||||
)
|
|
||||||
headers := make(map[string][]string)
|
|
||||||
if req.Extra == nil {
|
|
||||||
method = http.MethodGet
|
|
||||||
} else {
|
|
||||||
extra := req.Extra.(*fhttp.ReqExtra)
|
|
||||||
if extra.Method != "" {
|
|
||||||
method = extra.Method
|
|
||||||
} else {
|
|
||||||
method = http.MethodGet
|
|
||||||
}
|
|
||||||
if len(extra.Header) > 0 {
|
|
||||||
for k, v := range extra.Header {
|
|
||||||
headers[k] = []string{v}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if extra.Body != "" {
|
|
||||||
body = bytes.NewBufferString(extra.Body)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if _, ok := headers[base.HttpHeaderUserAgent]; !ok {
|
|
||||||
headers[base.HttpHeaderUserAgent] = []string{base.AgentName}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ctx != nil {
|
|
||||||
httpReq, err = http.NewRequestWithContext(ctx, method, url.String(), body)
|
|
||||||
} else {
|
|
||||||
httpReq, err = http.NewRequest(method, url.String(), body)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
httpReq.Header = headers
|
|
||||||
return httpReq, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type fetcherData struct {
|
type fetcherData struct {
|
||||||
Chunks []*chunk
|
Chunks []*chunk
|
||||||
}
|
}
|
||||||
@@ -466,9 +470,7 @@ func (fb *FetcherBuilder) Restore() (v any, f func(meta *fetcher.FetcherMeta, v
|
|||||||
fetcher.meta = meta
|
fetcher.meta = meta
|
||||||
base.ParseReqExtra[fhttp.ReqExtra](fetcher.meta.Req)
|
base.ParseReqExtra[fhttp.ReqExtra](fetcher.meta.Req)
|
||||||
base.ParseOptsExtra[fhttp.OptsExtra](fetcher.meta.Opts)
|
base.ParseOptsExtra[fhttp.OptsExtra](fetcher.meta.Opts)
|
||||||
if len(fd.Chunks) == 0 {
|
if len(fd.Chunks) > 0 {
|
||||||
fetcher.chunks = fetcher.splitChunk()
|
|
||||||
} else {
|
|
||||||
fetcher.chunks = fd.Chunks
|
fetcher.chunks = fd.Chunks
|
||||||
}
|
}
|
||||||
return fetcher
|
return fetcher
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ func TestFetcher_DownloadError(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestFetcher_DownloadLimit(t *testing.T) {
|
func TestFetcher_DownloadLimit(t *testing.T) {
|
||||||
listener := test.StartTestLimitServer()
|
listener := test.StartTestLimitServer(4, 0)
|
||||||
defer listener.Close()
|
defer listener.Close()
|
||||||
|
|
||||||
downloadNormal(listener, 1, t)
|
downloadNormal(listener, 1, t)
|
||||||
@@ -314,11 +314,11 @@ func buildConfigFetcher() fetcher.Fetcher {
|
|||||||
mockCfg := config{
|
mockCfg := config{
|
||||||
Connections: 16,
|
Connections: 16,
|
||||||
}
|
}
|
||||||
newController.GetConfig = func(v any) (bool, error) {
|
newController.GetConfig = func(v any) bool {
|
||||||
if err := json.Unmarshal([]byte(test.ToJson(mockCfg)), v); err != nil {
|
if err := json.Unmarshal([]byte(test.ToJson(mockCfg)), v); err != nil {
|
||||||
return false, err
|
return false
|
||||||
}
|
}
|
||||||
return true, nil
|
return true
|
||||||
}
|
}
|
||||||
fetcher.Setup(newController)
|
fetcher.Setup(newController)
|
||||||
return fetcher
|
return fetcher
|
||||||
|
|||||||
@@ -138,7 +138,7 @@ func StartTestErrorServer() net.Listener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// StartTestLimitServer connections limit server
|
// StartTestLimitServer connections limit server
|
||||||
func StartTestLimitServer() net.Listener {
|
func StartTestLimitServer(maxConnections int32, delay int64) net.Listener {
|
||||||
var connections atomic.Int32
|
var connections atomic.Int32
|
||||||
|
|
||||||
return startTestServer(func() http.Handler {
|
return startTestServer(func() http.Handler {
|
||||||
@@ -148,7 +148,7 @@ func StartTestLimitServer() net.Listener {
|
|||||||
connections.Add(-1)
|
connections.Add(-1)
|
||||||
}()
|
}()
|
||||||
connections.Add(1)
|
connections.Add(1)
|
||||||
if connections.Load() > 4 {
|
if maxConnections != 0 && connections.Load() > maxConnections {
|
||||||
writer.WriteHeader(403)
|
writer.WriteHeader(403)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -162,7 +162,7 @@ func StartTestLimitServer() net.Listener {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
io.Copy(writer, file)
|
slowCopy(writer, file, delay)
|
||||||
} else {
|
} else {
|
||||||
// split range
|
// split range
|
||||||
s := strings.Split(r, "=")
|
s := strings.Split(r, "=")
|
||||||
@@ -203,13 +203,57 @@ func StartTestLimitServer() net.Listener {
|
|||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
file.Seek(start, 0)
|
file.Seek(start, 0)
|
||||||
io.CopyN(writer, file, end-start+1)
|
slowCopyN(writer, file, end-start+1, delay)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return mux
|
return mux
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// slowCopyN copies n bytes from src to dst, speed limit is bytes per second
|
||||||
|
func slowCopy(dst io.Writer, src io.Reader, delay int64) (written int64, err error) {
|
||||||
|
buf := make([]byte, 32*1024)
|
||||||
|
for {
|
||||||
|
nr, er := src.Read(buf)
|
||||||
|
if nr > 0 {
|
||||||
|
nw, ew := dst.Write(buf[0:nr])
|
||||||
|
if nw > 0 {
|
||||||
|
written += int64(nw)
|
||||||
|
}
|
||||||
|
if ew != nil {
|
||||||
|
err = ew
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if nr != nw {
|
||||||
|
err = io.ErrShortWrite
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if er != nil {
|
||||||
|
if er != io.EOF {
|
||||||
|
err = er
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if delay > 0 {
|
||||||
|
time.Sleep(time.Millisecond * time.Duration(delay))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return written, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func slowCopyN(dst io.Writer, src io.Reader, n int64, delay int64) (written int64, err error) {
|
||||||
|
written, err = slowCopy(dst, io.LimitReader(src, n), delay)
|
||||||
|
if written == n {
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
if written < n && err == nil {
|
||||||
|
// src stopped early; must have been EOF.
|
||||||
|
err = io.EOF
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func startTestServer(serverHandle func() http.Handler) net.Listener {
|
func startTestServer(serverHandle func() http.Handler) net.Listener {
|
||||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -23,7 +23,3 @@ const (
|
|||||||
|
|
||||||
HttpHeaderRangeFormat = "bytes=%d-%d"
|
HttpHeaderRangeFormat = "bytes=%d-%d"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
AgentName = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36"
|
|
||||||
)
|
|
||||||
+5
-2
@@ -3,6 +3,7 @@ package base
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/GopeedLab/gopeed/pkg/util"
|
"github.com/GopeedLab/gopeed/pkg/util"
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Request download request
|
// Request download request
|
||||||
@@ -47,11 +48,13 @@ func (r *Resource) Validate() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resource) CalcSize() {
|
func (r *Resource) CalcSize(selectFiles []int) {
|
||||||
var size int64
|
var size int64
|
||||||
for _, file := range r.Files {
|
for i, file := range r.Files {
|
||||||
|
if len(selectFiles) == 0 || slices.Contains(selectFiles, i) {
|
||||||
size += file.Size
|
size += file.Size
|
||||||
}
|
}
|
||||||
|
}
|
||||||
r.Size = size
|
r.Size = size
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+229
-158
File diff suppressed because it is too large.
Load diff
@@ -205,10 +205,7 @@ func TestDownloader_Protocol_Config(t *testing.T) {
|
|||||||
defer downloader.Clear()
|
defer downloader.Clear()
|
||||||
|
|
||||||
var httpCfg map[string]any
|
var httpCfg map[string]any
|
||||||
exits, err := downloader.getProtocolConfig("http", &httpCfg)
|
exits := downloader.getProtocolConfig("http", &httpCfg)
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if exits {
|
if exits {
|
||||||
t.Errorf("getProtocolConfig() got = %v, want %v", exits, false)
|
t.Errorf("getProtocolConfig() got = %v, want %v", exits, false)
|
||||||
}
|
}
|
||||||
@@ -234,7 +231,7 @@ func TestDownloader_Protocol_Config(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = downloader.PutConfig(storeCfg); err != nil {
|
if err := downloader.PutConfig(storeCfg); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,14 +3,14 @@ package engine
|
|||||||
import (
|
import (
|
||||||
_ "embed"
|
_ "embed"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"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/xhr"
|
"github.com/GopeedLab/gopeed/pkg/download/engine/inject/xhr"
|
||||||
"github.com/dop251/goja"
|
"github.com/dop251/goja"
|
||||||
"github.com/dop251/goja_nodejs/eventloop"
|
"github.com/dop251/goja_nodejs/eventloop"
|
||||||
"github.com/dop251/goja_nodejs/process"
|
|
||||||
"github.com/dop251/goja_nodejs/url"
|
"github.com/dop251/goja_nodejs/url"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed polyfill/out/index.js
|
//go:embed polyfill/out/index.js
|
||||||
@@ -27,12 +27,16 @@ type Engine struct {
|
|||||||
func (e *Engine) RunString(script string) (value any, err error) {
|
func (e *Engine) RunString(script string) (value any, err error) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
err = fmt.Errorf("%v", r)
|
err = r.(error)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
var result goja.Value
|
var result goja.Value
|
||||||
e.loop.Run(func(runtime *goja.Runtime) {
|
e.loop.Run(func(runtime *goja.Runtime) {
|
||||||
result, err = runtime.RunString(script)
|
result, err = runtime.RunString(script)
|
||||||
|
if err == nil {
|
||||||
|
go e.await(result)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@@ -45,9 +49,10 @@ func (e *Engine) RunString(script string) (value any, err error) {
|
|||||||
func (e *Engine) CallFunction(fn goja.Callable, args ...any) (value any, err error) {
|
func (e *Engine) CallFunction(fn goja.Callable, args ...any) (value any, err error) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
err = fmt.Errorf("%v", r)
|
err = r.(error)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
var result goja.Value
|
var result goja.Value
|
||||||
e.loop.Run(func(runtime *goja.Runtime) {
|
e.loop.Run(func(runtime *goja.Runtime) {
|
||||||
if args == nil {
|
if args == nil {
|
||||||
@@ -59,13 +64,45 @@ func (e *Engine) CallFunction(fn goja.Callable, args ...any) (value any, err err
|
|||||||
}
|
}
|
||||||
result, err = fn(nil, jsArgs...)
|
result, err = fn(nil, jsArgs...)
|
||||||
}
|
}
|
||||||
|
if err == nil {
|
||||||
|
go e.await(result)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return
|
||||||
}
|
}
|
||||||
return resolveResult(result)
|
return resolveResult(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// loop.Run will hang if the script result has a non-stop code, such as setInterval.
|
||||||
|
// This method will stop the event loop when the promise result is resolved.
|
||||||
|
func (e *Engine) await(value any) {
|
||||||
|
if value == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, ok := value.(goja.Value); ok {
|
||||||
|
// if result is promise, wait for it to be resolved
|
||||||
|
if p, ok := v.Export().(*goja.Promise); ok {
|
||||||
|
if p.State() != goja.PromiseStatePending {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// check promise state every 100 milliseconds, until it is resolved
|
||||||
|
for {
|
||||||
|
time.Sleep(time.Millisecond * 100)
|
||||||
|
if p.State() == goja.PromiseStatePending {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// stop the event loop
|
||||||
|
e.loop.StopNoWait()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (e *Engine) Close() {
|
func (e *Engine) Close() {
|
||||||
e.loop.Stop()
|
e.loop.Stop()
|
||||||
}
|
}
|
||||||
@@ -78,8 +115,8 @@ func NewEngine() *Engine {
|
|||||||
loop.Run(func(runtime *goja.Runtime) {
|
loop.Run(func(runtime *goja.Runtime) {
|
||||||
engine.Runtime = runtime
|
engine.Runtime = runtime
|
||||||
runtime.SetFieldNameMapper(goja.TagFieldNameMapper("json", true))
|
runtime.SetFieldNameMapper(goja.TagFieldNameMapper("json", true))
|
||||||
|
vm.Enable(runtime)
|
||||||
url.Enable(runtime)
|
url.Enable(runtime)
|
||||||
process.Enable(runtime)
|
|
||||||
if err := file.Enable(runtime); err != nil {
|
if err := file.Enable(runtime); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -96,6 +133,10 @@ func NewEngine() *Engine {
|
|||||||
if err := runtime.Set("global", runtime.GlobalObject()); err != nil {
|
if err := runtime.Set("global", runtime.GlobalObject()); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// polyfill window.location
|
||||||
|
if _, err := runtime.RunString("global.location = new URL('http://localhost');"); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
return
|
return
|
||||||
})
|
})
|
||||||
return engine
|
return engine
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ func TestPolyfill(t *testing.T) {
|
|||||||
doTestPolyfill(t, "Blob")
|
doTestPolyfill(t, "Blob")
|
||||||
doTestPolyfill(t, "FormData")
|
doTestPolyfill(t, "FormData")
|
||||||
doTestPolyfill(t, "fetch")
|
doTestPolyfill(t, "fetch")
|
||||||
|
doTestPolyfill(t, "__gopeed_create_vm")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFetch(t *testing.T) {
|
func TestFetch(t *testing.T) {
|
||||||
@@ -191,6 +192,65 @@ function testTimeout(){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestVm(t *testing.T) {
|
||||||
|
engine := NewEngine()
|
||||||
|
|
||||||
|
value, err := engine.RunString(`
|
||||||
|
const vm = __gopeed_create_vm()
|
||||||
|
vm.set('a', 1)
|
||||||
|
vm.set('b', 2)
|
||||||
|
const result = vm.runString('a=a+1;b=b+1;a+b;')
|
||||||
|
const out = {
|
||||||
|
"a": vm.get('a'),
|
||||||
|
"b": vm.get('b'),
|
||||||
|
"result": result
|
||||||
|
}
|
||||||
|
out
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
want := map[string]any{
|
||||||
|
"a": 2,
|
||||||
|
"b": 3,
|
||||||
|
"result": 5,
|
||||||
|
}
|
||||||
|
if !test.JsonEqual(value, want) {
|
||||||
|
t.Fatalf("vm test failed, want %v, got %v", want, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNonStopLoop(t *testing.T) {
|
||||||
|
engine := NewEngine()
|
||||||
|
|
||||||
|
_, err := engine.RunString(`
|
||||||
|
function leak(){
|
||||||
|
setInterval(() => {
|
||||||
|
},500)
|
||||||
|
}
|
||||||
|
|
||||||
|
function test(){
|
||||||
|
leak()
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
resolve('done')
|
||||||
|
}, 1000)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
val, err := callTestFun(engine, "test")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if val != "done" {
|
||||||
|
t.Fatalf("infinite loop test failed, want %s, got %s", "done", val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func doTestPolyfill(t *testing.T, module string) {
|
func doTestPolyfill(t *testing.T, module string) {
|
||||||
value, err := Run(fmt.Sprintf(`
|
value, err := Run(fmt.Sprintf(`
|
||||||
!!globalThis['%s']
|
!!globalThis['%s']
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package vm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/dop251/goja"
|
||||||
|
"github.com/dop251/goja_nodejs/eventloop"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Vm struct {
|
||||||
|
loop *eventloop.EventLoop
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vm *Vm) Set(name string, value any) {
|
||||||
|
vm.loop.Run(func(runtime *goja.Runtime) {
|
||||||
|
runtime.Set(name, value)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vm *Vm) Get(name string) (value any) {
|
||||||
|
vm.loop.Run(func(runtime *goja.Runtime) {
|
||||||
|
value = runtime.Get(name)
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vm *Vm) RunString(script string) (value any, err error) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
err = r.(error)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
vm.loop.Run(func(runtime *goja.Runtime) {
|
||||||
|
value, err = runtime.RunString(script)
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func Enable(runtime *goja.Runtime) error {
|
||||||
|
return runtime.Set("__gopeed_create_vm", func(call goja.FunctionCall) goja.Value {
|
||||||
|
return runtime.ToValue(&Vm{
|
||||||
|
loop: eventloop.NewEventLoop(),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
+54
-13
@@ -243,7 +243,7 @@ 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.CalcSize()
|
ctx.Res.CalcSize(nil)
|
||||||
}
|
}
|
||||||
res = ctx.Res
|
res = ctx.Res
|
||||||
},
|
},
|
||||||
@@ -251,12 +251,12 @@ func (d *Downloader) triggerOnResolve(req *base.Request) (res *base.Resource) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Downloader) triggerOnStart(task *Task) {
|
func (d *Downloader) triggerOnStart(task *Task) (req *base.Request) {
|
||||||
doTrigger(d,
|
doTrigger(d,
|
||||||
EventOnStart,
|
EventOnStart,
|
||||||
task.Meta.Req,
|
task.Meta.Req,
|
||||||
&OnStartContext{
|
&OnStartContext{
|
||||||
Task: task.clone(),
|
Task: task,
|
||||||
},
|
},
|
||||||
func(ext *Extension, gopeed *Instance, ctx *OnStartContext) {
|
func(ext *Extension, gopeed *Instance, ctx *OnStartContext) {
|
||||||
// Validate request structure
|
// Validate request structure
|
||||||
@@ -265,8 +265,7 @@ func (d *Downloader) triggerOnStart(task *Task) {
|
|||||||
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
|
||||||
}
|
}
|
||||||
// Modify real task request
|
req = ctx.Task.Meta.Req
|
||||||
task.Meta.Req = ctx.Task.Meta.Req
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -422,8 +421,50 @@ func (e *Extension) update(newExt *Extension) error {
|
|||||||
e.Homepage = newExt.Homepage
|
e.Homepage = newExt.Homepage
|
||||||
e.Repository = newExt.Repository
|
e.Repository = newExt.Repository
|
||||||
e.Scripts = newExt.Scripts
|
e.Scripts = newExt.Scripts
|
||||||
// don't override settings
|
// merge settings
|
||||||
//e.Settings = newExt.Settings
|
// if new setting not exist in old settings, append it
|
||||||
|
for _, newSetting := range newExt.Settings {
|
||||||
|
var exist bool
|
||||||
|
for _, setting := range e.Settings {
|
||||||
|
if setting.Name == newSetting.Name {
|
||||||
|
exist = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !exist {
|
||||||
|
e.Settings = append(e.Settings, newSetting)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// if old setting not exist in new settings, remove it
|
||||||
|
for i := 0; i < len(e.Settings); i++ {
|
||||||
|
var exist bool
|
||||||
|
for _, setting := range newExt.Settings {
|
||||||
|
if setting.Name == e.Settings[i].Name {
|
||||||
|
exist = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !exist {
|
||||||
|
e.Settings = append(e.Settings[:i], e.Settings[i+1:]...)
|
||||||
|
i--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// if new setting exist in old settings, update it
|
||||||
|
for _, newSetting := range newExt.Settings {
|
||||||
|
for _, setting := range e.Settings {
|
||||||
|
if setting.Name == newSetting.Name {
|
||||||
|
setting.Title = newSetting.Title
|
||||||
|
setting.Description = newSetting.Description
|
||||||
|
setting.Options = newSetting.Options
|
||||||
|
// if type changed, reset value
|
||||||
|
if setting.Type != newSetting.Type {
|
||||||
|
setting.Type = newSetting.Type
|
||||||
|
setting.Value = newSetting.Value
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
e.UpdatedAt = time.Now()
|
e.UpdatedAt = time.Now()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -557,28 +598,28 @@ type InstanceLogger struct {
|
|||||||
logger *logger.Logger
|
logger *logger.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *InstanceLogger) Debug(msg ...any) {
|
func (l *InstanceLogger) Debug(msg ...goja.Value) {
|
||||||
if l.devMode {
|
if l.devMode {
|
||||||
l.logger.Debug().Msg(l.append(msg...))
|
l.logger.Debug().Msg(l.append(msg...))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *InstanceLogger) Info(msg ...any) {
|
func (l *InstanceLogger) Info(msg ...goja.Value) {
|
||||||
l.logger.Info().Msg(l.append(msg...))
|
l.logger.Info().Msg(l.append(msg...))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *InstanceLogger) Warn(msg ...any) {
|
func (l *InstanceLogger) Warn(msg ...goja.Value) {
|
||||||
l.logger.Warn().Msg(l.append(msg...))
|
l.logger.Warn().Msg(l.append(msg...))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *InstanceLogger) Error(msg ...any) {
|
func (l *InstanceLogger) Error(msg ...goja.Value) {
|
||||||
l.logger.Error().Msg(l.append(msg...))
|
l.logger.Error().Msg(l.append(msg...))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *InstanceLogger) append(msg ...any) string {
|
func (l *InstanceLogger) append(msg ...goja.Value) string {
|
||||||
strMsg := make([]string, len(msg))
|
strMsg := make([]string, len(msg))
|
||||||
for i, m := range msg {
|
for i, m := range msg {
|
||||||
strMsg[i] = fmt.Sprint(m)
|
strMsg[i] = m.String()
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("[%s] %s", l.identity, strings.Join(strMsg, " "))
|
return fmt.Sprintf("[%s] %s", l.identity, strings.Join(strMsg, " "))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
|
"github.com/dop251/goja"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@@ -95,6 +96,15 @@ func TestDownloader_InstallExtensionByGitFull(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestDownloader_UpgradeExtension(t *testing.T) {
|
func TestDownloader_UpgradeExtension(t *testing.T) {
|
||||||
|
getSetting := func(settings []*Setting, name string) *Setting {
|
||||||
|
for _, setting := range settings {
|
||||||
|
if setting.Name == name {
|
||||||
|
return setting
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
setupDownloader(func(downloader *Downloader) {
|
setupDownloader(func(downloader *Downloader) {
|
||||||
installedExt, err := downloader.InstallExtensionByFolder("./testdata/extensions/update", false)
|
installedExt, err := downloader.InstallExtensionByFolder("./testdata/extensions/update", false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -122,14 +132,35 @@ func TestDownloader_UpgradeExtension(t *testing.T) {
|
|||||||
t.Fatal("extension update fail")
|
t.Fatal("extension update fail")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check setting update
|
||||||
|
s1 := getSetting(upgradeExt.Settings, "s1")
|
||||||
|
if s1.Title == "S1 old" {
|
||||||
|
t.Fatal("setting update fail")
|
||||||
|
}
|
||||||
|
// check setting type update
|
||||||
|
s2 := getSetting(upgradeExt.Settings, "s2")
|
||||||
|
if s2.Type == "number" {
|
||||||
|
t.Fatal("setting type update fail")
|
||||||
|
}
|
||||||
|
// check setting remove
|
||||||
|
d1 := getSetting(upgradeExt.Settings, "d1")
|
||||||
|
if d1 != nil {
|
||||||
|
t.Fatal("setting remove fail")
|
||||||
|
}
|
||||||
|
// check setting add
|
||||||
|
s3 := getSetting(upgradeExt.Settings, "s3")
|
||||||
|
if s3 == nil {
|
||||||
|
t.Fatal("setting add fail")
|
||||||
|
}
|
||||||
|
|
||||||
rr, err := downloader.Resolve(&base.Request{
|
rr, err := downloader.Resolve(&base.Request{
|
||||||
URL: "https://github.com/GopeedLab/gopeed/releases",
|
URL: "https://test.com",
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if len(rr.Res.Files) == 1 {
|
if rr.Res.Name != "test" {
|
||||||
t.Fatal("resolve error")
|
t.Fatal("script update fail")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -326,10 +357,10 @@ func TestDownloader_Extension_Logger(t *testing.T) {
|
|||||||
il := newInstanceLogger(&Extension{
|
il := newInstanceLogger(&Extension{
|
||||||
Name: "test",
|
Name: "test",
|
||||||
}, logger)
|
}, logger)
|
||||||
il.Debug("msg1", "msg2")
|
il.Debug(goja.NaN(), goja.Undefined())
|
||||||
il.Info("msg1", "msg2")
|
il.Info(goja.NaN(), goja.Undefined())
|
||||||
il.Warn("msg1", "msg2")
|
il.Warn(goja.NaN(), goja.Undefined())
|
||||||
il.Error("msg1", "msg2")
|
il.Error(goja.NaN(), goja.Undefined())
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupDownloader(fn func(downloader *Downloader)) {
|
func setupDownloader(fn func(downloader *Downloader)) {
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ type Task struct {
|
|||||||
Meta *fetcher.FetcherMeta `json:"meta"`
|
Meta *fetcher.FetcherMeta `json:"meta"`
|
||||||
Status base.Status `json:"status"`
|
Status base.Status `json:"status"`
|
||||||
Progress *Progress `json:"progress"`
|
Progress *Progress `json:"progress"`
|
||||||
Size int64 `json:"size"`
|
|
||||||
CreatedAt time.Time `json:"createdAt"`
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
|
|
||||||
fetcherBuilder fetcher.FetcherBuilder
|
fetcherBuilder fetcher.FetcherBuilder
|
||||||
@@ -50,7 +49,6 @@ func (t *Task) clone() *Task {
|
|||||||
Meta: t.Meta,
|
Meta: t.Meta,
|
||||||
Status: t.Status,
|
Status: t.Status,
|
||||||
Progress: t.Progress,
|
Progress: t.Progress,
|
||||||
Size: t.Size,
|
|
||||||
CreatedAt: t.CreatedAt,
|
CreatedAt: t.CreatedAt,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -222,6 +222,9 @@ func (b *BoltStorage) Pop(bucket string, key string, v any) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if len(data) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
return json.Unmarshal(data, v)
|
return json.Unmarshal(data, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+28
-4
@@ -1,13 +1,13 @@
|
|||||||
{
|
{
|
||||||
"name": "github-release",
|
"name": "extension-test",
|
||||||
"author": "gopeed",
|
"author": "gopeed",
|
||||||
"title": "Github Release",
|
"title": "Gopeed Extension Test",
|
||||||
"description": "Download github release assets",
|
"description": "Test extension settings and upgrade",
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"homepage": "https://gopeed.com",
|
"homepage": "https://gopeed.com",
|
||||||
"repository": {
|
"repository": {
|
||||||
"url": "https://github.com/GopeedLab/gopeed-extension-samples",
|
"url": "https://github.com/GopeedLab/gopeed-extension-samples",
|
||||||
"directory": "github-release-sample"
|
"directory": "extension-test"
|
||||||
},
|
},
|
||||||
"scripts": [
|
"scripts": [
|
||||||
{
|
{
|
||||||
@@ -19,5 +19,29 @@
|
|||||||
},
|
},
|
||||||
"entry": "index.js"
|
"entry": "index.js"
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"settings": [
|
||||||
|
{
|
||||||
|
"name": "s1",
|
||||||
|
"title": "S1 old",
|
||||||
|
"description": "Test setting update",
|
||||||
|
"type": "string",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "s2",
|
||||||
|
"title": "s2 number old",
|
||||||
|
"description": "Test setting type update",
|
||||||
|
"type": "number",
|
||||||
|
"required": true,
|
||||||
|
"value": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "d1",
|
||||||
|
"title": "Delete test",
|
||||||
|
"description": "Test setting delete",
|
||||||
|
"type": "string",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
+41
-14
@@ -105,9 +105,10 @@ func BuildServer(startCfg *model.StartConfig) (*http.Server, net.Listener, error
|
|||||||
r.Methods(http.MethodDelete).Path("/api/v1/extensions/{identity}").HandlerFunc(DeleteExtension)
|
r.Methods(http.MethodDelete).Path("/api/v1/extensions/{identity}").HandlerFunc(DeleteExtension)
|
||||||
r.Methods(http.MethodGet).Path("/api/v1/extensions/{identity}/update").HandlerFunc(UpdateCheckExtension)
|
r.Methods(http.MethodGet).Path("/api/v1/extensions/{identity}/update").HandlerFunc(UpdateCheckExtension)
|
||||||
r.Methods(http.MethodPost).Path("/api/v1/extensions/{identity}/update").HandlerFunc(UpdateExtension)
|
r.Methods(http.MethodPost).Path("/api/v1/extensions/{identity}/update").HandlerFunc(UpdateExtension)
|
||||||
r.PathPrefix("/fs/extensions").Handler(http.FileServer(new(extensionFileSystem)))
|
|
||||||
r.Path("/api/v1/proxy").HandlerFunc(DoProxy)
|
r.Path("/api/v1/proxy").HandlerFunc(DoProxy)
|
||||||
if startCfg.WebEnable {
|
if startCfg.WebEnable {
|
||||||
|
r.PathPrefix("/fs/tasks").Handler(http.FileServer(new(taskFileSystem)))
|
||||||
|
r.PathPrefix("/fs/extensions").Handler(http.FileServer(new(extensionFileSystem)))
|
||||||
r.PathPrefix("/").Handler(http.FileServer(http.FS(startCfg.WebFS)))
|
r.PathPrefix("/").Handler(http.FileServer(http.FS(startCfg.WebFS)))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,30 +139,56 @@ func BuildServer(startCfg *model.StartConfig) (*http.Server, net.Listener, error
|
|||||||
return srv, listener, nil
|
return srv, listener, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func resolvePath(urlPath string, prefix string) (identity string, path string, err error) {
|
||||||
|
// remove prefix
|
||||||
|
clearPath := strings.TrimPrefix(urlPath, prefix)
|
||||||
|
// match extension identity, eg: /fs/extensions/identity/xxx
|
||||||
|
reg := regexp.MustCompile(`^/([^/]+)/(.*)$`)
|
||||||
|
if !reg.MatchString(clearPath) {
|
||||||
|
err = os.ErrNotExist
|
||||||
|
return
|
||||||
|
}
|
||||||
|
matched := reg.FindStringSubmatch(clearPath)
|
||||||
|
if len(matched) != 3 {
|
||||||
|
err = os.ErrNotExist
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return matched[1], matched[2], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle task file resource
|
||||||
|
type taskFileSystem struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *taskFileSystem) Open(name string) (http.File, error) {
|
||||||
|
// get extension identity
|
||||||
|
identity, path, err := resolvePath(name, "/fs/tasks")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
task := Downloader.GetTask(identity)
|
||||||
|
if task == nil {
|
||||||
|
return nil, os.ErrNotExist
|
||||||
|
}
|
||||||
|
return os.Open(filepath.Join(task.Meta.RootDirPath(), path))
|
||||||
|
}
|
||||||
|
|
||||||
// handle extension file resource
|
// handle extension file resource
|
||||||
type extensionFileSystem struct {
|
type extensionFileSystem struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *extensionFileSystem) Open(name string) (http.File, error) {
|
func (e *extensionFileSystem) Open(name string) (http.File, error) {
|
||||||
// remove prefix
|
|
||||||
path := strings.TrimPrefix(name, "/fs/extensions")
|
|
||||||
// match extension identity, eg: /fs/extensions/identity/xxx
|
|
||||||
reg := regexp.MustCompile(`^/([^/]+)/(.*)$`)
|
|
||||||
if !reg.MatchString(path) {
|
|
||||||
return nil, os.ErrNotExist
|
|
||||||
}
|
|
||||||
matched := reg.FindStringSubmatch(path)
|
|
||||||
if len(matched) != 3 {
|
|
||||||
return nil, os.ErrNotExist
|
|
||||||
}
|
|
||||||
// get extension identity
|
// get extension identity
|
||||||
identity := matched[1]
|
identity, path, err := resolvePath(name, "/fs/extensions")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
extension, err := Downloader.GetExtension(identity)
|
extension, err := Downloader.GetExtension(identity)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, os.ErrNotExist
|
return nil, os.ErrNotExist
|
||||||
}
|
}
|
||||||
extensionPath := Downloader.ExtensionPath(extension)
|
extensionPath := Downloader.ExtensionPath(extension)
|
||||||
return os.Open(filepath.Join(extensionPath, matched[2]))
|
return os.Open(filepath.Join(extensionPath, path))
|
||||||
}
|
}
|
||||||
|
|
||||||
func ReadJson(r *http.Request, w http.ResponseWriter, v any) bool {
|
func ReadJson(r *http.Request, w http.ResponseWriter, v any) bool {
|
||||||
|
|||||||
@@ -204,19 +204,16 @@ 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 * 500)
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -494,6 +491,7 @@ func doTest(handler func()) {
|
|||||||
cfg.Init()
|
cfg.Init()
|
||||||
cfg.Storage = storage
|
cfg.Storage = storage
|
||||||
cfg.StorageDir = ".test_storage"
|
cfg.StorageDir = ".test_storage"
|
||||||
|
cfg.WebEnable = true
|
||||||
fileListener := doStart(cfg)
|
fileListener := doStart(cfg)
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := fileListener.Close(); err != nil {
|
if err := fileListener.Close(); err != nil {
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ android {
|
|||||||
applicationId "com.gopeed"
|
applicationId "com.gopeed"
|
||||||
// You can update the following values to match your application needs.
|
// You can update the following values to match your application needs.
|
||||||
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration.
|
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration.
|
||||||
minSdkVersion 19
|
minSdkVersion 21
|
||||||
targetSdkVersion 29
|
targetSdkVersion 29
|
||||||
versionCode flutterVersionCode.toInteger()
|
versionCode flutterVersionCode.toInteger()
|
||||||
versionName flutterVersionName
|
versionName flutterVersionName
|
||||||
|
|||||||
@@ -72,6 +72,12 @@
|
|||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
<!-- Add android:stopWithTask option only when necessary. -->
|
||||||
|
<service
|
||||||
|
android:name="com.pravera.flutter_foreground_task.service.ForegroundService"
|
||||||
|
android:stopWithTask="true" />
|
||||||
|
|
||||||
<!-- Don't delete the meta-data below.
|
<!-- Don't delete the meta-data below.
|
||||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||||
<meta-data
|
<meta-data
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="1024px" height="1024px" style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<g><path style="opacity:0.998" fill="#5dc76c" d="M 951.5,763.5 C 940.739,755.276 929.405,747.776 917.5,741C 908.477,736.319 899.143,732.485 889.5,729.5C 885.844,625.779 840.844,545.613 754.5,489C 674.802,441.453 590.135,430.12 500.5,455C 419.755,480.605 359.588,530.771 320,605.5C 278.585,565.816 230.752,555.649 176.5,575C 131.948,594.935 106.448,629.102 100,677.5C 99.3333,685.833 99.3333,694.167 100,702.5C 102.081,714.824 105.081,726.824 109,738.5C 95.4882,745.492 82.9882,753.826 71.5,763.5C 37.7121,705.639 16.5454,643.306 8,576.5C -9.41698,416.759 35.9164,278.093 144,160.5C 244.661,58.2827 367.161,5.94939 511.5,3.5C 678.975,7.48829 814.142,74.8216 917,205.5C 1011.33,336.426 1040,480.759 1003,638.5C 991.564,682.671 974.397,724.337 951.5,763.5 Z"/></g>
|
||||||
|
<g><path style="opacity:0.999" fill="#fefefe" d="M 951.5,763.5 C 949.28,768.272 946.78,772.938 944,777.5C 938.305,786.913 932.138,795.913 925.5,804.5C 915.954,796.888 905.288,791.055 893.5,787C 878.282,782.958 863.115,778.791 848,774.5C 854.013,716.891 841.346,663.891 810,615.5C 768.341,553.228 711.174,512.395 638.5,493C 560.311,479.566 490.311,497.566 428.5,547C 411.963,560.536 397.13,575.703 384,592.5C 366.745,616.923 348.911,640.923 330.5,664.5C 310.135,651.302 290.469,637.135 271.5,622C 238.448,602.307 206.782,604.307 176.5,628C 159.375,642.752 148.875,661.252 145,683.5C 143.968,692.716 144.968,701.716 148,710.5C 154.334,725.339 160.167,740.339 165.5,755.5C 138.118,764.688 114.451,779.688 94.5,800.5C 85.653,788.968 77.9863,776.635 71.5,763.5C 82.9882,753.826 95.4882,745.492 109,738.5C 105.081,726.824 102.081,714.824 100,702.5C 99.3333,694.167 99.3333,685.833 100,677.5C 106.448,629.102 131.948,594.935 176.5,575C 230.752,555.649 278.585,565.816 320,605.5C 359.588,530.771 419.755,480.605 500.5,455C 590.135,430.12 674.802,441.453 754.5,489C 840.844,545.613 885.844,625.779 889.5,729.5C 899.143,732.485 908.477,736.319 917.5,741C 929.405,747.776 940.739,755.276 951.5,763.5 Z"/></g>
|
||||||
|
<g><path style="opacity:0.998" fill="#5dc76c" d="M 925.5,804.5 C 850.791,908.608 750.791,975.775 625.5,1006C 609.308,1009.7 592.975,1012.53 576.5,1014.5C 615.708,969.938 655.042,925.438 694.5,881C 664.168,880.5 633.835,880.333 603.5,880.5C 603.5,811.167 603.5,741.833 603.5,672.5C 542.167,672.5 480.833,672.5 419.5,672.5C 419.5,741.5 419.5,810.5 419.5,879.5C 388.493,879.167 357.493,879.5 326.5,880.5C 326.624,881.107 326.957,881.44 327.5,881.5C 366.805,925.808 406.138,970.141 445.5,1014.5C 318.63,996.317 212.796,939.651 128,844.5C 115.85,830.525 104.683,815.859 94.5,800.5C 114.451,779.688 138.118,764.688 165.5,755.5C 160.167,740.339 154.334,725.339 148,710.5C 144.968,701.716 143.968,692.716 145,683.5C 148.875,661.252 159.375,642.752 176.5,628C 206.782,604.307 238.448,602.307 271.5,622C 290.469,637.135 310.135,651.302 330.5,664.5C 348.911,640.923 366.745,616.923 384,592.5C 397.13,575.703 411.963,560.536 428.5,547C 490.311,497.566 560.311,479.566 638.5,493C 711.174,512.395 768.341,553.228 810,615.5C 841.346,663.891 854.013,716.891 848,774.5C 863.115,778.791 878.282,782.958 893.5,787C 905.288,791.055 915.954,796.888 925.5,804.5 Z"/></g>
|
||||||
|
<g><path style="opacity:0.999" fill="#fefefe" d="M 576.5,1014.5 C 532.836,1019.82 489.169,1019.82 445.5,1014.5C 406.138,970.141 366.805,925.808 327.5,881.5C 358.167,880.833 388.833,880.167 419.5,879.5C 419.5,810.5 419.5,741.5 419.5,672.5C 480.833,672.5 542.167,672.5 603.5,672.5C 603.5,741.833 603.5,811.167 603.5,880.5C 633.835,880.333 664.168,880.5 694.5,881C 655.042,925.438 615.708,969.938 576.5,1014.5 Z"/></g>
|
||||||
|
<g><path style="opacity:1" fill="#ade2b5" d="M 419.5,879.5 C 388.833,880.167 358.167,880.833 327.5,881.5C 326.957,881.44 326.624,881.107 326.5,880.5C 357.493,879.5 388.493,879.167 419.5,879.5 Z"/></g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 4.1 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 33 KiB |
@@ -5,13 +5,14 @@
|
|||||||
"confirm": "Confirm",
|
"confirm": "Confirm",
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
"selectAll": "Select All",
|
"selectAll": "Select All",
|
||||||
"task": "Task",
|
"task": "Tasks",
|
||||||
"downloading": "downloading",
|
"downloading": "downloading",
|
||||||
"downloaded": "downloaded",
|
"downloaded": "downloaded",
|
||||||
"setting": "Setting",
|
"setting": "Settings",
|
||||||
"donate": "Donate",
|
"donate": "Donate",
|
||||||
"exit": "Exit",
|
"exit": "Exit",
|
||||||
"create": "Create Task",
|
"create": "Create Task",
|
||||||
|
"directDownload": "Direct Download",
|
||||||
"advancedOptions": "Advanced Options",
|
"advancedOptions": "Advanced Options",
|
||||||
"downloadLink": "Download Link",
|
"downloadLink": "Download Link",
|
||||||
"downloadLinkValid": "Please enter the download link",
|
"downloadLinkValid": "Please enter the download link",
|
||||||
@@ -65,6 +66,12 @@
|
|||||||
"extensionInstallSuccess": "Installed successfully",
|
"extensionInstallSuccess": "Installed successfully",
|
||||||
"extensionUpdateSuccess": "Updated successfully",
|
"extensionUpdateSuccess": "Updated successfully",
|
||||||
"extensionDelete": "Delete Extension",
|
"extensionDelete": "Delete Extension",
|
||||||
"extensionAlreadyLatest": "It's already the latest version"
|
"extensionAlreadyLatest": "It's already the latest version",
|
||||||
|
"extensionFind": "Find Extensions",
|
||||||
|
"extensionDevelop": "Develop Extensions",
|
||||||
|
"history": "History",
|
||||||
|
"clearHistory": "Clear History",
|
||||||
|
"noHistoryFound": "No History Found",
|
||||||
|
"serviceTitle": "Download Service",
|
||||||
|
"serviceText": "Running"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -12,14 +12,16 @@
|
|||||||
"donate": "Пожертвовать",
|
"donate": "Пожертвовать",
|
||||||
"exit": "Выход",
|
"exit": "Выход",
|
||||||
"create": "Создать задачу",
|
"create": "Создать задачу",
|
||||||
"downloadLink": "Ссылка для скачивание",
|
"advancedOptions": "Расширенные опции",
|
||||||
|
"downloadLink": "Ссылка на скачивание",
|
||||||
"downloadLinkValid": "Пожалуйста, введите ссылку для скачивания",
|
"downloadLinkValid": "Пожалуйста, введите ссылку для скачивания",
|
||||||
"downloadLinkHit": "Пожалуйста, введите ссылку для скачивания, HTTP/HTTPS/MAGNET supported@append",
|
"downloadLinkHit": "Пожалуйста, введите ссылку для скачивания, HTTP/HTTPS/MAGNET поддерживаются@append",
|
||||||
"downloadLinkHitDesktop": ", или перетащите сюда торрент-файл",
|
"downloadLinkHitDesktop": ", или перетащите сюда торрент-файл",
|
||||||
"download": "Скачать",
|
"download": "Скачать",
|
||||||
"noFileSelected": "Файл не выбран",
|
"noFileSelected": "Файл не выбран",
|
||||||
"noStoragePermission": "Требуется доступ к хранилищу",
|
"noStoragePermission": "Требуется доступ к хранилищу",
|
||||||
"selectFile": "Выберать файл",
|
"selectFile": "Выбрать файл",
|
||||||
|
"rename": "Переименовать",
|
||||||
"basic": "Основные параметры",
|
"basic": "Основные параметры",
|
||||||
"advanced": "Расширенные",
|
"advanced": "Расширенные",
|
||||||
"general": "Общие",
|
"general": "Общие",
|
||||||
@@ -57,5 +59,15 @@
|
|||||||
"delete": "Удалить",
|
"delete": "Удалить",
|
||||||
"newVersionTitle": "Обнаружена новая версия @version",
|
"newVersionTitle": "Обнаружена новая версия @version",
|
||||||
"newVersionUpdate": "Обновить",
|
"newVersionUpdate": "Обновить",
|
||||||
"newVersionLater": "позже"
|
"newVersionLater": "позже",
|
||||||
|
"extensions": "Расширения",
|
||||||
|
"extensionInstallSuccess": "Установка завершена",
|
||||||
|
"extensionUpdateSuccess": "Обновление завершено",
|
||||||
|
"extensionDelete": "Удалить расширение",
|
||||||
|
"extensionAlreadyLatest": "Это последняя версия",
|
||||||
|
"extensionFind": "Найти расширения",
|
||||||
|
"extensionDevelop": "Разработка расширений",
|
||||||
|
"history": "История",
|
||||||
|
"clearHistory": "Очистить историю",
|
||||||
|
"noHistoryFound": "История не найдена"
|
||||||
}
|
}
|
||||||
@@ -12,6 +12,7 @@
|
|||||||
"donate": "打赏",
|
"donate": "打赏",
|
||||||
"exit": "退出",
|
"exit": "退出",
|
||||||
"create": "创建任务",
|
"create": "创建任务",
|
||||||
|
"directDownload": "直接下载",
|
||||||
"advancedOptions": "高级选项",
|
"advancedOptions": "高级选项",
|
||||||
"downloadLink": "下载链接",
|
"downloadLink": "下载链接",
|
||||||
"downloadLinkValid": "请输入下载链接",
|
"downloadLinkValid": "请输入下载链接",
|
||||||
@@ -65,5 +66,12 @@
|
|||||||
"extensionInstallSuccess": "安装成功",
|
"extensionInstallSuccess": "安装成功",
|
||||||
"extensionUpdateSuccess": "更新成功",
|
"extensionUpdateSuccess": "更新成功",
|
||||||
"extensionDelete": "删除扩展",
|
"extensionDelete": "删除扩展",
|
||||||
"extensionAlreadyLatest": "已经是最新版本"
|
"extensionAlreadyLatest": "已经是最新版本",
|
||||||
|
"extensionFind": "获取扩展",
|
||||||
|
"extensionDevelop": "开发扩展",
|
||||||
|
"history": "历史记录",
|
||||||
|
"clearHistory": "清空历史记录",
|
||||||
|
"noHistoryFound": "暂无历史记录",
|
||||||
|
"serviceTitle": "下载服务",
|
||||||
|
"serviceText": "运行中"
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"label": "中文 (正體) ",
|
"label": "中文 (繁體) ",
|
||||||
"error": "錯誤",
|
"error": "錯誤",
|
||||||
"tip": "提示",
|
"tip": "提示",
|
||||||
"confirm": "確定",
|
"confirm": "確定",
|
||||||
@@ -12,6 +12,7 @@
|
|||||||
"donate": "斗內",
|
"donate": "斗內",
|
||||||
"exit": "離開",
|
"exit": "離開",
|
||||||
"create": "新增任務",
|
"create": "新增任務",
|
||||||
|
"directDownload": "直接下載",
|
||||||
"advancedOptions": "進階選項",
|
"advancedOptions": "進階選項",
|
||||||
"downloadLink": "下載鏈接",
|
"downloadLink": "下載鏈接",
|
||||||
"downloadLinkValid": "請輸入下載鏈接",
|
"downloadLinkValid": "請輸入下載鏈接",
|
||||||
@@ -27,16 +28,16 @@
|
|||||||
"general": "通用設定",
|
"general": "通用設定",
|
||||||
"downloadDir": "下載目録",
|
"downloadDir": "下載目録",
|
||||||
"downloadDirValid": "請選擇下載目録",
|
"downloadDirValid": "請選擇下載目録",
|
||||||
"connections": "同時連接數量",
|
"connections": "同時連接數",
|
||||||
"maxRunning": "最大同時下載數",
|
"maxRunning": "最大同時下載數",
|
||||||
"items": "@count 項",
|
"items": "@count 項",
|
||||||
"subscribeTracker": "已訂閲的 Tracker",
|
"subscribeTracker": "已訂閲的追蹤者列表",
|
||||||
"subscribeFail": "訂閲失敗",
|
"subscribeFail": "訂閲失敗",
|
||||||
"update": "更新",
|
"update": "更新",
|
||||||
"updateDaily": "每日自動更新",
|
"updateDaily": "每天自動更新",
|
||||||
"lastUpdate": "上次更新時間: @time",
|
"lastUpdate": "上次更新時間: @time",
|
||||||
"addTracker": "增加新的 Tracker",
|
"addTracker": "增加新的追蹤者",
|
||||||
"addTrackerHit": "請輸入 Tracker 服務器地址,每行一條",
|
"addTrackerHit": "請輸入追蹤者服務器地址,每行一條",
|
||||||
"ui": "介面",
|
"ui": "介面",
|
||||||
"theme": "佈景主題",
|
"theme": "佈景主題",
|
||||||
"themeSystem": "跟隨系統",
|
"themeSystem": "跟隨系統",
|
||||||
@@ -46,12 +47,12 @@
|
|||||||
"about": "關於",
|
"about": "關於",
|
||||||
"homepage": "訪問主頁",
|
"homepage": "訪問主頁",
|
||||||
"version": "版本資訊",
|
"version": "版本資訊",
|
||||||
"protocol": "通訊協定",
|
"protocol": "端口設定",
|
||||||
"port": "端口",
|
"port": "端口",
|
||||||
"apiToken": "接口 Token",
|
"apiToken": "接口秘鑰",
|
||||||
"notSet": "尚未設定",
|
"notSet": "未設定",
|
||||||
"set": "已設定",
|
"set": "已設定",
|
||||||
"effectAfterRestart": "該選項將在重新啓動軟體後生效",
|
"effectAfterRestart": "重啟後生效",
|
||||||
"startAll": "全部開始",
|
"startAll": "全部開始",
|
||||||
"pauseAll": "全部暫停",
|
"pauseAll": "全部暫停",
|
||||||
"deleteTask": "刪除任務",
|
"deleteTask": "刪除任務",
|
||||||
@@ -61,9 +62,16 @@
|
|||||||
"newVersionUpdate": "立刻更新",
|
"newVersionUpdate": "立刻更新",
|
||||||
"newVersionLater": "稍後更新",
|
"newVersionLater": "稍後更新",
|
||||||
"extensions": "外掛程序",
|
"extensions": "外掛程序",
|
||||||
"extensionInstallUrl": "安裝鏈接",
|
"extensionInstallUrl": "安裝URL",
|
||||||
"extensionInstallSuccess": "安裝完成",
|
"extensionInstallSuccess": "安裝成功",
|
||||||
"extensionUpdateSuccess": "更新完成",
|
"extensionUpdateSuccess": "更新完成",
|
||||||
"extensionDelete": "解除安裝",
|
"extensionDelete": "解除安裝",
|
||||||
"extensionAlreadyLatest": "已是最新版本"
|
"extensionAlreadyLatest": "已是最新版本",
|
||||||
|
"extensionFind": "取得外掛",
|
||||||
|
"extensionDevelop": "開發外掛",
|
||||||
|
"history": "歷史記錄",
|
||||||
|
"clearHistory": "清空記錄",
|
||||||
|
"noHistoryFound": "還沒有記錄",
|
||||||
|
"serviceTitle": "下載服務",
|
||||||
|
"serviceText": "執行中"
|
||||||
}
|
}
|
||||||
@@ -33,6 +33,15 @@ import Libgopeed
|
|||||||
})
|
})
|
||||||
|
|
||||||
GeneratedPluginRegistrant.register(with: self)
|
GeneratedPluginRegistrant.register(with: self)
|
||||||
|
|
||||||
|
SwiftFlutterForegroundTaskPlugin.setPluginRegistrantCallback(registerPlugins)
|
||||||
|
if #available(iOS 10.0, *) {
|
||||||
|
UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate
|
||||||
|
}
|
||||||
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func registerPlugins(registry: FlutterPluginRegistry) {
|
||||||
|
GeneratedPluginRegistrant.register(with: registry)
|
||||||
|
}
|
||||||
@@ -1 +1,2 @@
|
|||||||
#import "GeneratedPluginRegistrant.h"
|
#import "GeneratedPluginRegistrant.h"
|
||||||
|
#import <flutter_foreground_task/FlutterForegroundTaskPlugin.h>
|
||||||
@@ -30,6 +30,8 @@ class ProtocolConfig {
|
|||||||
|
|
||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
class HttpConfig {
|
class HttpConfig {
|
||||||
|
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 = 0;
|
int connections = 0;
|
||||||
|
|
||||||
HttpConfig();
|
HttpConfig();
|
||||||
@@ -41,6 +43,7 @@ class HttpConfig {
|
|||||||
|
|
||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
class BtConfig {
|
class BtConfig {
|
||||||
|
int listenPort = 0;
|
||||||
List<String> trackers = [];
|
List<String> trackers = [];
|
||||||
|
|
||||||
BtConfig();
|
BtConfig();
|
||||||
|
|||||||
@@ -33,19 +33,23 @@ Map<String, dynamic> _$ProtocolConfigToJson(ProtocolConfig instance) =>
|
|||||||
'bt': instance.bt.toJson(),
|
'bt': instance.bt.toJson(),
|
||||||
};
|
};
|
||||||
|
|
||||||
HttpConfig _$HttpConfigFromJson(Map<String, dynamic> json) =>
|
HttpConfig _$HttpConfigFromJson(Map<String, dynamic> json) => HttpConfig()
|
||||||
HttpConfig()..connections = json['connections'] as int;
|
..userAgent = json['userAgent'] as String
|
||||||
|
..connections = json['connections'] as int;
|
||||||
|
|
||||||
Map<String, dynamic> _$HttpConfigToJson(HttpConfig instance) =>
|
Map<String, dynamic> _$HttpConfigToJson(HttpConfig instance) =>
|
||||||
<String, dynamic>{
|
<String, dynamic>{
|
||||||
|
'userAgent': instance.userAgent,
|
||||||
'connections': instance.connections,
|
'connections': instance.connections,
|
||||||
};
|
};
|
||||||
|
|
||||||
BtConfig _$BtConfigFromJson(Map<String, dynamic> json) => BtConfig()
|
BtConfig _$BtConfigFromJson(Map<String, dynamic> json) => BtConfig()
|
||||||
|
..listenPort = json['listenPort'] as int
|
||||||
..trackers =
|
..trackers =
|
||||||
(json['trackers'] as List<dynamic>).map((e) => e as String).toList();
|
(json['trackers'] as List<dynamic>).map((e) => e as String).toList();
|
||||||
|
|
||||||
Map<String, dynamic> _$BtConfigToJson(BtConfig instance) => <String, dynamic>{
|
Map<String, dynamic> _$BtConfigToJson(BtConfig instance) => <String, dynamic>{
|
||||||
|
'listenPort': instance.listenPort,
|
||||||
'trackers': instance.trackers,
|
'trackers': instance.trackers,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ class Task {
|
|||||||
Meta meta;
|
Meta meta;
|
||||||
Status status;
|
Status status;
|
||||||
Progress progress;
|
Progress progress;
|
||||||
int size;
|
|
||||||
DateTime createdAt;
|
DateTime createdAt;
|
||||||
|
|
||||||
Task({
|
Task({
|
||||||
@@ -20,7 +19,6 @@ class Task {
|
|||||||
required this.meta,
|
required this.meta,
|
||||||
required this.status,
|
required this.status,
|
||||||
required this.progress,
|
required this.progress,
|
||||||
required this.size,
|
|
||||||
required this.createdAt,
|
required this.createdAt,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ Task _$TaskFromJson(Map<String, dynamic> json) => Task(
|
|||||||
meta: Meta.fromJson(json['meta'] as Map<String, dynamic>),
|
meta: Meta.fromJson(json['meta'] as Map<String, dynamic>),
|
||||||
status: $enumDecode(_$StatusEnumMap, json['status']),
|
status: $enumDecode(_$StatusEnumMap, json['status']),
|
||||||
progress: Progress.fromJson(json['progress'] as Map<String, dynamic>),
|
progress: Progress.fromJson(json['progress'] as Map<String, dynamic>),
|
||||||
size: json['size'] as int,
|
|
||||||
createdAt: DateTime.parse(json['createdAt'] as String),
|
createdAt: DateTime.parse(json['createdAt'] as String),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -20,7 +19,6 @@ Map<String, dynamic> _$TaskToJson(Task instance) => <String, dynamic>{
|
|||||||
'meta': instance.meta.toJson(),
|
'meta': instance.meta.toJson(),
|
||||||
'status': _$StatusEnumMap[instance.status]!,
|
'status': _$StatusEnumMap[instance.status]!,
|
||||||
'progress': instance.progress.toJson(),
|
'progress': instance.progress.toJson(),
|
||||||
'size': instance.size,
|
|
||||||
'createdAt': instance.createdAt.toIso8601String(),
|
'createdAt': instance.createdAt.toIso8601String(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Loaded 50 of 79 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user