Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
06c7fe7a22 | ||
|
|
8b3b5d4394 | ||
|
|
2ee73ab619 | ||
|
|
15413dbc95 | ||
|
|
c7c43fac80 | ||
|
|
4283bdfa5a | ||
|
|
328ba8592c | ||
|
|
a191a4140d | ||
|
|
3521837964 | ||
|
|
b30cd78f86 | ||
|
|
0583e4aaa6 | ||
|
|
a89c55b5c4 | ||
|
|
68c40d7f3f | ||
|
|
42a0b89d95 | ||
|
|
1860f534cd | ||
|
|
5c2d2ad7b4 | ||
|
|
db6352ada8 | ||
|
|
d1a5ed8b3d | ||
|
|
be9583be0f | ||
|
|
d2c70b077b | ||
|
|
53b3eea4fc | ||
|
|
54347f41b6 | ||
|
|
cea454b015 | ||
|
|
070db64ca9 | ||
|
|
6a8956b04d | ||
|
|
057504bb81 | ||
|
|
470b488824 | ||
|
|
c8c9df1ae8 | ||
|
|
33d787c74e | ||
|
|
67338a5e83 | ||
|
|
723697192d | ||
|
|
4294d07d6a | ||
|
|
3936fd786f | ||
|
|
45197e9a3b | ||
|
|
c9692e76e5 | ||
|
|
0a9e39ed75 | ||
|
|
1971f582b5 | ||
|
|
c62d6be3da | ||
|
|
7d5e503894 | ||
|
|
6c4066fde4 | ||
|
|
ffdf3e717f | ||
|
|
f66814b4ae | ||
|
|
0c3e201a51 | ||
|
|
c32a5d750f | ||
|
|
207c287c97 | ||
|
|
56ef25065e | ||
|
|
bd6d6be71f | ||
|
|
1d3a028bae | ||
|
|
27011fb96b | ||
|
|
c696b59c4b | ||
|
|
78b5b8a78b | ||
|
|
c81ca7f5cd | ||
|
|
e39d50fe09 | ||
|
|
086269f8bd | ||
|
|
00d2183961 | ||
|
|
5f914dc4c4 | ||
|
|
9bf204cf04 | ||
|
|
a816b9ccc8 | ||
|
|
d60d370463 | ||
|
|
84d054b494 | ||
|
|
1aa96e147f |
No files matched your search
@@ -16,7 +16,7 @@ categories:
|
|||||||
- title: '🧰 Maintenance'
|
- title: '🧰 Maintenance'
|
||||||
label: 'chore'
|
label: 'chore'
|
||||||
- title: '📖 Document'
|
- title: '📖 Document'
|
||||||
label: 'doc'
|
label: 'docs'
|
||||||
- title: '🚀 CI/CD'
|
- title: '🚀 CI/CD'
|
||||||
label: 'ci'
|
label: 'ci'
|
||||||
- title: '🌎 Internationalization'
|
- title: '🌎 Internationalization'
|
||||||
@@ -34,7 +34,7 @@ version-resolver:
|
|||||||
- 'patch'
|
- 'patch'
|
||||||
default: patch
|
default: patch
|
||||||
template: |
|
template: |
|
||||||
# Changes
|
# Release notes
|
||||||
|
|
||||||
$CHANGES
|
$CHANGES
|
||||||
|
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ jobs:
|
|||||||
overwrite: true
|
overwrite: true
|
||||||
build-macos-arm64-lib:
|
build-macos-arm64-lib:
|
||||||
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'macos' }}
|
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'macos' }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: macos-latest
|
||||||
needs: [ get-release ]
|
needs: [ get-release ]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
@@ -147,16 +147,14 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
VERSION: ${{ needs.get-release.outputs.version }}
|
VERSION: ${{ needs.get-release.outputs.version }}
|
||||||
run: |
|
run: |
|
||||||
go install src.techknowlogick.com/xgo@latest
|
go build -tags nosqlite -ldflags="-w -s -X github.com/GopeedLab/gopeed/pkg/base.Version=$VERSION" -buildmode=c-shared -o libgopeed.dylib github.com/GopeedLab/gopeed/bind/desktop
|
||||||
xgo -go go-$GO_VERSION.x --targets=darwin/arm64 -tags="nosqlite" -ldflags="-w -s -X github.com/GopeedLab/gopeed/pkg/base.Version=$VERSION" -buildmode=c-shared -pkg=bind/desktop -out=libgopeed .
|
|
||||||
mv libgopeed-*.dylib libgopeed.dylib
|
|
||||||
- uses: actions/upload-artifact@v3
|
- uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: macos-arm64-lib
|
name: macos-arm64-lib
|
||||||
path: libgopeed.dylib
|
path: libgopeed.dylib
|
||||||
build-macos:
|
build-macos:
|
||||||
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'macos' }}
|
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'macos' }}
|
||||||
runs-on: macos-latest
|
runs-on: macos-13
|
||||||
needs: [ get-release, build-macos-arm64-lib ]
|
needs: [ get-release, build-macos-arm64-lib ]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
@@ -171,7 +169,7 @@ jobs:
|
|||||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||||
- name: Install appdmg
|
- name: Install appdmg
|
||||||
run: |
|
run: |
|
||||||
python3 -m pip install setuptools
|
python3 -m pip install setuptools --break-system-packages
|
||||||
npm install -g appdmg
|
npm install -g appdmg
|
||||||
- uses: actions/download-artifact@v3
|
- uses: actions/download-artifact@v3
|
||||||
with:
|
with:
|
||||||
@@ -215,7 +213,7 @@ jobs:
|
|||||||
overwrite: true
|
overwrite: true
|
||||||
build-linux:
|
build-linux:
|
||||||
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'linux' }}
|
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'linux' }}
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-latest
|
||||||
needs: [ get-release ]
|
needs: [ get-release ]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
@@ -228,9 +226,7 @@ jobs:
|
|||||||
- run: |
|
- run: |
|
||||||
sudo apt update -y
|
sudo apt update -y
|
||||||
# flutter build dependencies
|
# flutter build dependencies
|
||||||
sudo apt install -y ninja-build libgtk-3-dev libappindicator3-dev
|
sudo apt install -y ninja-build libgtk-3-dev libayatana-appindicator3-1 libayatana-appindicator3-dev
|
||||||
# rpm build dependencies
|
|
||||||
sudo apt install -y rpm patchelf
|
|
||||||
# appimage build dependencies
|
# appimage build dependencies
|
||||||
sudo apt install -y libfuse2 locate
|
sudo apt install -y libfuse2 locate
|
||||||
wget -O appimagetool "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage"
|
wget -O appimagetool "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage"
|
||||||
@@ -262,9 +258,6 @@ jobs:
|
|||||||
- uses: actions/setup-go@v4
|
- uses: actions/setup-go@v4
|
||||||
with:
|
with:
|
||||||
go-version: ${{ env.GO_VERSION }}
|
go-version: ${{ env.GO_VERSION }}
|
||||||
- uses: subosito/flutter-action@v2
|
|
||||||
with:
|
|
||||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
|
||||||
- name: Setup LXD
|
- name: Setup LXD
|
||||||
uses: canonical/[email protected]
|
uses: canonical/[email protected]
|
||||||
with:
|
with:
|
||||||
@@ -302,20 +295,55 @@ jobs:
|
|||||||
|
|
||||||
apps:
|
apps:
|
||||||
gopeed:
|
gopeed:
|
||||||
command: gopeed
|
command: bin/gopeed
|
||||||
extensions: [gnome]
|
extensions: [gnome]
|
||||||
plugs:
|
plugs:
|
||||||
- network
|
- network
|
||||||
|
- home
|
||||||
slots:
|
slots:
|
||||||
- dbus-gopeed
|
- dbus-gopeed
|
||||||
parts:
|
parts:
|
||||||
|
flutter-git:
|
||||||
|
source: https://github.com/flutter/flutter.git
|
||||||
|
source-tag: $FLUTTER_VERSION
|
||||||
|
source-depth: 1
|
||||||
|
plugin: nil
|
||||||
|
override-build: |
|
||||||
|
mkdir -p \$CRAFT_PART_INSTALL/usr/bin
|
||||||
|
mkdir -p \$CRAFT_PART_INSTALL/usr/libexec
|
||||||
|
cp -r \$CRAFT_PART_SRC \$CRAFT_PART_INSTALL/usr/libexec/flutter
|
||||||
|
ln -s \$CRAFT_PART_INSTALL/usr/libexec/flutter/bin/flutter \$CRAFT_PART_INSTALL/usr/bin/flutter
|
||||||
|
ln -s \$SNAPCRAFT_PART_INSTALL/usr/libexec/flutter/bin/dart \$SNAPCRAFT_PART_INSTALL/usr/bin/dart
|
||||||
|
\$CRAFT_PART_INSTALL/usr/bin/flutter doctor
|
||||||
|
build-packages:
|
||||||
|
- clang
|
||||||
|
- cmake
|
||||||
|
- curl
|
||||||
|
- libgtk-3-dev
|
||||||
|
- ninja-build
|
||||||
|
- unzip
|
||||||
|
- xz-utils
|
||||||
|
- zip
|
||||||
|
override-prime: ''
|
||||||
|
zenity:
|
||||||
|
plugin: nil
|
||||||
|
stage-packages:
|
||||||
|
- zenity
|
||||||
gopeed:
|
gopeed:
|
||||||
|
after: [flutter-git,zenity]
|
||||||
source: .
|
source: .
|
||||||
plugin: flutter
|
plugin: nil
|
||||||
stage-packages:
|
stage-packages:
|
||||||
- libgtk-3-dev
|
- libgtk-3-dev
|
||||||
- libappindicator3-dev
|
- libappindicator3-dev
|
||||||
flutter-target: lib/main.dart
|
override-build: |
|
||||||
|
# work around pub get stack overflow # https://github.com/dart-lang/sdk/issues/51068#issuecomment-1396588253
|
||||||
|
set +e
|
||||||
|
dart pub get
|
||||||
|
set -eux
|
||||||
|
flutter build linux --release -v
|
||||||
|
mkdir -p \$CRAFT_PART_INSTALL/bin/
|
||||||
|
cp -r build/linux/*/release/bundle/* \$CRAFT_PART_INSTALL/bin/
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
cp linux/assets/com.gopeed.Gopeed.desktop snap/gui/gopeed.desktop
|
cp linux/assets/com.gopeed.Gopeed.desktop snap/gui/gopeed.desktop
|
||||||
@@ -383,7 +411,7 @@ jobs:
|
|||||||
go install golang.org/x/mobile/cmd/gomobile@latest
|
go install golang.org/x/mobile/cmd/gomobile@latest
|
||||||
go get golang.org/x/mobile/bind
|
go get golang.org/x/mobile/bind
|
||||||
gomobile init
|
gomobile init
|
||||||
gomobile bind -tags nosqlite,netgo -ldflags="-w -s -X github.com/GopeedLab/gopeed/pkg/base.Version=$VERSION" -o ui/flutter/ios/Frameworks/Libgopeed.xcframework -target=ios github.com/GopeedLab/gopeed/bind/mobile
|
gomobile bind -tags nosqlite -ldflags="-w -s -X github.com/GopeedLab/gopeed/pkg/base.Version=$VERSION" -o ui/flutter/ios/Frameworks/Libgopeed.xcframework -target=ios github.com/GopeedLab/gopeed/bind/mobile
|
||||||
cd ui/flutter
|
cd ui/flutter
|
||||||
flutter build ios --no-codesign
|
flutter build ios --no-codesign
|
||||||
mkdir Payload
|
mkdir Payload
|
||||||
@@ -471,7 +499,9 @@ jobs:
|
|||||||
VERSION: ${{ needs.get-release.outputs.version }}
|
VERSION: ${{ needs.get-release.outputs.version }}
|
||||||
with:
|
with:
|
||||||
push: ${{ github.event.inputs.test == 'true' && 'false' || 'true' }}
|
push: ${{ github.event.inputs.test == 'true' && 'false' || 'true' }}
|
||||||
build-args: VERSION=${{ env.VERSION }}
|
build-args: |
|
||||||
|
VERSION=${{ env.VERSION }}
|
||||||
|
IN_DOCKER=true
|
||||||
platforms: |
|
platforms: |
|
||||||
linux/386
|
linux/386
|
||||||
linux/amd64
|
linux/amd64
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ The internationalization files of Gopeed are located in the `ui/flutter/lib/i18n
|
|||||||
You only need to add the corresponding language file in this directory.
|
You only need to add the corresponding language file in this directory.
|
||||||
|
|
||||||
Please refer to `en_us.dart` for translation.
|
Please refer to `en_us.dart` for translation.
|
||||||
|
words prefixed with `@` are not meant to be translated.
|
||||||
|
|
||||||
## flutter development
|
## flutter development
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
# Hướng dẫn đóng góp cho Gopeed
|
||||||
|
|
||||||
|
Trước tiên, cảm ơn bạn đã quan tâm đến việc đóng góp cho Gopeed. Hướng dẫn này sẽ giúp bạn tham gia
|
||||||
|
phát triển Gopeed một cách tốt hơn.
|
||||||
|
|
||||||
|
## Mô tả nhánh
|
||||||
|
|
||||||
|
Dự án này chỉ có một nhánh chính duy nhất, đó là nhánh `main`. Nếu bạn muốn tham gia vào
|
||||||
|
phát triển Gopeed, hãy fork dự án này trước, sau đó phát triển trong dự án fork của bạn. Sau khi
|
||||||
|
hoàn thành phát triển, gửi một PR đến dự án này và merge vào nhánh `main`.
|
||||||
|
|
||||||
|
## Phát triển cục bộ
|
||||||
|
|
||||||
|
Đề nghị phát triển và gỡ lỗi thông qua web. Đầu tiên, khởi động dịch vụ backend bằng cách chạy
|
||||||
|
lệnh `go run cmd/api/main.go` trong dòng lệnh, cổng mặc định của dịch vụ là `9999`, sau đó
|
||||||
|
khởi động dự án flutter frontend trong chế độ `debug` để chạy.
|
||||||
|
|
||||||
|
## Dịch thuật
|
||||||
|
|
||||||
|
Các tệp quốc tế hóa của Gopeed được đặt trong thư mục `ui/flutter/lib/i18n/langs`.
|
||||||
|
Bạn chỉ cần thêm tệp ngôn ngữ tương ứng trong thư mục này.
|
||||||
|
|
||||||
|
Vui lòng tham khảo `en_us.dart` để biết cách dịch thuật.
|
||||||
|
|
||||||
|
## Phát triển flutter
|
||||||
|
|
||||||
|
Đừng quên chạy `dart format ./ui/flutter` trước khi commit để giữ mã của bạn theo định dạng dart chuẩn.
|
||||||
|
|
||||||
|
Bật build_runner watcher nếu bạn muốn chỉnh sửa api/models:
|
||||||
@@ -12,12 +12,21 @@ RUN go mod download
|
|||||||
COPY . .
|
COPY . .
|
||||||
COPY --from=flutter /app/build/web ./cmd/web/dist
|
COPY --from=flutter /app/build/web ./cmd/web/dist
|
||||||
ARG VERSION=dev
|
ARG VERSION=dev
|
||||||
RUN CGO_ENABLED=0 go build -tags nosqlite,web -ldflags="-s -w -X github.com/GopeedLab/gopeed/pkg/base.Version=${VERSION}" -o dist/gopeed github.com/GopeedLab/gopeed/cmd/web
|
RUN CGO_ENABLED=0 go build -tags nosqlite,web \
|
||||||
|
-ldflags="-s -w -X github.com/GopeedLab/gopeed/pkg/base.Version=$VERSION -X github.com/GopeedLab/gopeed/pkg/base.InDocker=true" \
|
||||||
|
-o dist/gopeed github.com/GopeedLab/gopeed/cmd/web
|
||||||
|
|
||||||
FROM alpine:3.14.2
|
FROM alpine:3.14.2
|
||||||
LABEL maintainer="monkeyWie"
|
LABEL maintainer="monkeyWie"
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY --from=go /app/dist/gopeed ./
|
COPY --from=go /app/dist/gopeed ./
|
||||||
|
COPY entrypoint.sh ./entrypoint.sh
|
||||||
|
RUN apk update && \
|
||||||
|
apk upgrade --no-cache && \
|
||||||
|
apk add --no-cache bash su-exec; \
|
||||||
|
chmod +x ./entrypoint.sh && \
|
||||||
|
rm -rf /var/cache/apk/*
|
||||||
VOLUME ["/app/storage"]
|
VOLUME ["/app/storage"]
|
||||||
|
ENV PUID=0 PGID=0 UMASK=022
|
||||||
EXPOSE 9999
|
EXPOSE 9999
|
||||||
ENTRYPOINT ["./gopeed"]
|
ENTRYPOINT ["./entrypoint.sh"]
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
[](https://ko-fi.com/R6R6IJGN6)
|
[](https://ko-fi.com/R6R6IJGN6)
|
||||||
|
|
||||||
[English](/README.md) | [中文](/README_zh-CN.md) | [日本語](/README_ja-JP.md) | [正體中文](/README_zh-TW.md)
|
[English](/README.md) | [中文](/README_zh-CN.md) | [日本語](/README_ja-JP.md) | [正體中文](/README_zh-TW.md) | [Tiếng Việt](/README_vi-VN.md)
|
||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
|
|
||||||
@@ -40,24 +40,24 @@ Visit ✈ [Official Website](https://gopeed.com) | 📖 [Official Docs](https://
|
|||||||
<tr>
|
<tr>
|
||||||
<td>MacOS</td>
|
<td>MacOS</td>
|
||||||
<td><code>DMG Installer</code></td>
|
<td><code>DMG Installer</code></td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64-portable.zip">Link</a></td>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-macos.dmg">Link</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td rowspan=4>Linux</td>
|
<td rowspan=4>Linux</td>
|
||||||
|
<td><code>Flathub</code></td>
|
||||||
|
<td><a href="https://flathub.org/apps/com.gopeed.Gopeed">Link</a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
<td><code>SNAP</code></td>
|
<td><code>SNAP</code></td>
|
||||||
<td><a href="https://snapcraft.io/gopeed">Link</a></td>
|
<td><a href="https://snapcraft.io/gopeed">Link</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><code>DEB</code></td>
|
<td><code>DEB</code></td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux.deb">Link</a></td>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-amd64.deb">Link</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><code>AppImage</code></td>
|
<td><code>AppImage</code></td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-amd64.AppImage">Link</a></td>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-x86_64.AppImage">Link</a></td>
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><code>Portable TAR</code></td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-amd64-portable.tar.gz">Link</a></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Android</td>
|
<td>Android</td>
|
||||||
@@ -92,6 +92,10 @@ use `go install`:
|
|||||||
go install github.com/GopeedLab/gopeed/cmd/gopeed@latest
|
go install github.com/GopeedLab/gopeed/cmd/gopeed@latest
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Browser Extension
|
||||||
|
|
||||||
|
Gopeed also provides a browser extension to take over browser downloads, supporting browsers such as Chrome, Edge, Firefox, etc., please refer to: [https://github.com/GopeedLab/browser-extension](https://github.com/GopeedLab/browser-extension)
|
||||||
|
|
||||||
## Donate
|
## Donate
|
||||||
|
|
||||||
If you like this project, please consider [donating](https://docs.gopeed.com/donate.html) to support the development of this project, thank you!
|
If you like this project, please consider [donating](https://docs.gopeed.com/donate.html) to support the development of this project, thank you!
|
||||||
@@ -168,7 +172,7 @@ command:
|
|||||||
- android
|
- android
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
gomobile bind -tags nosqlite -ldflags="-w -s" -o ui/flutter/android/app/libs/libgopeed.aar -target=android -androidapi 19 -javapkg=com.gopeed github.com/GopeedLab/gopeed/bind/mobile
|
gomobile bind -tags nosqlite -ldflags="-w -s" -o ui/flutter/android/app/libs/libgopeed.aar -target=android -androidapi 19 -javapkg="com.gopeed" github.com/GopeedLab/gopeed/bind/mobile
|
||||||
cd ui/flutter
|
cd ui/flutter
|
||||||
flutter build apk
|
flutter build apk
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
[](https://ko-fi.com/R6R6IJGN6)
|
[](https://ko-fi.com/R6R6IJGN6)
|
||||||
|
|
||||||
[English](/README.md) | [中文](/README_zh-CN.md) | [日本語](/README_ja-JP.md) | [正體中文](/README_zh-TW.md)
|
[English](/README.md) | [中文](/README_zh-CN.md) | [日本語](/README_ja-JP.md) | [正體中文](/README_zh-TW.md) | [Tiếng Việt](/README_vi-VN.md)
|
||||||
|
|
||||||
## はじめに
|
## はじめに
|
||||||
|
|
||||||
@@ -40,24 +40,24 @@ Gopeed (正式名 Go Speed) は `Golang` + `Flutter` によって開発された
|
|||||||
<tr>
|
<tr>
|
||||||
<td>MacOS</td>
|
<td>MacOS</td>
|
||||||
<td><code>DMG Installer</code></td>
|
<td><code>DMG Installer</code></td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64-portable.zip">Link</a></td>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-macos.dmg">Link</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td rowspan=4>Linux</td>
|
<td rowspan=4>Linux</td>
|
||||||
|
<td><code>Flathub</code></td>
|
||||||
|
<td><a href="https://flathub.org/apps/com.gopeed.Gopeed">Link</a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
<td><code>SNAP</code></td>
|
<td><code>SNAP</code></td>
|
||||||
<td><a href="https://snapcraft.io/gopeed">Link</a></td>
|
<td><a href="https://snapcraft.io/gopeed">Link</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><code>DEB</code></td>
|
<td><code>DEB</code></td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux.deb">Link</a></td>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-amd64.deb">Link</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><code>AppImage</code></td>
|
<td><code>AppImage</code></td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-amd64.AppImage">Link</a></td>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-x86_64.AppImage">Link</a></td>
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><code>Portable TAR</code></td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-amd64-portable.tar.gz">Link</a></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Android</td>
|
<td>Android</td>
|
||||||
@@ -160,7 +160,7 @@ gomobile init
|
|||||||
- android
|
- android
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
gomobile bind -tags nosqlite -ldflags="-w -s" -o ui/flutter/android/app/libs/libgopeed.aar -target=android -androidapi 19 -javapkg=com.gopeed github.com/GopeedLab/gopeed/bind/mobile
|
gomobile bind -tags nosqlite -ldflags="-w -s" -o ui/flutter/android/app/libs/libgopeed.aar -target=android -androidapi 19 -javapkg="com.gopeed" github.com/GopeedLab/gopeed/bind/mobile
|
||||||
cd ui/flutter
|
cd ui/flutter
|
||||||
flutter build apk
|
flutter build apk
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -0,0 +1,211 @@
|
|||||||
|
[](https://github.com/GopeedLab/gopeed/actions?query=workflow%3Atest)
|
||||||
|
[](https://codecov.io/gh/GopeedLab/gopeed)
|
||||||
|
[](https://github.com/GopeedLab/gopeed/releases)
|
||||||
|
[](https://github.com/GopeedLab/gopeed/releases)
|
||||||
|
[](https://docs.gopeed.com/donate.html)
|
||||||
|
[](https://discord.gg/ZUJqJrwCGB)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
[](https://ko-fi.com/R6R6IJGN6)
|
||||||
|
|
||||||
|
[English](/README.md) | [中文](/README_zh-CN.md) | [日本語](/README_ja-JP.md) | [正體中文](/README_zh-TW.md) | [Tiếng Việt](/README_vi-VN.md)
|
||||||
|
|
||||||
|
## Giới thiệu
|
||||||
|
|
||||||
|
Gopeed (tên đầy đủ Go Speed), một công cụ tải xuống tốc độ cao được phát triển bởi `Golang` + `Flutter`, hỗ trợ giao thức (HTTP, BitTorrent, Magnet) và hỗ trợ tất cả các nền tảng. Ngoài các chức năng tải xuống cơ bản, Gopeed còn là một công cụ tải xuống có thể tùy chỉnh cao cho phép triển khai thêm tính năng thông qua việc tích hợp với [APIs](https://docs.gopeed.com/dev-api.html) hoặc cài đặt và phát triển các [tiện ích mở rộng](https://docs.gopeed.com/dev-extension.html).
|
||||||
|
|
||||||
|
Truy cập ✈ [Trang web chính thức](https://gopeed.com) | 📖 [Tài liệu chính thức](https://docs.gopeed.com)
|
||||||
|
|
||||||
|
## Tải về
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Nền tảng</th>
|
||||||
|
<th>Loại gói</th>
|
||||||
|
<th>Liên kết tải xuống</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td rowspan=2>Windows</td>
|
||||||
|
<td><code>Bộ cài đặt EXE</code></td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64.zip">Liên kết</a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>ZIP Portable</code></td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64-portable.zip">Liên kết</a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>MacOS</td>
|
||||||
|
<td><code>Bộ cài đặt DMG</code></td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-macos.dmg">Liên kết</a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td rowspan=4>Linux</td>
|
||||||
|
<td><code>Flathub</code></td>
|
||||||
|
<td><a href="https://flathub.org/apps/com.gopeed.Gopeed">Liên kết</a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>SNAP</code></td>
|
||||||
|
<td><a href="https://snapcraft.io/gopeed">Liên kết</a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>DEB</code></td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-amd64.deb">Liên kết</a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>AppImage</code></td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-x86_64.AppImage">Liên kết</a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Android</td>
|
||||||
|
<td><code>APK</code></td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android.apk">Liên kết</a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>iOS</td>
|
||||||
|
<td><code>IPA</code></td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-ios.ipa">Liên kết</a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Web</td>
|
||||||
|
<td></td>
|
||||||
|
<td><a href="https://github.com/GopeedLab/gopeed/releases/latest">Liên kết</a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Docker</td>
|
||||||
|
<td></td>
|
||||||
|
<td><a href="https://hub.docker.com/r/liwei2633/gopeed">Liên kết</a></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
Thêm thông tin về cài đặt, vui lòng tham khảo [Cài đặt](https://docs.gopeed.com/install.html)
|
||||||
|
|
||||||
|
### Command tool
|
||||||
|
|
||||||
|
Sử dụng `go install`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go install github.com/GopeedLab/gopeed/cmd/gopeed@latest
|
||||||
|
```
|
||||||
|
|
||||||
|
## Quyên góp
|
||||||
|
|
||||||
|
Nếu bạn thích dự án này, xin vui lòng xem xét [quyên góp](https://docs.gopeed.com/donate.html) để hỗ trợ phát triển dự án này, cảm ơn bạn!
|
||||||
|
|
||||||
|
## Trưng bày
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
Dự án này được chia thành hai phần, phần giao diện sử dụng `flutter`, phần backend sử dụng `Golang`, và hai phía giao tiếp thông qua giao thức `http`. Trên hệ thống unix, sử dụng `unix socket`, và trên hệ thống windows, sử dụng giao thức `tcp`.
|
||||||
|
|
||||||
|
> Mã giao diện nằm trong thư mục `ui/flutter`.
|
||||||
|
|
||||||
|
### Environment
|
||||||
|
|
||||||
|
1. Golang 1.21+
|
||||||
|
2. Flutter 3.16+
|
||||||
|
|
||||||
|
### Clone
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone [email protected]:GopeedLab/gopeed.git
|
||||||
|
```
|
||||||
|
|
||||||
|
### Đóng góp
|
||||||
|
|
||||||
|
Vui lòng tham khảo [CONTRIBUTING_vi-VN.md](/CONTRIBUTING_vi-VN.md)
|
||||||
|
|
||||||
|
### Xây dựng
|
||||||
|
|
||||||
|
#### Desktop
|
||||||
|
|
||||||
|
Trước tiên, bạn cần cấu hình môi trường theo tài liệu chính thức của [Tài liệu trang web máy tính để bàn Flutter](https://docs.flutter.dev/development/platform-integration/desktop), sau đó bạn cần đảm bảo môi trường cgo được thiết lập đúng. Để biết hướng dẫn chi tiết về cách thiết lập môi trường cgo, vui lòng tham khảo các tài liệu tương ứng có sẵn trực tuyến.
|
||||||
|
|
||||||
|
command:
|
||||||
|
|
||||||
|
- windows
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go build -tags nosqlite -ldflags="-w -s" -buildmode=c-shared -o ui/flutter/windows/libgopeed.dll github.com/GopeedLab/gopeed/bind/desktop
|
||||||
|
cd ui/flutter
|
||||||
|
flutter build windows
|
||||||
|
```
|
||||||
|
|
||||||
|
- macos
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go build -tags nosqlite -ldflags="-w -s" -buildmode=c-shared -o ui/flutter/macos/Frameworks/libgopeed.dylib github.com/GopeedLab/gopeed/bind/desktop
|
||||||
|
cd ui/flutter
|
||||||
|
flutter build macos
|
||||||
|
```
|
||||||
|
|
||||||
|
- linux
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go build -tags nosqlite -ldflags="-w -s" -buildmode=c-shared -o ui/flutter/linux/bundle/lib/libgopeed.so github.com/GopeedLab/gopeed/bind/desktop
|
||||||
|
cd ui/flutter
|
||||||
|
flutter build linux
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Mobile
|
||||||
|
|
||||||
|
Giống như trước đây, bạn cũng cần chuẩn bị môi trường `cgo` và sau đó cài đặt `gomobile`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go install golang.org/x/mobile/cmd/gomobile@latest
|
||||||
|
go get golang.org/x/mobile/bind
|
||||||
|
gomobile init
|
||||||
|
```
|
||||||
|
|
||||||
|
command:
|
||||||
|
|
||||||
|
- android
|
||||||
|
|
||||||
|
```bash
|
||||||
|
gomobile bind -tags nosqlite -ldflags="-w -s" -o ui/flutter/android/app/libs/libgopeed.aar -target=android -androidapi 19 -javapkg="com.gopeed" github.com/GopeedLab/gopeed/bind/mobile
|
||||||
|
cd ui/flutter
|
||||||
|
flutter build apk
|
||||||
|
```
|
||||||
|
|
||||||
|
- ios
|
||||||
|
|
||||||
|
```bash
|
||||||
|
gomobile bind -tags nosqlite -ldflags="-w -s" -o ui/flutter/ios/Frameworks/Libgopeed.xcframework -target=ios github.com/GopeedLab/gopeed/bind/mobile
|
||||||
|
cd ui/flutter
|
||||||
|
flutter build ios --no-codesign
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Web
|
||||||
|
|
||||||
|
command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd ui/flutter
|
||||||
|
flutter build web
|
||||||
|
cd ../../
|
||||||
|
rm -rf cmd/web/dist
|
||||||
|
cp -r ui/flutter/build/web cmd/web/dist
|
||||||
|
go build -tags nosqlite,web -ldflags="-s -w" -o bin/ github.com/GopeedLab/gopeed/cmd/web
|
||||||
|
```
|
||||||
|
|
||||||
|
## Tín dụng
|
||||||
|
|
||||||
|
### Người đóng góp
|
||||||
|
|
||||||
|
<a href="https://github.com/GopeedLab/gopeed/graphs/contributors">
|
||||||
|
<img src="https://contrib.rocks/image?repo=GopeedLab/gopeed" />
|
||||||
|
</a>
|
||||||
|
|
||||||
|
### JetBrains
|
||||||
|
|
||||||
|
[](https://www.jetbrains.com/?from=gopeed)
|
||||||
|
|
||||||
|
## Giấy phép
|
||||||
|
|
||||||
|
[GPLv3](LICENSE)
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
[](https://ko-fi.com/R6R6IJGN6)
|
[](https://ko-fi.com/R6R6IJGN6)
|
||||||
|
|
||||||
[English](/README.md) | [中文](/README_zh-CN.md) | [日本語](/README_ja-JP.md) | [正體中文](/README_zh-TW.md)
|
[English](/README.md) | [中文](/README_zh-CN.md) | [日本語](/README_ja-JP.md) | [正體中文](/README_zh-TW.md) | [Tiếng Việt](/README_vi-VN.md)
|
||||||
|
|
||||||
## 介绍
|
## 介绍
|
||||||
|
|
||||||
@@ -40,24 +40,24 @@ Gopeed(全称 Go Speed),直译过来中文名叫做`够快下载器`(不
|
|||||||
<tr>
|
<tr>
|
||||||
<td>MacOS</td>
|
<td>MacOS</td>
|
||||||
<td><code>DMG Installer</code></td>
|
<td><code>DMG Installer</code></td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64-portable.zip">前往</a></td>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-macos.dmg">前往</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td rowspan=4>Linux</td>
|
<td rowspan=4>Linux</td>
|
||||||
|
<td><code>Flathub</code></td>
|
||||||
|
<td><a href="https://flathub.org/apps/com.gopeed.Gopeed">前往</a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
<td><code>SNAP</code></td>
|
<td><code>SNAP</code></td>
|
||||||
<td><a href="https://snapcraft.io/gopeed">前往</a></td>
|
<td><a href="https://snapcraft.io/gopeed">前往</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><code>DEB</code></td>
|
<td><code>DEB</code></td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux.deb">前往</a></td>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-amd64.deb">前往</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><code>AppImage</code></td>
|
<td><code>AppImage</code></td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-amd64.AppImage">前往</a></td>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-x86_64.AppImage">前往</a></td>
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><code>Portable TAR</code></td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-amd64-portable.tar.gz">前往</a></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Android</td>
|
<td>Android</td>
|
||||||
@@ -92,6 +92,10 @@ Gopeed(全称 Go Speed),直译过来中文名叫做`够快下载器`(不
|
|||||||
go install github.com/GopeedLab/gopeed/cmd/gopeed@latest
|
go install github.com/GopeedLab/gopeed/cmd/gopeed@latest
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 浏览器扩展
|
||||||
|
|
||||||
|
Gopeed 还提供了浏览器扩展用于接管浏览器下载,支持 Chrome、Edge、Firefox 等浏览器,具体请参考:[https://github.com/GopeedLab/browser-extension](https://github.com/GopeedLab/browser-extension)
|
||||||
|
|
||||||
## 赞助
|
## 赞助
|
||||||
|
|
||||||
如果觉得项目对你有帮助,请考虑[赞助](https://docs.gopeed.com/zh/donate)以支持这个项目的发展,非常感谢!
|
如果觉得项目对你有帮助,请考虑[赞助](https://docs.gopeed.com/zh/donate)以支持这个项目的发展,非常感谢!
|
||||||
@@ -168,7 +172,7 @@ gomobile init
|
|||||||
- android
|
- android
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
gomobile bind -tags nosqlite -ldflags="-w -s" -o ui/flutter/android/app/libs/libgopeed.aar -target=android -androidapi 19 -javapkg=com.gopeed github.com/GopeedLab/gopeed/bind/mobile
|
gomobile bind -tags nosqlite -ldflags="-w -s" -o ui/flutter/android/app/libs/libgopeed.aar -target=android -androidapi 19 -javapkg="com.gopeed" github.com/GopeedLab/gopeed/bind/mobile
|
||||||
cd ui/flutter
|
cd ui/flutter
|
||||||
flutter build apk
|
flutter build apk
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
[](https://ko-fi.com/R6R6IJGN6)
|
[](https://ko-fi.com/R6R6IJGN6)
|
||||||
|
|
||||||
[English](/README.md) | [中文](/README_zh-CN.md) | [日本語](/README_ja-JP.md) | [正體中文](/README_zh-TW.md)
|
[English](/README.md) | [中文](/README_zh-CN.md) | [日本語](/README_ja-JP.md) | [正體中文](/README_zh-TW.md) | [Tiếng Việt](/README_vi-VN.md)
|
||||||
|
|
||||||
## 簡介
|
## 簡介
|
||||||
|
|
||||||
@@ -40,24 +40,24 @@ Gopeed(全稱 Go Speed),是一款使用`Golang`+`Flutter`編寫的高速
|
|||||||
<tr>
|
<tr>
|
||||||
<td>MacOS</td>
|
<td>MacOS</td>
|
||||||
<td><code>DMG Installer</code></td>
|
<td><code>DMG Installer</code></td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64-portable.zip">前往</a></td>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-macos.dmg">前往</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td rowspan=4>Linux</td>
|
<td rowspan=4>Linux</td>
|
||||||
|
<td><code>Flathub</code></td>
|
||||||
|
<td><a href="https://flathub.org/apps/com.gopeed.Gopeed">前往</a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
<td><code>SNAP</code></td>
|
<td><code>SNAP</code></td>
|
||||||
<td><a href="https://snapcraft.io/gopeed">前往</a></td>
|
<td><a href="https://snapcraft.io/gopeed">前往</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><code>DEB</code></td>
|
<td><code>DEB</code></td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux.deb">前往</a></td>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-amd64.deb">前往</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><code>AppImage</code></td>
|
<td><code>AppImage</code></td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-amd64.AppImage">前往</a></td>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-x86_64.AppImage">前往</a></td>
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><code>Portable TAR</code></td>
|
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-amd64-portable.tar.gz">前往</a></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Android</td>
|
<td>Android</td>
|
||||||
@@ -168,7 +168,7 @@ gomobile init
|
|||||||
- android
|
- android
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
gomobile bind -tags nosqlite -ldflags="-w -s" -o ui/flutter/android/app/libs/libgopeed.aar -target=android -androidapi 19 -javapkg=com.gopeed github.com/GopeedLab/gopeed/bind/mobile
|
gomobile bind -tags nosqlite -ldflags="-w -s" -o ui/flutter/android/app/libs/libgopeed.aar -target=android -androidapi 19 -javapkg="com.gopeed" github.com/GopeedLab/gopeed/bind/mobile
|
||||||
cd ui/flutter
|
cd ui/flutter
|
||||||
flutter build apk
|
flutter build apk
|
||||||
```
|
```
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 309 KiB |
@@ -65,6 +65,9 @@ var (
|
|||||||
|
|
||||||
func printProgress(task *download.Task, title string) {
|
func printProgress(task *download.Task, title string) {
|
||||||
var rate float64
|
var rate float64
|
||||||
|
if task.Meta.Res == nil {
|
||||||
|
task = emptyTask
|
||||||
|
}
|
||||||
if task.Meta.Res.Size <= 0 {
|
if task.Meta.Res.Size <= 0 {
|
||||||
rate = 0
|
rate = 0
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
_ "embed"
|
_ "embed"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/GopeedLab/gopeed/pkg/base"
|
||||||
"github.com/GopeedLab/gopeed/pkg/rest"
|
"github.com/GopeedLab/gopeed/pkg/rest"
|
||||||
"github.com/GopeedLab/gopeed/pkg/rest/model"
|
"github.com/GopeedLab/gopeed/pkg/rest/model"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -24,14 +25,20 @@ func Start(cfg *model.StartConfig) {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
if downloadCfg.FirstLoad {
|
if downloadCfg.FirstLoad {
|
||||||
// Set default download dir to user download dir
|
// Set default download dir, in docker, it will be ${exe}/Downloads, else it will be ${user}/Downloads
|
||||||
|
var downloadDir string
|
||||||
|
if base.InDocker == "true" {
|
||||||
|
downloadDir = filepath.Join(filepath.Dir(cfg.StorageDir), "Downloads")
|
||||||
|
} else {
|
||||||
userDir, err := os.UserHomeDir()
|
userDir, err := os.UserHomeDir()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
downloadDir := filepath.Join(userDir, "Downloads")
|
downloadDir = filepath.Join(userDir, "Downloads")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if downloadDir != "" {
|
||||||
downloadCfg.DownloadDir = downloadDir
|
downloadCfg.DownloadDir = downloadDir
|
||||||
rest.Downloader.PutConfig(downloadCfg)
|
rest.Downloader.PutConfig(downloadCfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
fmt.Printf("Server start success on http://%s\n", listener.Addr().String())
|
fmt.Printf("Server start success on http://%s\n", listener.Addr().String())
|
||||||
if err := srv.Serve(listener); err != nil && err != http.ErrServerClosed {
|
if err := srv.Serve(listener); err != nil && err != http.ErrServerClosed {
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ type args struct {
|
|||||||
Username *string `json:"username"`
|
Username *string `json:"username"`
|
||||||
Password *string `json:"password"`
|
Password *string `json:"password"`
|
||||||
ApiToken *string `json:"apiToken"`
|
ApiToken *string `json:"apiToken"`
|
||||||
|
StorageDir *string `json:"storageDir"`
|
||||||
|
|
||||||
configPath *string
|
configPath *string
|
||||||
}
|
}
|
||||||
@@ -25,6 +26,7 @@ func parse() *args {
|
|||||||
cliArgs.Username = flag.String("u", "gopeed", "HTTP Basic Auth Username")
|
cliArgs.Username = flag.String("u", "gopeed", "HTTP Basic Auth Username")
|
||||||
cliArgs.Password = flag.String("p", "", "HTTP Basic Auth Pwd")
|
cliArgs.Password = flag.String("p", "", "HTTP Basic Auth Pwd")
|
||||||
cliArgs.ApiToken = flag.String("T", "", "API token, that can only be used when basic authentication is enabled.")
|
cliArgs.ApiToken = flag.String("T", "", "API token, that can only be used when basic authentication is enabled.")
|
||||||
|
cliArgs.StorageDir = flag.String("d", "", "Storage directory")
|
||||||
cliArgs.configPath = flag.String("c", "./config.json", "Config file path")
|
cliArgs.configPath = flag.String("c", "./config.json", "Config file path")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
@@ -45,6 +47,9 @@ func parse() *args {
|
|||||||
if cfgArgs.ApiToken == nil {
|
if cfgArgs.ApiToken == nil {
|
||||||
cfgArgs.ApiToken = cliArgs.ApiToken
|
cfgArgs.ApiToken = cliArgs.ApiToken
|
||||||
}
|
}
|
||||||
|
if cfgArgs.StorageDir == nil {
|
||||||
|
cfgArgs.StorageDir = cliArgs.StorageDir
|
||||||
|
}
|
||||||
return cfgArgs
|
return cfgArgs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,11 +31,16 @@ func main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var dir string
|
||||||
|
if args.StorageDir != nil && *args.StorageDir != "" {
|
||||||
|
dir = *args.StorageDir
|
||||||
|
} else {
|
||||||
exe, err := os.Executable()
|
exe, err := os.Executable()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
dir := filepath.Dir(exe)
|
dir = filepath.Dir(exe)
|
||||||
|
}
|
||||||
|
|
||||||
cfg := &model.StartConfig{
|
cfg := &model.StartConfig{
|
||||||
Network: "tcp",
|
Network: "tcp",
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
chown -R ${PUID}:${PGID} /app
|
||||||
|
|
||||||
|
umask ${UMASK}
|
||||||
|
|
||||||
|
exec su-exec ${PUID}:${PGID} ./gopeed "$@"
|
||||||
@@ -12,9 +12,11 @@ require (
|
|||||||
github.com/gorilla/handlers v1.5.1
|
github.com/gorilla/handlers v1.5.1
|
||||||
github.com/gorilla/mux v1.8.0
|
github.com/gorilla/mux v1.8.0
|
||||||
github.com/matoous/go-nanoid/v2 v2.0.0
|
github.com/matoous/go-nanoid/v2 v2.0.0
|
||||||
|
github.com/mattn/go-ieproxy v0.0.12
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/rs/zerolog v1.31.0
|
github.com/rs/zerolog v1.31.0
|
||||||
github.com/virtuald/go-paniclog v0.0.0-20190812204905-43a7fa316459
|
github.com/virtuald/go-paniclog v0.0.0-20190812204905-43a7fa316459
|
||||||
|
github.com/xiaoqidun/setft v0.0.0-20220310121541-be86327699ad
|
||||||
go.etcd.io/bbolt v1.3.8
|
go.etcd.io/bbolt v1.3.8
|
||||||
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a
|
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a
|
||||||
golang.org/x/sync v0.6.0
|
golang.org/x/sync v0.6.0
|
||||||
@@ -96,15 +98,14 @@ require (
|
|||||||
github.com/stretchr/testify v1.8.4 // indirect
|
github.com/stretchr/testify v1.8.4 // indirect
|
||||||
github.com/tidwall/btree v1.7.0 // indirect
|
github.com/tidwall/btree v1.7.0 // indirect
|
||||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||||
github.com/xiaoqidun/setft v0.0.0-20220310121541-be86327699ad // indirect
|
|
||||||
go.opentelemetry.io/otel v1.22.0 // indirect
|
go.opentelemetry.io/otel v1.22.0 // indirect
|
||||||
go.opentelemetry.io/otel/metric v1.22.0 // indirect
|
go.opentelemetry.io/otel/metric v1.22.0 // indirect
|
||||||
go.opentelemetry.io/otel/trace v1.22.0 // indirect
|
go.opentelemetry.io/otel/trace v1.22.0 // indirect
|
||||||
golang.org/x/crypto v0.18.0 // indirect
|
golang.org/x/crypto v0.23.0 // indirect
|
||||||
golang.org/x/mod v0.14.0 // indirect
|
golang.org/x/mod v0.14.0 // indirect
|
||||||
golang.org/x/net v0.20.0 // indirect
|
golang.org/x/net v0.25.0 // indirect
|
||||||
golang.org/x/sys v0.16.0 // indirect
|
golang.org/x/sys v0.20.0 // indirect
|
||||||
golang.org/x/text v0.14.0 // indirect
|
golang.org/x/text v0.15.0 // indirect
|
||||||
golang.org/x/time v0.5.0 // indirect
|
golang.org/x/time v0.5.0 // indirect
|
||||||
golang.org/x/tools v0.17.0 // indirect
|
golang.org/x/tools v0.17.0 // indirect
|
||||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ crawshaw.io/sqlite v0.3.2/go.mod h1:igAO5JulrQ1DbdZdtVq48mnZUBAPOeFzer7VhDWNtW4=
|
|||||||
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
|
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
|
||||||
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||||
filippo.io/edwards25519 v1.0.0-rc.1 h1:m0VOOB23frXZvAOK44usCgLWvtsxIoMCTBGJZlpmGfU=
|
filippo.io/edwards25519 v1.0.0-rc.1 h1:m0VOOB23frXZvAOK44usCgLWvtsxIoMCTBGJZlpmGfU=
|
||||||
|
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/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.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
|
||||||
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
|
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
|
||||||
@@ -23,9 +24,11 @@ github.com/acomagu/bufpipe v1.0.4/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ
|
|||||||
github.com/ajwerner/btree v0.0.0-20211221152037-f427b3e689c0 h1:byYvvbfSo3+9efR4IeReh77gVs4PnNDR3AMOE9NJ7a0=
|
github.com/ajwerner/btree v0.0.0-20211221152037-f427b3e689c0 h1:byYvvbfSo3+9efR4IeReh77gVs4PnNDR3AMOE9NJ7a0=
|
||||||
github.com/ajwerner/btree v0.0.0-20211221152037-f427b3e689c0/go.mod h1:q37NoqncT41qKc048STsifIt69LfUJ8SrWWcz/yam5k=
|
github.com/ajwerner/btree v0.0.0-20211221152037-f427b3e689c0/go.mod h1:q37NoqncT41qKc048STsifIt69LfUJ8SrWWcz/yam5k=
|
||||||
github.com/alecthomas/assert/v2 v2.0.0-alpha3 h1:pcHeMvQ3OMstAWgaeaXIAL8uzB9xMm2zlxt+/4ml8lk=
|
github.com/alecthomas/assert/v2 v2.0.0-alpha3 h1:pcHeMvQ3OMstAWgaeaXIAL8uzB9xMm2zlxt+/4ml8lk=
|
||||||
|
github.com/alecthomas/assert/v2 v2.0.0-alpha3/go.mod h1:+zD0lmDXTeQj7TgDgCt0ePWxb0hMC1G+PGTsTCv1B9o=
|
||||||
github.com/alecthomas/atomic v0.1.0-alpha2 h1:dqwXmax66gXvHhsOS4pGPZKqYOlTkapELkLb3MNdlH8=
|
github.com/alecthomas/atomic v0.1.0-alpha2 h1:dqwXmax66gXvHhsOS4pGPZKqYOlTkapELkLb3MNdlH8=
|
||||||
github.com/alecthomas/atomic v0.1.0-alpha2/go.mod h1:zD6QGEyw49HIq19caJDc2NMXAy8rNi9ROrxtMXATfyI=
|
github.com/alecthomas/atomic v0.1.0-alpha2/go.mod h1:zD6QGEyw49HIq19caJDc2NMXAy8rNi9ROrxtMXATfyI=
|
||||||
github.com/alecthomas/repr v0.0.0-20210801044451-80ca428c5142 h1:8Uy0oSf5co/NZXje7U1z8Mpep++QJOldL2hs/sBQf48=
|
github.com/alecthomas/repr v0.0.0-20210801044451-80ca428c5142 h1:8Uy0oSf5co/NZXje7U1z8Mpep++QJOldL2hs/sBQf48=
|
||||||
|
github.com/alecthomas/repr v0.0.0-20210801044451-80ca428c5142/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
|
||||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||||
@@ -86,6 +89,7 @@ github.com/anacrolix/upnp v0.1.3/go.mod h1:Qyhbqo69gwNWvEk1xNTXsS5j7hMHef9hdr984
|
|||||||
github.com/anacrolix/utp v0.2.0 h1:65Cdmr6q9WSw2KsM+rtJFu7rqDzLl2bdysf4KlNPcFI=
|
github.com/anacrolix/utp v0.2.0 h1:65Cdmr6q9WSw2KsM+rtJFu7rqDzLl2bdysf4KlNPcFI=
|
||||||
github.com/anacrolix/utp v0.2.0/go.mod h1:HGk4GYQw1O/3T1+yhqT/F6EcBd+AAwlo9dYErNy7mj8=
|
github.com/anacrolix/utp v0.2.0/go.mod h1:HGk4GYQw1O/3T1+yhqT/F6EcBd+AAwlo9dYErNy7mj8=
|
||||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
||||||
|
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
|
||||||
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
||||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
||||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||||
@@ -139,6 +143,7 @@ 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 h1:6EUwBLQ/Mcr1EYLE4Tn1VdW1A4ckqCQWZBw8Hr0kjpQ=
|
||||||
github.com/edsrzf/mmap-go v1.1.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q=
|
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/elazarl/goproxy v0.0.0-20221015165544-a0805db90819 h1:RIB4cRk+lBqKK3Oy0r2gRX4ui7tuhiZq2SuTtTCi0/0=
|
||||||
|
github.com/elazarl/goproxy v0.0.0-20221015165544-a0805db90819/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
|
||||||
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||||
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
||||||
github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ=
|
github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ=
|
||||||
@@ -146,9 +151,11 @@ github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSw
|
|||||||
github.com/frankban/quicktest v1.9.0/go.mod h1:ui7WezCLWMWxVWr1GETZY3smRy0G4KWq9vcPtJmFl7Y=
|
github.com/frankban/quicktest v1.9.0/go.mod h1:ui7WezCLWMWxVWr1GETZY3smRy0G4KWq9vcPtJmFl7Y=
|
||||||
github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||||
|
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||||
github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY=
|
github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY=
|
||||||
|
github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4=
|
||||||
github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE=
|
github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE=
|
||||||
github.com/glycerine/go-unsnap-stream v0.0.0-20181221182339-f9677308dec2/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE=
|
github.com/glycerine/go-unsnap-stream v0.0.0-20181221182339-f9677308dec2/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE=
|
||||||
github.com/glycerine/go-unsnap-stream v0.0.0-20190901134440-81cf024a9e0a/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE=
|
github.com/glycerine/go-unsnap-stream v0.0.0-20190901134440-81cf024a9e0a/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE=
|
||||||
@@ -160,6 +167,7 @@ github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmS
|
|||||||
github.com/go-git/go-billy/v5 v5.4.1 h1:Uwp5tDRkPr+l/TnbHOQzp+tmJfLceOlbVucgpTz8ix4=
|
github.com/go-git/go-billy/v5 v5.4.1 h1:Uwp5tDRkPr+l/TnbHOQzp+tmJfLceOlbVucgpTz8ix4=
|
||||||
github.com/go-git/go-billy/v5 v5.4.1/go.mod h1:vjbugF6Fz7JIflbVpl1hJsGjSHNltrSw45YK/ukIvQg=
|
github.com/go-git/go-billy/v5 v5.4.1/go.mod h1:vjbugF6Fz7JIflbVpl1hJsGjSHNltrSw45YK/ukIvQg=
|
||||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20230305113008-0c11038e723f h1:Pz0DHeFij3XFhoBRGUDPzSJ+w2UcK5/0JvF8DRI58r8=
|
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20230305113008-0c11038e723f h1:Pz0DHeFij3XFhoBRGUDPzSJ+w2UcK5/0JvF8DRI58r8=
|
||||||
|
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20230305113008-0c11038e723f/go.mod h1:8LHG1a3SRW71ettAD/jW13h8c6AqjVSeL11RAdgaqpo=
|
||||||
github.com/go-git/go-git/v5 v5.8.1 h1:Zo79E4p7TRk0xoRgMq0RShiTHGKcKI4+DI6BfJc/Q+A=
|
github.com/go-git/go-git/v5 v5.8.1 h1:Zo79E4p7TRk0xoRgMq0RShiTHGKcKI4+DI6BfJc/Q+A=
|
||||||
github.com/go-git/go-git/v5 v5.8.1/go.mod h1:FHFuoD6yGz5OSKEBK+aWN9Oah0q54Jxl0abmj6GnqAo=
|
github.com/go-git/go-git/v5 v5.8.1/go.mod h1:FHFuoD6yGz5OSKEBK+aWN9Oah0q54Jxl0abmj6GnqAo=
|
||||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
@@ -212,6 +220,7 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
|||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/pprof v0.0.0-20230207041349-798e818bf904/go.mod h1:uglQLonpP8qtYCYyzA+8c/9qtqgA3qsXGYqCPKARAFg=
|
github.com/google/pprof v0.0.0-20230207041349-798e818bf904/go.mod h1:uglQLonpP8qtYCYyzA+8c/9qtqgA3qsXGYqCPKARAFg=
|
||||||
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a h1:fEBsGL/sjAuJrgah5XqmmYsTLzJp/TO9Lhy39gkverk=
|
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a h1:fEBsGL/sjAuJrgah5XqmmYsTLzJp/TO9Lhy39gkverk=
|
||||||
@@ -233,6 +242,7 @@ github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/
|
|||||||
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
|
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
|
||||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
||||||
|
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
||||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||||
github.com/huandu/xstrings v1.0.0/go.mod h1:4qWG/gcEcfX4z/mBDHJ++3ReCw9ibxbsNJbcucJdbSo=
|
github.com/huandu/xstrings v1.0.0/go.mod h1:4qWG/gcEcfX4z/mBDHJ++3ReCw9ibxbsNJbcucJdbSo=
|
||||||
github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4=
|
github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4=
|
||||||
@@ -270,6 +280,8 @@ github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A=
|
|||||||
github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
|
github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
|
||||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||||
|
github.com/mattn/go-ieproxy v0.0.12 h1:OZkUFJC3ESNZPQ+6LzC3VJIFSnreeFLQyqvBWtvfL2M=
|
||||||
|
github.com/mattn/go-ieproxy v0.0.12/go.mod h1:Vn+N61199DAnVeTgaF8eoB9PvLO8P3OBnG95ENh7B7c=
|
||||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
@@ -454,8 +466,8 @@ golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45
|
|||||||
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
|
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
|
||||||
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
|
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
|
||||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||||
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
|
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
|
||||||
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20220428152302-39d4317da171/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE=
|
golang.org/x/exp v0.0.0-20220428152302-39d4317da171/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE=
|
||||||
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a h1:Q8/wZp0KX97QFTc2ywcOE0YRjZPVIx+MXInMzdvQqcA=
|
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a h1:Q8/wZp0KX97QFTc2ywcOE0YRjZPVIx+MXInMzdvQqcA=
|
||||||
@@ -500,8 +512,8 @@ golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
|
|||||||
golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
|
golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
|
||||||
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
|
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
|
||||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||||
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
|
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
|
||||||
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
@@ -556,8 +568,8 @@ golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|||||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
|
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
@@ -570,7 +582,8 @@ golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo=
|
|||||||
golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
|
golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
|
||||||
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
|
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
|
||||||
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||||
golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE=
|
golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
|
||||||
|
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
@@ -585,8 +598,8 @@ golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
|||||||
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
|
||||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/url"
|
"github.com/GopeedLab/gopeed/pkg/base"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Controller struct {
|
type Controller struct {
|
||||||
GetConfig func(v any) bool
|
GetConfig func(v any) bool
|
||||||
ProxyUrl *url.URL
|
ProxyConfig *base.DownloaderProxyConfig
|
||||||
FileController
|
FileController
|
||||||
//ContextDialer() (proxy.Dialer, error)
|
//ContextDialer() (proxy.Dialer, error)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import (
|
|||||||
"github.com/GopeedLab/gopeed/pkg/util"
|
"github.com/GopeedLab/gopeed/pkg/util"
|
||||||
"github.com/anacrolix/torrent"
|
"github.com/anacrolix/torrent"
|
||||||
"github.com/anacrolix/torrent/metainfo"
|
"github.com/anacrolix/torrent/metainfo"
|
||||||
"net/http"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -33,6 +32,7 @@ type Fetcher struct {
|
|||||||
meta *fetcher.FetcherMeta
|
meta *fetcher.FetcherMeta
|
||||||
|
|
||||||
torrentReady atomic.Bool
|
torrentReady atomic.Bool
|
||||||
|
torrentDrop atomic.Bool
|
||||||
create atomic.Bool
|
create atomic.Bool
|
||||||
progress fetcher.Progress
|
progress fetcher.Progress
|
||||||
}
|
}
|
||||||
@@ -68,9 +68,7 @@ func (f *Fetcher) initClient() (err error) {
|
|||||||
cfg.Bep20 = fmt.Sprintf("-GP%s-", parseBep20())
|
cfg.Bep20 = fmt.Sprintf("-GP%s-", parseBep20())
|
||||||
cfg.ExtendedHandshakeClientVersion = fmt.Sprintf("Gopeed %s", base.Version)
|
cfg.ExtendedHandshakeClientVersion = fmt.Sprintf("Gopeed %s", base.Version)
|
||||||
cfg.ListenPort = f.config.ListenPort
|
cfg.ListenPort = f.config.ListenPort
|
||||||
if f.ctl.ProxyUrl != nil {
|
cfg.HTTPProxy = f.ctl.ProxyConfig.ToHandler()
|
||||||
cfg.HTTPProxy = http.ProxyURL(f.ctl.ProxyUrl)
|
|
||||||
}
|
|
||||||
cfg.DefaultStorage = newFileOpts(newFileClientOpts{
|
cfg.DefaultStorage = newFileOpts(newFileClientOpts{
|
||||||
ClientBaseDir: cfg.DataDir,
|
ClientBaseDir: cfg.DataDir,
|
||||||
HandleFileTorrent: func(infoHash metainfo.Hash, ft *fileTorrentImpl) {
|
HandleFileTorrent: func(infoHash metainfo.Hash, ft *fileTorrentImpl) {
|
||||||
@@ -85,10 +83,6 @@ func (f *Fetcher) initClient() (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f *Fetcher) Resolve(req *base.Request) error {
|
func (f *Fetcher) Resolve(req *base.Request) error {
|
||||||
if err := base.ParseReqExtra[bt.ReqExtra](req); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := f.addTorrent(req); err != nil {
|
if err := f.addTorrent(req); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -143,6 +137,7 @@ func (f *Fetcher) Pause() (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f *Fetcher) Close() (err error) {
|
func (f *Fetcher) Close() (err error) {
|
||||||
|
f.torrentDrop.Store(false)
|
||||||
f.safeDrop()
|
f.safeDrop()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -158,6 +153,9 @@ func (f *Fetcher) safeDrop() {
|
|||||||
|
|
||||||
func (f *Fetcher) Wait() (err error) {
|
func (f *Fetcher) Wait() (err error) {
|
||||||
for {
|
for {
|
||||||
|
if f.torrentDrop.Load() {
|
||||||
|
break
|
||||||
|
}
|
||||||
if f.torrentReady.Load() && len(f.meta.Opts.SelectFiles) > 0 {
|
if f.torrentReady.Load() && len(f.meta.Opts.SelectFiles) > 0 {
|
||||||
done := true
|
done := true
|
||||||
for _, selectIndex := range f.meta.Opts.SelectFiles {
|
for _, selectIndex := range f.meta.Opts.SelectFiles {
|
||||||
@@ -238,6 +236,9 @@ func (f *Fetcher) updateRes() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f *Fetcher) addTorrent(req *base.Request) (err error) {
|
func (f *Fetcher) addTorrent(req *base.Request) (err error) {
|
||||||
|
if err = base.ParseReqExtra[bt.ReqExtra](req); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
if err = f.initClient(); err != nil {
|
if err = f.initClient(); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,6 @@ import (
|
|||||||
"github.com/GopeedLab/gopeed/internal/test"
|
"github.com/GopeedLab/gopeed/internal/test"
|
||||||
"github.com/GopeedLab/gopeed/pkg/base"
|
"github.com/GopeedLab/gopeed/pkg/base"
|
||||||
"github.com/GopeedLab/gopeed/pkg/protocol/bt"
|
"github.com/GopeedLab/gopeed/pkg/protocol/bt"
|
||||||
"github.com/GopeedLab/gopeed/pkg/util"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -60,7 +58,14 @@ func TestFetcher_ResolveWithProxy(t *testing.T) {
|
|||||||
proxyListener := test.StartSocks5Server(usr, pwd)
|
proxyListener := test.StartSocks5Server(usr, pwd)
|
||||||
defer proxyListener.Close()
|
defer proxyListener.Close()
|
||||||
|
|
||||||
doResolve(t, buildConfigFetcher(util.BuildProxyUrl("socks5", proxyListener.Addr().String(), usr, pwd)))
|
doResolve(t, buildConfigFetcher(&base.DownloaderProxyConfig{
|
||||||
|
Enable: true,
|
||||||
|
System: false,
|
||||||
|
Scheme: "socks5",
|
||||||
|
Host: proxyListener.Addr().String(),
|
||||||
|
Usr: usr,
|
||||||
|
Pwd: pwd,
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
func doResolve(t *testing.T, fetcher fetcher.Fetcher) {
|
func doResolve(t *testing.T, fetcher fetcher.Fetcher) {
|
||||||
@@ -100,7 +105,7 @@ func buildFetcher() fetcher.Fetcher {
|
|||||||
return fetcher
|
return fetcher
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildConfigFetcher(proxyUrl *url.URL) fetcher.Fetcher {
|
func buildConfigFetcher(proxyConfig *base.DownloaderProxyConfig) fetcher.Fetcher {
|
||||||
fetcher := new(FetcherBuilder).Build()
|
fetcher := new(FetcherBuilder).Build()
|
||||||
newController := controller.NewController()
|
newController := controller.NewController()
|
||||||
mockCfg := config{
|
mockCfg := config{
|
||||||
@@ -114,7 +119,7 @@ func buildConfigFetcher(proxyUrl *url.URL) fetcher.Fetcher {
|
|||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
newController.ProxyUrl = proxyUrl
|
newController.ProxyConfig = proxyConfig
|
||||||
fetcher.Setup(newController)
|
fetcher.Setup(newController)
|
||||||
return fetcher
|
return fetcher
|
||||||
}
|
}
|
||||||
@@ -280,10 +280,6 @@ func (f *Fetcher) fetchChunk(index int, ctx context.Context) (err error) {
|
|||||||
chunk := f.chunks[index]
|
chunk := f.chunks[index]
|
||||||
chunk.retryTimes = 0
|
chunk.retryTimes = 0
|
||||||
|
|
||||||
httpReq, err := f.buildRequest(ctx, f.meta.Req)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var (
|
var (
|
||||||
client = f.buildClient()
|
client = f.buildClient()
|
||||||
buf = make([]byte, 8192)
|
buf = make([]byte, 8192)
|
||||||
@@ -314,8 +310,13 @@ func (f *Fetcher) fetchChunk(index int, ctx context.Context) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
httpReq *http.Request
|
||||||
resp *http.Response
|
resp *http.Response
|
||||||
)
|
)
|
||||||
|
httpReq, err = f.buildRequest(ctx, f.meta.Req)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
if f.meta.Res.Range {
|
if f.meta.Res.Range {
|
||||||
httpReq.Header.Set(base.HttpHeaderRange,
|
httpReq.Header.Set(base.HttpHeaderRange,
|
||||||
fmt.Sprintf(base.HttpHeaderRangeFormat, chunk.Begin+chunk.Downloaded, chunk.End))
|
fmt.Sprintf(base.HttpHeaderRangeFormat, chunk.Begin+chunk.Downloaded, chunk.End))
|
||||||
@@ -450,12 +451,11 @@ func (f *Fetcher) splitChunk() (chunks []*chunk) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f *Fetcher) buildClient() *http.Client {
|
func (f *Fetcher) buildClient() *http.Client {
|
||||||
transport := &http.Transport{}
|
transport := &http.Transport{
|
||||||
|
Proxy: f.ctl.ProxyConfig.ToHandler(),
|
||||||
|
}
|
||||||
// Cookie handle
|
// Cookie handle
|
||||||
jar, _ := cookiejar.New(nil)
|
jar, _ := cookiejar.New(nil)
|
||||||
if f.ctl.ProxyUrl != nil {
|
|
||||||
transport.Proxy = http.ProxyURL(f.ctl.ProxyUrl)
|
|
||||||
}
|
|
||||||
return &http.Client{
|
return &http.Client{
|
||||||
Transport: transport,
|
Transport: transport,
|
||||||
Jar: jar,
|
Jar: jar,
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import (
|
|||||||
"github.com/GopeedLab/gopeed/internal/test"
|
"github.com/GopeedLab/gopeed/internal/test"
|
||||||
"github.com/GopeedLab/gopeed/pkg/base"
|
"github.com/GopeedLab/gopeed/pkg/base"
|
||||||
"github.com/GopeedLab/gopeed/pkg/protocol/http"
|
"github.com/GopeedLab/gopeed/pkg/protocol/http"
|
||||||
"github.com/GopeedLab/gopeed/pkg/util"
|
|
||||||
"net"
|
"net"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@@ -338,7 +337,11 @@ func downloadResume(listener net.Listener, connections int, t *testing.T) {
|
|||||||
func downloadWithProxy(httpListener net.Listener, proxyListener net.Listener, t *testing.T) {
|
func downloadWithProxy(httpListener net.Listener, proxyListener net.Listener, t *testing.T) {
|
||||||
fetcher := downloadReady(httpListener, 4, t)
|
fetcher := downloadReady(httpListener, 4, t)
|
||||||
ctl := controller.NewController()
|
ctl := controller.NewController()
|
||||||
ctl.ProxyUrl = util.BuildProxyUrl("socks5", proxyListener.Addr().String(), "", "")
|
ctl.ProxyConfig = &base.DownloaderProxyConfig{
|
||||||
|
Enable: true,
|
||||||
|
Scheme: "socks5",
|
||||||
|
Host: proxyListener.Addr().String(),
|
||||||
|
}
|
||||||
fetcher.Setup(ctl)
|
fetcher.Setup(ctl)
|
||||||
err := fetcher.Start()
|
err := fetcher.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -24,6 +24,11 @@ const (
|
|||||||
Dir = "./"
|
Dir = "./"
|
||||||
BuildFile = Dir + BuildName
|
BuildFile = Dir + BuildName
|
||||||
|
|
||||||
|
ExternalDownloadUrl = "https://raw.githubusercontent.com/GopeedLab/gopeed/v1.5.6/_docs/img/banner.png"
|
||||||
|
ExternalDownloadName = "banner.png"
|
||||||
|
ExternalDownloadSize = 26416
|
||||||
|
//ExternalDownloadMd5 = "c67c6e3cae79a95342485676571e8a5c"
|
||||||
|
|
||||||
DownloadName = "download.data"
|
DownloadName = "download.data"
|
||||||
DownloadRename = "download (1).data"
|
DownloadRename = "download (1).data"
|
||||||
DownloadFile = Dir + DownloadName
|
DownloadFile = Dir + DownloadName
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package base
|
|||||||
|
|
||||||
// Version is the build version, set at build time, using `go build -ldflags "-X github.com/GopeedLab/gopeed/pkg/base.Version=1.0.0"`.
|
// Version is the build version, set at build time, using `go build -ldflags "-X github.com/GopeedLab/gopeed/pkg/base.Version=1.0.0"`.
|
||||||
var Version string
|
var Version string
|
||||||
|
var InDocker string
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
if Version == "" {
|
if Version == "" {
|
||||||
@@ -3,7 +3,10 @@ package base
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/GopeedLab/gopeed/pkg/util"
|
"github.com/GopeedLab/gopeed/pkg/util"
|
||||||
|
"github.com/mattn/go-ieproxy"
|
||||||
"golang.org/x/exp/slices"
|
"golang.org/x/exp/slices"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -128,3 +131,88 @@ func ParseOptsExtra[E any](opts *Options) error {
|
|||||||
opts.Extra = &t
|
opts.Extra = &t
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DownloaderStoreConfig is the config that can restore the downloader.
|
||||||
|
type DownloaderStoreConfig struct {
|
||||||
|
FirstLoad bool `json:"-"` // FirstLoad is the flag that the config is first time init and not from store
|
||||||
|
|
||||||
|
DownloadDir string `json:"downloadDir"` // DownloadDir is the default directory to save the downloaded files
|
||||||
|
MaxRunning int `json:"maxRunning"` // MaxRunning is the max running download count
|
||||||
|
ProtocolConfig map[string]any `json:"protocolConfig"` // ProtocolConfig is special config for each protocol
|
||||||
|
Extra map[string]any `json:"extra"`
|
||||||
|
Proxy *DownloaderProxyConfig `json:"proxy"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *DownloaderStoreConfig) Init() *DownloaderStoreConfig {
|
||||||
|
if cfg.MaxRunning == 0 {
|
||||||
|
cfg.MaxRunning = 5
|
||||||
|
}
|
||||||
|
if cfg.Proxy == nil {
|
||||||
|
cfg.Proxy = &DownloaderProxyConfig{}
|
||||||
|
}
|
||||||
|
return cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
type DownloaderProxyConfig struct {
|
||||||
|
Enable bool `json:"enable"`
|
||||||
|
// System is the flag that use system proxy
|
||||||
|
System bool `json:"system"`
|
||||||
|
Scheme string `json:"scheme"`
|
||||||
|
Host string `json:"host"`
|
||||||
|
Usr string `json:"usr"`
|
||||||
|
Pwd string `json:"pwd"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *DownloaderProxyConfig) ToHandler() func(r *http.Request) (*url.URL, error) {
|
||||||
|
if cfg == nil || cfg.Enable == false {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if cfg.System {
|
||||||
|
ieproxy.ReloadConf()
|
||||||
|
return ieproxy.GetProxyFunc()
|
||||||
|
}
|
||||||
|
if cfg.Scheme == "" || cfg.Host == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return http.ProxyURL(util.BuildProxyUrl(cfg.Scheme, cfg.Host, cfg.Usr, cfg.Pwd))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToUrl returns the proxy url, just for git clone
|
||||||
|
func (cfg *DownloaderProxyConfig) ToUrl() *url.URL {
|
||||||
|
if cfg == nil || cfg.Enable == false {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if cfg.System {
|
||||||
|
ieproxy.ReloadConf()
|
||||||
|
static := ieproxy.GetConf().Static
|
||||||
|
if static.Active && len(static.Protocols) > 0 {
|
||||||
|
// If only one protocol, use it
|
||||||
|
if len(static.Protocols) == 1 {
|
||||||
|
for _, v := range static.Protocols {
|
||||||
|
return parseUrlSafe(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Check https
|
||||||
|
if v, ok := static.Protocols["https"]; ok {
|
||||||
|
return parseUrlSafe(v)
|
||||||
|
}
|
||||||
|
// Check http
|
||||||
|
if v, ok := static.Protocols["http"]; ok {
|
||||||
|
return parseUrlSafe(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if cfg.Scheme == "" || cfg.Host == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return util.BuildProxyUrl(cfg.Scheme, cfg.Host, cfg.Usr, cfg.Pwd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseUrlSafe(rawUrl string) *url.URL {
|
||||||
|
u, err := url.Parse(rawUrl)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return u
|
||||||
|
}
|
||||||
@@ -110,7 +110,7 @@ func (d *Downloader) Setup() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// load config from storage
|
// load config from storage
|
||||||
var cfg DownloaderStoreConfig
|
var cfg base.DownloaderStoreConfig
|
||||||
exist, err := d.storage.Get(bucketConfig, "config", &cfg)
|
exist, err := d.storage.Get(bucketConfig, "config", &cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -118,7 +118,7 @@ func (d *Downloader) Setup() error {
|
|||||||
if exist {
|
if exist {
|
||||||
d.cfg.DownloaderStoreConfig = &cfg
|
d.cfg.DownloaderStoreConfig = &cfg
|
||||||
} else {
|
} else {
|
||||||
d.cfg.DownloaderStoreConfig = &DownloaderStoreConfig{
|
d.cfg.DownloaderStoreConfig = &base.DownloaderStoreConfig{
|
||||||
FirstLoad: true,
|
FirstLoad: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -172,8 +172,8 @@ func (d *Downloader) Setup() error {
|
|||||||
if len(d.tasks) > 0 {
|
if len(d.tasks) > 0 {
|
||||||
for _, task := range d.tasks {
|
for _, task := range d.tasks {
|
||||||
func() {
|
func() {
|
||||||
task.lock.Lock()
|
task.statusLock.Lock()
|
||||||
defer task.lock.Unlock()
|
defer task.statusLock.Unlock()
|
||||||
if task.Status != base.DownloadStatusRunning {
|
if task.Status != base.DownloadStatusRunning {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -225,7 +225,7 @@ func (d *Downloader) setupFetcher(fetcher fetcher.Fetcher) {
|
|||||||
ctl.GetConfig = func(v any) bool {
|
ctl.GetConfig = func(v any) bool {
|
||||||
return d.getProtocolConfig(fetcher.Name(), v)
|
return d.getProtocolConfig(fetcher.Name(), v)
|
||||||
}
|
}
|
||||||
ctl.ProxyUrl = d.cfg.ProxyUrl()
|
ctl.ProxyConfig = d.cfg.Proxy
|
||||||
fetcher.Setup(ctl)
|
fetcher.Setup(ctl)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -301,10 +301,19 @@ func (d *Downloader) CreateDirect(req *base.Request, opts *base.Options) (taskId
|
|||||||
return d.doCreate(fetcher, opts)
|
return d.doCreate(fetcher, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Downloader) Create(rrId string, opts *base.Options) (taskId string, err error) {
|
func (d *Downloader) CreateDirectBatch(reqs []*base.Request, opts *base.Options) (taskId []string, err error) {
|
||||||
if opts == nil {
|
taskIds := make([]string, 0)
|
||||||
opts = &base.Options{}
|
for _, req := range reqs {
|
||||||
|
taskId, err := d.CreateDirect(req, opts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
taskIds = append(taskIds, taskId)
|
||||||
|
}
|
||||||
|
return taskIds, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Downloader) Create(rrId string, opts *base.Options) (taskId string, err error) {
|
||||||
d.fetcherMapLock.RLock()
|
d.fetcherMapLock.RLock()
|
||||||
fetcher, ok := d.fetcherCache[rrId]
|
fetcher, ok := d.fetcherCache[rrId]
|
||||||
d.fetcherMapLock.RUnlock()
|
d.fetcherMapLock.RUnlock()
|
||||||
@@ -332,8 +341,7 @@ func (d *Downloader) Pause(id string) (err error) {
|
|||||||
if task.Status == base.DownloadStatusPause {
|
if task.Status == base.DownloadStatusPause {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = d.doPause(task)
|
if err = d.doPause(task); err != nil {
|
||||||
if err != nil {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
d.notifyRunning()
|
d.notifyRunning()
|
||||||
@@ -366,19 +374,7 @@ func (d *Downloader) Continue(id string) (err error) {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
func() {
|
return d.doStart(task)
|
||||||
task.lock.Lock()
|
|
||||||
defer task.lock.Unlock()
|
|
||||||
|
|
||||||
if task.Status == base.DownloadStatusRunning {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = d.doStart(task); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Downloader) PauseAll() (err error) {
|
func (d *Downloader) PauseAll() (err error) {
|
||||||
@@ -391,13 +387,7 @@ func (d *Downloader) PauseAll() (err error) {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
for _, task := range d.tasks {
|
for _, task := range d.tasks {
|
||||||
err = func() error {
|
if err = d.doPause(task); err != nil {
|
||||||
task.lock.Lock()
|
|
||||||
defer task.lock.Unlock()
|
|
||||||
|
|
||||||
return d.doPause(task)
|
|
||||||
}()
|
|
||||||
if err != nil {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -427,13 +417,7 @@ func (d *Downloader) ContinueAll() (err error) {
|
|||||||
|
|
||||||
for _, task := range continuedTasks {
|
for _, task := range continuedTasks {
|
||||||
tt := task
|
tt := task
|
||||||
err = func() error {
|
if err = d.doStart(tt); err != nil {
|
||||||
tt.lock.Lock()
|
|
||||||
defer tt.lock.Unlock()
|
|
||||||
|
|
||||||
return d.doStart(tt)
|
|
||||||
}()
|
|
||||||
if err != nil {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -525,17 +509,21 @@ func (d *Downloader) Stats(id string) (sr any, err error) {
|
|||||||
|
|
||||||
func (d *Downloader) doDelete(task *Task, force bool) (err error) {
|
func (d *Downloader) doDelete(task *Task, force bool) (err error) {
|
||||||
err = func() error {
|
err = func() error {
|
||||||
if task.fetcher != nil {
|
d.lock.Lock()
|
||||||
if err := task.fetcher.Close(); err != nil {
|
defer d.lock.Unlock()
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err := d.storage.Delete(bucketTask, task.ID); err != nil {
|
if err := d.storage.Delete(bucketTask, task.ID); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := d.storage.Delete(bucketSave, task.ID); err != nil {
|
if err := d.storage.Delete(bucketSave, task.ID); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if task.fetcher != nil {
|
||||||
|
if err := task.fetcher.Close(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
if force && task.Meta.Res != nil {
|
if force && task.Meta.Res != nil {
|
||||||
if task.Meta.Res.Name != "" {
|
if task.Meta.Res.Name != "" {
|
||||||
if err := os.RemoveAll(task.Meta.FolderPath()); err != nil {
|
if err := os.RemoveAll(task.Meta.FolderPath()); err != nil {
|
||||||
@@ -627,11 +615,11 @@ func (d *Downloader) GetTasksByStatues(statues []base.Status) []*Task {
|
|||||||
return tasks
|
return tasks
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Downloader) GetConfig() (*DownloaderStoreConfig, error) {
|
func (d *Downloader) GetConfig() (*base.DownloaderStoreConfig, error) {
|
||||||
return d.cfg.DownloaderStoreConfig, nil
|
return d.cfg.DownloaderStoreConfig, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Downloader) PutConfig(v *DownloaderStoreConfig) error {
|
func (d *Downloader) PutConfig(v *base.DownloaderStoreConfig) error {
|
||||||
d.cfg.DownloaderStoreConfig = v
|
d.cfg.DownloaderStoreConfig = v
|
||||||
return d.storage.Put(bucketConfig, "config", v)
|
return d.storage.Put(bucketConfig, "config", v)
|
||||||
}
|
}
|
||||||
@@ -750,6 +738,9 @@ func (d *Downloader) doCreate(fetcher fetcher.Fetcher, opts *base.Options) (task
|
|||||||
if opts == nil {
|
if opts == nil {
|
||||||
opts = &base.Options{}
|
opts = &base.Options{}
|
||||||
}
|
}
|
||||||
|
if opts.SelectFiles == nil {
|
||||||
|
opts.SelectFiles = make([]int, 0)
|
||||||
|
}
|
||||||
|
|
||||||
meta := fetcher.Meta()
|
meta := fetcher.Meta()
|
||||||
meta.Opts = opts
|
meta.Opts = opts
|
||||||
@@ -794,31 +785,52 @@ func (d *Downloader) doCreate(fetcher fetcher.Fetcher, opts *base.Options) (task
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
d.doStart(task)
|
err = d.doStart(task)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
go d.watch(task)
|
go d.watch(task)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *Downloader) statusMut(task *Task, fn func() (bool, error)) (bool, error) {
|
||||||
|
task.statusLock.Lock()
|
||||||
|
defer task.statusLock.Unlock()
|
||||||
|
|
||||||
|
return fn()
|
||||||
|
}
|
||||||
|
|
||||||
func (d *Downloader) doStart(task *Task) (err error) {
|
func (d *Downloader) doStart(task *Task) (err error) {
|
||||||
if task.Status != base.DownloadStatusRunning && task.Status != base.DownloadStatusDone {
|
var isCreate bool
|
||||||
err := d.restoreFetcher(task)
|
isReturn, err := d.statusMut(task, func() (isReturn bool, err error) {
|
||||||
|
if task.Status == base.DownloadStatusRunning || task.Status == base.DownloadStatusDone {
|
||||||
|
isReturn = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = d.restoreFetcher(task)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
d.Logger.Error().Stack().Err(err).Msgf("restore fetcher failed, task id: %s", task.ID)
|
d.Logger.Error().Stack().Err(err).Msgf("restore fetcher failed, task id: %s", task.ID)
|
||||||
return err
|
return
|
||||||
}
|
}
|
||||||
}
|
isCreate = task.Status == base.DownloadStatusReady
|
||||||
|
|
||||||
isCreate := task.Status == base.DownloadStatusReady
|
|
||||||
|
|
||||||
doStart := func() error {
|
|
||||||
task.lock.Lock()
|
|
||||||
defer task.lock.Unlock()
|
|
||||||
|
|
||||||
d.triggerOnStart(task)
|
d.triggerOnStart(task)
|
||||||
task.updateStatus(base.DownloadStatusRunning)
|
task.updateStatus(base.DownloadStatusRunning)
|
||||||
|
|
||||||
|
return
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
d.Logger.Error().Stack().Err(err).Msgf("start task failed, task id: %s", task.ID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if isReturn {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
handler := func() error {
|
||||||
|
task.lock.Lock()
|
||||||
|
defer task.lock.Unlock()
|
||||||
|
|
||||||
if task.Meta.Res == nil {
|
if task.Meta.Res == nil {
|
||||||
err := task.fetcher.Resolve(task.Meta.Req)
|
err := task.fetcher.Resolve(task.Meta.Req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -865,8 +877,7 @@ func (d *Downloader) doStart(task *Task) (err error) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
go func() {
|
go func() {
|
||||||
err := doStart()
|
if err := handler(); err != nil {
|
||||||
if err != nil {
|
|
||||||
d.doOnError(task, err)
|
d.doOnError(task, err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
@@ -875,10 +886,28 @@ func (d *Downloader) doStart(task *Task) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *Downloader) doPause(task *Task) (err error) {
|
func (d *Downloader) doPause(task *Task) (err error) {
|
||||||
err = func() error {
|
isReturn, err := d.statusMut(task, func() (isReturn bool, err error) {
|
||||||
if task.Status != base.DownloadStatusDone {
|
if task.Status == base.DownloadStatusPause || task.Status == base.DownloadStatusDone {
|
||||||
|
isReturn = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
task.updateStatus(base.DownloadStatusPause)
|
task.updateStatus(base.DownloadStatusPause)
|
||||||
task.timer.Pause()
|
task.timer.Pause()
|
||||||
|
return
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
d.Logger.Error().Stack().Err(err).Msgf("pause task failed, task id: %s", task.ID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if isReturn {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
handler := func() error {
|
||||||
|
task.lock.Lock()
|
||||||
|
defer task.lock.Unlock()
|
||||||
|
|
||||||
if task.fetcher != nil {
|
if task.fetcher != nil {
|
||||||
if err := task.fetcher.Pause(); err != nil {
|
if err := task.fetcher.Pause(); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -888,14 +917,13 @@ func (d *Downloader) doPause(task *Task) (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
d.emit(EventKeyPause, task)
|
d.emit(EventKeyPause, task)
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}()
|
|
||||||
if err != nil {
|
|
||||||
d.Logger.Error().Stack().Err(err).Msgf("pause task failed, task id: %s", task.ID)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
go func() {
|
||||||
|
if err := handler(); err != nil {
|
||||||
|
d.Logger.Error().Stack().Err(err).Msgf("pause task handle failed, task id: %s", task.ID)
|
||||||
|
}
|
||||||
|
}()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -923,6 +951,8 @@ func (d *Downloader) buildFetcher(url string) (fetcher.Fetcher, error) {
|
|||||||
|
|
||||||
func initTask(task *Task) {
|
func initTask(task *Task) {
|
||||||
task.timer = util.NewTimer(task.Progress.Used)
|
task.timer = util.NewTimer(task.Progress.Used)
|
||||||
|
|
||||||
|
task.statusLock = &sync.Mutex{}
|
||||||
task.lock = &sync.Mutex{}
|
task.lock = &sync.Mutex{}
|
||||||
task.speedArr = make([]int64, 0)
|
task.speedArr = make([]int64, 0)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"github.com/GopeedLab/gopeed/internal/test"
|
"github.com/GopeedLab/gopeed/internal/test"
|
||||||
"github.com/GopeedLab/gopeed/pkg/base"
|
"github.com/GopeedLab/gopeed/pkg/base"
|
||||||
"github.com/GopeedLab/gopeed/pkg/protocol/http"
|
"github.com/GopeedLab/gopeed/pkg/protocol/http"
|
||||||
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@@ -83,39 +84,95 @@ func TestDownloader_Create(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDownloader_CreateWithProxy(t *testing.T) {
|
func TestDownloader_CreateDirectBatch(t *testing.T) {
|
||||||
// No proxy
|
listener := test.StartTestFileServer()
|
||||||
doTestDownloaderCreateWithProxy(t, false, func(proxyCfg *DownloaderProxyConfig) *DownloaderProxyConfig {
|
defer listener.Close()
|
||||||
return nil
|
|
||||||
})
|
downloader := NewDownloader(nil)
|
||||||
// Disable proxy
|
if err := downloader.Setup(); err != nil {
|
||||||
doTestDownloaderCreateWithProxy(t, false, func(proxyCfg *DownloaderProxyConfig) *DownloaderProxyConfig {
|
t.Fatal(err)
|
||||||
proxyCfg.Enable = false
|
}
|
||||||
return proxyCfg
|
defer downloader.Clear()
|
||||||
})
|
|
||||||
// Invalid proxy scheme
|
reqs := make([]*base.Request, 0)
|
||||||
doTestDownloaderCreateWithProxy(t, false, func(proxyCfg *DownloaderProxyConfig) *DownloaderProxyConfig {
|
for i := 0; i < 6; i++ {
|
||||||
proxyCfg.Scheme = ""
|
req := &base.Request{
|
||||||
return proxyCfg
|
URL: "http://" + listener.Addr().String() + "/" + test.BuildName,
|
||||||
})
|
}
|
||||||
// Invalid proxy host
|
reqs = append(reqs, req)
|
||||||
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) {
|
_, err := downloader.CreateDirectBatch(reqs, &base.Options{
|
||||||
httpListener := test.StartTestFileServer()
|
Path: test.Dir,
|
||||||
defer httpListener.Close()
|
Name: test.DownloadName,
|
||||||
|
Extra: http.OptsExtra{
|
||||||
|
Connections: 4,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks := downloader.GetTasks()
|
||||||
|
if len(tasks) != len(reqs) {
|
||||||
|
t.Errorf("CreateDirectBatch() got = %v, want %v", len(tasks), len(reqs))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDownloader_CreateWithProxy(t *testing.T) {
|
||||||
|
// No proxy
|
||||||
|
doTestDownloaderCreateWithProxy(t, false, func(proxyCfg *base.DownloaderProxyConfig) *base.DownloaderProxyConfig {
|
||||||
|
return nil
|
||||||
|
}, nil)
|
||||||
|
// Disable proxy
|
||||||
|
doTestDownloaderCreateWithProxy(t, false, func(proxyCfg *base.DownloaderProxyConfig) *base.DownloaderProxyConfig {
|
||||||
|
proxyCfg.Enable = false
|
||||||
|
return proxyCfg
|
||||||
|
}, nil)
|
||||||
|
// Enable system proxy but not set proxy environment variable
|
||||||
|
doTestDownloaderCreateWithProxy(t, false, func(proxyCfg *base.DownloaderProxyConfig) *base.DownloaderProxyConfig {
|
||||||
|
proxyCfg.System = true
|
||||||
|
return proxyCfg
|
||||||
|
}, nil)
|
||||||
|
// Enable proxy but error proxy environment variable
|
||||||
|
doTestDownloaderCreateWithProxy(t, false, func(proxyCfg *base.DownloaderProxyConfig) *base.DownloaderProxyConfig {
|
||||||
|
os.Setenv("HTTP_PROXY", "http://127.0.0.1:1234")
|
||||||
|
os.Setenv("HTTPS_PROXY", "http://127.0.0.1:1234")
|
||||||
|
proxyCfg.System = true
|
||||||
|
return proxyCfg
|
||||||
|
}, func(err error) {
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("doTestDownloaderCreateWithProxy() got = nil, want error")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// Enable system proxy and set proxy environment variable
|
||||||
|
doTestDownloaderCreateWithProxy(t, false, func(proxyCfg *base.DownloaderProxyConfig) *base.DownloaderProxyConfig {
|
||||||
|
os.Setenv("HTTP_PROXY", proxyCfg.ToUrl().String())
|
||||||
|
os.Setenv("HTTPS_PROXY", proxyCfg.ToUrl().String())
|
||||||
|
proxyCfg.System = true
|
||||||
|
return proxyCfg
|
||||||
|
}, nil)
|
||||||
|
// Invalid proxy scheme
|
||||||
|
doTestDownloaderCreateWithProxy(t, false, func(proxyCfg *base.DownloaderProxyConfig) *base.DownloaderProxyConfig {
|
||||||
|
proxyCfg.Scheme = ""
|
||||||
|
return proxyCfg
|
||||||
|
}, nil)
|
||||||
|
// Invalid proxy host
|
||||||
|
doTestDownloaderCreateWithProxy(t, false, func(proxyCfg *base.DownloaderProxyConfig) *base.DownloaderProxyConfig {
|
||||||
|
proxyCfg.Host = ""
|
||||||
|
return proxyCfg
|
||||||
|
}, nil)
|
||||||
|
// Use proxy without auth
|
||||||
|
doTestDownloaderCreateWithProxy(t, false, func(proxyCfg *base.DownloaderProxyConfig) *base.DownloaderProxyConfig {
|
||||||
|
return proxyCfg
|
||||||
|
}, nil)
|
||||||
|
// Use proxy with auth
|
||||||
|
doTestDownloaderCreateWithProxy(t, true, func(proxyCfg *base.DownloaderProxyConfig) *base.DownloaderProxyConfig {
|
||||||
|
return proxyCfg
|
||||||
|
}, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func doTestDownloaderCreateWithProxy(t *testing.T, auth bool, buildProxyConfig func(proxyCfg *base.DownloaderProxyConfig) *base.DownloaderProxyConfig, errHandler func(err error)) {
|
||||||
usr, pwd := "", ""
|
usr, pwd := "", ""
|
||||||
if auth {
|
if auth {
|
||||||
usr, pwd = "admin", "123"
|
usr, pwd = "admin", "123"
|
||||||
@@ -128,7 +185,7 @@ func doTestDownloaderCreateWithProxy(t *testing.T, auth bool, buildProxyConfig f
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
defer downloader.Clear()
|
defer downloader.Clear()
|
||||||
downloader.cfg.DownloaderStoreConfig.Proxy = buildProxyConfig(&DownloaderProxyConfig{
|
downloader.cfg.DownloaderStoreConfig.Proxy = buildProxyConfig(&base.DownloaderProxyConfig{
|
||||||
Enable: true,
|
Enable: true,
|
||||||
Scheme: "socks5",
|
Scheme: "socks5",
|
||||||
Host: proxyListener.Addr().String(),
|
Host: proxyListener.Addr().String(),
|
||||||
@@ -137,20 +194,24 @@ func doTestDownloaderCreateWithProxy(t *testing.T, auth bool, buildProxyConfig f
|
|||||||
})
|
})
|
||||||
|
|
||||||
req := &base.Request{
|
req := &base.Request{
|
||||||
URL: "http://" + httpListener.Addr().String() + "/" + test.BuildName,
|
URL: test.ExternalDownloadUrl,
|
||||||
}
|
}
|
||||||
rr, err := downloader.Resolve(req)
|
rr, err := downloader.Resolve(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if errHandler == nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
errHandler(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
want := &base.Resource{
|
want := &base.Resource{
|
||||||
Size: test.BuildSize,
|
Size: test.ExternalDownloadSize,
|
||||||
Range: true,
|
Range: true,
|
||||||
Files: []*base.FileInfo{
|
Files: []*base.FileInfo{
|
||||||
{
|
{
|
||||||
Name: test.BuildName,
|
Name: test.ExternalDownloadName,
|
||||||
Path: "",
|
Path: "",
|
||||||
Size: test.BuildSize,
|
Size: test.ExternalDownloadSize,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -285,7 +346,7 @@ func TestDownloader_Protocol_Config(t *testing.T) {
|
|||||||
t.Errorf("getProtocolConfig() got = %v, want %v", exits, false)
|
t.Errorf("getProtocolConfig() got = %v, want %v", exits, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
storeCfg := &DownloaderStoreConfig{
|
storeCfg := &base.DownloaderStoreConfig{
|
||||||
DownloadDir: "./downloads",
|
DownloadDir: "./downloads",
|
||||||
ProtocolConfig: map[string]any{
|
ProtocolConfig: map[string]any{
|
||||||
"http": map[string]any{
|
"http": map[string]any{
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package engine
|
|||||||
import (
|
import (
|
||||||
_ "embed"
|
_ "embed"
|
||||||
"errors"
|
"errors"
|
||||||
|
"github.com/GopeedLab/gopeed/pkg/base"
|
||||||
gojaerror "github.com/GopeedLab/gopeed/pkg/download/engine/inject/error"
|
gojaerror "github.com/GopeedLab/gopeed/pkg/download/engine/inject/error"
|
||||||
"github.com/GopeedLab/gopeed/pkg/download/engine/inject/file"
|
"github.com/GopeedLab/gopeed/pkg/download/engine/inject/file"
|
||||||
"github.com/GopeedLab/gopeed/pkg/download/engine/inject/formdata"
|
"github.com/GopeedLab/gopeed/pkg/download/engine/inject/formdata"
|
||||||
@@ -11,7 +12,6 @@ import (
|
|||||||
"github.com/dop251/goja"
|
"github.com/dop251/goja"
|
||||||
"github.com/dop251/goja_nodejs/eventloop"
|
"github.com/dop251/goja_nodejs/eventloop"
|
||||||
gojaurl "github.com/dop251/goja_nodejs/url"
|
gojaurl "github.com/dop251/goja_nodejs/url"
|
||||||
"net/url"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -110,7 +110,7 @@ func (e *Engine) Close() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
ProxyURL *url.URL
|
ProxyConfig *base.DownloaderProxyConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewEngine(cfg *Config) *Engine {
|
func NewEngine(cfg *Config) *Engine {
|
||||||
@@ -135,7 +135,7 @@ func NewEngine(cfg *Config) *Engine {
|
|||||||
if err := formdata.Enable(runtime); err != nil {
|
if err := formdata.Enable(runtime); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := xhr.Enable(runtime, cfg.ProxyURL); err != nil {
|
if err := xhr.Enable(runtime, cfg.ProxyConfig.ToHandler()); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if _, err := runtime.RunString(polyfillScript); err != nil {
|
if _, err := runtime.RunString(polyfillScript); err != nil {
|
||||||
@@ -145,6 +145,10 @@ func NewEngine(cfg *Config) *Engine {
|
|||||||
if err := runtime.Set("global", runtime.GlobalObject()); err != nil {
|
if err := runtime.Set("global", runtime.GlobalObject()); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// polyfill window
|
||||||
|
if err := runtime.Set("window", runtime.GlobalObject()); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
// polyfill window.location
|
// polyfill window.location
|
||||||
if _, err := runtime.RunString("global.location = new URL('http://localhost');"); err != nil {
|
if _, err := runtime.RunString("global.location = new URL('http://localhost');"); err != nil {
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -7,10 +7,10 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/GopeedLab/gopeed/internal/test"
|
"github.com/GopeedLab/gopeed/internal/test"
|
||||||
|
"github.com/GopeedLab/gopeed/pkg/base"
|
||||||
gojaerror "github.com/GopeedLab/gopeed/pkg/download/engine/inject/error"
|
gojaerror "github.com/GopeedLab/gopeed/pkg/download/engine/inject/error"
|
||||||
"github.com/GopeedLab/gopeed/pkg/download/engine/inject/file"
|
"github.com/GopeedLab/gopeed/pkg/download/engine/inject/file"
|
||||||
gojautil "github.com/GopeedLab/gopeed/pkg/download/engine/util"
|
gojautil "github.com/GopeedLab/gopeed/pkg/download/engine/util"
|
||||||
"github.com/GopeedLab/gopeed/pkg/util"
|
|
||||||
"github.com/dop251/goja"
|
"github.com/dop251/goja"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
@@ -80,6 +80,30 @@ async function testFormData(file){
|
|||||||
return await resp.json();
|
return await resp.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function testHeader(){
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const xhr = new XMLHttpRequest();
|
||||||
|
xhr.open('GET', host+'/header');
|
||||||
|
xhr.setRequestHeader('X-Gopeed-Test', 'test1');
|
||||||
|
xhr.setRequestHeader('x-gopeed-test', 'test2');
|
||||||
|
xhr.setRequestHeader('x-Gopeed-test', 'test3');
|
||||||
|
xhr.onload = function(){
|
||||||
|
const testHeader1 = xhr.getResponseHeader("X-Gopeed-Test");
|
||||||
|
const testHeader2 = xhr.getResponseHeader("x-gopeed-test");
|
||||||
|
const testHeader3 = xhr.getResponseHeader("x-Gopeed-test");
|
||||||
|
const expect = 'test1, test2, test3';
|
||||||
|
const all = xhr.getAllResponseHeaders();
|
||||||
|
if(testHeader1 === expect && testHeader2 === expect && testHeader3 === expect
|
||||||
|
&& all.includes('X-Gopeed-Test: '+expect)){
|
||||||
|
resolve();
|
||||||
|
}else{
|
||||||
|
reject();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
xhr.send();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function testProgress(){
|
function testProgress(){
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const xhr = new XMLHttpRequest();
|
const xhr = new XMLHttpRequest();
|
||||||
@@ -190,14 +214,19 @@ function testTimeout(){
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
_, err = callTestFun(engine, "testHeader")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("header test failed", err)
|
||||||
|
}
|
||||||
|
|
||||||
_, err = callTestFun(engine, "testProgress")
|
_, err = callTestFun(engine, "testProgress")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("progress test failed")
|
t.Fatal("progress test failed", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = callTestFun(engine, "testAbort")
|
_, err = callTestFun(engine, "testAbort")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("abort test failed")
|
t.Fatal("abort test failed", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = callTestFun(engine, "testTimeout")
|
_, err = callTestFun(engine, "testTimeout")
|
||||||
@@ -217,8 +246,16 @@ func doTestFetchWithProxy(t *testing.T, usr, pwd string) {
|
|||||||
|
|
||||||
proxyListener := test.StartSocks5Server(usr, pwd)
|
proxyListener := test.StartSocks5Server(usr, pwd)
|
||||||
defer proxyListener.Close()
|
defer proxyListener.Close()
|
||||||
|
engine := NewEngine(&Config{
|
||||||
engine := NewEngine(&Config{ProxyURL: util.BuildProxyUrl("socks5", proxyListener.Addr().String(), usr, pwd)})
|
ProxyConfig: &base.DownloaderProxyConfig{
|
||||||
|
Enable: true,
|
||||||
|
System: false,
|
||||||
|
Scheme: "socks5",
|
||||||
|
Host: proxyListener.Addr().String(),
|
||||||
|
Usr: usr,
|
||||||
|
Pwd: pwd,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
if _, err := engine.RunString(fmt.Sprintf("var host = 'http://%s';", httpListener.Addr().String())); err != nil {
|
if _, err := engine.RunString(fmt.Sprintf("var host = 'http://%s';", httpListener.Addr().String())); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -320,6 +357,15 @@ func startServer() net.Listener {
|
|||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
w.Write([]byte("ok"))
|
w.Write([]byte("ok"))
|
||||||
})
|
})
|
||||||
|
mux.HandleFunc("/header", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
for k, v := range r.Header {
|
||||||
|
if strings.HasPrefix(k, "X-Gopeed") {
|
||||||
|
w.Header().Set(k, strings.Join(v, ", "))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
w.Write([]byte("ok"))
|
||||||
|
})
|
||||||
mux.HandleFunc("/text", func(w http.ResponseWriter, r *http.Request) {
|
mux.HandleFunc("/text", func(w http.ResponseWriter, r *http.Request) {
|
||||||
buf, _ := io.ReadAll(r.Body)
|
buf, _ := io.ReadAll(r.Body)
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package xhr
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"errors"
|
||||||
"github.com/GopeedLab/gopeed/pkg/download/engine/inject/file"
|
"github.com/GopeedLab/gopeed/pkg/download/engine/inject/file"
|
||||||
"github.com/GopeedLab/gopeed/pkg/download/engine/inject/formdata"
|
"github.com/GopeedLab/gopeed/pkg/download/engine/inject/formdata"
|
||||||
"github.com/GopeedLab/gopeed/pkg/download/engine/util"
|
"github.com/GopeedLab/gopeed/pkg/download/engine/util"
|
||||||
@@ -11,6 +12,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -117,10 +119,10 @@ type XMLHttpRequestUpload struct {
|
|||||||
type XMLHttpRequest struct {
|
type XMLHttpRequest struct {
|
||||||
method string
|
method string
|
||||||
url string
|
url string
|
||||||
requestHeaders map[string]string
|
requestHeaders http.Header
|
||||||
responseHeaders map[string]string
|
responseHeaders http.Header
|
||||||
aborted bool
|
aborted bool
|
||||||
proxyUrl *url.URL
|
proxyHandler func(r *http.Request) (*url.URL, error)
|
||||||
|
|
||||||
Upload *XMLHttpRequestUpload `json:"upload"`
|
Upload *XMLHttpRequestUpload `json:"upload"`
|
||||||
Timeout int `json:"timeout"`
|
Timeout int `json:"timeout"`
|
||||||
@@ -136,13 +138,13 @@ type XMLHttpRequest struct {
|
|||||||
func (xhr *XMLHttpRequest) Open(method, url string) {
|
func (xhr *XMLHttpRequest) Open(method, url string) {
|
||||||
xhr.method = method
|
xhr.method = method
|
||||||
xhr.url = url
|
xhr.url = url
|
||||||
xhr.requestHeaders = make(map[string]string)
|
xhr.requestHeaders = make(http.Header)
|
||||||
xhr.responseHeaders = make(map[string]string)
|
xhr.responseHeaders = make(http.Header)
|
||||||
xhr.doReadystatechange(1)
|
xhr.doReadystatechange(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (xhr *XMLHttpRequest) SetRequestHeader(key, value string) {
|
func (xhr *XMLHttpRequest) SetRequestHeader(key, value string) {
|
||||||
xhr.requestHeaders[key] = value
|
xhr.requestHeaders.Add(key, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (xhr *XMLHttpRequest) Send(data goja.Value) {
|
func (xhr *XMLHttpRequest) Send(data goja.Value) {
|
||||||
@@ -193,18 +195,15 @@ func (xhr *XMLHttpRequest) Send(data goja.Value) {
|
|||||||
xhr.callOnerror()
|
xhr.callOnerror()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
req.Header = xhr.requestHeaders
|
||||||
if contentType != "" {
|
if contentType != "" {
|
||||||
req.Header.Set("Content-Type", contentType)
|
req.Header.Set("Content-Type", contentType)
|
||||||
}
|
}
|
||||||
if contentLength > 0 {
|
if contentLength > 0 {
|
||||||
req.ContentLength = contentLength
|
req.ContentLength = contentLength
|
||||||
}
|
}
|
||||||
for k, v := range xhr.requestHeaders {
|
transport := &http.Transport{
|
||||||
req.Header.Set(k, v)
|
Proxy: xhr.proxyHandler,
|
||||||
}
|
|
||||||
transport := &http.Transport{}
|
|
||||||
if xhr.proxyUrl != nil {
|
|
||||||
transport.Proxy = http.ProxyURL(xhr.proxyUrl)
|
|
||||||
}
|
}
|
||||||
client := &http.Client{
|
client := &http.Client{
|
||||||
Transport: transport,
|
Transport: transport,
|
||||||
@@ -213,7 +212,8 @@ func (xhr *XMLHttpRequest) Send(data goja.Value) {
|
|||||||
resp, err := client.Do(req)
|
resp, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// handle timeout error
|
// handle timeout error
|
||||||
if err, ok := err.(net.Error); ok && err.Timeout() {
|
var ne net.Error
|
||||||
|
if errors.As(err, &ne) && ne.Timeout() {
|
||||||
if xhr.Timeout > 0 {
|
if xhr.Timeout > 0 {
|
||||||
xhr.Upload.callOntimeout()
|
xhr.Upload.callOntimeout()
|
||||||
xhr.callOntimeout()
|
xhr.callOntimeout()
|
||||||
@@ -229,9 +229,7 @@ func (xhr *XMLHttpRequest) Send(data goja.Value) {
|
|||||||
if !xhr.aborted {
|
if !xhr.aborted {
|
||||||
xhr.Upload.callOnload()
|
xhr.Upload.callOnload()
|
||||||
}
|
}
|
||||||
for k, v := range resp.Header {
|
xhr.responseHeaders = resp.Header
|
||||||
xhr.responseHeaders[k] = v[0]
|
|
||||||
}
|
|
||||||
xhr.Status = resp.StatusCode
|
xhr.Status = resp.StatusCode
|
||||||
xhr.StatusText = resp.Status
|
xhr.StatusText = resp.Status
|
||||||
xhr.doReadystatechange(2)
|
xhr.doReadystatechange(2)
|
||||||
@@ -260,7 +258,7 @@ func (xhr *XMLHttpRequest) Abort() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (xhr *XMLHttpRequest) GetResponseHeader(key string) string {
|
func (xhr *XMLHttpRequest) GetResponseHeader(key string) string {
|
||||||
return xhr.responseHeaders[key]
|
return strings.Join(xhr.responseHeaders.Values(key), ", ")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (xhr *XMLHttpRequest) GetAllResponseHeaders() string {
|
func (xhr *XMLHttpRequest) GetAllResponseHeaders() string {
|
||||||
@@ -268,7 +266,7 @@ func (xhr *XMLHttpRequest) GetAllResponseHeaders() string {
|
|||||||
for k, v := range xhr.responseHeaders {
|
for k, v := range xhr.responseHeaders {
|
||||||
buf.WriteString(k)
|
buf.WriteString(k)
|
||||||
buf.WriteString(": ")
|
buf.WriteString(": ")
|
||||||
buf.WriteString(v)
|
buf.WriteString(strings.Join(v, ", "))
|
||||||
buf.WriteString("\r\n")
|
buf.WriteString("\r\n")
|
||||||
}
|
}
|
||||||
return buf.String()
|
return buf.String()
|
||||||
@@ -313,7 +311,7 @@ func (xhr *XMLHttpRequest) parseData(data goja.Value) any {
|
|||||||
return data.String()
|
return data.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func Enable(runtime *goja.Runtime, proxyUrl *url.URL) error {
|
func Enable(runtime *goja.Runtime, proxyHandler func(r *http.Request) (*url.URL, error)) error {
|
||||||
progressEvent := runtime.ToValue(func(call goja.ConstructorCall) *goja.Object {
|
progressEvent := runtime.ToValue(func(call goja.ConstructorCall) *goja.Object {
|
||||||
if len(call.Arguments) < 1 {
|
if len(call.Arguments) < 1 {
|
||||||
util.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.")
|
||||||
@@ -327,7 +325,7 @@ func Enable(runtime *goja.Runtime, proxyUrl *url.URL) error {
|
|||||||
})
|
})
|
||||||
xhr := runtime.ToValue(func(call goja.ConstructorCall) *goja.Object {
|
xhr := runtime.ToValue(func(call goja.ConstructorCall) *goja.Object {
|
||||||
instance := &XMLHttpRequest{
|
instance := &XMLHttpRequest{
|
||||||
proxyUrl: proxyUrl,
|
proxyHandler: proxyHandler,
|
||||||
Upload: &XMLHttpRequestUpload{
|
Upload: &XMLHttpRequestUpload{
|
||||||
EventProp: &EventProp{
|
EventProp: &EventProp{
|
||||||
eventListeners: make(map[string]func(event *ProgressEvent)),
|
eventListeners: make(map[string]func(event *ProgressEvent)),
|
||||||
|
|||||||
@@ -199,7 +199,7 @@ func (d *Downloader) fetchExtensionByGit(url string, handler func(tempExtPath st
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
proxyOptions := transport.ProxyOptions{}
|
proxyOptions := transport.ProxyOptions{}
|
||||||
proxyUrl := d.cfg.ProxyUrl()
|
proxyUrl := d.cfg.DownloaderStoreConfig.Proxy.ToUrl()
|
||||||
if proxyUrl != nil {
|
if proxyUrl != nil {
|
||||||
proxyOptions.URL = proxyUrl.Scheme + "://" + proxyUrl.Host
|
proxyOptions.URL = proxyUrl.Scheme + "://" + proxyUrl.Host
|
||||||
proxyOptions.Username = proxyUrl.User.Username()
|
proxyOptions.Username = proxyUrl.User.Username()
|
||||||
@@ -341,7 +341,7 @@ func doTrigger[T any](d *Downloader, event ActivationEvent, req *base.Request, c
|
|||||||
req.Labels = make(map[string]string)
|
req.Labels = make(map[string]string)
|
||||||
}
|
}
|
||||||
engine := engine.NewEngine(&engine.Config{
|
engine := engine.NewEngine(&engine.Config{
|
||||||
ProxyURL: d.cfg.ProxyUrl(),
|
ProxyConfig: d.cfg.Proxy,
|
||||||
})
|
})
|
||||||
defer engine.Close()
|
defer engine.Close()
|
||||||
err = engine.Runtime.Set("gopeed", gopeed)
|
err = engine.Runtime.Set("gopeed", gopeed)
|
||||||
|
|||||||
@@ -238,7 +238,7 @@ func TestDownloader_Extension_OnError(t *testing.T) {
|
|||||||
select {
|
select {
|
||||||
case err = <-errCh:
|
case err = <-errCh:
|
||||||
break
|
break
|
||||||
case <-time.After(time.Second * 10):
|
case <-time.After(time.Second * 30):
|
||||||
err = errors.New("timeout")
|
err = errors.New("timeout")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,9 +7,7 @@ import (
|
|||||||
"github.com/GopeedLab/gopeed/internal/protocol/http"
|
"github.com/GopeedLab/gopeed/internal/protocol/http"
|
||||||
"github.com/GopeedLab/gopeed/pkg/base"
|
"github.com/GopeedLab/gopeed/pkg/base"
|
||||||
"github.com/GopeedLab/gopeed/pkg/util"
|
"github.com/GopeedLab/gopeed/pkg/util"
|
||||||
|
|
||||||
gonanoid "github.com/matoous/go-nanoid/v2"
|
gonanoid "github.com/matoous/go-nanoid/v2"
|
||||||
"net/url"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -31,6 +29,7 @@ type Task struct {
|
|||||||
fetcherBuilder fetcher.FetcherBuilder
|
fetcherBuilder fetcher.FetcherBuilder
|
||||||
fetcher fetcher.Fetcher
|
fetcher fetcher.Fetcher
|
||||||
timer *util.Timer
|
timer *util.Timer
|
||||||
|
statusLock *sync.Mutex
|
||||||
lock *sync.Mutex
|
lock *sync.Mutex
|
||||||
speedArr []int64
|
speedArr []int64
|
||||||
}
|
}
|
||||||
@@ -86,7 +85,7 @@ type DownloaderConfig struct {
|
|||||||
|
|
||||||
ProductionMode bool
|
ProductionMode bool
|
||||||
|
|
||||||
*DownloaderStoreConfig
|
*base.DownloaderStoreConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *DownloaderConfig) Init() *DownloaderConfig {
|
func (cfg *DownloaderConfig) Init() *DownloaderConfig {
|
||||||
@@ -107,42 +106,3 @@ func (cfg *DownloaderConfig) Init() *DownloaderConfig {
|
|||||||
}
|
}
|
||||||
return cfg
|
return cfg
|
||||||
}
|
}
|
||||||
|
|
||||||
// DownloaderStoreConfig is the config that can restore the downloader.
|
|
||||||
type DownloaderStoreConfig struct {
|
|
||||||
FirstLoad bool `json:"-"` // FirstLoad is the flag that the config is first time init and not from store
|
|
||||||
|
|
||||||
DownloadDir string `json:"downloadDir"` // DownloadDir is the default directory to save the downloaded files
|
|
||||||
MaxRunning int `json:"maxRunning"` // MaxRunning is the max running download count
|
|
||||||
ProtocolConfig map[string]any `json:"protocolConfig"` // ProtocolConfig is special config for each protocol
|
|
||||||
Extra map[string]any `json:"extra"`
|
|
||||||
Proxy *DownloaderProxyConfig `json:"proxy"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *DownloaderStoreConfig) Init() *DownloaderStoreConfig {
|
|
||||||
if cfg.MaxRunning == 0 {
|
|
||||||
cfg.MaxRunning = 5
|
|
||||||
}
|
|
||||||
if cfg.Proxy == nil {
|
|
||||||
cfg.Proxy = &DownloaderProxyConfig{}
|
|
||||||
}
|
|
||||||
return cfg
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *DownloaderStoreConfig) ProxyUrl() *url.URL {
|
|
||||||
if cfg.Proxy == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if cfg.Proxy.Enable == false || cfg.Proxy.Scheme == "" || cfg.Proxy.Host == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return util.BuildProxyUrl(cfg.Proxy.Scheme, cfg.Proxy.Host, cfg.Proxy.Usr, cfg.Proxy.Pwd)
|
|
||||||
}
|
|
||||||
|
|
||||||
type DownloaderProxyConfig struct {
|
|
||||||
Enable bool `json:"enable"`
|
|
||||||
Scheme string `json:"scheme"`
|
|
||||||
Host string `json:"host"`
|
|
||||||
Usr string `json:"usr"`
|
|
||||||
Pwd string `json:"pwd"`
|
|
||||||
}
|
|
||||||
@@ -69,7 +69,9 @@ func (n *MemStorage) Setup(buckets []string) error {
|
|||||||
func (n *MemStorage) Put(bucket string, key string, v any) error {
|
func (n *MemStorage) Put(bucket string, key string, v any) error {
|
||||||
n.lock.Lock()
|
n.lock.Lock()
|
||||||
defer n.lock.Unlock()
|
defer n.lock.Unlock()
|
||||||
memData[bucket][key] = v
|
if bucketData, ok := memData[bucket]; ok {
|
||||||
|
bucketData[key] = v
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,8 +8,20 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"runtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func Info(w http.ResponseWriter, r *http.Request) {
|
||||||
|
info := map[string]any{
|
||||||
|
"version": base.Version,
|
||||||
|
"runtime": runtime.Version(),
|
||||||
|
"os": runtime.GOOS,
|
||||||
|
"arch": runtime.GOARCH,
|
||||||
|
}
|
||||||
|
info["inDocker"] = base.InDocker == "true"
|
||||||
|
WriteJson(w, model.NewOkResult(info))
|
||||||
|
}
|
||||||
|
|
||||||
func Resolve(w http.ResponseWriter, r *http.Request) {
|
func Resolve(w http.ResponseWriter, r *http.Request) {
|
||||||
var req base.Request
|
var req base.Request
|
||||||
if ReadJson(r, w, &req) {
|
if ReadJson(r, w, &req) {
|
||||||
@@ -45,6 +57,22 @@ func CreateTask(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func CreateTaskBatch(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req model.CreateTaskBatch
|
||||||
|
if ReadJson(r, w, &req) {
|
||||||
|
if len(req.Reqs) == 0 {
|
||||||
|
WriteJson(w, model.NewErrorResult("param invalid: reqs", model.CodeInvalidParam))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
taskIds, err := Downloader.CreateDirectBatch(req.Reqs, req.Opt)
|
||||||
|
if err != nil {
|
||||||
|
WriteJson(w, model.NewErrorResult(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
WriteJson(w, model.NewOkResult(taskIds))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func PauseTask(w http.ResponseWriter, r *http.Request) {
|
func PauseTask(w http.ResponseWriter, r *http.Request) {
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
taskId := vars["id"]
|
taskId := vars["id"]
|
||||||
@@ -142,7 +170,7 @@ func GetConfig(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func PutConfig(w http.ResponseWriter, r *http.Request) {
|
func PutConfig(w http.ResponseWriter, r *http.Request) {
|
||||||
var cfg download.DownloaderStoreConfig
|
var cfg base.DownloaderStoreConfig
|
||||||
if ReadJson(r, w, &cfg) {
|
if ReadJson(r, w, &cfg) {
|
||||||
if err := Downloader.PutConfig(&cfg); err != nil {
|
if err := Downloader.PutConfig(&cfg); err != nil {
|
||||||
WriteJson(w, model.NewErrorResult(err.Error()))
|
WriteJson(w, model.NewErrorResult(err.Error()))
|
||||||
@@ -309,7 +337,7 @@ func writeError(w http.ResponseWriter, msg string) {
|
|||||||
w.Write([]byte(msg))
|
w.Write([]byte(msg))
|
||||||
}
|
}
|
||||||
|
|
||||||
func getServerConfig() *download.DownloaderStoreConfig {
|
func getServerConfig() *base.DownloaderStoreConfig {
|
||||||
cfg, _ := Downloader.GetConfig()
|
cfg, _ := Downloader.GetConfig()
|
||||||
return cfg
|
return cfg
|
||||||
}
|
}
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
package model
|
|
||||||
|
|
||||||
type Command struct {
|
|
||||||
Protocol string `json:"protocol"`
|
|
||||||
Action string `json:"action"`
|
|
||||||
Params any `json:"params"`
|
|
||||||
}
|
|
||||||
@@ -7,3 +7,8 @@ type CreateTask struct {
|
|||||||
Req *base.Request `json:"req"`
|
Req *base.Request `json:"req"`
|
||||||
Opt *base.Options `json:"opt"`
|
Opt *base.Options `json:"opt"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CreateTaskBatch struct {
|
||||||
|
Reqs []*base.Request `json:"reqs"`
|
||||||
|
Opt *base.Options `json:"opt"`
|
||||||
|
}
|
||||||
@@ -20,30 +20,41 @@ import (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
srv *http.Server
|
srv *http.Server
|
||||||
|
runningPort int
|
||||||
|
|
||||||
Downloader *download.Downloader
|
Downloader *download.Downloader
|
||||||
)
|
)
|
||||||
|
|
||||||
func Start(startCfg *model.StartConfig) (int, error) {
|
func Start(startCfg *model.StartConfig) (port int, err error) {
|
||||||
srv, listener, err := BuildServer(startCfg)
|
// avoid repeat start
|
||||||
|
if srv != nil {
|
||||||
|
return runningPort, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var listener net.Listener
|
||||||
|
srv, listener, err = BuildServer(startCfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
if err := srv.Serve(listener); err != nil && err != http.ErrServerClosed {
|
if err := srv.Serve(listener); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
port := 0
|
|
||||||
if addr, ok := listener.Addr().(*net.TCPAddr); ok {
|
if addr, ok := listener.Addr().(*net.TCPAddr); ok {
|
||||||
port = addr.Port
|
port = addr.Port
|
||||||
|
runningPort = port
|
||||||
}
|
}
|
||||||
return port, nil
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func Stop() {
|
func Stop() {
|
||||||
|
defer func() {
|
||||||
|
srv = nil
|
||||||
|
}()
|
||||||
|
|
||||||
if srv != nil {
|
if srv != nil {
|
||||||
if err := srv.Shutdown(context.TODO()); err != nil {
|
if err := srv.Shutdown(context.TODO()); err != nil {
|
||||||
Downloader.Logger.Warn().Err(err).Msg("shutdown server failed")
|
Downloader.Logger.Warn().Err(err).Msg("shutdown server failed")
|
||||||
@@ -88,8 +99,10 @@ func BuildServer(startCfg *model.StartConfig) (*http.Server, net.Listener, error
|
|||||||
}
|
}
|
||||||
|
|
||||||
var r = mux.NewRouter()
|
var r = mux.NewRouter()
|
||||||
|
r.Methods(http.MethodGet).Path("/api/v1/info").HandlerFunc(Info)
|
||||||
r.Methods(http.MethodPost).Path("/api/v1/resolve").HandlerFunc(Resolve)
|
r.Methods(http.MethodPost).Path("/api/v1/resolve").HandlerFunc(Resolve)
|
||||||
r.Methods(http.MethodPost).Path("/api/v1/tasks").HandlerFunc(CreateTask)
|
r.Methods(http.MethodPost).Path("/api/v1/tasks").HandlerFunc(CreateTask)
|
||||||
|
r.Methods(http.MethodPost).Path("/api/v1/tasks/batch").HandlerFunc(CreateTaskBatch)
|
||||||
r.Methods(http.MethodPut).Path("/api/v1/tasks/{id}/pause").HandlerFunc(PauseTask)
|
r.Methods(http.MethodPut).Path("/api/v1/tasks/{id}/pause").HandlerFunc(PauseTask)
|
||||||
r.Methods(http.MethodPut).Path("/api/v1/tasks/{id}/continue").HandlerFunc(ContinueTask)
|
r.Methods(http.MethodPut).Path("/api/v1/tasks/{id}/continue").HandlerFunc(ContinueTask)
|
||||||
r.Methods(http.MethodPut).Path("/api/v1/tasks/pause").HandlerFunc(PauseAllTask)
|
r.Methods(http.MethodPut).Path("/api/v1/tasks/pause").HandlerFunc(PauseAllTask)
|
||||||
|
|||||||
@@ -63,6 +63,18 @@ var (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestInfo(t *testing.T) {
|
||||||
|
matchKeys := []string{"version", "runtime", "os", "arch", "inDocker"}
|
||||||
|
doTest(func() {
|
||||||
|
resp := httpRequestCheckOk[map[string]any](http.MethodGet, "/api/v1/info", nil)
|
||||||
|
for _, key := range matchKeys {
|
||||||
|
if _, ok := resp[key]; !ok {
|
||||||
|
t.Errorf("Info() missing key = %v", key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestResolve(t *testing.T) {
|
func TestResolve(t *testing.T) {
|
||||||
doTest(func() {
|
doTest(func() {
|
||||||
resp := httpRequestCheckOk[*download.ResolveResult](http.MethodPost, "/api/v1/resolve", taskReq)
|
resp := httpRequestCheckOk[*download.ResolveResult](http.MethodPost, "/api/v1/resolve", taskReq)
|
||||||
@@ -125,6 +137,21 @@ func TestCreateDirectTask(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCreateDirectTaskBatch(t *testing.T) {
|
||||||
|
doTest(func() {
|
||||||
|
reqs := make([]*base.Request, 0)
|
||||||
|
for i := 0; i < 6; i++ {
|
||||||
|
reqs = append(reqs, createReq.Req)
|
||||||
|
}
|
||||||
|
taskIds := httpRequestCheckOk[[]string](http.MethodPost, "/api/v1/tasks/batch", &model.CreateTaskBatch{
|
||||||
|
Reqs: reqs,
|
||||||
|
})
|
||||||
|
if len(taskIds) != len(reqs) {
|
||||||
|
t.Errorf("CreateDirectTaskBatch() got = %v, want %v", len(taskIds), len(reqs))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestCreateDirectTaskWithResoled(t *testing.T) {
|
func TestCreateDirectTaskWithResoled(t *testing.T) {
|
||||||
doTest(func() {
|
doTest(func() {
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
@@ -324,7 +351,7 @@ func TestGetTasks(t *testing.T) {
|
|||||||
|
|
||||||
func TestGetAndPutConfig(t *testing.T) {
|
func TestGetAndPutConfig(t *testing.T) {
|
||||||
doTest(func() {
|
doTest(func() {
|
||||||
cfg := httpRequestCheckOk[*download.DownloaderStoreConfig](http.MethodGet, "/api/v1/config", nil)
|
cfg := httpRequestCheckOk[*base.DownloaderStoreConfig](http.MethodGet, "/api/v1/config", nil)
|
||||||
cfg.DownloadDir = "./download"
|
cfg.DownloadDir = "./download"
|
||||||
cfg.Extra = map[string]any{
|
cfg.Extra = map[string]any{
|
||||||
"serverConfig": &Config{
|
"serverConfig": &Config{
|
||||||
@@ -335,7 +362,7 @@ func TestGetAndPutConfig(t *testing.T) {
|
|||||||
}
|
}
|
||||||
httpRequestCheckOk[any](http.MethodPut, "/api/v1/config", cfg)
|
httpRequestCheckOk[any](http.MethodPut, "/api/v1/config", cfg)
|
||||||
|
|
||||||
newCfg := httpRequestCheckOk[*download.DownloaderStoreConfig](http.MethodGet, "/api/v1/config", nil)
|
newCfg := httpRequestCheckOk[*base.DownloaderStoreConfig](http.MethodGet, "/api/v1/config", nil)
|
||||||
if !test.JsonEqual(cfg, newCfg) {
|
if !test.JsonEqual(cfg, newCfg) {
|
||||||
t.Errorf("GetAndPutConfig() got = %v, want %v", test.ToJson(newCfg), test.ToJson(cfg))
|
t.Errorf("GetAndPutConfig() got = %v, want %v", test.ToJson(newCfg), test.ToJson(cfg))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package util
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -51,3 +52,14 @@ func BuildProxyUrl(scheme, host, usr, pwd string) *url.URL {
|
|||||||
Host: host,
|
Host: host,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ProxyUrlToHandler gets the proxy handler from the proxy url.
|
||||||
|
func ProxyUrlToHandler(proxyUrl *url.URL) func(*http.Request) (*url.URL, error) {
|
||||||
|
if proxyUrl == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if proxyUrl.Scheme == "system" {
|
||||||
|
return http.ProxyFromEnvironment
|
||||||
|
}
|
||||||
|
return http.ProxyURL(proxyUrl)
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
package="com.gopeed">
|
package="com.gopeed">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name="${applicationName}"
|
android:name="${applicationName}"
|
||||||
@@ -71,6 +72,13 @@
|
|||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.SEND" />
|
||||||
|
<action android:name="android.intent.action.SEND_MULTIPLE" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<data android:mimeType="*/*" />
|
||||||
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<!-- Add android:stopWithTask option only when necessary. -->
|
<!-- Add android:stopWithTask option only when necessary. -->
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
||||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
||||||
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
||||||
|
B44F54F47E581A39546303C9 /* libresolv.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 029EF293EAA92EB3D18BBF19 /* libresolv.tbd */; };
|
||||||
D081C25C294826C0006EB10B /* libc++.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = D081C25B294826C0006EB10B /* libc++.tbd */; };
|
D081C25C294826C0006EB10B /* libc++.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = D081C25B294826C0006EB10B /* libc++.tbd */; };
|
||||||
D0E623A929482D160001185E /* Libgopeed.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0E623A729482D030001185E /* Libgopeed.xcframework */; };
|
D0E623A929482D160001185E /* Libgopeed.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0E623A729482D030001185E /* Libgopeed.xcframework */; };
|
||||||
D0E623AA29482D160001185E /* Libgopeed.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D0E623A729482D030001185E /* Libgopeed.xcframework */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
D0E623AA29482D160001185E /* Libgopeed.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D0E623A729482D030001185E /* Libgopeed.xcframework */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||||
@@ -34,6 +35,7 @@
|
|||||||
/* End PBXCopyFilesBuildPhase section */
|
/* End PBXCopyFilesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
|
029EF293EAA92EB3D18BBF19 /* libresolv.tbd */ = {isa = PBXFileReference; includeInIndex = 1; name = libresolv.tbd; path = usr/lib/libresolv.tbd; sourceTree = "<group>"; };
|
||||||
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
|
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
|
||||||
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
|
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
|
||||||
25B61E78A388C87913468FD6 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
|
25B61E78A388C87913468FD6 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
|
||||||
@@ -63,6 +65,7 @@
|
|||||||
D0E623A929482D160001185E /* Libgopeed.xcframework in Frameworks */,
|
D0E623A929482D160001185E /* Libgopeed.xcframework in Frameworks */,
|
||||||
D081C25C294826C0006EB10B /* libc++.tbd in Frameworks */,
|
D081C25C294826C0006EB10B /* libc++.tbd in Frameworks */,
|
||||||
DCA5B0FA95A8007ADEDACA1B /* Pods_Runner.framework in Frameworks */,
|
DCA5B0FA95A8007ADEDACA1B /* Pods_Runner.framework in Frameworks */,
|
||||||
|
B44F54F47E581A39546303C9 /* libresolv.tbd in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@@ -130,6 +133,7 @@
|
|||||||
D0E623A729482D030001185E /* Libgopeed.xcframework */,
|
D0E623A729482D030001185E /* Libgopeed.xcframework */,
|
||||||
D081C25B294826C0006EB10B /* libc++.tbd */,
|
D081C25B294826C0006EB10B /* libc++.tbd */,
|
||||||
70BC8D37FAC641C7F20125E8 /* Pods_Runner.framework */,
|
70BC8D37FAC641C7F20125E8 /* Pods_Runner.framework */,
|
||||||
|
029EF293EAA92EB3D18BBF19 /* libresolv.tbd */,
|
||||||
);
|
);
|
||||||
name = Frameworks;
|
name = Frameworks;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 714 B After Width: | Height: | Size: 890 B |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.9 KiB |