Compare commits

...
68 Commits
Author SHA1 Message Date
liwei 195dfac74b test 2024-01-19 14:31:03 +08:00
liwei 1c1fab2002 test 2024-01-19 11:54:36 +08:00
madoka773 abccbabc78 fix: wrong debian installed size (#335) 2024-01-19 11:17:50 +08:00
Levi c26eb26b8f fix: default download path on mobile (#337) 2024-01-19 11:12:27 +08:00
Levi 74beea6044 feat: auto create torrent task for http download (#334) 2024-01-18 18:38:44 +08:00
Levi e548d7e0c9 feat: save window size and resize on startup (#332) 2024-01-18 15:00:05 +08:00
madoka773 a996d57914 ci: fix debian 12 depends (#329) 2024-01-18 11:16:50 +08:00
Levi f7dc60ad50 fix: proxy setting only numeric input (#328) 2024-01-18 09:17:06 +08:00
Levi 8425a96ac9 feat: add mirror.ghproxy.com trackers subscribe cdn (#330) 2024-01-17 22:09:37 +08:00
Tracey Lesley 99f4e8c895 feat: add download stats info (#323) 2024-01-17 21:59:01 +08:00
Elliot cc0f61b527 feat: improve zh-TW translation (#327) 2024-01-17 18:16:40 +08:00
Levi 922ef969a6 feat: add thanks item for contributors (#325) 2024-01-16 19:02:20 +08:00
Tracey Lesley 3a542054fc fix: compatible with not follow the http range response standard 2024-01-14 09:58:14 +08:00
Levi 895a8d2ee4 fix: foreground task scope (#314) 2024-01-05 21:49:21 +08:00
madoka773 29d0f5ebcf ci: use flutter distributor (#313) 2024-01-04 12:46:16 +08:00
Levi 31228e3edf release: v1.5.3 2023-12-15 19:23:16 +08:00
Levi 31a32d7e14 fix: handle api panic (#302) 2023-12-15 19:20:56 +08:00
Levi 65fd6fb489 fix: http pause handle (#303) 2023-12-15 19:15:38 +08:00
Levi 953b878a89 fix: adapt mobile proxy settings ui (#301) 2023-12-15 19:09:48 +08:00
Elliot ae4ca3da58 feat: improve zh-TW translation (#300) 2023-12-14 18:06:49 +08:00
Levi d09bd180af release: v1.5.2 2023-12-13 09:38:34 +08:00
liwei 3d24e353c5 docs: docker data path mounted 2023-12-13 08:57:57 +08:00
Levi ce7489b0c0 feat: support volume storage dir on docker (#291) 2023-12-13 08:51:22 +08:00
Levi 2b5091ba21 feat: improve extension engine (#298)
1. support onError event
2. add built-in MessageError
2023-12-12 19:00:42 +08:00
Levi ff32971aca fix: repair invalid filename (#296) 2023-12-12 18:40:36 +08:00
Levi f2af61f9aa fix: sort of completed tasks (#297) 2023-12-12 18:36:40 +08:00
Elliot d935b9d631 feat: improve zh_TW translate (#287) 2023-12-04 23:01:49 +08:00
liwei ae66d1d83d docs: fix contributing 2023-12-04 13:41:27 +08:00
madoka773 aa91ca1d50 ci: linux build use libappindicator (#286) 2023-12-04 13:36:27 +08:00
liwei 2d4bdea5cb fix: android file open 2023-12-02 22:51:44 +08:00
liwei 2f2393ac20 fix: i18n word 2023-12-02 19:22:37 +08:00
Levi 5a761e244a release: v1.5.1 2023-12-02 18:47:07 +08:00
Levi 6538766780 fix: incorrect file name in task files view (#285) 2023-12-02 18:45:16 +08:00
Levi 16b7a3d5b3 feat: support proxy (#284) 2023-12-02 18:36:52 +08:00
Levi ef97528114 ci: fix window 2023-11-29 21:12:06 +08:00
Levi b58669bb46 ci: fix windows build 2023-11-29 20:35:20 +08:00
Levi 51d28c5e53 release: v1.5.0 2023-11-29 20:18:08 +08:00
Levi db15650664 feat: support browse files on web (#283) 2023-11-29 18:24:01 +08:00
Levi ee7da179c1 chore: upgrade flutter to v3.16.1 (#282) 2023-11-29 13:32:07 +08:00
Levi 6bb32372ab feat: support more configurations (#281) 2023-11-29 12:52:51 +08:00
Levi 7d6428c967 fix: task can not completed (#280) 2023-11-28 18:27:36 +08:00
Levi bba5107dd5 fix: crash on android arm-32 (#279) 2023-11-27 11:10:50 +08:00
liwei ddfe76b635 fix: script engine event loop stop 2023-11-25 00:43:40 +08:00
Levi aad7395dde feat: enhanced extension script engine (#277)
1. update goja version
2. add node vm polyfill
3. non-stop event loop handle
4 improve logger print
2023-11-24 22:41:16 +08:00
Levi 6f541715df fix: extension download path (#274) 2023-11-24 18:16:03 +08:00
neovali 848797b2fd fix: .deb build (#269) 2023-11-15 21:01:28 +08:00
Levi 0a05bf41e7 fix: incorrect storage path in linux (#270) 2023-11-15 16:26:29 +08:00
liwei 485656d1d4 ci: fix linux desktop entry 2023-11-13 16:23:26 +08:00
liwei c074e0c2bc chore: generate locales 2023-11-13 15:55:02 +08:00
yuloury 0db0a2c987 ci: fix deb desktop entry (#268) 2023-11-11 09:23:17 +08:00
Elliot b06a79b5c7 feat: improve zh_TW translate 2023-11-10 09:11:50 +08:00
liwei 4840c4b8a4 ci: fix build env 2023-11-09 18:49:35 +08:00
Levi e72b097aa2 release: v1.4.4 2023-11-09 17:09:15 +08:00
Levi af52c4ce7e feat: support create task directly (#262) 2023-11-09 16:52:02 +08:00
Levi aae79c2449 feat: running foreground service on mobile (#261) 2023-11-07 17:52:10 +08:00
Levi 83838afce9 feat: support more linux distributions (#254) 2023-11-01 14:54:24 +08:00
Levi 4690107e32 fix: gopeed cli BT download (#258) 2023-10-31 10:16:28 +08:00
liwei 74b08b3631 chore: build locales.g.dart 2023-10-31 09:24:55 +08:00
Kiber_kotan 953d348c0c feat: improve Russian translation (#257) 2023-10-30 11:27:43 +08:00
Levi 00cea68b6e feat: add extension intro links (#252) 2023-10-26 18:35:22 +08:00
Levi daa9d4c707 release: v1.4.3 2023-10-24 16:17:48 +08:00
liwei 6bc587752a chore: update introduction 2023-10-24 16:16:16 +08:00
Levi 11befc09bd refractor: downloader task action dispatch (#249) 2023-10-24 16:02:39 +08:00
liwei ff6c0da262 chore: locales build 2023-10-23 13:55:13 +08:00
Levi aab4a37a7e feat: adapt mobile extension list (#242) 2023-10-21 16:36:22 +08:00
Levi 74c9491893 fix: linux system tray tooltip (#243) 2023-10-21 16:29:15 +08:00
Levi 256faf2cbd feat: add extension setting tooltip (#247) 2023-10-21 16:16:46 +08:00
Om Jogani 32e3620f5e feat: Implemented History Panel for reusing previously used Magnet & Torrent Links (#245) 2023-10-21 15:52:34 +08:00
121 changed files with 3817 additions and 2008 deletions

No files matched your search

+7 -1
View File
@@ -19,6 +19,8 @@ categories:
label: 'doc'
- title: '🚀 CI/CD'
label: 'ci'
- title: '🌎 Internationalization'
label: 'translation'
change-template: '- $TITLE @$AUTHOR (#$NUMBER)'
version-resolver:
major:
@@ -32,6 +34,10 @@ version-resolver:
- 'patch'
default: patch
template: |
## Changes
# Changes
$CHANGES
# 更新日志
$CHANGES
+114 -43
View File
@@ -2,10 +2,19 @@ name: build
on:
workflow_dispatch:
inputs:
platform:
description: 'Build platform'
required: true
default: 'all'
channel:
description: 'Snap channel'
required: true
default: 'stable'
env:
GO_VERSION: "1.19"
FLUTTER_VERSION: "3.10.6"
FLUTTER_VERSION: "3.16.1"
permissions:
contents: write
@@ -24,6 +33,7 @@ jobs:
myToken: ${{ github.token }}
build-windows:
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'windows' }}
runs-on: windows-latest
needs: [ get-release ]
steps:
@@ -46,7 +56,7 @@ jobs:
flutter build windows
$mingw = "C:\Program Files\Git\mingw64\bin"
$system = "C:\Windows\System32"
$release = "build\windows\runner\Release\"
$release = "build\windows\x64\runner\Release\"
cp $mingw\libstdc++-6.dll $release
cp $mingw\libgcc_s_seh-1.dll $release
cp $mingw\libwinpthread-1.dll $release
@@ -100,7 +110,7 @@ jobs:
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
[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
[UninstallDelete]
@@ -124,6 +134,7 @@ jobs:
asset_path: ui/flutter/build/windows/Output/*
overwrite: true
build-macos-arm64-lib:
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'macos' }}
runs-on: ubuntu-latest
needs: [ get-release ]
steps:
@@ -134,13 +145,14 @@ jobs:
- name: Build
run: |
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
- uses: actions/upload-artifact@v3
with:
name: macos-arm64-lib
path: libgopeed.dylib
build-macos:
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'macos' }}
runs-on: macos-latest
needs: [ get-release, build-macos-arm64-lib ]
steps:
@@ -155,7 +167,9 @@ jobs:
with:
flutter-version: ${{ env.FLUTTER_VERSION }}
- name: Install appdmg
run: npm install -g appdmg
run: |
python3 -m pip install setuptools
npm install -g appdmg
- uses: actions/download-artifact@v3
with:
name: macos-arm64-lib
@@ -197,7 +211,8 @@ jobs:
asset_path: ui/flutter/build/macos/Build/Products/Release/dist/*
overwrite: true
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 ]
steps:
- uses: actions/checkout@v3
@@ -208,55 +223,108 @@ jobs:
with:
flutter-version: ${{ env.FLUTTER_VERSION }}
- run: |
sudo apt-get update -y
sudo apt-get install -y ninja-build libgtk-3-dev libayatana-appindicator3-dev
sudo apt update -y
# flutter build dependencies
sudo apt install -y ninja-build libgtk-3-dev libappindicator3-dev
# rpm build dependencies
sudo apt install -y rpm patchelf
# appimage build dependencies
sudo apt install -y libfuse2 locate
wget -O appimagetool "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage"
chmod +x appimagetool
sudo mv appimagetool /usr/local/bin/
- name: Build
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
flutter build linux
mkdir -p debian/gui
cp assets/icon/icon_1024.png debian/gui/gopeed.png
cat>debian/debian.yaml<<EOF
flutter_app:
command: gopeed
arch: x64
parent: /usr/local/lib
control:
Package: gopeed
Version: ${VERSION:1}
Architecture: amd64
Essential: no
Priority: optional
Depends:
Maintainer: monkeyWie
Description: High speed downloader that supports all platforms.
EOF
cat>debian/gui/gopeed.desktop<<EOF
[Desktop Entry]
Version=${VERSION:1}
Name=Gopeed
GenericName=Gopeed
Comment=High speed downloader that supports all platforms.
Terminal=false
Type=Application
Categories=Utility
Keywords=Flutter;Downloader;
EOF
dart pub global activate flutter_to_debian
flutter_to_debian
mv debian/packages/gopeed*.deb debian/packages/Gopeed-$VERSION-linux-amd64.deb
dart pub global activate -sgit https://github.com/GopeedLab/flutter_distributor.git --git-path packages/flutter_distributor
flutter_distributor package --platform linux --targets appimage,deb
cd dist/*
mv gopeed-*-linux.AppImage Gopeed-${VERSION}-linux-x86_64.AppImage
mv gopeed-*-linux.deb Gopeed-${VERSION}-linux-amd64.deb
- name: Upload
uses: shogo82148/actions-upload-release-asset@v1
with:
upload_url: ${{ needs.get-release.outputs.upload_url }}
asset_path: ui/flutter/debian/packages/*
asset_path: ui/flutter/dist/*/*
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
- libappindicator3-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:
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'android' }}
runs-on: ubuntu-latest
needs: [ get-release ]
steps:
@@ -294,6 +362,7 @@ jobs:
asset_path: ui/flutter/dist/*
overwrite: true
build-ios:
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'ios' }}
runs-on: macos-latest
needs: [ get-release ]
steps:
@@ -326,6 +395,7 @@ jobs:
asset_path: ui/flutter/dist/*
overwrite: true
build-web:
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'web' }}
runs-on: ubuntu-latest
needs: [ get-release ]
steps:
@@ -373,6 +443,7 @@ jobs:
asset_path: dist/zip/*
overwrite: true
build-docker:
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'docker' }}
runs-on: ubuntu-latest
needs: [ get-release ]
steps:
+1 -1
View File
@@ -28,7 +28,7 @@ on:
env:
GO_VERSION: "1.19"
FLUTTER_VERSION: "3.10.6"
FLUTTER_VERSION: "3.16.1"
jobs:
# lint:
+1 -1
View File
@@ -4,7 +4,7 @@
*.dll
*.so
*.dylib
ui/flutter/dist/
# Test binary, built with `go test -c`
*.test
+1 -22
View File
@@ -17,17 +17,9 @@ start the front-end flutter project in `debug` mode to run.
## Translation
The internationalization files of Gopeed are located in the `ui/flutter/assets/locales` directory.
The internationalization files of Gopeed are located in the `ui/flutter/lib/i18n/langs` directory.
You only need to add the corresponding language file in this directory.
Generate locales after you edit locales:
```
get generate locales
```
Please refer to `en_us.dart` for translation.
## flutter development
@@ -39,16 +31,3 @@ Turn on build_runner watcher if you want to edit api/models:
```
flutter pub run build_runner watch
```
get-cli commands usages:
```
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 -22
View File
@@ -17,17 +17,9 @@
## 翻訳
Gopeed の国際化ファイルは `ui/flutter/assets/locales` ディレクトリにあります。
Gopeed の国際化ファイルは `ui/flutter/lib/i18n/langs` ディレクトリにあります。
このディレクトリに対応する言語ファイルを追加するだけでよいです。
ロケール編集後にロケールを生成:
```
get generate locales
```
翻訳については `en_us.dart` を参照してください。
## flutter での開発
@@ -39,16 +31,3 @@ 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
```
+2 -20
View File
@@ -14,15 +14,9 @@ flutter 项目即可运行。
## 翻译
Gopeed 的国际化文件位于 `ui/flutter/assets/locales` 目录下,只需要在该目录下添加对应的语言文件即可。
Gopeed 的国际化文件位于 `ui/flutter/lib/i18n/langs` 目录下,只需要在该目录下添加对应的语言文件即可。
编辑locales后请运行以下命令:
```
get generate locales
```
请注意以 `en_US.json` 为参照进行翻译。
请注意以 `en_us.dart` 为参照进行翻译。
## flutter开发
@@ -34,15 +28,3 @@ get generate locales
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
```
+28
View File
@@ -0,0 +1,28 @@
# Gopeed 協助指南
首先感謝您願意幫助我們改進並優化該項目,這份指南將會幫助您更好的參與 Gopeed 的開發。
## 分支說明
本項目只有一個分支,即 `main` 分支,如果您想要參與 Gopeed 的開發,請先 fork 該項目,再在您自己的 fork 中進行開發,開發完成後再開啟PR,以合併至 `main` 分支。
## 離線開發
建議使用 web 端進行開發與調試,首先啟動服務,使用指令 `go run cmd/api/main.go` 啟動 ,該服務默認連接埠為 `9999`,接著以 `debug` 模式啟動前端 flutter 項目即可。
## 翻譯
Gopeed 的翻譯文件位於 `ui/flutter/lib/i18n/langs` 目錄中,只需要修改或新建翻譯文件即可。
請以 `en_us.dart` 作為參照。
## flutter開發
每次提交PR前請務必執行 `dart format ./ui/flutter`
如果需要編輯 api/models,請打開build_runner watcher:
```
flutter pub run build_runner watch
```
+2 -1
View File
@@ -1,4 +1,4 @@
FROM instrumentisto/flutter:3.10.6 AS flutter
FROM instrumentisto/flutter:3.16.1 AS flutter
WORKDIR /app
COPY ./ui/flutter/pubspec.yaml ./ui/flutter/pubspec.lock ./
RUN flutter pub get
@@ -17,5 +17,6 @@ FROM alpine:3.14.2
LABEL maintainer="monkeyWie"
WORKDIR /app
COPY --from=go /app/dist/gopeed ./
VOLUME ["/app/storage"]
EXPOSE 9999
ENTRYPOINT ["./gopeed"]
+7 -5
View File
@@ -9,13 +9,14 @@
[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/R6R6IJGN6)
[English](/README.md) | [中文](/README_zh-CN.md) | [日本語](/README_ja-JP.md)
[English](/README.md) | [中文](/README_zh-CN.md) | [日本語](/README_ja-JP.md) | [正體中文](/README_zh-TW.md)
## 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) | 📖 [Official Docs](https://docs.gopeed.com)
## Install
@@ -46,9 +47,11 @@ go install github.com/GopeedLab/gopeed/cmd/gopeed@latest
#### Start directly
```bash
docker run -d -p 9999:9999 -v /path/to/download:/root/Downloads liwei2633/gopeed
docker run -d -p 9999:9999 -v /path/to/download:/root/Downloads -v /path/to/storage:/app/storage liwei2633/gopeed
```
[More details for docker](https://docs.gopeed.com/install.html#docker-installation)
#### Using Docker Compose
```bash
@@ -159,7 +162,6 @@ 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
```
## Credits
+5 -4
View File
@@ -9,11 +9,11 @@
[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/R6R6IJGN6)
[English](/README.md) | [中文](/README_zh-CN.md) | [日本語](/README_ja-JP.md)
[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)
@@ -46,9 +46,11 @@ go install github.com/GopeedLab/gopeed/cmd/gopeed@latest
#### 直接始める
```bash
docker run -d -p 9999:9999 -v /path/to/download:/root/Downloads liwei2633/gopeed
docker run -d -p 9999:9999 -v /path/to/download:/root/Downloads -v /path/to/storage:/app/storage liwei2633/gopeed
```
[Dockerの使用については](https://docs.gopeed.com/install.html#docker-installation)
#### Docker Compose の使用
```bash
@@ -159,7 +161,6 @@ 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
```
## 感謝
+6 -5
View File
@@ -9,13 +9,13 @@
[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/R6R6IJGN6)
[English](/README.md) | [中文](/README_zh-CN.md) | [日本語](/README_ja-JP.md)
[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/)
## 安装
@@ -46,9 +46,11 @@ go install github.com/GopeedLab/gopeed/cmd/gopeed@latest
#### 直接运行
```bash
docker run -d -p 9999:9999 -v /path/to/download:/root/Downloads liwei2633/gopeed
docker run -d -p 9999:9999 -v /path/to/download:/root/Downloads -v /path/to/storage:/app/storage liwei2633/gopeed
```
[更多关于 Docker 的使用](https://docs.gopeed.com/zh/install.html#docker-%E5%AE%89%E8%A3%85)
#### 使用 Docker Compose
```bash
@@ -159,7 +161,6 @@ 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
```
## 感谢
+180
View File
@@ -0,0 +1,180 @@
[![Test Status](https://github.com/GopeedLab/gopeed/workflows/test/badge.svg)](https://github.com/GopeedLab/gopeed/actions?query=workflow%3Atest)
[![Codecov](https://codecov.io/gh/GopeedLab/gopeed/branch/main/graph/badge.svg)](https://codecov.io/gh/GopeedLab/gopeed)
[![Release](https://img.shields.io/github/release/GopeedLab/gopeed.svg)](https://github.com/GopeedLab/gopeed/releases)
[![Download](https://img.shields.io/github/downloads/GopeedLab/gopeed/total.svg)](https://github.com/GopeedLab/gopeed/releases)
[![Donate](https://img.shields.io/badge/%24-donate-ff69b4.svg)](https://docs.gopeed.com/donate.html)
[![Discord](https://img.shields.io/discord/1037992631881449472?label=Discord&logo=discord&style=social)](https://discord.gg/ZUJqJrwCGB)
![](_docs/img/banner.png)
[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/R6R6IJGN6)
[English](/README.md) | [中文](/README_zh-CN.md) | [日本語](/README_ja-JP.md) | [正體中文](/README_zh-TW.md)
## 簡介
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 -v /path/to/storage:/app/storage liwei2633/gopeed
```
[更多關於 Docker 的使用](https://docs.gopeed.com/zh/install.html#docker-%E5%AE%89%E8%A3%85)
#### 使用 Docker Compose
```bash
docker-compose up -d
```
#### 訪問服務
當 docker 容器運作時,可以通過 `http://localhost:9999` 訪問 web 頁面。
## 贊助
如果你認為該項目對你有所幫助,請考慮[贊助](https://docs.gopeed.com/zh/donate)以支持該項目的持續發展,謝謝!
## 軟體介面
![](_docs/img/ui-demo.png)
## 開發
該項目分為前端與後端,前端使用`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
```
## 感謝
### 貢獻者
<a href="https://github.com/GopeedLab/gopeed/graphs/contributors">
<img src="https://contrib.rocks/image?repo=GopeedLab/gopeed" />
</a>
### JetBrains
[![goland](_docs/img/goland.svg)](https://www.jetbrains.com/?from=gopeed)
## 軟體許可
該軟體遵循 [GPLv3](LICENSE) 。
+4 -3
View File
@@ -8,9 +8,10 @@ import (
// only for local development
func main() {
cfg := &model.StartConfig{
Network: "tcp",
Address: "127.0.0.1:9999",
Storage: model.StorageBolt,
Network: "tcp",
Address: "127.0.0.1:9999",
Storage: model.StorageBolt,
WebEnable: true,
}
cmd.Start(cfg)
}
+14 -1
View File
@@ -2,6 +2,7 @@ package main
import (
"fmt"
"github.com/GopeedLab/gopeed/internal/fetcher"
"github.com/GopeedLab/gopeed/pkg/base"
"github.com/GopeedLab/gopeed/pkg/download"
"github.com/GopeedLab/gopeed/pkg/protocol/http"
@@ -47,16 +48,28 @@ func main() {
if err != nil {
panic(err)
}
printProgress(emptyTask, "downloading...")
wg.Wait()
}
var (
lastLineLen = 0
sb = new(strings.Builder)
emptyTask = &download.Task{
Progress: &download.Progress{},
Meta: &fetcher.FetcherMeta{
Res: &base.Resource{},
},
}
)
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)
speed := util.ByteFmt(task.Progress.Speed)
totalSize := util.ByteFmt(task.Meta.Res.Size)
+1 -1
View File
@@ -23,7 +23,7 @@ func parse() *args {
cliArgs.Address = flag.String("A", "0.0.0.0", "Bind Address")
cliArgs.Port = flag.Int("P", 9999, "Bind Port")
cliArgs.Username = flag.String("u", "gopeed", "HTTP Basic Auth Username")
cliArgs.Password = flag.String("p", "", "HTTP Basic Auth Password")
cliArgs.Password = flag.String("p", "", "HTTP Basic Auth Pwd")
cliArgs.ApiToken = flag.String("T", "", "API token, that can only be used when basic authentication is enabled.")
cliArgs.configPath = flag.String("c", "./config.json", "Config file path")
flag.Parse()
+9
View File
@@ -9,6 +9,8 @@ import (
"github.com/GopeedLab/gopeed/cmd"
"github.com/GopeedLab/gopeed/pkg/rest/model"
"io/fs"
"os"
"path/filepath"
)
//go:embed dist/*
@@ -29,10 +31,17 @@ func main() {
}
}
exe, err := os.Executable()
if err != nil {
panic(err)
}
dir := filepath.Dir(exe)
cfg := &model.StartConfig{
Network: "tcp",
Address: fmt.Sprintf("%s:%d", *args.Address, *args.Port),
Storage: model.StorageBolt,
StorageDir: filepath.Join(dir, "storage"),
ApiToken: *args.ApiToken,
ProductionMode: true,
WebEnable: true,
+3 -2
View File
@@ -4,8 +4,9 @@ services:
gopeed:
container_name: gopeed
ports:
- 9999:9999 # HTTP port (host:container)
- "9999:9999" # HTTP port (host:container)
image: liwei2633/gopeed
volumes:
- ~/Downloads:/root/Downloads # replace with your prefer path
- ~/gopeed/Downloads:/root/Downloads # mount download path
#- ~/gopeed/storage:/app/storage # if you need to mount storage path, uncomment this line
restart: unless-stopped
+12 -11
View File
@@ -5,14 +5,17 @@ go 1.19
require (
github.com/anacrolix/missinggo/v2 v2.7.2-0.20230527121029-a582b4f397b9
github.com/anacrolix/torrent v1.52.5
github.com/dop251/goja v0.0.0-20230828202809-3dbe69dd2b8e
github.com/dop251/goja_nodejs v0.0.0-20230821135201-94e508132562
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5
github.com/dop251/goja v0.0.0-20231027120936-b396bb4c349d
github.com/dop251/goja_nodejs v0.0.0-20231122114759-e84d9a924c5c
github.com/go-git/go-git/v5 v5.8.1
github.com/gorilla/handlers v1.5.1
github.com/gorilla/mux v1.8.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
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
)
@@ -45,7 +48,7 @@ require (
github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 // indirect
github.com/cloudflare/circl v1.3.3 // 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/edsrzf/mmap-go v1.1.0 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
@@ -57,7 +60,7 @@ require (
github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // 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/gorilla/websocket v1.5.0 // indirect
github.com/huandu/xstrings v1.3.2 // indirect
@@ -88,19 +91,17 @@ require (
github.com/pjbgf/sha1cd v0.3.0 // indirect
github.com/pkg/errors v0.9.1 // 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/skeema/knownhosts v1.2.0 // indirect
github.com/tidwall/btree v1.6.0 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
go.opentelemetry.io/otel v1.8.0 // indirect
go.opentelemetry.io/otel/trace v1.8.0 // indirect
golang.org/x/crypto v0.11.0 // indirect
golang.org/x/exp v0.0.0-20220613132600-b0d781184e0d // indirect
golang.org/x/crypto v0.14.0 // indirect
golang.org/x/mod v0.8.0 // indirect
golang.org/x/net v0.12.0 // indirect
golang.org/x/sys v0.13.0 // indirect
golang.org/x/text v0.11.0 // indirect
golang.org/x/net v0.17.0 // indirect
golang.org/x/sys v0.14.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/time v0.0.0-20220609170525-579cf78fd858 // indirect
golang.org/x/tools v0.6.0 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
+21 -31
View File
@@ -8,7 +8,6 @@ crawshaw.io/sqlite v0.3.3-0.20220618202545-d1964889ea3c/go.mod h1:igAO5JulrQ1Dbd
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
filippo.io/edwards25519 v1.0.0-rc.1 h1:m0VOOB23frXZvAOK44usCgLWvtsxIoMCTBGJZlpmGfU=
filippo.io/edwards25519 v1.0.0-rc.1/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
@@ -34,7 +33,6 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alexflint/go-scalar v1.1.0/go.mod h1:LoFvNMqS1CPrMVltza4LvnGKhaSpc3oyLEBUZVhhS2o=
github.com/anacrolix/chansync v0.3.0 h1:lRu9tbeuw3wl+PhMu/r+JJCRu5ArFXIluOgdF0ao6/U=
github.com/anacrolix/chansync v0.3.0/go.mod h1:DZsatdsdXxD0WiwcGl0nJVwyjCKMDv+knl1q2iBjA2k=
github.com/anacrolix/dht/v2 v2.19.2-0.20221121215055-066ad8494444 h1:8V0K09lrGoeT2KRJNOtspA7q+OMxGwQqK/Ug0IiaaRE=
@@ -74,7 +72,6 @@ github.com/anacrolix/mmsg v1.0.0 h1:btC7YLjOn29aTUAExJiVUhQOuf/8rhm+/nWCMAnL3Hg=
github.com/anacrolix/mmsg v1.0.0/go.mod h1:x8kRaJY/dCrY9Al0PEcj1mb/uFHwP6GCJ9fLl4thEPc=
github.com/anacrolix/multiless v0.3.0 h1:5Bu0DZncjE4e06b9r1Ap2tUY4Au0NToBP5RpuEngSis=
github.com/anacrolix/multiless v0.3.0/go.mod h1:TrCLEZfIDbMVfLoQt5tOoiBS/uq4y8+ojuEVVvTNPX4=
github.com/anacrolix/publicip v0.2.0/go.mod h1:67G1lVkLo8UjdEcJkwScWVTvlJ35OCDsRJoWXl/wi4g=
github.com/anacrolix/stm v0.2.0/go.mod h1:zoVQRvSiGjGoTmbM0vSLIiaKjWtNPeTvXUSdJQA4hsg=
github.com/anacrolix/stm v0.4.0 h1:tOGvuFwaBjeu1u9X1eIh9TX8OEedEiEQ1se1FjhFnXY=
github.com/anacrolix/stm v0.4.0/go.mod h1:GCkwqWoAsP7RfLW+jw+Z0ovrt2OO7wRzcTtFYMYY5t8=
@@ -85,7 +82,6 @@ github.com/anacrolix/sync v0.4.0/go.mod h1:BbecHL6jDSExojhNtgTFSBcdGerzNc64tz3DC
github.com/anacrolix/tagflag v0.0.0-20180109131632-2146c8d41bf0/go.mod h1:1m2U/K6ZT+JZG0+bdMK6qauP49QT4wE5pmhJXOKKCHw=
github.com/anacrolix/tagflag v1.0.0/go.mod h1:1m2U/K6ZT+JZG0+bdMK6qauP49QT4wE5pmhJXOKKCHw=
github.com/anacrolix/tagflag v1.1.0/go.mod h1:Scxs9CV10NQatSmbyjqmqmeQNwGzlNe0CMUMIxqHIG8=
github.com/anacrolix/tagflag v1.3.0/go.mod h1:Scxs9CV10NQatSmbyjqmqmeQNwGzlNe0CMUMIxqHIG8=
github.com/anacrolix/torrent v1.52.5 h1:jWowdx+EU6zFVfBwmnL0d3H4J6vTFEGOrHI35YdfIT8=
github.com/anacrolix/torrent v1.52.5/go.mod h1:CcM8oPMYye5J42cSqJrmUpqwRFgSsJQ1jCEHwygqnqQ=
github.com/anacrolix/upnp v0.1.3-0.20220123035249-922794e51c96 h1:QAVZ3pN/J4/UziniAhJR2OZ9Ox5kOY2053tBbbqUPYA=
@@ -95,6 +91,7 @@ github.com/anacrolix/utp v0.1.0/go.mod h1:MDwc+vsGEq7RMw6lr2GKOEqjWny5hO5OZXRVNa
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
github.com/benbjohnson/immutable v0.2.0/go.mod h1:uc6OHo6PN2++n98KHLxW8ef4W42ylHiQSENghE1ezxI=
@@ -124,17 +121,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/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.7.0 h1:7lJfhqlPssTb1WQx4yvTHN0uElPEv52sbaECrAQxjAo=
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/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-20230828202809-3dbe69dd2b8e h1:UvQD6hTSfeM6hhTQ24Dlw2RppP05W7SWbWb6kubJAog=
github.com/dop251/goja v0.0.0-20230828202809-3dbe69dd2b8e/go.mod h1:QMWlm50DNe14hD7t24KEqZuUdC9sOTy8W6XbCU1mlw4=
github.com/dop251/goja v0.0.0-20231027120936-b396bb4c349d h1:wi6jN5LVt/ljaBG4ue79Ekzb12QfJ52L9Q98tl8SWhw=
github.com/dop251/goja v0.0.0-20231027120936-b396bb4c349d/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-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-20230821135201-94e508132562/go.mod h1:X2TOTJ+Uamd454RFp7ig2tmP3hQg0Z2Qk8gbVQmU0mk=
github.com/dop251/goja_nodejs v0.0.0-20231122114759-e84d9a924c5c h1:hLoodLRD4KLWIH8eyAQCLcH8EqIrjac7fCkp/fHnvuQ=
github.com/dop251/goja_nodejs v0.0.0-20231122114759-e84d9a924c5c/go.mod h1:bhGPmCgCCTSRfiMYWjpS46IDo9EUZXlsuUaPXSWGbv0=
github.com/dustin/go-humanize v0.0.0-20180421182945-02af3965c54e/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
@@ -144,7 +141,6 @@ github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFP
github.com/edsrzf/mmap-go v1.1.0 h1:6EUwBLQ/Mcr1EYLE4Tn1VdW1A4ckqCQWZBw8Hr0kjpQ=
github.com/edsrzf/mmap-go v1.1.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q=
github.com/elazarl/goproxy v0.0.0-20221015165544-a0805db90819 h1:RIB4cRk+lBqKK3Oy0r2gRX4ui7tuhiZq2SuTtTCi0/0=
github.com/elliotchance/orderedmap v1.4.0/go.mod h1:wsDwEaX5jEoyhbs7x93zk2H/qv0zwuhg4inXhDkYqys=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ=
@@ -213,8 +209,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.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
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-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/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
@@ -241,7 +238,6 @@ github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq
github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
@@ -281,7 +277,6 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb
github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg=
github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM=
github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw=
github.com/multiformats/go-base36 v0.1.0/go.mod h1:kFGE83c6s80PklsHO9sRn2NCoffoRdUUOENyW/Vv6sM=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
@@ -367,7 +362,6 @@ github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsT
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
github.com/prometheus/procfs v0.0.11/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
@@ -403,8 +397,8 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/tidwall/btree v1.6.0 h1:LDZfKfQIBHGHWSwckhXI0RPSXzlo+KYdjK7FWSqOzzg=
github.com/tidwall/btree v1.6.0/go.mod h1:twD9XRA5jj9VUQGELzDO4HPQTNJsoWWfYEL+EUQ2cKY=
github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
@@ -418,8 +412,8 @@ github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
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/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.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
@@ -439,8 +433,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.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.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
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-20220613132600-b0d781184e0d h1:vtUKgx8dahOomfFzLREU8nSv25YHnTgLBn4rDnWZdU0=
golang.org/x/exp v0.0.0-20220613132600-b0d781184e0d/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA=
@@ -482,9 +476,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.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.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50=
golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -534,10 +527,9 @@ golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.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.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
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-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -545,8 +537,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.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
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.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c=
golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@@ -557,9 +548,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.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.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
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/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+5 -3
View File
@@ -1,12 +1,14 @@
package controller
import (
"net/url"
"os"
"path/filepath"
)
type Controller struct {
GetConfig func(v any) (bool, error)
GetConfig func(v any) bool
ProxyUrl *url.URL
FileController
//ContextDialer() (proxy.Dialer, error)
}
@@ -21,8 +23,8 @@ type DefaultFileController struct {
func NewController() *Controller {
return &Controller{
FileController: &DefaultFileController{},
GetConfig: func(v any) (bool, error) {
return false, nil
GetConfig: func(v any) bool {
return false
},
}
}
+11
View File
@@ -21,6 +21,8 @@ type Fetcher interface {
Pause() error
Close() error
// Stats refreshes health statistics and returns the latest information
Stats() any
// Meta returns the meta information of the download.
Meta() *FetcherMeta
// Progress returns the progress of the download.
@@ -57,6 +59,15 @@ func (m *FetcherMeta) SingleFilepath() string {
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.
type FetcherBuilder interface {
// Schemes returns the schemes supported by the fetcher.
+2 -1
View File
@@ -1,5 +1,6 @@
package bt
type config struct {
Trackers []string `json:"trackers"`
ListenPort int `json:"listenPort"`
Trackers []string `json:"trackers"`
}
+38 -13
View File
@@ -9,6 +9,7 @@ import (
"github.com/GopeedLab/gopeed/pkg/util"
"github.com/anacrolix/torrent"
"github.com/anacrolix/torrent/metainfo"
"net/http"
"path/filepath"
"sync"
"sync/atomic"
@@ -23,7 +24,8 @@ var (
)
type Fetcher struct {
ctl *controller.Controller
ctl *controller.Controller
config *config
torrent *torrent.Torrent
meta *fetcher.FetcherMeta
@@ -42,6 +44,13 @@ func (f *Fetcher) Setup(ctl *controller.Controller) {
if f.meta == nil {
f.meta = &fetcher.FetcherMeta{}
}
exist := f.ctl.GetConfig(&f.config)
if !exist {
f.config = &config{
ListenPort: 0,
Trackers: []string{},
}
}
return
}
@@ -54,7 +63,10 @@ func (f *Fetcher) initClient() (err error) {
}
cfg := torrent.NewDefaultClientConfig()
cfg.ListenPort = 0
cfg.ListenPort = f.config.ListenPort
if f.ctl.ProxyUrl != nil {
cfg.HTTPProxy = http.ProxyURL(f.ctl.ProxyUrl)
}
cfg.DefaultStorage = newFileOpts(newFileClientOpts{
ClientBaseDir: cfg.DataDir,
HandleFileTorrent: func(infoHash metainfo.Hash, ft *fileTorrentImpl) {
@@ -87,7 +99,6 @@ func (f *Fetcher) Create(opts *base.Options) (err error) {
if f.meta.Res != nil {
torrentDirMap[f.meta.Res.Hash] = f.meta.FolderPath()
}
f.progress = make(fetcher.Progress, len(f.meta.Opts.SelectFiles))
return nil
}
@@ -101,6 +112,14 @@ func (f *Fetcher) Start() (err error) {
ft.setTorrentDir(f.meta.FolderPath())
}
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) {
f.torrent.DownloadAll()
} else {
@@ -109,6 +128,7 @@ func (f *Fetcher) Start() (err error) {
file.Download()
}
}
return
}
@@ -134,7 +154,7 @@ func (f *Fetcher) safeDrop() {
func (f *Fetcher) Wait() (err error) {
for {
if f.torrentReady.Load() {
if f.torrentReady.Load() && len(f.meta.Opts.SelectFiles) > 0 {
done := true
for _, selectIndex := range f.meta.Opts.SelectFiles {
file := f.torrent.Files()[selectIndex]
@@ -154,7 +174,7 @@ func (f *Fetcher) Wait() (err error) {
}
}
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
@@ -169,6 +189,16 @@ func (f *Fetcher) Meta() *fetcher.FetcherMeta {
return f.meta
}
func (f *Fetcher) Stats() any {
stats := f.torrent.Stats()
baseStats := &bt.Stats{
TotalPeers: stats.TotalPeers,
ActivePeers: stats.ActivePeers,
ConnectedSeeders: stats.ConnectedSeeders,
}
return baseStats
}
func (f *Fetcher) Progress() fetcher.Progress {
if !f.torrentReady.Load() {
return f.progress
@@ -195,7 +225,7 @@ func (f *Fetcher) updateRes() {
Size: file.Length(),
}
}
res.CalcSize()
res.CalcSize(nil)
f.meta.Res = res
if f.meta.Opts != nil {
f.meta.Opts.InitSelectFiles(len(res.Files))
@@ -224,11 +254,6 @@ func (f *Fetcher) addTorrent(req *base.Request) (err error) {
if err != nil {
return
}
var cfg config
exist, err := f.ctl.GetConfig(&cfg)
if err != nil {
return
}
// use map to deduplicate
trackers := make(map[string]bool)
@@ -240,8 +265,8 @@ func (f *Fetcher) addTorrent(req *base.Request) (err error) {
}
}
}
if exist && len(cfg.Trackers) > 0 {
for _, tracker := range cfg.Trackers {
if len(f.config.Trackers) > 0 {
for _, tracker := range f.config.Trackers {
trackers[tracker] = true
}
}
+16 -5
View File
@@ -8,6 +8,8 @@ import (
"github.com/GopeedLab/gopeed/internal/test"
"github.com/GopeedLab/gopeed/pkg/base"
"github.com/GopeedLab/gopeed/pkg/protocol/bt"
"github.com/GopeedLab/gopeed/pkg/util"
"net/url"
"os"
"reflect"
"testing"
@@ -50,7 +52,15 @@ func TestFetcher_Resolve_DataUri_Torrent(t *testing.T) {
}
func TestFetcher_Config(t *testing.T) {
doResolve(t, buildConfigFetcher())
doResolve(t, buildConfigFetcher(nil))
}
func TestFetcher_ResolveWithProxy(t *testing.T) {
usr, pwd := "admin", "123"
proxyListener := test.StartSocks5Server(usr, pwd)
defer proxyListener.Close()
doResolve(t, buildConfigFetcher(util.BuildProxyUrl("socks5", proxyListener.Addr().String(), usr, pwd)))
}
func doResolve(t *testing.T, fetcher fetcher.Fetcher) {
@@ -90,7 +100,7 @@ func buildFetcher() fetcher.Fetcher {
return fetcher
}
func buildConfigFetcher() fetcher.Fetcher {
func buildConfigFetcher(proxyUrl *url.URL) fetcher.Fetcher {
fetcher := new(FetcherBuilder).Build()
newController := controller.NewController()
mockCfg := config{
@@ -98,12 +108,13 @@ func buildConfigFetcher() fetcher.Fetcher {
"udp://tracker.birkenwald.de:6969/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 {
return false, err
return false
}
return true, nil
return true
}
newController.ProxyUrl = proxyUrl
fetcher.Setup(newController)
return fetcher
}
+2 -1
View File
@@ -1,5 +1,6 @@
package http
type config struct {
Connections int `json:"connections"`
UserAgent string `json:"userAgent"`
Connections int `json:"connections"`
}
+89 -78
View File
@@ -18,6 +18,7 @@ import (
"os"
"path"
"strconv"
"strings"
"time"
)
@@ -51,6 +52,7 @@ func newChunk(begin int64, end int64) *chunk {
type Fetcher struct {
ctl *controller.Controller
config *config
doneCh chan error
meta *fetcher.FetcherMeta
@@ -71,6 +73,13 @@ func (f *Fetcher) Setup(ctl *controller.Controller) {
if f.meta == nil {
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
}
@@ -78,11 +87,11 @@ func (f *Fetcher) Resolve(req *base.Request) error {
if err := base.ParseReqExtra[fhttp.ReqExtra](req); err != nil {
return err
}
httpReq, err := buildRequest(nil, req)
httpReq, err := f.buildRequest(nil, req)
if err != nil {
return err
}
client := buildClient()
client := f.buildClient()
// send Range request to check whether the server supports breakpoint continuation
// just test one byte, Range: bytes=0-0
httpReq.Header.Set(base.HttpHeaderRange, fmt.Sprintf(base.HttpHeaderRangeFormat, 0, 0))
@@ -96,8 +105,8 @@ func (f *Fetcher) Resolve(req *base.Request) error {
Range: false,
Files: []*base.FileInfo{},
}
if base.HttpCodePartialContent == httpResp.StatusCode {
// 返回206响应码表示支持断点下载
if base.HttpCodePartialContent == httpResp.StatusCode || (base.HttpCodeOK == httpResp.StatusCode && httpResp.Header.Get(base.HttpHeaderAcceptRanges) == base.HttpHeaderBytes && strings.HasPrefix(httpResp.Header.Get(base.HttpHeaderContentRange), base.HttpHeaderBytes)) {
// 1.返回206响应码表示支持断点下载 2.不返回206但是Accept-Ranges首部并且等于bytes也表示支持断点下载
res.Range = true
// 解析资源大小: bytes 0-1000/1001 => 1001
contentTotal := path.Base(httpResp.Header.Get(base.HttpHeaderContentRange))
@@ -160,16 +169,7 @@ func (f *Fetcher) Create(opts *base.Options) error {
}
extra := opts.Extra.(*fhttp.OptsExtra)
if extra.Connections == 0 {
var cfg config
exist, err := f.ctl.GetConfig(&cfg)
if err != nil {
return err
}
if exist {
extra.Connections = cfg.Connections
} else {
extra.Connections = 1
}
extra.Connections = f.config.Connections
}
return nil
}
@@ -218,6 +218,11 @@ func (f *Fetcher) Meta() *fetcher.FetcherMeta {
return f.meta
}
func (f *Fetcher) Stats() any {
// todo http download health
return &fhttp.Stats{}
}
func (f *Fetcher) Progress() fetcher.Progress {
p := make(fetcher.Progress, 0)
if len(f.chunks) > 0 {
@@ -260,12 +265,12 @@ func (f *Fetcher) fetchChunk(index int, ctx context.Context) (err error) {
chunk := f.chunks[index]
chunk.retryTimes = 0
httpReq, err := buildRequest(ctx, f.meta.Req)
httpReq, err := f.buildRequest(ctx, f.meta.Req)
if err != nil {
return err
}
var (
client = buildClient()
client = f.buildClient()
buf = make([]byte, 8192)
maxRetries = 3
)
@@ -307,22 +312,13 @@ func (f *Fetcher) fetchChunk(index int, ctx context.Context) (err error) {
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != base.HttpCodeOK && resp.StatusCode != base.HttpCodePartialContent {
err = NewRequestError(resp.StatusCode, resp.Status)
return err
}
return nil
}()
if err != nil {
// retry request after 1 second
chunk.retryTimes = chunk.retryTimes + 1
time.Sleep(time.Second)
continue
}
// Http request success, reset retry times
chunk.retryTimes = 0
err = func() error {
defer resp.Body.Close()
// Http request success, reset retry times
chunk.retryTimes = 0
for {
n, err := resp.Body.Read(buf)
if n > 0 {
@@ -339,7 +335,18 @@ func (f *Fetcher) fetchChunk(index int, ctx context.Context) (err error) {
return err
}
}
return nil
}()
if err != nil {
// If canceled, do not retry
if errors.Is(err, context.Canceled) {
return
}
// retry request after 1 second
chunk.retryTimes = chunk.retryTimes + 1
time.Sleep(time.Second)
continue
}
// if chunk is completed, reset other not complete chunks retry times
if err == nil {
for _, c := range f.chunks {
@@ -353,6 +360,52 @@ func (f *Fetcher) fetchChunk(index int, ctx context.Context) (err error) {
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) {
if f.meta.Res.Range {
connections := f.meta.Opts.Extra.(*fhttp.OptsExtra).Connections
@@ -381,59 +434,19 @@ func (f *Fetcher) splitChunk() (chunks []*chunk) {
return
}
func buildClient() *http.Client {
func (f *Fetcher) buildClient() *http.Client {
transport := &http.Transport{}
// Cookie handle
jar, _ := cookiejar.New(nil)
if f.ctl.ProxyUrl != nil {
transport.Proxy = http.ProxyURL(f.ctl.ProxyUrl)
}
return &http.Client{
Jar: jar,
Transport: transport,
Jar: jar,
}
}
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 {
Chunks []*chunk
}
@@ -466,9 +479,7 @@ func (fb *FetcherBuilder) Restore() (v any, f func(meta *fetcher.FetcherMeta, v
fetcher.meta = meta
base.ParseReqExtra[fhttp.ReqExtra](fetcher.meta.Req)
base.ParseOptsExtra[fhttp.OptsExtra](fetcher.meta.Opts)
if len(fd.Chunks) == 0 {
fetcher.chunks = fetcher.splitChunk()
} else {
if len(fd.Chunks) > 0 {
fetcher.chunks = fd.Chunks
}
return fetcher
+34 -4
View File
@@ -8,6 +8,7 @@ import (
"github.com/GopeedLab/gopeed/internal/test"
"github.com/GopeedLab/gopeed/pkg/base"
"github.com/GopeedLab/gopeed/pkg/protocol/http"
"github.com/GopeedLab/gopeed/pkg/util"
"net"
"reflect"
"testing"
@@ -123,7 +124,7 @@ func TestFetcher_DownloadError(t *testing.T) {
}
func TestFetcher_DownloadLimit(t *testing.T) {
listener := test.StartTestLimitServer()
listener := test.StartTestLimitServer(4, 0)
defer listener.Close()
downloadNormal(listener, 1, t)
@@ -141,6 +142,15 @@ func TestFetcher_DownloadResume(t *testing.T) {
downloadResume(listener, 16, t)
}
func TestFetcher_DownloadWithProxy(t *testing.T) {
httpListener := test.StartTestFileServer()
defer httpListener.Close()
proxyListener := test.StartSocks5Server("", "")
defer proxyListener.Close()
downloadWithProxy(httpListener, proxyListener, t)
}
func TestFetcher_Config(t *testing.T) {
listener := test.StartTestFileServer()
defer listener.Close()
@@ -302,6 +312,26 @@ func downloadResume(listener net.Listener, connections int, t *testing.T) {
}
}
func downloadWithProxy(httpListener net.Listener, proxyListener net.Listener, t *testing.T) {
fetcher := downloadReady(httpListener, 4, t)
ctl := controller.NewController()
ctl.ProxyUrl = util.BuildProxyUrl("socks5", proxyListener.Addr().String(), "", "")
fetcher.Setup(ctl)
err := fetcher.Start()
if err != nil {
t.Fatal(err)
}
err = fetcher.Wait()
if err != nil {
t.Fatal(err)
}
want := test.FileMd5(test.BuildFile)
got := test.FileMd5(test.DownloadFile)
if want != got {
t.Errorf("Download() got = %v, want %v", got, want)
}
}
func buildFetcher() *Fetcher {
fetcher := new(FetcherBuilder).Build()
fetcher.Setup(controller.NewController())
@@ -314,11 +344,11 @@ func buildConfigFetcher() fetcher.Fetcher {
mockCfg := config{
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 {
return false, err
return false
}
return true, nil
return true
}
fetcher.Setup(newController)
return fetcher
+70 -4
View File
@@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"fmt"
"github.com/armon/go-socks5"
"io"
"math/rand"
"net"
@@ -138,7 +139,7 @@ func StartTestErrorServer() net.Listener {
}
// StartTestLimitServer connections limit server
func StartTestLimitServer() net.Listener {
func StartTestLimitServer(maxConnections int32, delay int64) net.Listener {
var connections atomic.Int32
return startTestServer(func() http.Handler {
@@ -148,7 +149,7 @@ func StartTestLimitServer() net.Listener {
connections.Add(-1)
}()
connections.Add(1)
if connections.Load() > 4 {
if maxConnections != 0 && connections.Load() > maxConnections {
writer.WriteHeader(403)
return
}
@@ -162,7 +163,7 @@ func StartTestLimitServer() net.Listener {
panic(err)
}
defer file.Close()
io.Copy(writer, file)
slowCopy(writer, file, delay)
} else {
// split range
s := strings.Split(r, "=")
@@ -203,13 +204,57 @@ func StartTestLimitServer() net.Listener {
}
defer file.Close()
file.Seek(start, 0)
io.CopyN(writer, file, end-start+1)
slowCopyN(writer, file, end-start+1, delay)
}
})
return mux
})
}
// slowCopyN copies n bytes from src to dst, speed limit is bytes per second
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 {
listener, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
@@ -271,3 +316,24 @@ func ifExistAndRemove(name string) error {
}
return nil
}
func StartSocks5Server(usr, pwd string) net.Listener {
conf := &socks5.Config{}
if usr != "" && pwd != "" {
conf.Credentials = socks5.StaticCredentials{
usr: pwd,
}
}
server, err := socks5.New(conf)
if err != nil {
panic(err)
}
listener, err := net.Listen("tcp", "127.0.0.1:0") // 你可以根据需要更改监听地址
if err != nil {
panic(err)
}
go server.Serve(listener)
return listener
}
+3 -5
View File
@@ -16,14 +16,12 @@ const (
HttpCodePartialContent = 206
HttpHeaderRange = "Range"
HttpHeaderAcceptRanges = "Accept-Ranges"
HttpHeaderContentLength = "Content-Length"
HttpHeaderContentRange = "Content-Range"
HttpHeaderContentDisposition = "Content-Disposition"
HttpHeaderUserAgent = "User-Agent"
HttpHeaderUserAgent = "Usr-Agent"
HttpHeaderBytes = "bytes"
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"
)
+6 -3
View File
@@ -3,6 +3,7 @@ package base
import (
"fmt"
"github.com/GopeedLab/gopeed/pkg/util"
"golang.org/x/exp/slices"
)
// Request download request
@@ -47,10 +48,12 @@ func (r *Resource) Validate() error {
return nil
}
func (r *Resource) CalcSize() {
func (r *Resource) CalcSize(selectFiles []int) {
var size int64
for _, file := range r.Files {
size += file.Size
for i, file := range r.Files {
if len(selectFiles) == 0 || slices.Contains(selectFiles, i) {
size += file.Size
}
}
r.Size = size
}
+332 -231
View File
File diff suppressed because it is too large. Load diff
+78 -5
View File
@@ -84,6 +84,82 @@ func TestDownloader_Create(t *testing.T) {
}
}
func TestDownloader_CreateWithProxy(t *testing.T) {
// No proxy
doTestDownloaderCreateWithProxy(t, false, func(proxyCfg *DownloaderProxyConfig) *DownloaderProxyConfig {
return nil
})
// Disable proxy
doTestDownloaderCreateWithProxy(t, false, func(proxyCfg *DownloaderProxyConfig) *DownloaderProxyConfig {
proxyCfg.Enable = false
return proxyCfg
})
// Invalid proxy scheme
doTestDownloaderCreateWithProxy(t, false, func(proxyCfg *DownloaderProxyConfig) *DownloaderProxyConfig {
proxyCfg.Scheme = ""
return proxyCfg
})
// Invalid proxy host
doTestDownloaderCreateWithProxy(t, false, func(proxyCfg *DownloaderProxyConfig) *DownloaderProxyConfig {
proxyCfg.Host = ""
return proxyCfg
})
// Use proxy without auth
doTestDownloaderCreateWithProxy(t, false, func(proxyCfg *DownloaderProxyConfig) *DownloaderProxyConfig {
return proxyCfg
})
// Use proxy with auth
doTestDownloaderCreateWithProxy(t, true, func(proxyCfg *DownloaderProxyConfig) *DownloaderProxyConfig {
return proxyCfg
})
}
func doTestDownloaderCreateWithProxy(t *testing.T, auth bool, buildProxyConfig func(proxyCfg *DownloaderProxyConfig) *DownloaderProxyConfig) {
httpListener := test.StartTestFileServer()
defer httpListener.Close()
usr, pwd := "", ""
if auth {
usr, pwd = "admin", "123"
}
proxyListener := test.StartSocks5Server(usr, pwd)
defer proxyListener.Close()
downloader := NewDownloader(nil)
if err := downloader.Setup(); err != nil {
t.Fatal(err)
}
defer downloader.Clear()
downloader.cfg.DownloaderStoreConfig.Proxy = buildProxyConfig(&DownloaderProxyConfig{
Enable: true,
Scheme: "socks5",
Host: proxyListener.Addr().String(),
Usr: usr,
Pwd: pwd,
})
req := &base.Request{
URL: "http://" + httpListener.Addr().String() + "/" + test.BuildName,
}
rr, err := downloader.Resolve(req)
if err != nil {
t.Fatal(err)
}
want := &base.Resource{
Size: test.BuildSize,
Range: true,
Files: []*base.FileInfo{
{
Name: test.BuildName,
Path: "",
Size: test.BuildSize,
},
},
}
if !reflect.DeepEqual(want, rr.Res) {
t.Errorf("Resolve() got = %v, want %v", rr.Res, want)
}
}
func TestDownloader_CreateRename(t *testing.T) {
listener := test.StartTestFileServer()
defer listener.Close()
@@ -205,10 +281,7 @@ func TestDownloader_Protocol_Config(t *testing.T) {
defer downloader.Clear()
var httpCfg map[string]any
exits, err := downloader.getProtocolConfig("http", &httpCfg)
if err != nil {
t.Fatal(err)
}
exits := downloader.getProtocolConfig("http", &httpCfg)
if exits {
t.Errorf("getProtocolConfig() got = %v, want %v", exits, false)
}
@@ -234,7 +307,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)
}
+69 -12
View File
@@ -3,14 +3,16 @@ package engine
import (
_ "embed"
"errors"
"fmt"
gojaerror "github.com/GopeedLab/gopeed/pkg/download/engine/inject/error"
"github.com/GopeedLab/gopeed/pkg/download/engine/inject/file"
"github.com/GopeedLab/gopeed/pkg/download/engine/inject/formdata"
"github.com/GopeedLab/gopeed/pkg/download/engine/inject/vm"
"github.com/GopeedLab/gopeed/pkg/download/engine/inject/xhr"
"github.com/dop251/goja"
"github.com/dop251/goja_nodejs/eventloop"
"github.com/dop251/goja_nodejs/process"
"github.com/dop251/goja_nodejs/url"
gojaurl "github.com/dop251/goja_nodejs/url"
"net/url"
"time"
)
//go:embed polyfill/out/index.js
@@ -27,12 +29,16 @@ type Engine struct {
func (e *Engine) RunString(script string) (value any, err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("%v", r)
err = r.(error)
}
}()
var result goja.Value
e.loop.Run(func(runtime *goja.Runtime) {
result, err = runtime.RunString(script)
if err == nil {
go e.await(result)
}
})
if err != nil {
return
@@ -45,9 +51,10 @@ func (e *Engine) RunString(script string) (value any, err error) {
func (e *Engine) CallFunction(fn goja.Callable, args ...any) (value any, err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("%v", r)
err = r.(error)
}
}()
var result goja.Value
e.loop.Run(func(runtime *goja.Runtime) {
if args == nil {
@@ -59,18 +66,57 @@ func (e *Engine) CallFunction(fn goja.Callable, args ...any) (value any, err err
}
result, err = fn(nil, jsArgs...)
}
if err == nil {
go e.await(result)
}
})
if err != nil {
return nil, err
return
}
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() {
e.loop.Stop()
}
func NewEngine() *Engine {
type Config struct {
ProxyURL *url.URL
}
func NewEngine(cfg *Config) *Engine {
if cfg == nil {
cfg = &Config{}
}
loop := eventloop.NewEventLoop()
engine := &Engine{
loop: loop,
@@ -78,15 +124,18 @@ func NewEngine() *Engine {
loop.Run(func(runtime *goja.Runtime) {
engine.Runtime = runtime
runtime.SetFieldNameMapper(goja.TagFieldNameMapper("json", true))
url.Enable(runtime)
process.Enable(runtime)
vm.Enable(runtime)
gojaurl.Enable(runtime)
if err := gojaerror.Enable(runtime); err != nil {
return
}
if err := file.Enable(runtime); err != nil {
return
}
if err := formdata.Enable(runtime); err != nil {
return
}
if err := xhr.Enable(runtime); err != nil {
if err := xhr.Enable(runtime, cfg.ProxyURL); err != nil {
return
}
if _, err := runtime.RunString(polyfillScript); err != nil {
@@ -96,13 +145,17 @@ func NewEngine() *Engine {
if err := runtime.Set("global", runtime.GlobalObject()); err != nil {
return
}
// polyfill window.location
if _, err := runtime.RunString("global.location = new URL('http://localhost');"); err != nil {
return
}
return
})
return engine
}
func Run(script string) (value any, err error) {
engine := NewEngine()
engine := NewEngine(nil)
return engine.RunString(script)
}
@@ -118,7 +171,11 @@ func resolveResult(value goja.Value) (any, error) {
case goja.PromiseStateFulfilled:
return p.Result().Export(), nil
case goja.PromiseStateRejected:
return nil, errors.New(p.Result().String())
if err, ok := p.Result().Export().(error); ok {
return nil, err
} else {
return nil, errors.New(p.Result().String())
}
}
}
return export, nil
+107 -1
View File
@@ -7,7 +7,10 @@ import (
"errors"
"fmt"
"github.com/GopeedLab/gopeed/internal/test"
gojaerror "github.com/GopeedLab/gopeed/pkg/download/engine/inject/error"
"github.com/GopeedLab/gopeed/pkg/download/engine/inject/file"
gojautil "github.com/GopeedLab/gopeed/pkg/download/engine/util"
"github.com/GopeedLab/gopeed/pkg/util"
"github.com/dop251/goja"
"io"
"net"
@@ -19,16 +22,28 @@ import (
)
func TestPolyfill(t *testing.T) {
doTestPolyfill(t, "MessageError")
doTestPolyfill(t, "XMLHttpRequest")
doTestPolyfill(t, "Blob")
doTestPolyfill(t, "FormData")
doTestPolyfill(t, "fetch")
doTestPolyfill(t, "__gopeed_create_vm")
}
func TestError(t *testing.T) {
engine := NewEngine(nil)
_, err := engine.RunString(`
throw new MessageError('test');
`)
if me, ok := gojautil.AssertError[*gojaerror.MessageError](err); !ok {
t.Fatalf("expect MessageError, but got %v", me)
}
}
func TestFetch(t *testing.T) {
server := startServer()
defer server.Close()
engine := NewEngine()
engine := NewEngine(nil)
if _, err := engine.RunString(fmt.Sprintf("var host = 'http://%s';", server.Addr().String())); err != nil {
t.Fatal(err)
}
@@ -191,6 +206,97 @@ function testTimeout(){
}
}
func TestFetchWithProxy(t *testing.T) {
doTestFetchWithProxy(t, "", "")
doTestFetchWithProxy(t, "admin", "123")
}
func doTestFetchWithProxy(t *testing.T, usr, pwd string) {
httpListener := startServer()
defer httpListener.Close()
proxyListener := test.StartSocks5Server(usr, pwd)
defer proxyListener.Close()
engine := NewEngine(&Config{ProxyURL: util.BuildProxyUrl("socks5", proxyListener.Addr().String(), usr, pwd)})
if _, err := engine.RunString(fmt.Sprintf("var host = 'http://%s';", httpListener.Addr().String())); err != nil {
t.Fatal(err)
}
respCode, err := engine.RunString(`
(async function(){
const resp = await fetch(host+'/get');
return resp.status;
})()
`)
if err != nil {
t.Fatal(err)
}
if respCode != int64(200) {
t.Fatalf("fetch with proxy failed, want %d, got %d", 200, respCode)
}
}
func TestVm(t *testing.T) {
engine := NewEngine(nil)
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(nil)
_, 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) {
value, err := Run(fmt.Sprintf(`
!!globalThis['%s']
@@ -0,0 +1,29 @@
package error
import (
"github.com/dop251/goja"
)
type MessageError struct {
Message string `json:"message"`
}
func (e *MessageError) Error() string {
return e.Message
}
func Enable(runtime *goja.Runtime) error {
messageError := runtime.ToValue(func(call goja.ConstructorCall) *goja.Object {
var message string
if len(call.Arguments) > 0 {
message = call.Arguments[0].String()
}
instance := &MessageError{
Message: message,
}
instanceValue := runtime.ToValue(instance).(*goja.Object)
instanceValue.SetPrototype(call.This.Prototype())
return instanceValue
})
return runtime.Set("MessageError", messageError)
}
-9
View File
@@ -1,9 +0,0 @@
package inject
import (
"github.com/dop251/goja"
)
func ThrowTypeError(vm *goja.Runtime, msg string) {
panic(vm.NewTypeError(msg))
}
+44
View File
@@ -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(),
})
})
}
+12 -4
View File
@@ -2,14 +2,15 @@ package xhr
import (
"bytes"
"github.com/GopeedLab/gopeed/pkg/download/engine/inject"
"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/util"
"github.com/dop251/goja"
"io"
"mime/multipart"
"net"
"net/http"
"net/url"
"time"
)
@@ -119,6 +120,7 @@ type XMLHttpRequest struct {
requestHeaders map[string]string
responseHeaders map[string]string
aborted bool
proxyUrl *url.URL
Upload *XMLHttpRequestUpload `json:"upload"`
Timeout int `json:"timeout"`
@@ -200,8 +202,13 @@ func (xhr *XMLHttpRequest) Send(data goja.Value) {
for k, v := range xhr.requestHeaders {
req.Header.Set(k, v)
}
transport := &http.Transport{}
if xhr.proxyUrl != nil {
transport.Proxy = http.ProxyURL(xhr.proxyUrl)
}
client := &http.Client{
Timeout: time.Duration(xhr.Timeout) * time.Millisecond,
Transport: transport,
Timeout: time.Duration(xhr.Timeout) * time.Millisecond,
}
resp, err := client.Do(req)
if err != nil {
@@ -306,10 +313,10 @@ func (xhr *XMLHttpRequest) parseData(data goja.Value) any {
return data.String()
}
func Enable(runtime *goja.Runtime) error {
func Enable(runtime *goja.Runtime, proxyUrl *url.URL) error {
progressEvent := runtime.ToValue(func(call goja.ConstructorCall) *goja.Object {
if len(call.Arguments) < 1 {
inject.ThrowTypeError(runtime, "Failed to construct 'ProgressEvent': 1 argument required, but only 0 present.")
util.ThrowTypeError(runtime, "Failed to construct 'ProgressEvent': 1 argument required, but only 0 present.")
}
instance := &ProgressEvent{
Type: call.Argument(0).String(),
@@ -320,6 +327,7 @@ func Enable(runtime *goja.Runtime) error {
})
xhr := runtime.ToValue(func(call goja.ConstructorCall) *goja.Object {
instance := &XMLHttpRequest{
proxyUrl: proxyUrl,
Upload: &XMLHttpRequestUpload{
EventProp: &EventProp{
eventListeners: make(map[string]func(event *ProgressEvent)),
+24
View File
@@ -0,0 +1,24 @@
package util
import (
"github.com/dop251/goja"
)
func ThrowTypeError(vm *goja.Runtime, msg string) {
panic(vm.NewTypeError(msg))
}
func AssertError[T error](err error) (t T, r bool) {
if err == nil {
return
}
if e, ok := err.(T); ok {
return e, true
}
if e, ok := err.(*goja.Exception); ok {
if ee, okk := e.Value().Export().(T); okk {
return ee, true
}
}
return
}
+128 -26
View File
@@ -6,9 +6,12 @@ import (
"github.com/GopeedLab/gopeed/internal/logger"
"github.com/GopeedLab/gopeed/pkg/base"
"github.com/GopeedLab/gopeed/pkg/download/engine"
gojaerror "github.com/GopeedLab/gopeed/pkg/download/engine/inject/error"
gojautil "github.com/GopeedLab/gopeed/pkg/download/engine/util"
"github.com/GopeedLab/gopeed/pkg/util"
"github.com/dop251/goja"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing/transport"
"io"
"os"
"path"
@@ -34,7 +37,7 @@ type ActivationEvent string
const (
EventOnResolve ActivationEvent = "onResolve"
EventOnStart ActivationEvent = "onStart"
//EventOnError ActivationEvent = "onError"
EventOnError ActivationEvent = "onError"
//EventOnDone ActivationEvent = "onDone"
)
@@ -195,11 +198,19 @@ func (d *Downloader) fetchExtensionByGit(url string, handler func(tempExtPath st
if err := util.RmAndMkDirAll(tempExtDir); err != nil {
return nil, err
}
proxyOptions := transport.ProxyOptions{}
proxyUrl := d.cfg.ProxyUrl()
if proxyUrl != nil {
proxyOptions.URL = proxyUrl.Scheme + "://" + proxyUrl.Host
proxyOptions.Username = proxyUrl.User.Username()
proxyOptions.Password, _ = proxyUrl.User.Password()
}
// clone project to extension temp dir
gitUrl := parentPath + projectPath + gitSuffix
if _, err := git.PlainClone(tempExtDir, false, &git.CloneOptions{
URL: gitUrl,
Depth: 1,
URL: gitUrl,
Depth: 1,
ProxyOptions: proxyOptions,
}); err != nil {
return nil, err
}
@@ -229,8 +240,8 @@ func (d *Downloader) parseExtensionByPath(path string) (*Extension, error) {
return &ext, nil
}
func (d *Downloader) triggerOnResolve(req *base.Request) (res *base.Resource) {
doTrigger(d,
func (d *Downloader) triggerOnResolve(req *base.Request) (res *base.Resource, err error) {
err = doTrigger(d,
EventOnResolve,
req,
&OnResolveContext{
@@ -243,7 +254,11 @@ func (d *Downloader) triggerOnResolve(req *base.Request) (res *base.Resource) {
gopeed.Logger.logger.Warn().Err(err).Msgf("[%s] resource invalid", ext.buildIdentity())
return
}
ctx.Res.CalcSize()
ctx.Res.Name = util.ReplaceInvalidFilename(ctx.Res.Name)
for _, file := range ctx.Res.Files {
file.Name = util.ReplaceInvalidFilename(file.Name)
}
ctx.Res.CalcSize(nil)
}
res = ctx.Res
},
@@ -256,7 +271,7 @@ func (d *Downloader) triggerOnStart(task *Task) {
EventOnStart,
task.Meta.Req,
&OnStartContext{
Task: task.clone(),
Task: NewExtensionTask(d, task),
},
func(ext *Extension, gopeed *Instance, ctx *OnStartContext) {
// Validate request structure
@@ -265,15 +280,25 @@ func (d *Downloader) triggerOnStart(task *Task) {
gopeed.Logger.logger.Warn().Err(err).Msgf("[%s] request invalid", ext.buildIdentity())
return
}
// Modify real task request
task.Meta.Req = ctx.Task.Meta.Req
}
},
)
return
}
func doTrigger[T any](d *Downloader, event ActivationEvent, req *base.Request, ctx T, handler func(ext *Extension, gopeed *Instance, ctx T)) {
func (d *Downloader) triggerOnError(task *Task, err error) {
doTrigger(d,
EventOnError,
task.Meta.Req,
&OnErrorContext{
Task: NewExtensionTask(d, task),
Error: err,
},
nil,
)
}
func doTrigger[T any](d *Downloader, event ActivationEvent, req *base.Request, ctx T, handler func(ext *Extension, gopeed *Instance, ctx T)) error {
// init extension global object
gopeed := &Instance{
Events: make(InstanceEvents),
@@ -315,7 +340,9 @@ func doTrigger[T any](d *Downloader, event ActivationEvent, req *base.Request, c
if req.Labels == nil {
req.Labels = make(map[string]string)
}
engine := engine.NewEngine()
engine := engine.NewEngine(&engine.Config{
ProxyURL: d.cfg.ProxyUrl(),
})
defer engine.Close()
err = engine.Runtime.Set("gopeed", gopeed)
if err != nil {
@@ -333,13 +360,20 @@ func doTrigger[T any](d *Downloader, event ActivationEvent, req *base.Request, c
gopeed.Logger.logger.Error().Err(err).Msgf("[%s] call function failed: %s", ext.buildIdentity(), event)
return
}
handler(ext, gopeed, ctx)
if handler != nil {
handler(ext, gopeed, ctx)
}
}
}()
}
}
}
return
// Only return MessageError
if me, ok := gojautil.AssertError[*gojaerror.MessageError](err); ok {
return me
}
return nil
}
func (d *Downloader) ExtensionPath(ext *Extension) string {
@@ -422,8 +456,50 @@ func (e *Extension) update(newExt *Extension) error {
e.Homepage = newExt.Homepage
e.Repository = newExt.Repository
e.Scripts = newExt.Scripts
// don't override settings
//e.Settings = newExt.Settings
// merge 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()
return nil
}
@@ -525,10 +601,10 @@ func (h InstanceEvents) OnStart(fn goja.Callable) {
h.register(EventOnStart, fn)
}
//func (h InstanceEvents) OnError(fn goja.Callable) {
// h.register(HookEventOnError, fn)
//}
//
func (h InstanceEvents) OnError(fn goja.Callable) {
h.register(EventOnError, fn)
}
//func (h InstanceEvents) OnDone(fn goja.Callable) {
// h.register(HookEventOnDone, fn)
//}
@@ -557,28 +633,28 @@ type InstanceLogger struct {
logger *logger.Logger
}
func (l *InstanceLogger) Debug(msg ...any) {
func (l *InstanceLogger) Debug(msg ...goja.Value) {
if l.devMode {
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...))
}
func (l *InstanceLogger) Warn(msg ...any) {
func (l *InstanceLogger) Warn(msg ...goja.Value) {
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...))
}
func (l *InstanceLogger) append(msg ...any) string {
func (l *InstanceLogger) append(msg ...goja.Value) string {
strMsg := make([]string, len(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, " "))
}
@@ -597,7 +673,33 @@ type OnResolveContext struct {
}
type OnStartContext struct {
Task *Task `json:"task"`
Task *ExtensionTask `json:"task"`
}
type OnErrorContext struct {
Task *ExtensionTask `json:"task"`
Error error `json:"error"`
}
type ExtensionTask struct {
*Task
download *Downloader
}
func NewExtensionTask(download *Downloader, task *Task) *ExtensionTask {
return &ExtensionTask{
Task: task.clone(),
download: download,
}
}
func (t *ExtensionTask) Continue() error {
return t.download.Continue(t.ID)
}
func (t *ExtensionTask) Pause() error {
return t.download.Pause(t.ID)
}
func parseSettings(settings []*Setting) map[string]any {
+104 -27
View File
@@ -4,6 +4,8 @@ import (
"errors"
"github.com/GopeedLab/gopeed/internal/logger"
"github.com/GopeedLab/gopeed/pkg/base"
gojaerror "github.com/GopeedLab/gopeed/pkg/download/engine/inject/error"
"github.com/dop251/goja"
"os"
"testing"
"time"
@@ -95,6 +97,15 @@ func TestDownloader_InstallExtensionByGitFull(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) {
installedExt, err := downloader.InstallExtensionByFolder("./testdata/extensions/update", false)
if err != nil {
@@ -122,19 +133,40 @@ func TestDownloader_UpgradeExtension(t *testing.T) {
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{
URL: "https://github.com/GopeedLab/gopeed/releases",
URL: "https://test.com",
})
if err != nil {
t.Fatal(err)
}
if len(rr.Res.Files) == 1 {
t.Fatal("resolve error")
if rr.Res.Name != "test" {
t.Fatal("script update fail")
}
})
}
func TestDownloader_ExtensionByOnStart(t *testing.T) {
func TestDownloader_Extension_OnStart(t *testing.T) {
downloadAndCheck := func(req *base.Request) {
setupDownloader(func(downloader *Downloader) {
if _, err := downloader.InstallExtensionByFolder("./testdata/extensions/on_start", false); err != nil {
@@ -161,10 +193,10 @@ func TestDownloader_ExtensionByOnStart(t *testing.T) {
}
task := downloader.GetTask(id)
if task.Meta.Req.URL != "https://github.com" {
panic("extension on start modify url error")
t.Fatalf("except url: https://github.com, actual: %s", task.Meta.Req.URL)
}
if task.Meta.Req.Labels["modified"] != "true" {
panic("extension on start modify label error")
t.Fatalf("except label: modified=true, actual: %s", task.Meta.Req.Labels["modified"])
}
})
}
@@ -176,13 +208,54 @@ func TestDownloader_ExtensionByOnStart(t *testing.T) {
// label match
downloadAndCheck(&base.Request{
URL: "https://xxx.com",
URL: "https://test.com",
Labels: map[string]string{
"test": "true",
},
})
}
func TestDownloader_Extension_OnError(t *testing.T) {
setupDownloader(func(downloader *Downloader) {
if _, err := downloader.InstallExtensionByFolder("./testdata/extensions/on_error", false); err != nil {
t.Fatal(err)
}
errCh := make(chan error, 1)
downloader.Listener(func(event *Event) {
if event.Key == EventKeyFinally {
errCh <- event.Err
}
})
id, err := downloader.CreateDirect(&base.Request{
URL: "https://github.com/gopeed/test/404",
Labels: map[string]string{
"test": "true",
},
}, nil)
if err != nil {
t.Fatal(err)
}
select {
case err = <-errCh:
break
case <-time.After(time.Second * 10):
err = errors.New("timeout")
}
if err != nil {
panic("extension on error download error: " + err.Error())
}
// extension on error modify url and continue download
task := downloader.GetTask(id)
if task.Meta.Req.URL != "https://github.com" {
t.Fatalf("except url: https://github.com, actual: %s", task.Meta.Req.URL)
}
if task.Status != base.DownloadStatusDone {
t.Fatalf("except status is done, actual: %s", task.Status)
}
})
}
func TestDownloader_Extension_Errors(t *testing.T) {
setupDownloader(func(downloader *Downloader) {
if _, err := downloader.InstallExtensionByFolder("./testdata/extensions/script_error", false); err != nil {
@@ -213,6 +286,26 @@ func TestDownloader_Extension_Errors(t *testing.T) {
t.Fatal("function error catch failed")
}
})
setupDownloader(func(downloader *Downloader) {
if _, err := downloader.InstallExtensionByFolder("./testdata/extensions/message_error", false); err != nil {
t.Fatal(err)
}
_, err := downloader.Resolve(&base.Request{
URL: "https://github.com/test",
})
if err == nil {
t.Fatalf("except error, but got nil")
}
me, ok := err.(*gojaerror.MessageError)
if !ok {
t.Fatalf("except MessageError type, but got %s", err)
}
want := "test"
if me.Error() != want {
t.Fatalf("except MessageError message %s, but got %s", want, me.Message)
}
})
}
func TestDownloader_Extension_Settings(t *testing.T) {
@@ -305,31 +398,15 @@ func TestDownloader_DeleteExtension(t *testing.T) {
})
}
func TestDownloader_Extension_OnResolve(t *testing.T) {
setupDownloader(func(downloader *Downloader) {
installedExt, err := downloader.InstallExtensionByFolder("./testdata/extensions/settings_all", false)
if err != nil {
t.Fatal(err)
}
if err := downloader.DeleteExtension(installedExt.Identity); err != nil {
t.Fatal(err)
}
extensions := downloader.GetExtensions()
if len(extensions) != 0 {
t.Fatal("extension delete fail")
}
})
}
func TestDownloader_Extension_Logger(t *testing.T) {
logger := logger.NewLogger(false, "")
il := newInstanceLogger(&Extension{
Name: "test",
}, logger)
il.Debug("msg1", "msg2")
il.Info("msg1", "msg2")
il.Warn("msg1", "msg2")
il.Error("msg1", "msg2")
il.Debug(goja.NaN(), goja.Undefined())
il.Info(goja.NaN(), goja.Undefined())
il.Warn(goja.NaN(), goja.Undefined())
il.Error(goja.NaN(), goja.Undefined())
}
func setupDownloader(fn func(downloader *Downloader)) {
+35 -6
View File
@@ -8,6 +8,7 @@ import (
"github.com/GopeedLab/gopeed/pkg/base"
"github.com/GopeedLab/gopeed/pkg/util"
gonanoid "github.com/matoous/go-nanoid/v2"
"net/url"
"sync"
"time"
)
@@ -22,8 +23,8 @@ type Task struct {
Meta *fetcher.FetcherMeta `json:"meta"`
Status base.Status `json:"status"`
Progress *Progress `json:"progress"`
Size int64 `json:"size"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
fetcherBuilder fetcher.FetcherBuilder
fetcher fetcher.Fetcher
@@ -41,17 +42,23 @@ func NewTask() *Task {
ID: id,
Status: base.DownloadStatusReady,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
}
func (t *Task) updateStatus(status base.Status) {
t.UpdatedAt = time.Now()
t.Status = status
}
func (t *Task) clone() *Task {
return &Task{
ID: t.ID,
Meta: t.Meta,
Status: t.Status,
Progress: t.Progress,
Size: t.Size,
CreatedAt: t.CreatedAt,
UpdatedAt: t.UpdatedAt,
}
}
@@ -103,15 +110,37 @@ func (cfg *DownloaderConfig) Init() *DownloaderConfig {
type DownloaderStoreConfig struct {
FirstLoad bool `json:"-"` // FirstLoad is the flag that the config is first time init and not from store
DownloadDir string `json:"downloadDir"` // DownloadDir is the default directory to save the downloaded files
MaxRunning int `json:"maxRunning"` // MaxRunning is the max running download count
ProtocolConfig map[string]any `json:"protocolConfig"` // ProtocolConfig is special config for each protocol
Extra map[string]any `json:"extra"` // Extra is the extra config
DownloadDir string `json:"downloadDir"` // DownloadDir is the default directory to save the downloaded files
MaxRunning int `json:"maxRunning"` // MaxRunning is the max running download count
ProtocolConfig map[string]any `json:"protocolConfig"` // ProtocolConfig is special config for each protocol
Extra map[string]any `json:"extra"`
Proxy *DownloaderProxyConfig `json:"proxy"`
}
func (cfg *DownloaderStoreConfig) Init() *DownloaderStoreConfig {
if cfg.MaxRunning == 0 {
cfg.MaxRunning = 5
}
if cfg.Proxy == nil {
cfg.Proxy = &DownloaderProxyConfig{}
}
return cfg
}
func (cfg *DownloaderStoreConfig) ProxyUrl() *url.URL {
if cfg.Proxy == nil {
return nil
}
if cfg.Proxy.Enable == false || cfg.Proxy.Scheme == "" || cfg.Proxy.Host == "" {
return nil
}
return util.BuildProxyUrl(cfg.Proxy.Scheme, cfg.Proxy.Host, cfg.Proxy.Usr, cfg.Proxy.Pwd)
}
type DownloaderProxyConfig struct {
Enable bool `json:"enable"`
Scheme string `json:"scheme"`
Host string `json:"host"`
Usr string `json:"usr"`
Pwd string `json:"pwd"`
}
+3
View File
@@ -222,6 +222,9 @@ func (b *BoltStorage) Pop(bucket string, key string, v any) error {
if err != nil {
return err
}
if len(data) == 0 {
return nil
}
return json.Unmarshal(data, v)
}
@@ -1,5 +1,6 @@
gopeed.events.onResolve(async function (ctx) {
const aaa = {};
// access undefined property
gopeed.logger.info(aaa.bbb.ccc);
ctx.res = {
@@ -0,0 +1,3 @@
gopeed.events.onResolve(async function (ctx) {
throw new MessageError("test");
});
@@ -0,0 +1,16 @@
{
"name": "message-error",
"title": "gopeed extension message error test",
"version": "0.0.1",
"scripts": [
{
"event": "onResolve",
"match": {
"urls": [
"*://github.com/*"
]
},
"entry": "index.js"
}
]
}
+8
View File
@@ -0,0 +1,8 @@
gopeed.events.onError(async function (ctx) {
gopeed.logger.info("url", ctx.task.meta.req.url);
gopeed.logger.info("error", ctx.error);
ctx.task.meta.req.url = "https://github.com";
ctx.task.pause();
ctx.task.continue();
});
+16
View File
@@ -0,0 +1,16 @@
{
"name": "on-error",
"title": "gopeed extension on error event test",
"version": "0.0.1",
"scripts": [
{
"event": "onError",
"match": {
"labels": [
"test"
]
},
"entry": "index.js"
}
]
}
+1
View File
@@ -1,4 +1,5 @@
gopeed.events.onStart(async function (ctx) {
gopeed.logger.info("url", ctx.task.meta.req.url);
ctx.task.meta.req.url = "https://github.com";
ctx.task.meta.req.labels['modified'] = 'true';
});
Loaded 50 of 121 files, more files were not shown because too many files have changed in this diff. Show more