mirror of
https://github.com/GopeedLab/gopeed.git
synced 2026-11-11 10:54:08 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f986f6253d | ||
|
|
2e1b621250 | ||
|
|
d50947b2e6 | ||
|
|
520a72415c | ||
|
|
a372b67edb | ||
|
|
2d86b5c132 | ||
|
|
63b3144633 | ||
|
|
1b0178fed9 | ||
|
|
83febe0a00 | ||
|
|
987580d400 | ||
|
|
0919936050 | ||
|
|
acc7ed8f6b | ||
|
|
ccb3267fba | ||
|
|
1249493a8a | ||
|
|
107141bbe7 | ||
|
|
b329fee107 | ||
|
|
3ce95f21fd | ||
|
|
f54ed5df5a | ||
|
|
727b7edb84 | ||
|
|
f5891ee03d | ||
|
|
724276ac69 | ||
|
|
a3f559c8e5 | ||
|
|
edd189a8a8 | ||
|
|
c970378fd7 | ||
|
|
8b61343aef |
No files matched your search
+103
-26
@@ -1,42 +1,119 @@
|
|||||||
name-template: 'v$NEXT_PATCH_VERSION 🌈'
|
name-template: "v$NEXT_PATCH_VERSION 🌈"
|
||||||
tag-template: 'v$NEXT_PATCH_VERSION'
|
tag-template: "v$NEXT_PATCH_VERSION"
|
||||||
categories:
|
categories:
|
||||||
- title: '🆕 Features'
|
- title: "🆕 Features"
|
||||||
labels:
|
labels:
|
||||||
- 'feat'
|
- "feat"
|
||||||
- 'feature'
|
- "feature"
|
||||||
- 'enhancement'
|
- "enhancement"
|
||||||
- title: '🐛 Bug Fixes'
|
- title: "🐛 Bug Fixes"
|
||||||
labels:
|
labels:
|
||||||
- 'fix'
|
- "fix"
|
||||||
- 'bugfix'
|
- "bugfix"
|
||||||
- 'bug'
|
- "bug"
|
||||||
- title: '🔧 Performance Improvements'
|
- title: "🔧 Performance Improvements"
|
||||||
labels:
|
labels:
|
||||||
- 'perf'
|
- "perf"
|
||||||
- title: '🧪 Tests'
|
- title: "🧪 Tests"
|
||||||
label: 'test'
|
label: "test"
|
||||||
- title: '🧰 Maintenance'
|
- title: "🧰 Maintenance"
|
||||||
label: 'chore'
|
label: "chore"
|
||||||
- title: '📖 Document'
|
- title: "📖 Document"
|
||||||
label: 'docs'
|
label: "docs"
|
||||||
- title: '🚀 CI/CD'
|
- title: "🚀 CI/CD"
|
||||||
label: 'ci'
|
label: "ci"
|
||||||
- title: '🌎 Internationalization'
|
- title: "🌎 Internationalization"
|
||||||
label: 'translation'
|
label: "translation"
|
||||||
change-template: '- $TITLE @$AUTHOR (#$NUMBER)'
|
change-template: "- $TITLE @$AUTHOR (#$NUMBER)"
|
||||||
version-resolver:
|
version-resolver:
|
||||||
major:
|
major:
|
||||||
labels:
|
labels:
|
||||||
- 'major'
|
- "major"
|
||||||
minor:
|
minor:
|
||||||
labels:
|
labels:
|
||||||
- 'minor'
|
- "minor"
|
||||||
patch:
|
patch:
|
||||||
labels:
|
labels:
|
||||||
- 'patch'
|
- "patch"
|
||||||
default: patch
|
default: patch
|
||||||
template: |
|
template: |
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th colspan="2">Windows</th>
|
||||||
|
<th>MacOS</th>
|
||||||
|
<th colspan="4">Linux</th>
|
||||||
|
<th>Android</th>
|
||||||
|
<th>iOS</th>
|
||||||
|
<th>Docker</th>
|
||||||
|
<th colspan="2">Qnap</th>
|
||||||
|
<th colspan="8">Web</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>EXE</code></td>
|
||||||
|
<td><code>Portable</code></td>
|
||||||
|
<td><code>DMG</code></td>
|
||||||
|
<td><code>Flathub</code></td>
|
||||||
|
<td><code>SNAP</code></td>
|
||||||
|
<td><code>DEB</code></td>
|
||||||
|
<td><code>AppImage</code></td>
|
||||||
|
<td><code>APK</code></td>
|
||||||
|
<td><code>IPA</code></td>
|
||||||
|
<td>-</td>
|
||||||
|
<td colspan="2"><code>QPKG</code></td>
|
||||||
|
<td colspan="3"><code>Windows</code></td>
|
||||||
|
<td colspan="2"><code>MacOS</code></td>
|
||||||
|
<td colspan="3"><code>Linux</code></td>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>amd64</td>
|
||||||
|
<td>amd64</td>
|
||||||
|
<td>-</td>
|
||||||
|
<td>amd64</td>
|
||||||
|
<td>amd64</td>
|
||||||
|
<td>amd64</td>
|
||||||
|
<td>amd64</td>
|
||||||
|
<td>-</td>
|
||||||
|
<td>-</td>
|
||||||
|
<td>-</td>
|
||||||
|
<td>amd64</td>
|
||||||
|
<td>arm64</td>
|
||||||
|
<td>amd64</td>
|
||||||
|
<td>amd64</td>
|
||||||
|
<td>386</td>
|
||||||
|
<td>amd64</td>
|
||||||
|
<td>amd64</td>
|
||||||
|
<td>amd64</td>
|
||||||
|
<td>amd64</td>
|
||||||
|
<td>386</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64.zip">Link</a></td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64-portable.zip">Link</a></td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-macos.dmg">Link</a></td>
|
||||||
|
<td><a href="https://flathub.org/apps/com.gopeed.Gopeed">Link</a></td>
|
||||||
|
<td><a href="https://snapcraft.io/gopeed">Link</a></td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-amd64.deb">Link</a></td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-x86_64.AppImage">Link</a></td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android.apk">Link</a></td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-ios.ipa">Link</a></td>
|
||||||
|
<td><a href="https://hub.docker.com/r/liwei2633/gopeed">Link</a></td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=gopeed-$version-qnap-amd64.qpkg">Link</a></td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=gopeed-$version-qnap-arm64.qpkg">Link</a></td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-windows-amd64.zip">Link</a></td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-windows-arm64.zip">Link</a></td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-windows-386.zip">Link</a></td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-macos-amd64.zip">Link</a></td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-macos-arm64.zip">Link</a></td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-linux-amd64.zip">Link</a></td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-linux-arm64.zip">Link</a></td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-linux-386.zip">Link</a></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
# Release notes
|
# Release notes
|
||||||
|
|
||||||
$CHANGES
|
$CHANGES
|
||||||
|
|||||||
+125
-60
@@ -4,17 +4,17 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
platform:
|
platform:
|
||||||
description: 'Build platform'
|
description: "Build platform"
|
||||||
required: true
|
required: true
|
||||||
default: 'all'
|
default: "all"
|
||||||
test:
|
test:
|
||||||
description: 'Test mode'
|
description: "Test mode"
|
||||||
required: true
|
required: true
|
||||||
default: "false"
|
default: "false"
|
||||||
|
|
||||||
env:
|
env:
|
||||||
GO_VERSION: "1.23"
|
GO_VERSION: "1.23"
|
||||||
FLUTTER_VERSION: "3.16.8"
|
FLUTTER_VERSION: "3.24.5"
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
@@ -35,7 +35,7 @@ jobs:
|
|||||||
build-windows:
|
build-windows:
|
||||||
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'windows' }}
|
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'windows' }}
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
needs: [ get-release ]
|
needs: [get-release]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-go@v4
|
- uses: actions/setup-go@v4
|
||||||
@@ -52,6 +52,7 @@ jobs:
|
|||||||
mv ChineseSimplified.isl "C:\Program Files (x86)\Inno Setup 6\Languages\"
|
mv ChineseSimplified.isl "C:\Program Files (x86)\Inno Setup 6\Languages\"
|
||||||
|
|
||||||
go build -tags nosqlite -ldflags="-w -s -X github.com/GopeedLab/gopeed/pkg/base.Version=$env:VERSION" -buildmode=c-shared -o ui/flutter/windows/libgopeed.dll github.com/GopeedLab/gopeed/bind/desktop
|
go build -tags nosqlite -ldflags="-w -s -X github.com/GopeedLab/gopeed/pkg/base.Version=$env:VERSION" -buildmode=c-shared -o ui/flutter/windows/libgopeed.dll github.com/GopeedLab/gopeed/bind/desktop
|
||||||
|
go build -ldflags="-w -s" -o ui/flutter/assets/host/host.exe github.com/GopeedLab/gopeed/cmd/host
|
||||||
cd ui/flutter
|
cd ui/flutter
|
||||||
$TAG = "v$env:VERSION"
|
$TAG = "v$env:VERSION"
|
||||||
flutter build windows
|
flutter build windows
|
||||||
@@ -97,11 +98,13 @@ jobs:
|
|||||||
PrivilegesRequired=lowest
|
PrivilegesRequired=lowest
|
||||||
OutputBaseFilename=gopeed
|
OutputBaseFilename=gopeed
|
||||||
SetupIconFile=..\..\assets\icon\icon.ico
|
SetupIconFile=..\..\assets\icon\icon.ico
|
||||||
|
UninstallDisplayIcon={app}\{#MyAppExeName}
|
||||||
Compression=lzma
|
Compression=lzma
|
||||||
SolidCompression=yes
|
SolidCompression=yes
|
||||||
WizardStyle=modern
|
WizardStyle=modern
|
||||||
LanguageDetectionMethod=uilanguage
|
LanguageDetectionMethod=uilanguage
|
||||||
ShowLanguageDialog=yes
|
ShowLanguageDialog=yes
|
||||||
|
CloseApplications=force
|
||||||
|
|
||||||
[Languages]
|
[Languages]
|
||||||
Name: "english"; MessagesFile: "compiler:Default.isl"
|
Name: "english"; MessagesFile: "compiler:Default.isl"
|
||||||
@@ -111,7 +114,7 @@ jobs:
|
|||||||
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
|
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
|
||||||
|
|
||||||
[Files]
|
[Files]
|
||||||
Source: ".\x64\runner\Release\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs
|
Source: ".\x64\runner\Release\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs;
|
||||||
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
|
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
|
||||||
|
|
||||||
[UninstallDelete]
|
[UninstallDelete]
|
||||||
@@ -137,7 +140,7 @@ jobs:
|
|||||||
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: macos-latest
|
runs-on: macos-latest
|
||||||
needs: [ get-release ]
|
needs: [get-release]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-go@v4
|
- uses: actions/setup-go@v4
|
||||||
@@ -148,14 +151,17 @@ jobs:
|
|||||||
VERSION: ${{ needs.get-release.outputs.version }}
|
VERSION: ${{ needs.get-release.outputs.version }}
|
||||||
run: |
|
run: |
|
||||||
go build -tags nosqlite -ldflags="-w -s -X github.com/GopeedLab/gopeed/pkg/base.Version=$VERSION" -buildmode=c-shared -o libgopeed.dylib github.com/GopeedLab/gopeed/bind/desktop
|
go build -tags nosqlite -ldflags="-w -s -X github.com/GopeedLab/gopeed/pkg/base.Version=$VERSION" -buildmode=c-shared -o libgopeed.dylib github.com/GopeedLab/gopeed/bind/desktop
|
||||||
- uses: actions/upload-artifact@v3
|
go build -ldflags="-w -s" github.com/GopeedLab/gopeed/cmd/host
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: macos-arm64-lib
|
name: macos-arm64-lib
|
||||||
path: libgopeed.dylib
|
path: |
|
||||||
|
libgopeed.dylib
|
||||||
|
host
|
||||||
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-13
|
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
|
||||||
- uses: actions/setup-go@v4
|
- uses: actions/setup-go@v4
|
||||||
@@ -171,7 +177,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
python3 -m pip install setuptools --break-system-packages
|
python3 -m pip install setuptools --break-system-packages
|
||||||
npm install -g appdmg
|
npm install -g appdmg
|
||||||
- uses: actions/download-artifact@v3
|
- uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: macos-arm64-lib
|
name: macos-arm64-lib
|
||||||
path: ui/flutter/lib-arm64
|
path: ui/flutter/lib-arm64
|
||||||
@@ -183,12 +189,18 @@ jobs:
|
|||||||
|
|
||||||
# amd64 lib
|
# amd64 lib
|
||||||
go build -tags nosqlite -ldflags="-w -s -X github.com/GopeedLab/gopeed/pkg/base.Version=$VERSION" -buildmode=c-shared -o ui/flutter/lib-amd64/libgopeed.dylib github.com/GopeedLab/gopeed/bind/desktop
|
go build -tags nosqlite -ldflags="-w -s -X github.com/GopeedLab/gopeed/pkg/base.Version=$VERSION" -buildmode=c-shared -o ui/flutter/lib-amd64/libgopeed.dylib github.com/GopeedLab/gopeed/bind/desktop
|
||||||
|
go build -ldflags="-w -s" -o ui/flutter/lib-amd64/host github.com/GopeedLab/gopeed/cmd/host
|
||||||
# universal binary
|
# universal binary
|
||||||
mkdir -p ui/flutter/macos/Frameworks
|
mkdir -p ui/flutter/macos/Frameworks
|
||||||
cp ui/flutter/lib-amd64/libgopeed.dylib ui/flutter/macos/Frameworks/amd64-lib
|
cp ui/flutter/lib-amd64/libgopeed.dylib ui/flutter/macos/Frameworks/amd64-lib
|
||||||
cp ui/flutter/lib-arm64/libgopeed.dylib ui/flutter/macos/Frameworks/arm64-lib
|
cp ui/flutter/lib-arm64/libgopeed.dylib ui/flutter/macos/Frameworks/arm64-lib
|
||||||
|
cp ui/flutter/lib-amd64/host ui/flutter/macos/Frameworks/amd64-host
|
||||||
|
cp ui/flutter/lib-arm64/host ui/flutter/macos/Frameworks/arm64-host
|
||||||
cd ui/flutter/macos/Frameworks
|
cd ui/flutter/macos/Frameworks
|
||||||
lipo -create -output libgopeed.dylib amd64-lib arm64-lib
|
lipo -create -output libgopeed.dylib amd64-lib arm64-lib
|
||||||
|
lipo -create -output host amd64-host arm64-host
|
||||||
|
rm -rf amd64-lib arm64-lib amd64-host arm64-host
|
||||||
|
mv host ../../assets/host/host
|
||||||
|
|
||||||
cd $PROJECT_DIR/ui/flutter
|
cd $PROJECT_DIR/ui/flutter
|
||||||
flutter build macos
|
flutter build macos
|
||||||
@@ -213,8 +225,8 @@ 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-latest
|
runs-on: ubuntu-22.04
|
||||||
needs: [ get-release ]
|
needs: [get-release]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-go@v4
|
- uses: actions/setup-go@v4
|
||||||
@@ -237,6 +249,7 @@ jobs:
|
|||||||
VERSION: ${{ needs.get-release.outputs.version }}
|
VERSION: ${{ needs.get-release.outputs.version }}
|
||||||
run: |
|
run: |
|
||||||
go build -tags nosqlite -ldflags="-w -s -X github.com/GopeedLab/gopeed/pkg/base.Version=$VERSION" -buildmode=c-shared -o ui/flutter/linux/bundle/lib/libgopeed.so github.com/GopeedLab/gopeed/bind/desktop
|
go build -tags nosqlite -ldflags="-w -s -X github.com/GopeedLab/gopeed/pkg/base.Version=$VERSION" -buildmode=c-shared -o ui/flutter/linux/bundle/lib/libgopeed.so github.com/GopeedLab/gopeed/bind/desktop
|
||||||
|
go build -ldflags="-w -s" -o ui/flutter/assets/host/host github.com/GopeedLab/gopeed/cmd/host
|
||||||
cd ui/flutter
|
cd ui/flutter
|
||||||
dart pub global activate -sgit https://github.com/GopeedLab/flutter_distributor.git --git-path packages/flutter_distributor
|
dart pub global activate -sgit https://github.com/GopeedLab/flutter_distributor.git --git-path packages/flutter_distributor
|
||||||
flutter_distributor package --platform linux --targets appimage,deb
|
flutter_distributor package --platform linux --targets appimage,deb
|
||||||
@@ -252,7 +265,7 @@ jobs:
|
|||||||
build-snap:
|
build-snap:
|
||||||
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'snap' }}
|
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'snap' }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [ get-release ]
|
needs: [get-release]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-go@v4
|
- uses: actions/setup-go@v4
|
||||||
@@ -267,6 +280,7 @@ jobs:
|
|||||||
VERSION: ${{ needs.get-release.outputs.version }}
|
VERSION: ${{ needs.get-release.outputs.version }}
|
||||||
run: |
|
run: |
|
||||||
go build -tags nosqlite -ldflags="-w -s -X github.com/GopeedLab/gopeed/pkg/base.Version=$VERSION" -buildmode=c-shared -o ui/flutter/linux/bundle/lib/libgopeed.so github.com/GopeedLab/gopeed/bind/desktop
|
go build -tags nosqlite -ldflags="-w -s -X github.com/GopeedLab/gopeed/pkg/base.Version=$VERSION" -buildmode=c-shared -o ui/flutter/linux/bundle/lib/libgopeed.so github.com/GopeedLab/gopeed/bind/desktop
|
||||||
|
go build -ldflags="-w -s" -o ui/flutter/assets/host/host github.com/GopeedLab/gopeed/cmd/host
|
||||||
cd ui/flutter
|
cd ui/flutter
|
||||||
|
|
||||||
sudo snap install snapcraft --classic
|
sudo snap install snapcraft --classic
|
||||||
@@ -280,11 +294,12 @@ jobs:
|
|||||||
description: High speed downloader that supports all platforms.
|
description: High speed downloader that supports all platforms.
|
||||||
|
|
||||||
confinement: strict
|
confinement: strict
|
||||||
base: core22
|
base: core24
|
||||||
grade: stable
|
grade: stable
|
||||||
|
|
||||||
architectures:
|
platforms:
|
||||||
- build-on: amd64
|
amd64:
|
||||||
|
build-on: amd64
|
||||||
build-for: amd64
|
build-for: amd64
|
||||||
|
|
||||||
slots:
|
slots:
|
||||||
@@ -295,13 +310,14 @@ jobs:
|
|||||||
|
|
||||||
apps:
|
apps:
|
||||||
gopeed:
|
gopeed:
|
||||||
|
command-chain:
|
||||||
|
- bin/gpu-2404-wrapper
|
||||||
command: bin/gopeed
|
command: bin/gopeed
|
||||||
extensions: [gnome]
|
extensions: [gnome]
|
||||||
|
common-id: com.gopeed.Gopeed
|
||||||
plugs:
|
plugs:
|
||||||
- network
|
- network
|
||||||
- home
|
- home
|
||||||
slots:
|
|
||||||
- dbus-gopeed
|
|
||||||
parts:
|
parts:
|
||||||
flutter-git:
|
flutter-git:
|
||||||
source: https://github.com/flutter/flutter.git
|
source: https://github.com/flutter/flutter.git
|
||||||
@@ -344,6 +360,29 @@ jobs:
|
|||||||
flutter build linux --release -v
|
flutter build linux --release -v
|
||||||
mkdir -p \$CRAFT_PART_INSTALL/bin/
|
mkdir -p \$CRAFT_PART_INSTALL/bin/
|
||||||
cp -r build/linux/*/release/bundle/* \$CRAFT_PART_INSTALL/bin/
|
cp -r build/linux/*/release/bundle/* \$CRAFT_PART_INSTALL/bin/
|
||||||
|
gpu-2404:
|
||||||
|
after: [gopeed]
|
||||||
|
source: https://github.com/canonical/gpu-snap.git
|
||||||
|
plugin: dump
|
||||||
|
override-prime: |
|
||||||
|
craftctl default
|
||||||
|
\${CRAFT_PART_SRC}/bin/gpu-2404-cleanup mesa-2404
|
||||||
|
prime:
|
||||||
|
- bin/gpu-2404-wrapper
|
||||||
|
|
||||||
|
plugs:
|
||||||
|
gpu-2404:
|
||||||
|
interface: content
|
||||||
|
target: \$SNAP/gpu-2404
|
||||||
|
default-provider: mesa-2404
|
||||||
|
|
||||||
|
layout:
|
||||||
|
/usr/share/libdrm:
|
||||||
|
bind: \$SNAP/gpu-2404/libdrm
|
||||||
|
/usr/share/drirc.d:
|
||||||
|
symlink: \$SNAP/gpu-2404/drirc.d
|
||||||
|
/usr/share/X11/XErrorDB:
|
||||||
|
symlink: \$SNAP/gpu-2404/X11/XErrorDB
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
cp linux/assets/com.gopeed.Gopeed.desktop snap/gui/gopeed.desktop
|
cp linux/assets/com.gopeed.Gopeed.desktop snap/gui/gopeed.desktop
|
||||||
@@ -357,7 +396,7 @@ jobs:
|
|||||||
build-android:
|
build-android:
|
||||||
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'android' }}
|
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'android' }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [ get-release ]
|
needs: [get-release]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-go@v4
|
- uses: actions/setup-go@v4
|
||||||
@@ -366,7 +405,7 @@ jobs:
|
|||||||
- uses: actions/setup-java@v3
|
- uses: actions/setup-java@v3
|
||||||
with:
|
with:
|
||||||
distribution: "zulu"
|
distribution: "zulu"
|
||||||
java-version: "11"
|
java-version: "17"
|
||||||
- uses: subosito/flutter-action@v2
|
- uses: subosito/flutter-action@v2
|
||||||
with:
|
with:
|
||||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||||
@@ -395,7 +434,7 @@ jobs:
|
|||||||
build-ios:
|
build-ios:
|
||||||
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'ios' }}
|
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'ios' }}
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
needs: [ get-release ]
|
needs: [get-release]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-go@v4
|
- uses: actions/setup-go@v4
|
||||||
@@ -426,9 +465,9 @@ jobs:
|
|||||||
asset_path: ui/flutter/dist/*
|
asset_path: ui/flutter/dist/*
|
||||||
overwrite: true
|
overwrite: true
|
||||||
build-web:
|
build-web:
|
||||||
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'web' }}
|
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'web' || github.event.inputs.platform == 'qnap' }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [ get-release ]
|
needs: [get-release]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-go@v4
|
- uses: actions/setup-go@v4
|
||||||
@@ -443,7 +482,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
VERSION=${VERSION:-$(date +%y%m%d%H%M)} # Use current time as version if not specified
|
VERSION=${VERSION:-$(date +%y%m%d%H%M)} # Use current time as version if not specified
|
||||||
cd ui/flutter
|
cd ui/flutter
|
||||||
flutter build web --web-renderer html
|
flutter build web --no-web-resources-cdn
|
||||||
cd ../../
|
cd ../../
|
||||||
cp -r ui/flutter/build/web cmd/web/dist
|
cp -r ui/flutter/build/web cmd/web/dist
|
||||||
mkdir -p dist/zip
|
mkdir -p dist/zip
|
||||||
@@ -469,49 +508,75 @@ jobs:
|
|||||||
|| true
|
|| true
|
||||||
done
|
done
|
||||||
done
|
done
|
||||||
sudo apt update -y
|
- uses: actions/upload-artifact@v4
|
||||||
sudo apt install -y pv python2 bsdmainutils
|
with:
|
||||||
wget -O qdk2_0.32.bionic_amd64.deb "https://github.com/qnap-dev/qdk2/releases/download/v0.32/qdk2_0.32.bionic_amd64.deb"
|
name: web-dist
|
||||||
dpkg -X qdk2_0.32.bionic_amd64.deb qdk2 # Direct installs will fail due to missing dependencies!
|
path: dist/zip/
|
||||||
[[ -d qdk2 ]] || exit 1
|
|
||||||
export PATH=$(pwd)/qdk2/usr/bin:$(pwd)/qdk2/usr/share/qdk2/QDK/bin:${PATH}
|
|
||||||
wget -O Gopeed.template.tar.gz "https://github.com/GopeedLab/QpkgBuild/raw/refs/heads/master/template/Gopeed.template.tar.gz"
|
|
||||||
tar -zxf Gopeed.template.tar.gz
|
|
||||||
[[ -d Gopeed ]] || exit 1
|
|
||||||
goos=linux
|
|
||||||
goarch_arr=(amd64 arm64)
|
|
||||||
for goarch in "${goarch_arr[@]}"; do
|
|
||||||
qarch=x86_64
|
|
||||||
[[ "${goarch}" == "arm64" ]] && qarch=arm_64
|
|
||||||
name=gopeed-web-v${VERSION}-${goos}-${goarch}
|
|
||||||
cp dist/${name}/${name}/* Gopeed/${qarch}/
|
|
||||||
done
|
|
||||||
cd Gopeed
|
|
||||||
sed -i -e 's/__QPKG_VER__/${VERSION}/g' qpkg.cfg
|
|
||||||
qbuild || exit 1
|
|
||||||
for f in build/*; do echo ">>> build=[${f}]"; done
|
|
||||||
goos=qnap
|
|
||||||
goarch_arr=(amd64 arm64)
|
|
||||||
for goarch in "${goarch_arr[@]}"; do
|
|
||||||
qarch=x86_64
|
|
||||||
[[ "${goarch}" == "arm64" ]] && qarch=arm_64
|
|
||||||
sname=Gopeed_${VERSION}_${qarch}.qpkg
|
|
||||||
dname=gopeed-${goos}-v${VERSION}-${goarch}.qpkg
|
|
||||||
echo "sname=${sname}, dname=${dname}"
|
|
||||||
[[ -f build/${sname} ]] && cp -ra build/${sname} ../dist/zip/${dname}
|
|
||||||
done
|
|
||||||
for f in ../dist/zip/*; do echo ">>> dist=[${f}]"; done
|
|
||||||
cd ..
|
|
||||||
- name: Upload
|
- name: Upload
|
||||||
uses: shogo82148/actions-upload-release-asset@v1
|
uses: shogo82148/actions-upload-release-asset@v1
|
||||||
with:
|
with:
|
||||||
upload_url: ${{ needs.get-release.outputs.upload_url }}
|
upload_url: ${{ needs.get-release.outputs.upload_url }}
|
||||||
asset_path: dist/zip/*
|
asset_path: dist/zip/*
|
||||||
overwrite: true
|
overwrite: true
|
||||||
|
build-qnap:
|
||||||
|
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'qnap' }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [get-release, build-web]
|
||||||
|
steps:
|
||||||
|
- uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: "3.8.18"
|
||||||
|
- uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: web-dist
|
||||||
|
path: dist/zip/
|
||||||
|
- name: Build
|
||||||
|
env:
|
||||||
|
VERSION: ${{ needs.get-release.outputs.version }}
|
||||||
|
run: |
|
||||||
|
sudo apt update -y
|
||||||
|
sudo apt install -y pv bsdmainutils
|
||||||
|
wget -O qdk2_0.32.bionic_amd64.deb "https://github.com/qnap-dev/qdk2/releases/download/v0.32/qdk2_0.32.bionic_amd64.deb"
|
||||||
|
dpkg -X qdk2_0.32.bionic_amd64.deb qdk2 # Direct installs will fail due to missing dependencies!
|
||||||
|
[[ -d qdk2 ]] || exit 1
|
||||||
|
|
||||||
|
export PATH=$(pwd)/qdk2/usr/bin:$(pwd)/qdk2/usr/share/qdk2/QDK/bin:${PATH}
|
||||||
|
wget -O Gopeed.template.tar.gz "https://github.com/GopeedLab/QpkgBuild/raw/refs/heads/master/template/Gopeed.template.tar.gz"
|
||||||
|
tar -zxf Gopeed.template.tar.gz
|
||||||
|
[[ -d Gopeed ]] || exit 1
|
||||||
|
|
||||||
|
goos=linux
|
||||||
|
goarch_arr=(amd64 arm64)
|
||||||
|
for goarch in "${goarch_arr[@]}"; do
|
||||||
|
qarch=x86_64
|
||||||
|
[[ "${goarch}" == "arm64" ]] && qarch=arm_64
|
||||||
|
name=gopeed-web-v${VERSION}-${goos}-${goarch}
|
||||||
|
unzip dist/zip/${name}.zip -d dist/${name}
|
||||||
|
cp dist/${name}/${name}/* Gopeed/${qarch}/
|
||||||
|
done
|
||||||
|
cd Gopeed
|
||||||
|
sed -i -e 's/__QPKG_VER__/${VERSION}/g' qpkg.cfg
|
||||||
|
qbuild || exit 1
|
||||||
|
|
||||||
|
mkdir -p ../dist/qnap
|
||||||
|
goos=qnap
|
||||||
|
for goarch in "${goarch_arr[@]}"; do
|
||||||
|
qarch=x86_64
|
||||||
|
[[ "${goarch}" == "arm64" ]] && qarch=arm_64
|
||||||
|
sname=Gopeed_${VERSION}_${qarch}.qpkg
|
||||||
|
dname=gopeed-v${VERSION}-${goos}-${goarch}.qpkg
|
||||||
|
[[ -f build/${sname} ]] && cp -ra build/${sname} ../dist/qnap/${dname}
|
||||||
|
done
|
||||||
|
- name: Upload
|
||||||
|
uses: shogo82148/actions-upload-release-asset@v1
|
||||||
|
with:
|
||||||
|
upload_url: ${{ needs.get-release.outputs.upload_url }}
|
||||||
|
asset_path: dist/qnap/*
|
||||||
|
overwrite: true
|
||||||
build-docker:
|
build-docker:
|
||||||
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'docker' }}
|
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'docker' }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [ get-release ]
|
needs: [get-release]
|
||||||
steps:
|
steps:
|
||||||
- name: Remove unnecessary files
|
- name: Remove unnecessary files
|
||||||
run: |
|
run: |
|
||||||
@@ -533,7 +598,7 @@ jobs:
|
|||||||
- name: Build flutter web
|
- name: Build flutter web
|
||||||
run: |
|
run: |
|
||||||
cd ui/flutter
|
cd ui/flutter
|
||||||
flutter build web --web-renderer html
|
flutter build web --no-web-resources-cdn
|
||||||
cd ../../
|
cd ../../
|
||||||
rm -rf cmd/web/dist
|
rm -rf cmd/web/dist
|
||||||
cp -r ui/flutter/build/web cmd/web/dist
|
cp -r ui/flutter/build/web cmd/web/dist
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ on:
|
|||||||
|
|
||||||
env:
|
env:
|
||||||
GO_VERSION: "1.23"
|
GO_VERSION: "1.23"
|
||||||
FLUTTER_VERSION: "3.16.8"
|
FLUTTER_VERSION: "3.24.5"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
# lint:
|
# lint:
|
||||||
@@ -63,7 +63,7 @@ jobs:
|
|||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ windows-latest, macos-latest, ubuntu-latest ]
|
os: [ windows-latest, macos-latest, ubuntu-22.04 ]
|
||||||
needs: [ test ]
|
needs: [ test ]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
@@ -73,7 +73,7 @@ jobs:
|
|||||||
- uses: subosito/flutter-action@v2
|
- uses: subosito/flutter-action@v2
|
||||||
with:
|
with:
|
||||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||||
- if: runner.os == 'windows'
|
- if: runner.os == 'Windows'
|
||||||
run: |
|
run: |
|
||||||
go build -tags nosqlite -ldflags="-w -s" -buildmode=c-shared -o ui/flutter/windows/libgopeed.dll github.com/GopeedLab/gopeed/bind/desktop
|
go build -tags nosqlite -ldflags="-w -s" -buildmode=c-shared -o ui/flutter/windows/libgopeed.dll github.com/GopeedLab/gopeed/bind/desktop
|
||||||
cd ui/flutter
|
cd ui/flutter
|
||||||
@@ -140,7 +140,7 @@ jobs:
|
|||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
cd ui/flutter
|
cd ui/flutter
|
||||||
flutter build web --web-renderer html
|
flutter build web --no-web-resources-cdn
|
||||||
cd ../../
|
cd ../../
|
||||||
rm -rf cmd/web/dist
|
rm -rf cmd/web/dist
|
||||||
cp -r ui/flutter/build/web cmd/web/dist
|
cp -r ui/flutter/build/web cmd/web/dist
|
||||||
@@ -160,7 +160,7 @@ jobs:
|
|||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
cd ui/flutter
|
cd ui/flutter
|
||||||
flutter build web --web-renderer html
|
flutter build web --no-web-resources-cdn
|
||||||
cd ../../
|
cd ../../
|
||||||
rm -rf cmd/web/dist
|
rm -rf cmd/web/dist
|
||||||
cp -r ui/flutter/build/web cmd/web/dist
|
cp -r ui/flutter/build/web cmd/web/dist
|
||||||
|
|||||||
@@ -22,66 +22,80 @@ Visit ✈ [Official Website](https://gopeed.com) | 📖 [Official Docs](https://
|
|||||||
## Download
|
## Download
|
||||||
|
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Platform</th>
|
<th colspan="2">Windows</th>
|
||||||
<th>Package Type</th>
|
<th>MacOS</th>
|
||||||
<th>Download Link</th>
|
<th colspan="4">Linux</th>
|
||||||
</tr>
|
<th>Android</th>
|
||||||
</thead>
|
<th>iOS</th>
|
||||||
<tbody>
|
<th>Docker</th>
|
||||||
<tr>
|
<th colspan="2">Qnap</th>
|
||||||
<td rowspan=2>Windows</td>
|
<th colspan="8">Web</th>
|
||||||
<td><code>EXE Installer</code></td>
|
</tr>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64.zip">Link</a></td>
|
<tr>
|
||||||
</tr>
|
<td><code>EXE</code></td>
|
||||||
<tr>
|
<td><code>Portable</code></td>
|
||||||
<td><code>Portable ZIP</code></td>
|
<td><code>DMG</code></td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64-portable.zip">Link</a></td>
|
<td><code>Flathub</code></td>
|
||||||
</tr>
|
<td><code>SNAP</code></td>
|
||||||
<tr>
|
<td><code>DEB</code></td>
|
||||||
<td>MacOS</td>
|
<td><code>AppImage</code></td>
|
||||||
<td><code>DMG Installer</code></td>
|
<td><code>APK</code></td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-macos.dmg">Link</a></td>
|
<td><code>IPA</code></td>
|
||||||
</tr>
|
<td>-</td>
|
||||||
<tr>
|
<td colspan="2"><code>QPKG</code></td>
|
||||||
<td rowspan=4>Linux</td>
|
<td colspan="3"><code>Windows</code></td>
|
||||||
<td><code>Flathub</code></td>
|
<td colspan="2"><code>MacOS</code></td>
|
||||||
<td><a href="https://flathub.org/apps/com.gopeed.Gopeed">Link</a></td>
|
<td colspan="3"><code>Linux</code></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
</thead>
|
||||||
<td><code>SNAP</code></td>
|
<tbody>
|
||||||
<td><a href="https://snapcraft.io/gopeed">Link</a></td>
|
<tr>
|
||||||
</tr>
|
<td>amd64</td>
|
||||||
<tr>
|
<td>amd64</td>
|
||||||
<td><code>DEB</code></td>
|
<td>-</td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-amd64.deb">Link</a></td>
|
<td>amd64</td>
|
||||||
</tr>
|
<td>amd64</td>
|
||||||
<tr>
|
<td>amd64</td>
|
||||||
<td><code>AppImage</code></td>
|
<td>amd64</td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-x86_64.AppImage">Link</a></td>
|
<td>-</td>
|
||||||
</tr>
|
<td>-</td>
|
||||||
<tr>
|
<td>-</td>
|
||||||
<td>Android</td>
|
<td>amd64</td>
|
||||||
<td><code>APK</code></td>
|
<td>arm64</td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android.apk">Link</a></td>
|
<td>amd64</td>
|
||||||
</tr>
|
<td>amd64</td>
|
||||||
<tr>
|
<td>386</td>
|
||||||
<td>iOS</td>
|
<td>amd64</td>
|
||||||
<td><code>IPA</code></td>
|
<td>amd64</td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-ios.ipa">Link</a></td>
|
<td>amd64</td>
|
||||||
</tr>
|
<td>amd64</td>
|
||||||
<tr>
|
<td>386</td>
|
||||||
<td>Web</td>
|
</tr>
|
||||||
<td></td>
|
<tr>
|
||||||
<td><a href="https://github.com/GopeedLab/gopeed/releases/latest">Link</a></td>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64.zip">Link</a></td>
|
||||||
</tr>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64-portable.zip">Link</a></td>
|
||||||
<tr>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-macos.dmg">Link</a></td>
|
||||||
<td>Docker</td>
|
<td><a href="https://flathub.org/apps/com.gopeed.Gopeed">Link</a></td>
|
||||||
<td></td>
|
<td><a href="https://snapcraft.io/gopeed">Link</a></td>
|
||||||
<td><a href="https://hub.docker.com/r/liwei2633/gopeed">Link</a></td>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-amd64.deb">Link</a></td>
|
||||||
</tr>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-x86_64.AppImage">Link</a></td>
|
||||||
</tbody>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android.apk">Link</a></td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-ios.ipa">Link</a></td>
|
||||||
|
<td><a href="https://hub.docker.com/r/liwei2633/gopeed">Link</a></td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=gopeed-$version-qnap-amd64.qpkg">Link</a></td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=gopeed-$version-qnap-arm64.qpkg">Link</a></td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-windows-amd64.zip">Link</a></td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-windows-arm64.zip">Link</a></td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-windows-386.zip">Link</a></td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-macos-amd64.zip">Link</a></td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-macos-arm64.zip">Link</a></td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-linux-amd64.zip">Link</a></td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-linux-arm64.zip">Link</a></td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-linux-386.zip">Link</a></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
More about installation, please refer to [Installation](https://docs.gopeed.com/install.html)
|
More about installation, please refer to [Installation](https://docs.gopeed.com/install.html)
|
||||||
@@ -115,7 +129,7 @@ This project is divided into two parts, the front end uses `flutter`, the back e
|
|||||||
### Environment
|
### Environment
|
||||||
|
|
||||||
1. Golang 1.23+
|
1. Golang 1.23+
|
||||||
2. Flutter 3.16+
|
2. Flutter 3.24+
|
||||||
|
|
||||||
### Clone
|
### Clone
|
||||||
|
|
||||||
@@ -174,7 +188,7 @@ command:
|
|||||||
- android
|
- android
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
gomobile bind -tags nosqlite -ldflags="-w -s" -o ui/flutter/android/app/libs/libgopeed.aar -target=android -androidapi 21 -javapkg="com.gopeed" github.com/GopeedLab/gopeed/bind/mobile
|
gomobile bind -tags nosqlite -ldflags="-w -s -checklinkname=0" -o ui/flutter/android/app/libs/libgopeed.aar -target=android -androidapi 21 -javapkg="com.gopeed" github.com/GopeedLab/gopeed/bind/mobile
|
||||||
cd ui/flutter
|
cd ui/flutter
|
||||||
flutter build apk
|
flutter build apk
|
||||||
```
|
```
|
||||||
|
|||||||
+76
-62
@@ -22,66 +22,80 @@ Gopeed (正式名 Go Speed) は `Golang` + `Flutter` によって開発された
|
|||||||
## インストール
|
## インストール
|
||||||
|
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Platform</th>
|
<th colspan="2">Windows</th>
|
||||||
<th>Package Type</th>
|
<th>MacOS</th>
|
||||||
<th>Download Link</th>
|
<th colspan="4">Linux</th>
|
||||||
</tr>
|
<th>Android</th>
|
||||||
</thead>
|
<th>iOS</th>
|
||||||
<tbody>
|
<th>Docker</th>
|
||||||
<tr>
|
<th colspan="2">Qnap</th>
|
||||||
<td rowspan=2>Windows</td>
|
<th colspan="8">Web</th>
|
||||||
<td><code>EXE Installer</code></td>
|
</tr>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64.zip">Link</a></td>
|
<tr>
|
||||||
</tr>
|
<td><code>EXE</code></td>
|
||||||
<tr>
|
<td><code>Portable</code></td>
|
||||||
<td><code>Portable ZIP</code></td>
|
<td><code>DMG</code></td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64-portable.zip">Link</a></td>
|
<td><code>Flathub</code></td>
|
||||||
</tr>
|
<td><code>SNAP</code></td>
|
||||||
<tr>
|
<td><code>DEB</code></td>
|
||||||
<td>MacOS</td>
|
<td><code>AppImage</code></td>
|
||||||
<td><code>DMG Installer</code></td>
|
<td><code>APK</code></td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-macos.dmg">Link</a></td>
|
<td><code>IPA</code></td>
|
||||||
</tr>
|
<td>-</td>
|
||||||
<tr>
|
<td colspan="2"><code>QPKG</code></td>
|
||||||
<td rowspan=4>Linux</td>
|
<td colspan="3"><code>Windows</code></td>
|
||||||
<td><code>Flathub</code></td>
|
<td colspan="2"><code>MacOS</code></td>
|
||||||
<td><a href="https://flathub.org/apps/com.gopeed.Gopeed">Link</a></td>
|
<td colspan="3"><code>Linux</code></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
</thead>
|
||||||
<td><code>SNAP</code></td>
|
<tbody>
|
||||||
<td><a href="https://snapcraft.io/gopeed">Link</a></td>
|
<tr>
|
||||||
</tr>
|
<td>amd64</td>
|
||||||
<tr>
|
<td>amd64</td>
|
||||||
<td><code>DEB</code></td>
|
<td>-</td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-amd64.deb">Link</a></td>
|
<td>amd64</td>
|
||||||
</tr>
|
<td>amd64</td>
|
||||||
<tr>
|
<td>amd64</td>
|
||||||
<td><code>AppImage</code></td>
|
<td>amd64</td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-x86_64.AppImage">Link</a></td>
|
<td>-</td>
|
||||||
</tr>
|
<td>-</td>
|
||||||
<tr>
|
<td>-</td>
|
||||||
<td>Android</td>
|
<td>amd64</td>
|
||||||
<td><code>APK</code></td>
|
<td>arm64</td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android.apk">Link</a></td>
|
<td>amd64</td>
|
||||||
</tr>
|
<td>amd64</td>
|
||||||
<tr>
|
<td>386</td>
|
||||||
<td>iOS</td>
|
<td>amd64</td>
|
||||||
<td><code>IPA</code></td>
|
<td>amd64</td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-ios.ipa">Link</a></td>
|
<td>amd64</td>
|
||||||
</tr>
|
<td>amd64</td>
|
||||||
<tr>
|
<td>386</td>
|
||||||
<td>Web</td>
|
</tr>
|
||||||
<td></td>
|
<tr>
|
||||||
<td><a href="https://github.com/GopeedLab/gopeed/releases/latest">Link</a></td>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64.zip">Link</a></td>
|
||||||
</tr>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64-portable.zip">Link</a></td>
|
||||||
<tr>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-macos.dmg">Link</a></td>
|
||||||
<td>Docker</td>
|
<td><a href="https://flathub.org/apps/com.gopeed.Gopeed">Link</a></td>
|
||||||
<td></td>
|
<td><a href="https://snapcraft.io/gopeed">Link</a></td>
|
||||||
<td><a href="https://hub.docker.com/r/liwei2633/gopeed">Link</a></td>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-amd64.deb">Link</a></td>
|
||||||
</tr>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-x86_64.AppImage">Link</a></td>
|
||||||
</tbody>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android.apk">Link</a></td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-ios.ipa">Link</a></td>
|
||||||
|
<td><a href="https://hub.docker.com/r/liwei2633/gopeed">Link</a></td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=gopeed-$version-qnap-amd64.qpkg">Link</a></td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=gopeed-$version-qnap-arm64.qpkg">Link</a></td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-windows-amd64.zip">Link</a></td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-windows-arm64.zip">Link</a></td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-windows-386.zip">Link</a></td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-macos-amd64.zip">Link</a></td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-macos-arm64.zip">Link</a></td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-linux-amd64.zip">Link</a></td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-linux-arm64.zip">Link</a></td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-linux-386.zip">Link</a></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
インストールについての詳細は、[インストール](https://docs.gopeed.com/install.html)を参照してください。
|
インストールについての詳細は、[インストール](https://docs.gopeed.com/install.html)を参照してください。
|
||||||
@@ -103,7 +117,7 @@ Gopeed (正式名 Go Speed) は `Golang` + `Flutter` によって開発された
|
|||||||
### 環境
|
### 環境
|
||||||
|
|
||||||
1. Go 言語 1.23+
|
1. Go 言語 1.23+
|
||||||
2. Flutter 3.16+
|
2. Flutter 3.24+
|
||||||
|
|
||||||
### クローン
|
### クローン
|
||||||
|
|
||||||
@@ -162,7 +176,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 21 -javapkg="com.gopeed" github.com/GopeedLab/gopeed/bind/mobile
|
gomobile bind -tags nosqlite -ldflags="-w -s -checklinkname=0" -o ui/flutter/android/app/libs/libgopeed.aar -target=android -androidapi 21 -javapkg="com.gopeed" github.com/GopeedLab/gopeed/bind/mobile
|
||||||
cd ui/flutter
|
cd ui/flutter
|
||||||
flutter build apk
|
flutter build apk
|
||||||
```
|
```
|
||||||
|
|||||||
+76
-62
@@ -22,66 +22,80 @@ Truy cập ✈ [Trang web chính thức](https://gopeed.com) | 📖 [Tài liệu
|
|||||||
## Tải về
|
## Tải về
|
||||||
|
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Nền tảng</th>
|
<th colspan="2">Windows</th>
|
||||||
<th>Loại gói</th>
|
<th>MacOS</th>
|
||||||
<th>Liên kết tải xuống</th>
|
<th colspan="4">Linux</th>
|
||||||
</tr>
|
<th>Android</th>
|
||||||
</thead>
|
<th>iOS</th>
|
||||||
<tbody>
|
<th>Docker</th>
|
||||||
<tr>
|
<th colspan="2">Qnap</th>
|
||||||
<td rowspan=2>Windows</td>
|
<th colspan="8">Web</th>
|
||||||
<td><code>Bộ cài đặt EXE</code></td>
|
</tr>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64.zip">Liên kết</a></td>
|
<tr>
|
||||||
</tr>
|
<td><code>EXE</code></td>
|
||||||
<tr>
|
<td><code>Portable</code></td>
|
||||||
<td><code>ZIP Portable</code></td>
|
<td><code>DMG</code></td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64-portable.zip">Liên kết</a></td>
|
<td><code>Flathub</code></td>
|
||||||
</tr>
|
<td><code>SNAP</code></td>
|
||||||
<tr>
|
<td><code>DEB</code></td>
|
||||||
<td>MacOS</td>
|
<td><code>AppImage</code></td>
|
||||||
<td><code>Bộ cài đặt DMG</code></td>
|
<td><code>APK</code></td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-macos.dmg">Liên kết</a></td>
|
<td><code>IPA</code></td>
|
||||||
</tr>
|
<td>-</td>
|
||||||
<tr>
|
<td colspan="2"><code>QPKG</code></td>
|
||||||
<td rowspan=4>Linux</td>
|
<td colspan="3"><code>Windows</code></td>
|
||||||
<td><code>Flathub</code></td>
|
<td colspan="2"><code>MacOS</code></td>
|
||||||
<td><a href="https://flathub.org/apps/com.gopeed.Gopeed">Liên kết</a></td>
|
<td colspan="3"><code>Linux</code></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
</thead>
|
||||||
<td><code>SNAP</code></td>
|
<tbody>
|
||||||
<td><a href="https://snapcraft.io/gopeed">Liên kết</a></td>
|
<tr>
|
||||||
</tr>
|
<td>amd64</td>
|
||||||
<tr>
|
<td>amd64</td>
|
||||||
<td><code>DEB</code></td>
|
<td>-</td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-amd64.deb">Liên kết</a></td>
|
<td>amd64</td>
|
||||||
</tr>
|
<td>amd64</td>
|
||||||
<tr>
|
<td>amd64</td>
|
||||||
<td><code>AppImage</code></td>
|
<td>amd64</td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-x86_64.AppImage">Liên kết</a></td>
|
<td>-</td>
|
||||||
</tr>
|
<td>-</td>
|
||||||
<tr>
|
<td>-</td>
|
||||||
<td>Android</td>
|
<td>amd64</td>
|
||||||
<td><code>APK</code></td>
|
<td>arm64</td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android.apk">Liên kết</a></td>
|
<td>amd64</td>
|
||||||
</tr>
|
<td>amd64</td>
|
||||||
<tr>
|
<td>386</td>
|
||||||
<td>iOS</td>
|
<td>amd64</td>
|
||||||
<td><code>IPA</code></td>
|
<td>amd64</td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-ios.ipa">Liên kết</a></td>
|
<td>amd64</td>
|
||||||
</tr>
|
<td>amd64</td>
|
||||||
<tr>
|
<td>386</td>
|
||||||
<td>Web</td>
|
</tr>
|
||||||
<td></td>
|
<tr>
|
||||||
<td><a href="https://github.com/GopeedLab/gopeed/releases/latest">Liên kết</a></td>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64.zip">Liên kết</a></td>
|
||||||
</tr>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64-portable.zip">Liên kết</a></td>
|
||||||
<tr>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-macos.dmg">Liên kết</a></td>
|
||||||
<td>Docker</td>
|
<td><a href="https://flathub.org/apps/com.gopeed.Gopeed">Liên kết</a></td>
|
||||||
<td></td>
|
<td><a href="https://snapcraft.io/gopeed">Liên kết</a></td>
|
||||||
<td><a href="https://hub.docker.com/r/liwei2633/gopeed">Liên kết</a></td>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-amd64.deb">Liên kết</a></td>
|
||||||
</tr>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-x86_64.AppImage">Liên kết</a></td>
|
||||||
</tbody>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android.apk">Liên kết</a></td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-ios.ipa">Liên kết</a></td>
|
||||||
|
<td><a href="https://hub.docker.com/r/liwei2633/gopeed">Liên kết</a></td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=gopeed-$version-qnap-amd64.qpkg">Liên kết</a></td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=gopeed-$version-qnap-arm64.qpkg">Liên kết</a></td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-windows-amd64.zip">Liên kết</a></td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-windows-arm64.zip">Liên kết</a></td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-windows-386.zip">Liên kết</a></td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-macos-amd64.zip">Liên kết</a></td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-macos-arm64.zip">Liên kết</a></td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-linux-amd64.zip">Liên kết</a></td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-linux-arm64.zip">Liên kết</a></td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-linux-386.zip">Liên kết</a></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
Thêm thông tin về cài đặt, vui lòng tham khảo [Cài đặt](https://docs.gopeed.com/install.html)
|
Thêm thông tin về cài đặt, vui lòng tham khảo [Cài đặt](https://docs.gopeed.com/install.html)
|
||||||
@@ -111,7 +125,7 @@ Dự án này được chia thành hai phần, phần giao diện sử dụng `f
|
|||||||
### Environment
|
### Environment
|
||||||
|
|
||||||
1. Golang 1.23+
|
1. Golang 1.23+
|
||||||
2. Flutter 3.16+
|
2. Flutter 3.24+
|
||||||
|
|
||||||
### Clone
|
### Clone
|
||||||
|
|
||||||
@@ -170,7 +184,7 @@ command:
|
|||||||
- android
|
- android
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
gomobile bind -tags nosqlite -ldflags="-w -s" -o ui/flutter/android/app/libs/libgopeed.aar -target=android -androidapi 21 -javapkg="com.gopeed" github.com/GopeedLab/gopeed/bind/mobile
|
gomobile bind -tags nosqlite -ldflags="-w -s -checklinkname=0" -o ui/flutter/android/app/libs/libgopeed.aar -target=android -androidapi 21 -javapkg="com.gopeed" github.com/GopeedLab/gopeed/bind/mobile
|
||||||
cd ui/flutter
|
cd ui/flutter
|
||||||
flutter build apk
|
flutter build apk
|
||||||
```
|
```
|
||||||
|
|||||||
+76
-62
@@ -22,66 +22,80 @@ Gopeed(全称 Go Speed),直译过来中文名叫做`够快下载器`(不
|
|||||||
## 下载
|
## 下载
|
||||||
|
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>运行平台</th>
|
<th colspan="2">Windows</th>
|
||||||
<th>安装包类型</th>
|
<th>MacOS</th>
|
||||||
<th>下载链接</th>
|
<th colspan="4">Linux</th>
|
||||||
</tr>
|
<th>Android</th>
|
||||||
</thead>
|
<th>iOS</th>
|
||||||
<tbody>
|
<th>Docker</th>
|
||||||
<tr>
|
<th colspan="2">Qnap</th>
|
||||||
<td rowspan=2>Windows</td>
|
<th colspan="8">Web</th>
|
||||||
<td><code>EXE Installer</code></td>
|
</tr>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64.zip">前往</a></td>
|
<tr>
|
||||||
</tr>
|
<td><code>EXE</code></td>
|
||||||
<tr>
|
<td><code>Portable</code></td>
|
||||||
<td><code>Portable ZIP</code></td>
|
<td><code>DMG</code></td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64-portable.zip">前往</a></td>
|
<td><code>Flathub</code></td>
|
||||||
</tr>
|
<td><code>SNAP</code></td>
|
||||||
<tr>
|
<td><code>DEB</code></td>
|
||||||
<td>MacOS</td>
|
<td><code>AppImage</code></td>
|
||||||
<td><code>DMG Installer</code></td>
|
<td><code>APK</code></td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-macos.dmg">前往</a></td>
|
<td><code>IPA</code></td>
|
||||||
</tr>
|
<td>-</td>
|
||||||
<tr>
|
<td colspan="2"><code>QPKG</code></td>
|
||||||
<td rowspan=4>Linux</td>
|
<td colspan="3"><code>Windows</code></td>
|
||||||
<td><code>Flathub</code></td>
|
<td colspan="2"><code>MacOS</code></td>
|
||||||
<td><a href="https://flathub.org/apps/com.gopeed.Gopeed">前往</a></td>
|
<td colspan="3"><code>Linux</code></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
</thead>
|
||||||
<td><code>SNAP</code></td>
|
<tbody>
|
||||||
<td><a href="https://snapcraft.io/gopeed">前往</a></td>
|
<tr>
|
||||||
</tr>
|
<td>amd64</td>
|
||||||
<tr>
|
<td>amd64</td>
|
||||||
<td><code>DEB</code></td>
|
<td>-</td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-amd64.deb">前往</a></td>
|
<td>amd64</td>
|
||||||
</tr>
|
<td>amd64</td>
|
||||||
<tr>
|
<td>amd64</td>
|
||||||
<td><code>AppImage</code></td>
|
<td>amd64</td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-x86_64.AppImage">前往</a></td>
|
<td>-</td>
|
||||||
</tr>
|
<td>-</td>
|
||||||
<tr>
|
<td>-</td>
|
||||||
<td>Android</td>
|
<td>amd64</td>
|
||||||
<td><code>APK</code></td>
|
<td>arm64</td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android.apk">前往</a></td>
|
<td>amd64</td>
|
||||||
</tr>
|
<td>amd64</td>
|
||||||
<tr>
|
<td>386</td>
|
||||||
<td>iOS</td>
|
<td>amd64</td>
|
||||||
<td><code>IPA</code></td>
|
<td>amd64</td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-ios.ipa">前往</a></td>
|
<td>amd64</td>
|
||||||
</tr>
|
<td>amd64</td>
|
||||||
<tr>
|
<td>386</td>
|
||||||
<td>Web</td>
|
</tr>
|
||||||
<td></td>
|
<tr>
|
||||||
<td><a href="https://github.com/GopeedLab/gopeed/releases/latest">前往</a></td>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64.zip">前往</a></td>
|
||||||
</tr>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64-portable.zip">前往</a></td>
|
||||||
<tr>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-macos.dmg">前往</a></td>
|
||||||
<td>Docker</td>
|
<td><a href="https://flathub.org/apps/com.gopeed.Gopeed">前往</a></td>
|
||||||
<td></td>
|
<td><a href="https://snapcraft.io/gopeed">前往</a></td>
|
||||||
<td><a href="https://hub.docker.com/r/liwei2633/gopeed">前往</a></td>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-amd64.deb">前往</a></td>
|
||||||
</tr>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-x86_64.AppImage">前往</a></td>
|
||||||
</tbody>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android.apk">前往</a></td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-ios.ipa">前往</a></td>
|
||||||
|
<td><a href="https://hub.docker.com/r/liwei2633/gopeed">前往</a></td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=gopeed-$version-qnap-amd64.qpkg">前往</a></td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=gopeed-$version-qnap-arm64.qpkg">前往</a></td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-windows-amd64.zip">前往</a></td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-windows-arm64.zip">前往</a></td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-windows-386.zip">前往</a></td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-macos-amd64.zip">前往</a></td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-macos-arm64.zip">前往</a></td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-linux-amd64.zip">前往</a></td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-linux-arm64.zip">前往</a></td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-linux-386.zip">前往</a></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
更多关于安装的内容请参考[安装文档](https://docs.gopeed.com/zh/install.html)
|
更多关于安装的内容请参考[安装文档](https://docs.gopeed.com/zh/install.html)
|
||||||
@@ -115,7 +129,7 @@ Gopeed 还提供了浏览器扩展用于接管浏览器下载,支持 Chrome、
|
|||||||
### 环境要求
|
### 环境要求
|
||||||
|
|
||||||
1. Golang 1.23+
|
1. Golang 1.23+
|
||||||
2. Flutter 3.16+
|
2. Flutter 3.24+
|
||||||
|
|
||||||
### 克隆项目
|
### 克隆项目
|
||||||
|
|
||||||
@@ -174,7 +188,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 21 -javapkg="com.gopeed" github.com/GopeedLab/gopeed/bind/mobile
|
gomobile bind -tags nosqlite -ldflags="-w -s -checklinkname=0" -o ui/flutter/android/app/libs/libgopeed.aar -target=android -androidapi 21 -javapkg="com.gopeed" github.com/GopeedLab/gopeed/bind/mobile
|
||||||
cd ui/flutter
|
cd ui/flutter
|
||||||
flutter build apk
|
flutter build apk
|
||||||
```
|
```
|
||||||
|
|||||||
+77
-63
@@ -22,71 +22,85 @@ Gopeed(全稱 Go Speed),是一款使用`Golang`+`Flutter`編寫的高速
|
|||||||
## 下載
|
## 下載
|
||||||
|
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>運行平台</th>
|
<th colspan="2">Windows</th>
|
||||||
<th>安裝包類型</th>
|
<th>MacOS</th>
|
||||||
<th>下載鏈接</th>
|
<th colspan="4">Linux</th>
|
||||||
</tr>
|
<th>Android</th>
|
||||||
</thead>
|
<th>iOS</th>
|
||||||
<tbody>
|
<th>Docker</th>
|
||||||
<tr>
|
<th colspan="2">Qnap</th>
|
||||||
<td rowspan=2>Windows</td>
|
<th colspan="8">Web</th>
|
||||||
<td><code>EXE Installer</code></td>
|
</tr>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64.zip">前往</a></td>
|
<tr>
|
||||||
</tr>
|
<td><code>EXE</code></td>
|
||||||
<tr>
|
<td><code>Portable</code></td>
|
||||||
<td><code>Portable ZIP</code></td>
|
<td><code>DMG</code></td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64-portable.zip">前往</a></td>
|
<td><code>Flathub</code></td>
|
||||||
</tr>
|
<td><code>SNAP</code></td>
|
||||||
<tr>
|
<td><code>DEB</code></td>
|
||||||
<td>MacOS</td>
|
<td><code>AppImage</code></td>
|
||||||
<td><code>DMG Installer</code></td>
|
<td><code>APK</code></td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-macos.dmg">前往</a></td>
|
<td><code>IPA</code></td>
|
||||||
</tr>
|
<td>-</td>
|
||||||
<tr>
|
<td colspan="2"><code>QPKG</code></td>
|
||||||
<td rowspan=4>Linux</td>
|
<td colspan="3"><code>Windows</code></td>
|
||||||
<td><code>Flathub</code></td>
|
<td colspan="2"><code>MacOS</code></td>
|
||||||
<td><a href="https://flathub.org/apps/com.gopeed.Gopeed">前往</a></td>
|
<td colspan="3"><code>Linux</code></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
</thead>
|
||||||
<td><code>SNAP</code></td>
|
<tbody>
|
||||||
<td><a href="https://snapcraft.io/gopeed">前往</a></td>
|
<tr>
|
||||||
</tr>
|
<td>amd64</td>
|
||||||
<tr>
|
<td>amd64</td>
|
||||||
<td><code>DEB</code></td>
|
<td>-</td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-amd64.deb">前往</a></td>
|
<td>amd64</td>
|
||||||
</tr>
|
<td>amd64</td>
|
||||||
<tr>
|
<td>amd64</td>
|
||||||
<td><code>AppImage</code></td>
|
<td>amd64</td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-x86_64.AppImage">前往</a></td>
|
<td>-</td>
|
||||||
</tr>
|
<td>-</td>
|
||||||
<tr>
|
<td>-</td>
|
||||||
<td>Android</td>
|
<td>amd64</td>
|
||||||
<td><code>APK</code></td>
|
<td>arm64</td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android.apk">前往</a></td>
|
<td>amd64</td>
|
||||||
</tr>
|
<td>amd64</td>
|
||||||
<tr>
|
<td>386</td>
|
||||||
<td>iOS</td>
|
<td>amd64</td>
|
||||||
<td><code>IPA</code></td>
|
<td>amd64</td>
|
||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-ios.ipa">前往</a></td>
|
<td>amd64</td>
|
||||||
</tr>
|
<td>amd64</td>
|
||||||
<tr>
|
<td>386</td>
|
||||||
<td>Web</td>
|
</tr>
|
||||||
<td></td>
|
<tr>
|
||||||
<td><a href="https://github.com/GopeedLab/gopeed/releases/latest">前往</a></td>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64.zip">前往</a></td>
|
||||||
</tr>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64-portable.zip">前往</a></td>
|
||||||
<tr>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-macos.dmg">前往</a></td>
|
||||||
<td>Docker</td>
|
<td><a href="https://flathub.org/apps/com.gopeed.Gopeed">前往</a></td>
|
||||||
<td></td>
|
<td><a href="https://snapcraft.io/gopeed">前往</a></td>
|
||||||
<td><a href="https://hub.docker.com/r/liwei2633/gopeed">前往</a></td>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-amd64.deb">前往</a></td>
|
||||||
</tr>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-x86_64.AppImage">前往</a></td>
|
||||||
</tbody>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android.apk">前往</a></td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-ios.ipa">前往</a></td>
|
||||||
|
<td><a href="https://hub.docker.com/r/liwei2633/gopeed">前往</a></td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=gopeed-$version-qnap-amd64.qpkg">前往</a></td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=gopeed-$version-qnap-arm64.qpkg">前往</a></td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-windows-amd64.zip">前往</a></td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-windows-arm64.zip">前往</a></td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-windows-386.zip">前往</a></td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-macos-amd64.zip">前往</a></td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-macos-arm64.zip">前往</a></td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-linux-amd64.zip">前往</a></td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-linux-arm64.zip">前往</a></td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-linux-386.zip">前往</a></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
更多關於安裝的內容請參考[安裝文檔](https://docs.gopeed.com/zh/install.html)
|
更多關於安裝的內容請參考[安裝文檔](https://docs.gopeed.com/zh/install.html)
|
||||||
|
|
||||||
### 使用CLI安裝
|
### 使用 CLI 安裝
|
||||||
|
|
||||||
使用`go install`安裝:
|
使用`go install`安裝:
|
||||||
|
|
||||||
@@ -111,7 +125,7 @@ go install github.com/GopeedLab/gopeed/cmd/gopeed@latest
|
|||||||
### 開發環境
|
### 開發環境
|
||||||
|
|
||||||
1. Golang 1.23+
|
1. Golang 1.23+
|
||||||
2. Flutter 3.16+
|
2. Flutter 3.24+
|
||||||
|
|
||||||
### 克隆項目
|
### 克隆項目
|
||||||
|
|
||||||
@@ -170,7 +184,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 21 -javapkg="com.gopeed" github.com/GopeedLab/gopeed/bind/mobile
|
gomobile bind -tags nosqlite -ldflags="-w -s -checklinkname=0" -o ui/flutter/android/app/libs/libgopeed.aar -target=android -androidapi 21 -javapkg="com.gopeed" github.com/GopeedLab/gopeed/bind/mobile
|
||||||
cd ui/flutter
|
cd ui/flutter
|
||||||
flutter build apk
|
flutter build apk
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -1,41 +0,0 @@
|
|||||||
package bind
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"github.com/GopeedLab/gopeed/pkg/api"
|
|
||||||
"github.com/GopeedLab/gopeed/pkg/api/model"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Global singleton instance
|
|
||||||
var instance *api.Instance
|
|
||||||
|
|
||||||
func Init(config *model.StartConfig) (err error) {
|
|
||||||
if instance != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
config.ProductionMode = true
|
|
||||||
instance, err = api.Create(config)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func Invoke(request *api.Request) string {
|
|
||||||
if instance == nil {
|
|
||||||
return BuildResult(errors.New("instance not initialized"))
|
|
||||||
}
|
|
||||||
return jsonEncode(api.Invoke(instance, request))
|
|
||||||
}
|
|
||||||
|
|
||||||
func BuildResult(data any) string {
|
|
||||||
if err, ok := data.(error); ok {
|
|
||||||
buf, _ := json.Marshal(model.NewErrorResult[any](err.Error()))
|
|
||||||
return string(buf)
|
|
||||||
}
|
|
||||||
return jsonEncode(model.NewOkResult(data))
|
|
||||||
}
|
|
||||||
|
|
||||||
func jsonEncode(data any) string {
|
|
||||||
buf, _ := json.Marshal(data)
|
|
||||||
return string(buf)
|
|
||||||
}
|
|
||||||
+13
-18
@@ -3,32 +3,27 @@ package main
|
|||||||
import "C"
|
import "C"
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"github.com/GopeedLab/gopeed/bind"
|
"github.com/GopeedLab/gopeed/pkg/rest"
|
||||||
"github.com/GopeedLab/gopeed/pkg/api"
|
"github.com/GopeedLab/gopeed/pkg/rest/model"
|
||||||
"github.com/GopeedLab/gopeed/pkg/api/model"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {}
|
func main() {}
|
||||||
|
|
||||||
//export Init
|
//export Start
|
||||||
func Init(cfg *C.char) *C.char {
|
func Start(cfg *C.char) (int, *C.char) {
|
||||||
var config model.StartConfig
|
var config model.StartConfig
|
||||||
if err := json.Unmarshal([]byte(C.GoString(cfg)), &config); err != nil {
|
if err := json.Unmarshal([]byte(C.GoString(cfg)), &config); err != nil {
|
||||||
return C.CString(err.Error())
|
return 0, C.CString(err.Error())
|
||||||
}
|
}
|
||||||
if err := bind.Init(&config); err != nil {
|
config.ProductionMode = true
|
||||||
return C.CString(err.Error())
|
realPort, err := rest.Start(&config)
|
||||||
|
if err != nil {
|
||||||
|
return 0, C.CString(err.Error())
|
||||||
}
|
}
|
||||||
|
return realPort, nil
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//export Invoke
|
//export Stop
|
||||||
func Invoke(req *C.char) *C.char {
|
func Stop() {
|
||||||
var request api.Request
|
rest.Stop()
|
||||||
if err := json.Unmarshal([]byte(C.GoString(req)), &request); err != nil {
|
|
||||||
return C.CString(bind.BuildResult(err))
|
|
||||||
}
|
|
||||||
|
|
||||||
return C.CString(bind.Invoke(&request))
|
|
||||||
}
|
}
|
||||||
+12
-7
@@ -3,15 +3,20 @@ package libgopeed
|
|||||||
// #cgo LDFLAGS: -static-libstdc++
|
// #cgo LDFLAGS: -static-libstdc++
|
||||||
import "C"
|
import "C"
|
||||||
import (
|
import (
|
||||||
"github.com/GopeedLab/gopeed/bind"
|
"encoding/json"
|
||||||
"github.com/GopeedLab/gopeed/pkg/api"
|
"github.com/GopeedLab/gopeed/pkg/rest"
|
||||||
"github.com/GopeedLab/gopeed/pkg/api/model"
|
"github.com/GopeedLab/gopeed/pkg/rest/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Init(cfg *model.StartConfig) error {
|
func Start(cfg string) (int, error) {
|
||||||
return bind.Init(cfg)
|
var config model.StartConfig
|
||||||
|
if err := json.Unmarshal([]byte(cfg), &config); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
config.ProductionMode = true
|
||||||
|
return rest.Start(&config)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Invoke(request *api.Request) string {
|
func Stop() {
|
||||||
return bind.Invoke(request)
|
rest.Stop()
|
||||||
}
|
}
|
||||||
+4
-10
@@ -2,21 +2,15 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/GopeedLab/gopeed/cmd"
|
"github.com/GopeedLab/gopeed/cmd"
|
||||||
"github.com/GopeedLab/gopeed/pkg/api/model"
|
"github.com/GopeedLab/gopeed/pkg/rest/model"
|
||||||
"github.com/GopeedLab/gopeed/pkg/base"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// only for local development
|
// only for local development
|
||||||
func main() {
|
func main() {
|
||||||
cfg := &model.StartConfig{
|
cfg := &model.StartConfig{
|
||||||
Storage: model.StorageBolt,
|
Network: "tcp",
|
||||||
DownloadConfig: &base.DownloaderStoreConfig{
|
Address: "127.0.0.1:9999",
|
||||||
Http: &base.DownloaderHttpConfig{
|
Storage: model.StorageBolt,
|
||||||
Enable: true,
|
|
||||||
Host: "127.0.0.1",
|
|
||||||
Port: 9999,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
WebEnable: true,
|
WebEnable: true,
|
||||||
}
|
}
|
||||||
cmd.Start(cfg)
|
cmd.Start(cfg)
|
||||||
|
|||||||
@@ -0,0 +1,123 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/pkg/browser"
|
||||||
|
"github.com/shirou/gopsutil/v4/process"
|
||||||
|
)
|
||||||
|
|
||||||
|
const identifier = "gopeed"
|
||||||
|
|
||||||
|
type Message struct {
|
||||||
|
Method string `json:"method"`
|
||||||
|
Params json.RawMessage `json:"params"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Response struct {
|
||||||
|
Code int `json:"code"`
|
||||||
|
Data any `json:"data,omitempty"`
|
||||||
|
Message string `json:"message,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var apiMap = map[string]func(params json.RawMessage) (data any, err error){
|
||||||
|
"ping": func(params json.RawMessage) (data any, err error) {
|
||||||
|
processes, err := process.Processes()
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, p := range processes {
|
||||||
|
name, err := p.Name()
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.Contains(strings.ToLower(name), strings.ToLower(identifier)) {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
},
|
||||||
|
"create": func(params json.RawMessage) (data any, err error) {
|
||||||
|
var strParams string
|
||||||
|
if err = json.Unmarshal(params, &strParams); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = browser.OpenURL(fmt.Sprintf("%s:///create?params=%s", identifier, strParams))
|
||||||
|
return
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
for {
|
||||||
|
// Read message length (first 4 bytes)
|
||||||
|
var length uint32
|
||||||
|
if err := binary.Read(os.Stdin, binary.NativeEndian, &length); err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
// Connection closed by Chrome
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sendError("Failed to read message length: " + err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the message
|
||||||
|
input := make([]byte, length)
|
||||||
|
if _, err := io.ReadFull(os.Stdin, input); err != nil {
|
||||||
|
sendError("Failed to read message: " + err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse message
|
||||||
|
var message Message
|
||||||
|
if err := json.Unmarshal(input, &message); err != nil {
|
||||||
|
sendError("Failed to parse message: " + err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle request
|
||||||
|
var data any
|
||||||
|
var err error
|
||||||
|
if handler, ok := apiMap[message.Method]; ok {
|
||||||
|
data, err = handler(message.Params)
|
||||||
|
} else {
|
||||||
|
err = errors.New("Unknown method: " + message.Method)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
sendError(err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
sendResponse(0, data, "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func sendResponse(code int, data interface{}, message string) {
|
||||||
|
response := Response{
|
||||||
|
Code: code,
|
||||||
|
Data: data,
|
||||||
|
Message: message,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode response
|
||||||
|
responseBytes, err := json.Marshal(response)
|
||||||
|
if err != nil {
|
||||||
|
sendError("Failed to encode response: " + err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write message length
|
||||||
|
binary.Write(os.Stdout, binary.NativeEndian, uint32(len(responseBytes)))
|
||||||
|
// Write message
|
||||||
|
os.Stdout.Write(responseBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func sendError(msg string) {
|
||||||
|
sendResponse(1, nil, msg)
|
||||||
|
}
|
||||||
+14
-18
@@ -3,9 +3,10 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
_ "embed"
|
_ "embed"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/GopeedLab/gopeed/pkg/api"
|
|
||||||
"github.com/GopeedLab/gopeed/pkg/api/model"
|
|
||||||
"github.com/GopeedLab/gopeed/pkg/base"
|
"github.com/GopeedLab/gopeed/pkg/base"
|
||||||
|
"github.com/GopeedLab/gopeed/pkg/rest"
|
||||||
|
"github.com/GopeedLab/gopeed/pkg/rest/model"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -17,22 +18,20 @@ var banner string
|
|||||||
|
|
||||||
func Start(cfg *model.StartConfig) {
|
func Start(cfg *model.StartConfig) {
|
||||||
fmt.Println(banner)
|
fmt.Println(banner)
|
||||||
instance, err := api.Create(cfg)
|
srv, listener, err := rest.BuildServer(cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
downloadCfg, err := rest.Downloader.GetConfig()
|
||||||
downloadCfgResult := instance.GetConfig()
|
if err != nil {
|
||||||
if downloadCfgResult.HasError() {
|
panic(err)
|
||||||
panic(downloadCfgResult.Msg)
|
|
||||||
}
|
}
|
||||||
downloadCfg := downloadCfgResult.Data
|
|
||||||
if downloadCfg.FirstLoad {
|
if downloadCfg.FirstLoad {
|
||||||
// Set default download config
|
// Set default download config
|
||||||
if cfg.DownloadConfig != nil {
|
if cfg.DownloadConfig != nil {
|
||||||
cfg.DownloadConfig.Merge(downloadCfg)
|
cfg.DownloadConfig.Merge(downloadCfg)
|
||||||
// TODO Use PatchConfig
|
// TODO Use PatchConfig
|
||||||
instance.PutConfig(cfg.DownloadConfig)
|
rest.Downloader.PutConfig(cfg.DownloadConfig)
|
||||||
downloadCfg = cfg.DownloadConfig
|
downloadCfg = cfg.DownloadConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,28 +48,25 @@ func Start(cfg *model.StartConfig) {
|
|||||||
}
|
}
|
||||||
if downloadDir != "" {
|
if downloadDir != "" {
|
||||||
downloadCfg.DownloadDir = downloadDir
|
downloadCfg.DownloadDir = downloadDir
|
||||||
instance.PutConfig(downloadCfg)
|
rest.Downloader.PutConfig(downloadCfg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
watchExit()
|
||||||
|
|
||||||
watchExit(instance)
|
fmt.Printf("Server start success on http://%s\n", listener.Addr().String())
|
||||||
|
if err := srv.Serve(listener); err != nil && err != http.ErrServerClosed {
|
||||||
port, start, err := api.ListenHttp(cfg.DownloadConfig.Http, instance)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
fmt.Printf("Server start success on http://%s:%d\n", cfg.DownloadConfig.Http.Host, port)
|
|
||||||
start()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func watchExit(instance *api.Instance) {
|
func watchExit() {
|
||||||
sigs := make(chan os.Signal, 1)
|
sigs := make(chan os.Signal, 1)
|
||||||
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
|
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
|
||||||
go func() {
|
go func() {
|
||||||
sig := <-sigs
|
sig := <-sigs
|
||||||
fmt.Printf("Server is shutting down due to signal: %s\n", sig)
|
fmt.Printf("Server is shutting down due to signal: %s\n", sig)
|
||||||
instance.Close()
|
rest.Downloader.Close()
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
+4
-4
@@ -10,7 +10,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type args struct {
|
type args struct {
|
||||||
Host *string `json:"host"`
|
Address *string `json:"address"`
|
||||||
Port *int `json:"port"`
|
Port *int `json:"port"`
|
||||||
Username *string `json:"username"`
|
Username *string `json:"username"`
|
||||||
Password *string `json:"password"`
|
Password *string `json:"password"`
|
||||||
@@ -24,7 +24,7 @@ type args struct {
|
|||||||
|
|
||||||
func parse() *args {
|
func parse() *args {
|
||||||
var cliArgs args
|
var cliArgs args
|
||||||
cliArgs.Host = flag.String("H", "0.0.0.0", "Bind Host")
|
cliArgs.Address = flag.String("A", "0.0.0.0", "Bind Address")
|
||||||
cliArgs.Port = flag.Int("P", 9999, "Bind Port")
|
cliArgs.Port = flag.Int("P", 9999, "Bind Port")
|
||||||
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")
|
||||||
@@ -35,8 +35,8 @@ func parse() *args {
|
|||||||
|
|
||||||
// args priority: config file > cli args
|
// args priority: config file > cli args
|
||||||
cfgArgs := loadConfig(*cliArgs.configPath)
|
cfgArgs := loadConfig(*cliArgs.configPath)
|
||||||
if cfgArgs.Host == nil {
|
if cfgArgs.Address == nil {
|
||||||
cfgArgs.Host = cliArgs.Host
|
cfgArgs.Address = cliArgs.Address
|
||||||
}
|
}
|
||||||
if cfgArgs.Port == nil {
|
if cfgArgs.Port == nil {
|
||||||
cfgArgs.Port = cliArgs.Port
|
cfgArgs.Port = cliArgs.Port
|
||||||
|
|||||||
+5
-12
@@ -5,9 +5,9 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"embed"
|
"embed"
|
||||||
|
"fmt"
|
||||||
"github.com/GopeedLab/gopeed/cmd"
|
"github.com/GopeedLab/gopeed/cmd"
|
||||||
"github.com/GopeedLab/gopeed/pkg/api/model"
|
"github.com/GopeedLab/gopeed/pkg/rest/model"
|
||||||
"github.com/GopeedLab/gopeed/pkg/base"
|
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -42,19 +42,12 @@ func main() {
|
|||||||
dir = filepath.Dir(exe)
|
dir = filepath.Dir(exe)
|
||||||
}
|
}
|
||||||
|
|
||||||
if args.DownloadConfig == nil {
|
|
||||||
args.DownloadConfig = &base.DownloaderStoreConfig{}
|
|
||||||
}
|
|
||||||
args.DownloadConfig.Http = &base.DownloaderHttpConfig{
|
|
||||||
Enable: true,
|
|
||||||
Host: *args.Host,
|
|
||||||
Port: *args.Port,
|
|
||||||
ApiToken: *args.ApiToken,
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg := &model.StartConfig{
|
cfg := &model.StartConfig{
|
||||||
|
Network: "tcp",
|
||||||
|
Address: fmt.Sprintf("%s:%d", *args.Address, *args.Port),
|
||||||
Storage: model.StorageBolt,
|
Storage: model.StorageBolt,
|
||||||
StorageDir: filepath.Join(dir, "storage"),
|
StorageDir: filepath.Join(dir, "storage"),
|
||||||
|
ApiToken: *args.ApiToken,
|
||||||
DownloadConfig: args.DownloadConfig,
|
DownloadConfig: args.DownloadConfig,
|
||||||
ProductionMode: true,
|
ProductionMode: true,
|
||||||
WebEnable: true,
|
WebEnable: true,
|
||||||
|
|||||||
@@ -24,6 +24,16 @@ require (
|
|||||||
golang.org/x/sync v0.9.0
|
golang.org/x/sync v0.9.0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/ebitengine/purego v0.8.1 // indirect
|
||||||
|
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||||
|
github.com/lufia/plan9stats v0.0.0-20220913051719-115f729f3c8c // indirect
|
||||||
|
github.com/power-devops/perfstat v0.0.0-20220216144756-c35f1ee13d7c // indirect
|
||||||
|
github.com/tklauser/go-sysconf v0.3.12 // indirect
|
||||||
|
github.com/tklauser/numcpus v0.6.1 // indirect
|
||||||
|
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||||
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
dario.cat/mergo v1.0.0 // indirect
|
dario.cat/mergo v1.0.0 // indirect
|
||||||
github.com/Microsoft/go-winio v0.6.1 // indirect
|
github.com/Microsoft/go-winio v0.6.1 // indirect
|
||||||
@@ -99,9 +109,11 @@ require (
|
|||||||
github.com/pion/turn/v4 v4.0.0 // indirect
|
github.com/pion/turn/v4 v4.0.0 // indirect
|
||||||
github.com/pion/webrtc/v4 v4.0.2 // indirect
|
github.com/pion/webrtc/v4 v4.0.2 // indirect
|
||||||
github.com/pjbgf/sha1cd v0.3.0 // indirect
|
github.com/pjbgf/sha1cd v0.3.0 // indirect
|
||||||
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c
|
||||||
github.com/protolambda/ctxlock v0.1.0 // indirect
|
github.com/protolambda/ctxlock v0.1.0 // indirect
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||||
github.com/sergi/go-diff v1.1.0 // indirect
|
github.com/sergi/go-diff v1.1.0 // indirect
|
||||||
|
github.com/shirou/gopsutil/v4 v4.24.12
|
||||||
github.com/skeema/knownhosts v1.2.0 // indirect
|
github.com/skeema/knownhosts v1.2.0 // indirect
|
||||||
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
||||||
github.com/tidwall/btree v1.7.0 // indirect
|
github.com/tidwall/btree v1.7.0 // indirect
|
||||||
@@ -113,7 +125,7 @@ require (
|
|||||||
golang.org/x/crypto v0.29.0 // indirect
|
golang.org/x/crypto v0.29.0 // indirect
|
||||||
golang.org/x/mod v0.22.0 // indirect
|
golang.org/x/mod v0.22.0 // indirect
|
||||||
golang.org/x/net v0.31.0 // indirect
|
golang.org/x/net v0.31.0 // indirect
|
||||||
golang.org/x/sys v0.27.0 // indirect
|
golang.org/x/sys v0.28.0 // indirect
|
||||||
golang.org/x/text v0.20.0 // indirect
|
golang.org/x/text v0.20.0 // indirect
|
||||||
golang.org/x/time v0.8.0 // indirect
|
golang.org/x/time v0.8.0 // indirect
|
||||||
golang.org/x/tools v0.27.0 // indirect
|
golang.org/x/tools v0.27.0 // indirect
|
||||||
|
|||||||
@@ -143,6 +143,8 @@ github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+m
|
|||||||
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
|
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
|
||||||
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
|
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
|
||||||
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
|
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
|
||||||
|
github.com/ebitengine/purego v0.8.1 h1:sdRKd6plj7KYW33EH5As6YKfe8m9zbN9JMrOjNVF/BE=
|
||||||
|
github.com/ebitengine/purego v0.8.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||||
github.com/edsrzf/mmap-go v1.2.0 h1:hXLYlkbaPzt1SaQk+anYwKSRNhufIDCchSPkUD6dD84=
|
github.com/edsrzf/mmap-go v1.2.0 h1:hXLYlkbaPzt1SaQk+anYwKSRNhufIDCchSPkUD6dD84=
|
||||||
github.com/edsrzf/mmap-go v1.2.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q=
|
github.com/edsrzf/mmap-go v1.2.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q=
|
||||||
github.com/elazarl/goproxy v0.0.0-20221015165544-a0805db90819 h1:RIB4cRk+lBqKK3Oy0r2gRX4ui7tuhiZq2SuTtTCi0/0=
|
github.com/elazarl/goproxy v0.0.0-20221015165544-a0805db90819 h1:RIB4cRk+lBqKK3Oy0r2gRX4ui7tuhiZq2SuTtTCi0/0=
|
||||||
@@ -187,6 +189,8 @@ github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
|||||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
|
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||||
|
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||||
github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
|
github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
|
||||||
github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
|
github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
|
||||||
github.com/go-sourcemap/sourcemap v2.1.4+incompatible h1:a+iTbH5auLKxaNwQFg0B+TCYl6lbukKPc7b5x0n1s6Q=
|
github.com/go-sourcemap/sourcemap v2.1.4+incompatible h1:a+iTbH5auLKxaNwQFg0B+TCYl6lbukKPc7b5x0n1s6Q=
|
||||||
@@ -288,6 +292,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
|||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/lufia/plan9stats v0.0.0-20220913051719-115f729f3c8c h1:VtwQ41oftZwlMnOEbMWQtSEUgU64U4s+GHk7hZK+jtY=
|
||||||
|
github.com/lufia/plan9stats v0.0.0-20220913051719-115f729f3c8c/go.mod h1:JKx41uQRwqlTZabZc+kILPrO/3jlKnQ2Z8b7YiVw5cE=
|
||||||
github.com/matoous/go-nanoid v1.5.0/go.mod h1:zyD2a71IubI24efhpvkJz+ZwfwagzgSO6UNiFsZKN7U=
|
github.com/matoous/go-nanoid v1.5.0/go.mod h1:zyD2a71IubI24efhpvkJz+ZwfwagzgSO6UNiFsZKN7U=
|
||||||
github.com/matoous/go-nanoid/v2 v2.0.0 h1:d19kur2QuLeHmJBkvYkFdhFBzLoo1XVm2GgTpL+9Tj0=
|
github.com/matoous/go-nanoid/v2 v2.0.0 h1:d19kur2QuLeHmJBkvYkFdhFBzLoo1XVm2GgTpL+9Tj0=
|
||||||
github.com/matoous/go-nanoid/v2 v2.0.0/go.mod h1:FtS4aGPVfEkxKxhdWPAspZpZSh1cOjtM7Ej/So3hR0g=
|
github.com/matoous/go-nanoid/v2 v2.0.0/go.mod h1:FtS4aGPVfEkxKxhdWPAspZpZSh1cOjtM7Ej/So3hR0g=
|
||||||
@@ -363,6 +369,8 @@ github.com/pion/webrtc/v4 v4.0.2 h1:fBwm5/hqSUybrCWl0DDBSTDrpbkcgkqpeLmXw9CsBQA=
|
|||||||
github.com/pion/webrtc/v4 v4.0.2/go.mod h1:moylBT2A4dNoEaYBCdV1nThM3TLwRHzWszIG+eSPaqQ=
|
github.com/pion/webrtc/v4 v4.0.2/go.mod h1:moylBT2A4dNoEaYBCdV1nThM3TLwRHzWszIG+eSPaqQ=
|
||||||
github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4=
|
github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4=
|
||||||
github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI=
|
github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI=
|
||||||
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
||||||
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
||||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
@@ -370,6 +378,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
|||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/power-devops/perfstat v0.0.0-20220216144756-c35f1ee13d7c h1:NRoLoZvkBTKvR5gQLgA3e0hqjkY9u1wm+iOL45VN/qI=
|
||||||
|
github.com/power-devops/perfstat v0.0.0-20220216144756-c35f1ee13d7c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||||
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
|
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
|
||||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||||
@@ -412,6 +422,8 @@ github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46 h1:GHRpF1pTW19a
|
|||||||
github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46/go.mod h1:uAQ5PCi+MFsC7HjREoAz1BU+Mq60+05gifQSsHSDG/8=
|
github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46/go.mod h1:uAQ5PCi+MFsC7HjREoAz1BU+Mq60+05gifQSsHSDG/8=
|
||||||
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
|
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
|
||||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||||
|
github.com/shirou/gopsutil/v4 v4.24.12 h1:qvePBOk20e0IKA1QXrIIU+jmk+zEiYVVx06WjBRlZo4=
|
||||||
|
github.com/shirou/gopsutil/v4 v4.24.12/go.mod h1:DCtMPAad2XceTeIAbGyVfycbYQNBGk2P8cvDi7/VN9o=
|
||||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||||
@@ -447,6 +459,10 @@ github.com/tidwall/btree v1.7.0/go.mod h1:twD9XRA5jj9VUQGELzDO4HPQTNJsoWWfYEL+EU
|
|||||||
github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
|
github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
|
||||||
github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
|
github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
|
||||||
github.com/tinylib/msgp v1.1.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
|
github.com/tinylib/msgp v1.1.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
|
||||||
|
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
|
||||||
|
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
||||||
|
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
|
||||||
|
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
|
||||||
github.com/willf/bitset v1.1.9/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
|
github.com/willf/bitset v1.1.9/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
|
||||||
github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
|
github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
|
||||||
github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU=
|
github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU=
|
||||||
@@ -458,6 +474,8 @@ github.com/xiaoqidun/setft v0.0.0-20220310121541-be86327699ad/go.mod h1:Jj8p9bgK
|
|||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
|
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||||
|
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||||
go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0=
|
go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0=
|
||||||
go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I=
|
go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I=
|
||||||
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
||||||
@@ -543,6 +561,7 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
|
|||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@@ -552,6 +571,7 @@ golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
@@ -562,13 +582,18 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
|
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
|
||||||
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||||
|
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||||
|
|||||||
@@ -11,7 +11,9 @@ 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"
|
||||||
|
"io"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -332,7 +334,7 @@ func (f *Fetcher) doUpload(fromUpload bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// get torrent stats maybe panic, see https://github.com/anacrolix/torrent/issues/972
|
// Get torrent stats maybe panic, see https://github.com/anacrolix/torrent/issues/972
|
||||||
func (f *Fetcher) torrentStats() torrent.TorrentStats {
|
func (f *Fetcher) torrentStats() torrent.TorrentStats {
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
@@ -359,17 +361,34 @@ func (f *Fetcher) addTorrent(req *base.Request, fromUpload bool) (err error) {
|
|||||||
schema := util.ParseSchema(req.URL)
|
schema := util.ParseSchema(req.URL)
|
||||||
if schema == "MAGNET" {
|
if schema == "MAGNET" {
|
||||||
f.torrent, err = client.AddMagnet(req.URL)
|
f.torrent, err = client.AddMagnet(req.URL)
|
||||||
} else if schema == "DATA" {
|
} else {
|
||||||
_, data := util.ParseDataUri(req.URL)
|
var reader io.Reader
|
||||||
buf := bytes.NewBuffer(data)
|
if schema == "FILE" {
|
||||||
|
fileUrl, _ := url.Parse(req.URL)
|
||||||
|
filePath := fileUrl.Path[1:]
|
||||||
|
reader, err = os.Open(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else if schema == "DATA" {
|
||||||
|
_, data := util.ParseDataUri(req.URL)
|
||||||
|
reader = bytes.NewBuffer(data)
|
||||||
|
} else {
|
||||||
|
reader, err = os.Open(req.URL)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer reader.(io.Closer).Close()
|
||||||
|
}
|
||||||
|
|
||||||
var metaInfo *metainfo.MetaInfo
|
var metaInfo *metainfo.MetaInfo
|
||||||
metaInfo, err = metainfo.Load(buf)
|
metaInfo, err = metainfo.Load(reader)
|
||||||
if err != nil {
|
// Hotfix for https://github.com/anacrolix/torrent/issues/992, ignore "expected EOF" error
|
||||||
|
// TODO remove this after the issue is fixed
|
||||||
|
if err != nil && !strings.Contains(err.Error(), "expected EOF") {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
f.torrent, err = client.AddTorrent(metaInfo)
|
f.torrent, err = client.AddTorrent(metaInfo)
|
||||||
} else {
|
|
||||||
f.torrent, err = client.AddTorrentFromFile(req.URL)
|
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
gohttp "net/http"
|
gohttp "net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
@@ -96,7 +97,7 @@ func doResolve(t *testing.T, fetcher fetcher.Fetcher) {
|
|||||||
Hash: "8a55cfbd5ca5d11507364765936c4f9e55b253ed",
|
Hash: "8a55cfbd5ca5d11507364765936c4f9e55b253ed",
|
||||||
}
|
}
|
||||||
if !reflect.DeepEqual(want, fetcher.Meta().Res) {
|
if !reflect.DeepEqual(want, fetcher.Meta().Res) {
|
||||||
t.Errorf("Resolve() got = %v, want %v", fetcher.Meta().Res, want)
|
t.Errorf("Resolve Single File Resolve() got = %v, want %v", fetcher.Meta().Res, want)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -136,10 +137,29 @@ func doResolve(t *testing.T, fetcher fetcher.Fetcher) {
|
|||||||
Hash: "ccbc92b0cd8deec16a2ef4be242a8c9243b1cedb",
|
Hash: "ccbc92b0cd8deec16a2ef4be242a8c9243b1cedb",
|
||||||
}
|
}
|
||||||
if !reflect.DeepEqual(want, fetcher.Meta().Res) {
|
if !reflect.DeepEqual(want, fetcher.Meta().Res) {
|
||||||
t.Errorf("Resolve() got = %v, want %v", fetcher.Meta().Res, want)
|
t.Errorf("Resolve Multi Files Resolve() got = %v, want %v", fetcher.Meta().Res, want)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("Resolve Unclean Torrent", func(t *testing.T) {
|
||||||
|
err := fetcher.Resolve(&base.Request{
|
||||||
|
URL: "./testdata/test.unclean.torrent",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Resolve Unclean Torrent Resolve() got = %v, want nil", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Resolve file scheme Torrent", func(t *testing.T) {
|
||||||
|
file, _ := filepath.Abs("./testdata/test.unclean.torrent")
|
||||||
|
uri := "file:///" + file
|
||||||
|
err := fetcher.Resolve(&base.Request{
|
||||||
|
URL: uri,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Resolve file scheme Torrent Resolve() got = %v, want nil", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFetcherManager_ParseName(t *testing.T) {
|
func TestFetcherManager_ParseName(t *testing.T) {
|
||||||
|
|||||||
Binary file not shown.
@@ -8,6 +8,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"mime"
|
"mime"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/cookiejar"
|
"net/http/cookiejar"
|
||||||
"net/url"
|
"net/url"
|
||||||
@@ -25,6 +26,9 @@ import (
|
|||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const connectTimeout = 15 * time.Second
|
||||||
|
const readTimeout = 15 * time.Second
|
||||||
|
|
||||||
type RequestError struct {
|
type RequestError struct {
|
||||||
Code int
|
Code int
|
||||||
Msg string
|
Msg string
|
||||||
@@ -103,9 +107,9 @@ func (f *Fetcher) Resolve(req *base.Request) error {
|
|||||||
if base.HttpCodePartialContent == httpResp.StatusCode || (base.HttpCodeOK == httpResp.StatusCode && httpResp.Header.Get(base.HttpHeaderAcceptRanges) == base.HttpHeaderBytes && strings.HasPrefix(httpResp.Header.Get(base.HttpHeaderContentRange), base.HttpHeaderBytes)) {
|
if base.HttpCodePartialContent == httpResp.StatusCode || (base.HttpCodeOK == httpResp.StatusCode && httpResp.Header.Get(base.HttpHeaderAcceptRanges) == base.HttpHeaderBytes && strings.HasPrefix(httpResp.Header.Get(base.HttpHeaderContentRange), base.HttpHeaderBytes)) {
|
||||||
// response 206 status code, support breakpoint continuation
|
// response 206 status code, support breakpoint continuation
|
||||||
res.Range = true
|
res.Range = true
|
||||||
// 解析资源大小: bytes 0-1000/1001 => 1001
|
// parse content length from Content-Range header, eg: bytes 0-1000/1001 or bytes 0-0/*
|
||||||
contentTotal := path.Base(httpResp.Header.Get(base.HttpHeaderContentRange))
|
contentTotal := path.Base(httpResp.Header.Get(base.HttpHeaderContentRange))
|
||||||
if contentTotal != "" {
|
if contentTotal != "" && contentTotal != "*" {
|
||||||
parse, err := strconv.ParseInt(contentTotal, 10, 64)
|
parse, err := strconv.ParseInt(contentTotal, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -250,23 +254,42 @@ func (f *Fetcher) Wait() (err error) {
|
|||||||
return <-f.doneCh
|
return <-f.doneCh
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type fetchResult struct {
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
func (f *Fetcher) fetch() {
|
func (f *Fetcher) fetch() {
|
||||||
var ctx context.Context
|
var ctx context.Context
|
||||||
ctx, f.cancel = context.WithCancel(context.Background())
|
ctx, f.cancel = context.WithCancel(context.Background())
|
||||||
f.eg, _ = errgroup.WithContext(ctx)
|
f.eg, _ = errgroup.WithContext(ctx)
|
||||||
|
chunkErrs := make([]error, len(f.chunks))
|
||||||
for i := 0; i < len(f.chunks); i++ {
|
for i := 0; i < len(f.chunks); i++ {
|
||||||
i := i
|
i := i
|
||||||
f.eg.Go(func() error {
|
f.eg.Go(func() error {
|
||||||
return f.fetchChunk(i, ctx)
|
err := f.fetchChunk(i, ctx)
|
||||||
|
// if canceled, fail fast
|
||||||
|
if errors.Is(err, context.Canceled) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
chunkErrs[i] = err
|
||||||
|
return nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
err := f.eg.Wait()
|
err := f.eg.Wait()
|
||||||
// check if canceled
|
// error returned only if canceled, just return
|
||||||
if errors.Is(err, context.Canceled) {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// check all fetch results, if any error, return
|
||||||
|
for _, chunkErr := range chunkErrs {
|
||||||
|
if chunkErr != nil {
|
||||||
|
err = chunkErr
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
f.file.Close()
|
f.file.Close()
|
||||||
// Update file last modified time
|
// Update file last modified time
|
||||||
if f.config.UseServerCtime && f.meta.Res.Files[0].Ctime != nil {
|
if f.config.UseServerCtime && f.meta.Res.Files[0].Ctime != nil {
|
||||||
@@ -289,24 +312,11 @@ func (f *Fetcher) fetchChunk(index int, ctx context.Context) (err error) {
|
|||||||
for {
|
for {
|
||||||
// if chunk is completed, return
|
// if chunk is completed, return
|
||||||
if f.meta.Res.Range && chunk.Downloaded >= chunk.End-chunk.Begin+1 {
|
if f.meta.Res.Range && chunk.Downloaded >= chunk.End-chunk.Begin+1 {
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if chunk.retryTimes >= maxRetries {
|
if chunk.retryTimes >= maxRetries {
|
||||||
if !f.meta.Res.Range {
|
return
|
||||||
return
|
|
||||||
}
|
|
||||||
// check if all failed
|
|
||||||
allFailed := true
|
|
||||||
for _, c := range f.chunks {
|
|
||||||
if chunk.Downloaded < chunk.End-chunk.Begin+1 && c.retryTimes < maxRetries {
|
|
||||||
allFailed = false
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if allFailed {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -333,10 +343,9 @@ func (f *Fetcher) fetchChunk(index int, ctx context.Context) (err error) {
|
|||||||
err = NewRequestError(resp.StatusCode, resp.Status)
|
err = NewRequestError(resp.StatusCode, resp.Status)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// Http request success, reset retry times
|
reader := NewTimeoutReader(resp.Body, readTimeout)
|
||||||
chunk.retryTimes = 0
|
|
||||||
for {
|
for {
|
||||||
n, err := resp.Body.Read(buf)
|
n, err := reader.Read(buf)
|
||||||
if n > 0 {
|
if n > 0 {
|
||||||
_, err := f.file.WriteAt(buf[:n], chunk.Begin+chunk.Downloaded)
|
_, err := f.file.WriteAt(buf[:n], chunk.Begin+chunk.Downloaded)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -351,7 +360,6 @@ func (f *Fetcher) fetchChunk(index int, ctx context.Context) (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
}()
|
}()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// If canceled, do not retry
|
// If canceled, do not retry
|
||||||
@@ -419,6 +427,10 @@ func (f *Fetcher) buildRequest(ctx context.Context, req *base.Request) (httpReq
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
httpReq.Header = headers
|
httpReq.Header = headers
|
||||||
|
// Override Host header
|
||||||
|
if host := headers.Get(base.HttpHeaderHost); host != "" {
|
||||||
|
httpReq.Host = host
|
||||||
|
}
|
||||||
return httpReq, nil
|
return httpReq, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -452,6 +464,9 @@ func (f *Fetcher) splitChunk() (chunks []*chunk) {
|
|||||||
|
|
||||||
func (f *Fetcher) buildClient() *http.Client {
|
func (f *Fetcher) buildClient() *http.Client {
|
||||||
transport := &http.Transport{
|
transport := &http.Transport{
|
||||||
|
DialContext: (&net.Dialer{
|
||||||
|
Timeout: connectTimeout,
|
||||||
|
}).DialContext,
|
||||||
Proxy: f.ctl.GetProxy(f.meta.Req.Proxy),
|
Proxy: f.ctl.GetProxy(f.meta.Req.Proxy),
|
||||||
TLSClientConfig: &tls.Config{
|
TLSClientConfig: &tls.Config{
|
||||||
InsecureSkipVerify: f.meta.Req.SkipVerifyCert,
|
InsecureSkipVerify: f.meta.Req.SkipVerifyCert,
|
||||||
@@ -499,7 +514,7 @@ func (fm *FetcherManager) ParseName(u string) string {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
// get filePath by URL
|
// Get filePath by URL
|
||||||
name = path.Base(url.Path)
|
name = path.Base(url.Path)
|
||||||
// If file name is empty, use host name
|
// If file name is empty, use host name
|
||||||
if name == "" || name == "/" || name == "." {
|
if name == "" || name == "/" || name == "." {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
gohttp "net/http"
|
gohttp "net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -59,6 +60,21 @@ func TestFetcher_Resolve(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFetcher_ResolveWithHostHeader(t *testing.T) {
|
||||||
|
fetcher := buildFetcher()
|
||||||
|
err := fetcher.Resolve(&base.Request{
|
||||||
|
URL: "https://bing.com",
|
||||||
|
Extra: &http.ReqExtra{
|
||||||
|
Header: map[string]string{
|
||||||
|
"Host": "test",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err == nil || !strings.Contains(err.Error(), "400") {
|
||||||
|
t.Errorf("Resolve() got = %v, want %v", err, "400")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func testResolve(startTestServer func() net.Listener, path string, want *base.Resource, t *testing.T) {
|
func testResolve(startTestServer func() net.Listener, path string, want *base.Resource, t *testing.T) {
|
||||||
listener := startTestServer()
|
listener := startTestServer()
|
||||||
defer listener.Close()
|
defer listener.Close()
|
||||||
@@ -132,6 +148,14 @@ func TestFetcher_DownloadLimit(t *testing.T) {
|
|||||||
downloadNormal(listener, 8, t)
|
downloadNormal(listener, 8, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFetcher_DownloadResponseBodyReadTimeout(t *testing.T) {
|
||||||
|
listener := test.StartTestLimitServer(16, readTimeout.Milliseconds()+5000)
|
||||||
|
defer listener.Close()
|
||||||
|
|
||||||
|
downloadError(listener, 1, t)
|
||||||
|
downloadError(listener, 4, t)
|
||||||
|
}
|
||||||
|
|
||||||
func TestFetcher_DownloadResume(t *testing.T) {
|
func TestFetcher_DownloadResume(t *testing.T) {
|
||||||
listener := test.StartTestFileServer()
|
listener := test.StartTestFileServer()
|
||||||
defer listener.Close()
|
defer listener.Close()
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TimeoutReader struct {
|
||||||
|
reader io.Reader
|
||||||
|
timeout time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTimeoutReader(r io.Reader, timeout time.Duration) *TimeoutReader {
|
||||||
|
return &TimeoutReader{
|
||||||
|
reader: r,
|
||||||
|
timeout: timeout,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tr *TimeoutReader) Read(p []byte) (n int, err error) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), tr.timeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
done := make(chan struct{})
|
||||||
|
var readErr error
|
||||||
|
var bytesRead int
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
bytesRead, readErr = tr.reader.Read(p)
|
||||||
|
close(done)
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-done:
|
||||||
|
return bytesRead, readErr
|
||||||
|
case <-ctx.Done():
|
||||||
|
return 0, ctx.Err()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTimeoutReader_Read(t *testing.T) {
|
||||||
|
data := []byte("Hello, World!")
|
||||||
|
reader := bytes.NewReader(data)
|
||||||
|
timeoutReader := NewTimeoutReader(reader, 1*time.Second)
|
||||||
|
|
||||||
|
buf := make([]byte, len(data))
|
||||||
|
n, err := timeoutReader.Read(buf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("expected no error, got %v", err)
|
||||||
|
}
|
||||||
|
if n != len(data) {
|
||||||
|
t.Fatalf("expected to read %d bytes, read %d", len(data), n)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(buf, data) {
|
||||||
|
t.Fatalf("expected %s, got %s", data, buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTimeoutReader_ReadTimeout(t *testing.T) {
|
||||||
|
reader := &slowReader{delay: 2 * time.Second}
|
||||||
|
timeoutReader := NewTimeoutReader(reader, 1*time.Second)
|
||||||
|
|
||||||
|
buf := make([]byte, 8192)
|
||||||
|
_, err := timeoutReader.Read(buf)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected timeout error, got nil")
|
||||||
|
}
|
||||||
|
if !errors.Is(err, context.DeadlineExceeded) {
|
||||||
|
t.Fatalf("expected %v, got %v", context.DeadlineExceeded, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type slowReader struct {
|
||||||
|
delay time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sr *slowReader) Read(p []byte) (n int, err error) {
|
||||||
|
time.Sleep(sr.delay)
|
||||||
|
return 0, io.EOF
|
||||||
|
}
|
||||||
+30
-19
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/GopeedLab/gopeed/pkg/base"
|
"github.com/GopeedLab/gopeed/pkg/base"
|
||||||
"github.com/armon/go-socks5"
|
"github.com/armon/go-socks5"
|
||||||
@@ -36,7 +37,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func StartTestFileServer() net.Listener {
|
func StartTestFileServer() net.Listener {
|
||||||
return startTestServer(func() http.Handler {
|
return startTestServer(func(sl *shutdownListener) http.Handler {
|
||||||
return http.FileServer(http.Dir(Dir))
|
return http.FileServer(http.Dir(Dir))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -52,7 +53,7 @@ func (s *SlowFileServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func StartTestSlowFileServer(delay time.Duration) net.Listener {
|
func StartTestSlowFileServer(delay time.Duration) net.Listener {
|
||||||
return startTestServer(func() http.Handler {
|
return startTestServer(func(sl *shutdownListener) http.Handler {
|
||||||
return &SlowFileServer{
|
return &SlowFileServer{
|
||||||
delay: delay,
|
delay: delay,
|
||||||
handler: http.FileServer(http.Dir(Dir)),
|
handler: http.FileServer(http.Dir(Dir)),
|
||||||
@@ -61,7 +62,7 @@ func StartTestSlowFileServer(delay time.Duration) net.Listener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func StartTestCustomServer() net.Listener {
|
func StartTestCustomServer() net.Listener {
|
||||||
return startTestServer(func() http.Handler {
|
return startTestServer(func(sl *shutdownListener) http.Handler {
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
mux.HandleFunc("/"+BuildName, func(writer http.ResponseWriter, request *http.Request) {
|
mux.HandleFunc("/"+BuildName, func(writer http.ResponseWriter, request *http.Request) {
|
||||||
file, err := os.Open(BuildFile)
|
file, err := os.Open(BuildFile)
|
||||||
@@ -88,7 +89,7 @@ func StartTestCustomServer() net.Listener {
|
|||||||
|
|
||||||
func StartTestRetryServer() net.Listener {
|
func StartTestRetryServer() net.Listener {
|
||||||
counter := 0
|
counter := 0
|
||||||
return startTestServer(func() http.Handler {
|
return startTestServer(func(sl *shutdownListener) http.Handler {
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
mux.HandleFunc("/"+BuildName, func(writer http.ResponseWriter, request *http.Request) {
|
mux.HandleFunc("/"+BuildName, func(writer http.ResponseWriter, request *http.Request) {
|
||||||
counter++
|
counter++
|
||||||
@@ -108,7 +109,7 @@ func StartTestRetryServer() net.Listener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func StartTestPostServer() net.Listener {
|
func StartTestPostServer() net.Listener {
|
||||||
return startTestServer(func() http.Handler {
|
return startTestServer(func(sl *shutdownListener) http.Handler {
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
mux.HandleFunc("/"+BuildName, func(writer http.ResponseWriter, request *http.Request) {
|
mux.HandleFunc("/"+BuildName, func(writer http.ResponseWriter, request *http.Request) {
|
||||||
if request.Method == "POST" && request.Header.Get("Authorization") != "" {
|
if request.Method == "POST" && request.Header.Get("Authorization") != "" {
|
||||||
@@ -132,7 +133,7 @@ func StartTestPostServer() net.Listener {
|
|||||||
|
|
||||||
func StartTestErrorServer() net.Listener {
|
func StartTestErrorServer() net.Listener {
|
||||||
counter := 0
|
counter := 0
|
||||||
return startTestServer(func() http.Handler {
|
return startTestServer(func(sl *shutdownListener) http.Handler {
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
mux.HandleFunc("/"+BuildName, func(writer http.ResponseWriter, request *http.Request) {
|
mux.HandleFunc("/"+BuildName, func(writer http.ResponseWriter, request *http.Request) {
|
||||||
counter++
|
counter++
|
||||||
@@ -149,7 +150,7 @@ func StartTestErrorServer() net.Listener {
|
|||||||
func StartTestLimitServer(maxConnections int32, delay int64) net.Listener {
|
func StartTestLimitServer(maxConnections int32, delay int64) net.Listener {
|
||||||
var connections atomic.Int32
|
var connections atomic.Int32
|
||||||
|
|
||||||
return startTestServer(func() http.Handler {
|
return startTestServer(func(sl *shutdownListener) http.Handler {
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
mux.HandleFunc("/"+BuildName, func(writer http.ResponseWriter, request *http.Request) {
|
mux.HandleFunc("/"+BuildName, func(writer http.ResponseWriter, request *http.Request) {
|
||||||
defer func() {
|
defer func() {
|
||||||
@@ -165,12 +166,14 @@ func StartTestLimitServer(maxConnections int32, delay int64) net.Listener {
|
|||||||
if r == "" {
|
if r == "" {
|
||||||
writer.Header().Set("Content-Length", fmt.Sprintf("%d", BuildSize))
|
writer.Header().Set("Content-Length", fmt.Sprintf("%d", BuildSize))
|
||||||
writer.WriteHeader(200)
|
writer.WriteHeader(200)
|
||||||
|
(writer.(http.Flusher)).Flush()
|
||||||
|
|
||||||
file, err := os.Open(BuildFile)
|
file, err := os.Open(BuildFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
slowCopy(writer, file, delay)
|
slowCopy(sl, writer, file, delay)
|
||||||
} else {
|
} else {
|
||||||
// split range
|
// split range
|
||||||
s := strings.Split(r, "=")
|
s := strings.Split(r, "=")
|
||||||
@@ -204,6 +207,8 @@ func StartTestLimitServer(maxConnections int32, delay int64) net.Listener {
|
|||||||
writer.Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", start, end, BuildSize))
|
writer.Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", start, end, BuildSize))
|
||||||
writer.Header().Set("Accept-Ranges", "bytes")
|
writer.Header().Set("Accept-Ranges", "bytes")
|
||||||
writer.WriteHeader(206)
|
writer.WriteHeader(206)
|
||||||
|
(writer.(http.Flusher)).Flush()
|
||||||
|
|
||||||
file, err := os.Open(BuildFile)
|
file, err := os.Open(BuildFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writer.WriteHeader(500)
|
writer.WriteHeader(500)
|
||||||
@@ -211,7 +216,7 @@ func StartTestLimitServer(maxConnections int32, delay int64) net.Listener {
|
|||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
file.Seek(start, 0)
|
file.Seek(start, 0)
|
||||||
slowCopyN(writer, file, end-start+1, delay)
|
slowCopyN(sl, writer, file, end-start+1, delay)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return mux
|
return mux
|
||||||
@@ -219,9 +224,12 @@ func StartTestLimitServer(maxConnections int32, delay int64) net.Listener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// slowCopyN copies n bytes from src to dst, speed limit is bytes per second
|
// slowCopyN copies n bytes from src to dst, speed limit is bytes per second
|
||||||
func slowCopy(dst io.Writer, src io.Reader, delay int64) (written int64, err error) {
|
func slowCopy(sl *shutdownListener, dst io.Writer, src io.Reader, delay int64) (written int64, err error) {
|
||||||
buf := make([]byte, 32*1024)
|
buf := make([]byte, 32*1024)
|
||||||
for {
|
for {
|
||||||
|
if sl.isShutdown {
|
||||||
|
return 0, errors.New("server shutdown")
|
||||||
|
}
|
||||||
nr, er := src.Read(buf)
|
nr, er := src.Read(buf)
|
||||||
if nr > 0 {
|
if nr > 0 {
|
||||||
nw, ew := dst.Write(buf[0:nr])
|
nw, ew := dst.Write(buf[0:nr])
|
||||||
@@ -250,8 +258,8 @@ func slowCopy(dst io.Writer, src io.Reader, delay int64) (written int64, err err
|
|||||||
return written, err
|
return written, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func slowCopyN(dst io.Writer, src io.Reader, n int64, delay int64) (written int64, err error) {
|
func slowCopyN(sl *shutdownListener, dst io.Writer, src io.Reader, n int64, delay int64) (written int64, err error) {
|
||||||
written, err = slowCopy(dst, io.LimitReader(src, n), delay)
|
written, err = slowCopy(sl, dst, io.LimitReader(src, n), delay)
|
||||||
if written == n {
|
if written == n {
|
||||||
return n, nil
|
return n, nil
|
||||||
}
|
}
|
||||||
@@ -262,7 +270,7 @@ func slowCopyN(dst io.Writer, src io.Reader, n int64, delay int64) (written int6
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func startTestServer(serverHandle func() http.Handler) net.Listener {
|
func startTestServer(serverHandle func(sl *shutdownListener) http.Handler) net.Listener {
|
||||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@@ -272,7 +280,7 @@ func startTestServer(serverHandle func() http.Handler) net.Listener {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
// 随机生成一个文件
|
// Write random data
|
||||||
l := int64(8192)
|
l := int64(8192)
|
||||||
buf := make([]byte, l)
|
buf := make([]byte, l)
|
||||||
size := int64(0)
|
size := int64(0)
|
||||||
@@ -289,21 +297,24 @@ func startTestServer(serverHandle func() http.Handler) net.Listener {
|
|||||||
size += l
|
size += l
|
||||||
}
|
}
|
||||||
server := &http.Server{}
|
server := &http.Server{}
|
||||||
server.Handler = serverHandle()
|
sl := &shutdownListener{
|
||||||
go server.Serve(listener)
|
|
||||||
|
|
||||||
return &shutdownListener{
|
|
||||||
server: server,
|
server: server,
|
||||||
Listener: listener,
|
Listener: listener,
|
||||||
}
|
}
|
||||||
|
server.Handler = serverHandle(sl)
|
||||||
|
go server.Serve(listener)
|
||||||
|
|
||||||
|
return sl
|
||||||
}
|
}
|
||||||
|
|
||||||
type shutdownListener struct {
|
type shutdownListener struct {
|
||||||
server *http.Server
|
server *http.Server
|
||||||
|
isShutdown bool
|
||||||
net.Listener
|
net.Listener
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *shutdownListener) Close() error {
|
func (c *shutdownListener) Close() error {
|
||||||
|
c.isShutdown = true
|
||||||
closeErr := c.server.Shutdown(context.Background())
|
closeErr := c.server.Shutdown(context.Background())
|
||||||
if err := ifExistAndRemove(BuildFile); err != nil {
|
if err := ifExistAndRemove(BuildFile); err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
|
|||||||
-400
@@ -1,400 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"github.com/GopeedLab/gopeed/pkg/api/model"
|
|
||||||
"github.com/GopeedLab/gopeed/pkg/base"
|
|
||||||
"github.com/GopeedLab/gopeed/pkg/download"
|
|
||||||
"github.com/GopeedLab/gopeed/pkg/util"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"reflect"
|
|
||||||
"runtime"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Instance struct {
|
|
||||||
startCfg *model.StartConfig
|
|
||||||
downloader *download.Downloader
|
|
||||||
|
|
||||||
httpLock sync.Mutex
|
|
||||||
srv *http.Server
|
|
||||||
listener net.Listener
|
|
||||||
}
|
|
||||||
|
|
||||||
func Create(startCfg *model.StartConfig) (*Instance, error) {
|
|
||||||
if startCfg == nil {
|
|
||||||
startCfg = &model.StartConfig{}
|
|
||||||
}
|
|
||||||
startCfg.Init()
|
|
||||||
|
|
||||||
i := &Instance{
|
|
||||||
startCfg: startCfg,
|
|
||||||
}
|
|
||||||
|
|
||||||
downloadCfg := &download.DownloaderConfig{
|
|
||||||
ProductionMode: startCfg.ProductionMode,
|
|
||||||
RefreshInterval: startCfg.RefreshInterval,
|
|
||||||
}
|
|
||||||
if startCfg.Storage == model.StorageBolt {
|
|
||||||
downloadCfg.Storage = download.NewBoltStorage(startCfg.StorageDir)
|
|
||||||
} else {
|
|
||||||
downloadCfg.Storage = download.NewMemStorage()
|
|
||||||
}
|
|
||||||
downloadCfg.StorageDir = startCfg.StorageDir
|
|
||||||
downloadCfg.Init()
|
|
||||||
downloader := download.NewDownloader(downloadCfg)
|
|
||||||
if err := downloader.Setup(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
i.downloader = downloader
|
|
||||||
i.httpLock = sync.Mutex{}
|
|
||||||
|
|
||||||
return i, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Instance) Listener(listener download.Listener) *model.Result[any] {
|
|
||||||
i.downloader.Listener(listener)
|
|
||||||
return model.NewNilResult()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Instance) StartHttp() *model.Result[int] {
|
|
||||||
i.httpLock.Lock()
|
|
||||||
defer i.httpLock.Unlock()
|
|
||||||
|
|
||||||
return i.startHttp()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Instance) startHttp() *model.Result[int] {
|
|
||||||
httpCfg := i.getHttpConfig()
|
|
||||||
if httpCfg == nil || !httpCfg.Enable {
|
|
||||||
return model.NewErrorResult[int]("HTTP API server not enabled")
|
|
||||||
}
|
|
||||||
if i.srv != nil {
|
|
||||||
return model.NewErrorResult[int]("HTTP API server already started")
|
|
||||||
}
|
|
||||||
|
|
||||||
port, start, err := ListenHttp(httpCfg, i)
|
|
||||||
if err != nil {
|
|
||||||
return model.NewErrorResult[int](err.Error())
|
|
||||||
}
|
|
||||||
httpCfg.RunningPort = port
|
|
||||||
go start()
|
|
||||||
return model.NewOkResult(port)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Instance) getHttpConfig() *base.DownloaderHttpConfig {
|
|
||||||
var httpCfg *base.DownloaderHttpConfig
|
|
||||||
if i.startCfg.DownloadConfig != nil && i.startCfg.DownloadConfig.Http != nil {
|
|
||||||
httpCfg = i.startCfg.DownloadConfig.Http
|
|
||||||
} else {
|
|
||||||
cfg, _ := i.downloader.GetConfig()
|
|
||||||
httpCfg = cfg.Http
|
|
||||||
}
|
|
||||||
return httpCfg
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Instance) StopHttp() *model.Result[any] {
|
|
||||||
i.httpLock.Lock()
|
|
||||||
defer i.httpLock.Unlock()
|
|
||||||
|
|
||||||
return i.stopHttp()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Instance) stopHttp() *model.Result[any] {
|
|
||||||
if i.srv != nil {
|
|
||||||
if err := i.srv.Shutdown(context.Background()); err != nil {
|
|
||||||
i.downloader.Logger.Warn().Err(err).Msg("shutdown http server failed")
|
|
||||||
}
|
|
||||||
i.srv = nil
|
|
||||||
i.listener = nil
|
|
||||||
}
|
|
||||||
httpCfg := i.getHttpConfig()
|
|
||||||
if httpCfg != nil {
|
|
||||||
httpCfg.RunningPort = 0
|
|
||||||
}
|
|
||||||
return model.NewNilResult()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Instance) RestartHttp() *model.Result[int] {
|
|
||||||
i.httpLock.Lock()
|
|
||||||
defer i.httpLock.Unlock()
|
|
||||||
|
|
||||||
i.stopHttp()
|
|
||||||
return i.startHttp()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Instance) Info() *model.Result[map[string]any] {
|
|
||||||
return model.NewOkResult(map[string]any{
|
|
||||||
"version": base.Version,
|
|
||||||
"runtime": runtime.Version(),
|
|
||||||
"os": runtime.GOOS,
|
|
||||||
"arch": runtime.GOARCH,
|
|
||||||
"inDocker": base.InDocker == "true",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Instance) Resolve(req *base.Request) *model.Result[*download.ResolveResult] {
|
|
||||||
rr, err := i.downloader.Resolve(req)
|
|
||||||
if err != nil {
|
|
||||||
return model.NewErrorResult[*download.ResolveResult](err.Error())
|
|
||||||
}
|
|
||||||
return model.NewOkResult(rr)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Instance) CreateTask(req *model.CreateTask) *model.Result[string] {
|
|
||||||
var (
|
|
||||||
taskId string
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
if req.Rid != "" {
|
|
||||||
taskId, err = i.downloader.Create(req.Rid, req.Opt)
|
|
||||||
} else if req.Req != nil {
|
|
||||||
taskId, err = i.downloader.CreateDirect(req.Req, req.Opt)
|
|
||||||
} else {
|
|
||||||
return model.NewErrorResult[string]("param invalid: rid or req", model.CodeInvalidParam)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return model.NewErrorResult[string](err.Error())
|
|
||||||
}
|
|
||||||
return model.NewOkResult(taskId)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Instance) CreateTaskBatch(req *model.CreateTaskBatch) *model.Result[[]string] {
|
|
||||||
if len(req.Reqs) == 0 {
|
|
||||||
return model.NewErrorResult[[]string]("param invalid: reqs", model.CodeInvalidParam)
|
|
||||||
}
|
|
||||||
taskIds, err := i.downloader.CreateDirectBatch(req.Reqs, req.Opt)
|
|
||||||
if err != nil {
|
|
||||||
return model.NewErrorResult[[]string](err.Error())
|
|
||||||
}
|
|
||||||
return model.NewOkResult(taskIds)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Instance) PauseTask(taskId string) *model.Result[any] {
|
|
||||||
err := i.downloader.Pause(&download.TaskFilter{
|
|
||||||
ID: []string{taskId},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return model.NewErrorResult[any](err.Error())
|
|
||||||
}
|
|
||||||
return model.NewNilResult()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Instance) PauseTasks(filter *download.TaskFilter) *model.Result[any] {
|
|
||||||
err := i.downloader.Pause(filter)
|
|
||||||
if err != nil {
|
|
||||||
return model.NewErrorResult[any](err.Error())
|
|
||||||
}
|
|
||||||
return model.NewNilResult()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Instance) ContinueTask(taskId string) *model.Result[any] {
|
|
||||||
err := i.downloader.Continue(&download.TaskFilter{
|
|
||||||
ID: []string{taskId},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return model.NewErrorResult[any](err.Error())
|
|
||||||
}
|
|
||||||
return model.NewNilResult()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Instance) ContinueTasks(filter *download.TaskFilter) *model.Result[any] {
|
|
||||||
err := i.downloader.Continue(filter)
|
|
||||||
if err != nil {
|
|
||||||
return model.NewErrorResult[any](err.Error())
|
|
||||||
}
|
|
||||||
return model.NewNilResult()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Instance) DeleteTask(taskId string, force bool) *model.Result[any] {
|
|
||||||
err := i.downloader.Delete(&download.TaskFilter{
|
|
||||||
ID: []string{taskId},
|
|
||||||
}, force)
|
|
||||||
if err != nil {
|
|
||||||
return model.NewErrorResult[any](err.Error())
|
|
||||||
}
|
|
||||||
return model.NewNilResult()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Instance) DeleteTasks(filter *download.TaskFilter, force bool) *model.Result[any] {
|
|
||||||
err := i.downloader.Delete(filter, force)
|
|
||||||
if err != nil {
|
|
||||||
return model.NewErrorResult[any](err.Error())
|
|
||||||
}
|
|
||||||
return model.NewNilResult()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Instance) GetTask(taskId string) *model.Result[*download.Task] {
|
|
||||||
if taskId == "" {
|
|
||||||
return model.NewErrorResult[*download.Task]("param invalid: id", model.CodeInvalidParam)
|
|
||||||
}
|
|
||||||
task := i.downloader.GetTask(taskId)
|
|
||||||
if task == nil {
|
|
||||||
return model.NewErrorResult[*download.Task]("task not found", model.CodeTaskNotFound)
|
|
||||||
}
|
|
||||||
return model.NewOkResult(task)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Instance) GetTasks(filter *download.TaskFilter) *model.Result[[]*download.Task] {
|
|
||||||
return model.NewOkResult(i.downloader.GetTasksByFilter(filter))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Instance) GetTaskStats(taskId string) *model.Result[any] {
|
|
||||||
stats, err := i.downloader.GetTaskStats(taskId)
|
|
||||||
if err != nil {
|
|
||||||
return model.NewErrorResult[any](err.Error())
|
|
||||||
}
|
|
||||||
return model.NewOkResult(stats)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Instance) GetConfig() *model.Result[*base.DownloaderStoreConfig] {
|
|
||||||
config, err := i.downloader.GetConfig()
|
|
||||||
if err != nil {
|
|
||||||
return model.NewErrorResult[*base.DownloaderStoreConfig](err.Error())
|
|
||||||
}
|
|
||||||
return model.NewOkResult(config)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Instance) PutConfig(cfg *base.DownloaderStoreConfig) *model.Result[any] {
|
|
||||||
err := i.downloader.PutConfig(cfg)
|
|
||||||
if err != nil {
|
|
||||||
return model.NewErrorResult[any](err.Error())
|
|
||||||
}
|
|
||||||
return model.NewNilResult()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Instance) InstallExtension(req *model.InstallExtension) *model.Result[string] {
|
|
||||||
var (
|
|
||||||
installedExt *download.Extension
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
if req.DevMode {
|
|
||||||
installedExt, err = i.downloader.InstallExtensionByFolder(req.URL, true)
|
|
||||||
} else {
|
|
||||||
installedExt, err = i.downloader.InstallExtensionByGit(req.URL)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return model.NewErrorResult[string](err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
return model.NewOkResult(installedExt.Identity)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Instance) GetExtension(identity string) *model.Result[*download.Extension] {
|
|
||||||
extension, err := i.downloader.GetExtension(identity)
|
|
||||||
if err != nil {
|
|
||||||
return model.NewErrorResult[*download.Extension](err.Error())
|
|
||||||
}
|
|
||||||
return model.NewOkResult(extension)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Instance) GetExtensions() *model.Result[[]*download.Extension] {
|
|
||||||
return model.NewOkResult(i.downloader.GetExtensions())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Instance) UpdateExtensionSettings(identity string, req *model.UpdateExtensionSettings) *model.Result[any] {
|
|
||||||
err := i.downloader.UpdateExtensionSettings(identity, req.Settings)
|
|
||||||
if err != nil {
|
|
||||||
return model.NewErrorResult[any](err.Error())
|
|
||||||
}
|
|
||||||
return model.NewNilResult()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Instance) SwitchExtension(identity string, req *model.SwitchExtension) *model.Result[any] {
|
|
||||||
err := i.downloader.SwitchExtension(identity, req.Status)
|
|
||||||
if err != nil {
|
|
||||||
return model.NewErrorResult[any](err.Error())
|
|
||||||
}
|
|
||||||
return model.NewNilResult()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Instance) DeleteExtension(identity string) *model.Result[any] {
|
|
||||||
err := i.downloader.DeleteExtension(identity)
|
|
||||||
if err != nil {
|
|
||||||
return model.NewErrorResult[any](err.Error())
|
|
||||||
}
|
|
||||||
return model.NewNilResult()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Instance) UpgradeCheckExtension(identity string) *model.Result[*model.UpgradeCheckExtensionResp] {
|
|
||||||
newVersion, err := i.downloader.UpgradeCheckExtension(identity)
|
|
||||||
if err != nil {
|
|
||||||
return model.NewErrorResult[*model.UpgradeCheckExtensionResp](err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
return model.NewOkResult(&model.UpgradeCheckExtensionResp{
|
|
||||||
NewVersion: newVersion,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Instance) UpgradeExtension(identity string) *model.Result[any] {
|
|
||||||
err := i.downloader.UpgradeExtension(identity)
|
|
||||||
if err != nil {
|
|
||||||
return model.NewErrorResult[any](err.Error())
|
|
||||||
}
|
|
||||||
return model.NewNilResult()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Instance) Close() *model.Result[any] {
|
|
||||||
i.StopHttp()
|
|
||||||
if i.downloader != nil {
|
|
||||||
if err := i.downloader.Close(); err != nil {
|
|
||||||
i.downloader.Logger.Warn().Err(err).Msg("close downloader failed")
|
|
||||||
}
|
|
||||||
i.downloader = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return model.NewNilResult()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Instance) Clear() *model.Result[any] {
|
|
||||||
if err := i.downloader.Clear(); err != nil {
|
|
||||||
return model.NewErrorResult[any](err.Error())
|
|
||||||
}
|
|
||||||
return model.NewNilResult()
|
|
||||||
}
|
|
||||||
|
|
||||||
type Request struct {
|
|
||||||
Method string `json:"method"`
|
|
||||||
Params []any `json:"params"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Invoke support dynamic call method
|
|
||||||
func Invoke(instance *Instance, request *Request) (ret any) {
|
|
||||||
defer func() {
|
|
||||||
if err := recover(); err != nil {
|
|
||||||
ret = model.NewErrorResult[any](fmt.Sprintf("%v", err))
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
method, args := request.Method, request.Params
|
|
||||||
dsType := reflect.ValueOf(instance)
|
|
||||||
fn := dsType.MethodByName(method)
|
|
||||||
numIn := fn.Type().NumIn()
|
|
||||||
in := make([]reflect.Value, numIn)
|
|
||||||
for i := 0; i < numIn; i++ {
|
|
||||||
paramType := fn.Type().In(i)
|
|
||||||
arg := args[i]
|
|
||||||
if arg == nil {
|
|
||||||
in[i] = reflect.Zero(fn.Type().In(i))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
var param reflect.Value
|
|
||||||
var paramPtr any
|
|
||||||
if paramType.Kind() == reflect.Ptr {
|
|
||||||
param = reflect.New(paramType.Elem())
|
|
||||||
paramPtr = param.Interface()
|
|
||||||
} else {
|
|
||||||
param = reflect.New(paramType).Elem()
|
|
||||||
paramPtr = param.Addr().Interface()
|
|
||||||
}
|
|
||||||
if err := util.MapToStruct(arg, paramPtr); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
in[i] = param
|
|
||||||
}
|
|
||||||
retVals := fn.Call(in)
|
|
||||||
return retVals[0].Interface()
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"github.com/GopeedLab/gopeed/pkg/api/model"
|
|
||||||
"github.com/GopeedLab/gopeed/pkg/download"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestInvoke(t *testing.T) {
|
|
||||||
doTestInvoke(t, "Info", []any{}, model.CodeOk)
|
|
||||||
doTestInvoke(t, "Info1", []any{}, model.CodeError)
|
|
||||||
doTestInvoke(t, "CreateTask", []any{nil}, model.CodeError)
|
|
||||||
doTestInvoke(t, "GetTasks", nil, model.CodeError)
|
|
||||||
doTestInvoke(t, "GetTasks", []any{}, model.CodeError)
|
|
||||||
doTestInvoke(t, "GetTasks", []any{"{abc:123"}, model.CodeError)
|
|
||||||
doTestInvoke(t, "GetTasks", []any{&download.TaskFilter{}}, model.CodeOk)
|
|
||||||
doTestInvoke(t, "SwitchExtension", []any{"test", &model.SwitchExtension{Status: true}}, model.CodeError)
|
|
||||||
}
|
|
||||||
|
|
||||||
func doTestInvoke(t *testing.T, method string, params []any, expectCode model.RespCode) {
|
|
||||||
instance, _ := Create(nil)
|
|
||||||
defer instance.Close()
|
|
||||||
|
|
||||||
result := Invoke(instance, &Request{
|
|
||||||
Method: method,
|
|
||||||
Params: params,
|
|
||||||
})
|
|
||||||
buf, err := json.Marshal(result)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var res model.Result[any]
|
|
||||||
if err := json.Unmarshal(buf, &res); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if res.Code != expectCode {
|
|
||||||
t.Fatalf("Invoke method [%s] failed, expect code [%d], got [%d], msg: %s", method, expectCode, res.Code, res.Msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-451
@@ -1,451 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"github.com/GopeedLab/gopeed/pkg/api/model"
|
|
||||||
"github.com/GopeedLab/gopeed/pkg/base"
|
|
||||||
"github.com/GopeedLab/gopeed/pkg/download"
|
|
||||||
"github.com/gorilla/handlers"
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
const instanceKey string = "apiInstance"
|
|
||||||
|
|
||||||
func ListenHttp(httpCfg *base.DownloaderHttpConfig, ai *Instance) (int, func(), error) {
|
|
||||||
var r = mux.NewRouter()
|
|
||||||
r.Methods(http.MethodGet).Path("/api/v1/info").HandlerFunc(Info)
|
|
||||||
r.Methods(http.MethodPost).Path("/api/v1/resolve").HandlerFunc(Resolve)
|
|
||||||
r.Methods(http.MethodPost).Path("/api/v1/tasks").HandlerFunc(CreateTask)
|
|
||||||
r.Methods(http.MethodPost).Path("/api/v1/tasks/batch").HandlerFunc(CreateTaskBatch)
|
|
||||||
r.Methods(http.MethodPut).Path("/api/v1/tasks/{id}/pause").HandlerFunc(PauseTask)
|
|
||||||
r.Methods(http.MethodPut).Path("/api/v1/tasks/pause").HandlerFunc(PauseTasks)
|
|
||||||
r.Methods(http.MethodPut).Path("/api/v1/tasks/{id}/continue").HandlerFunc(ContinueTask)
|
|
||||||
r.Methods(http.MethodPut).Path("/api/v1/tasks/continue").HandlerFunc(ContinueTasks)
|
|
||||||
r.Methods(http.MethodDelete).Path("/api/v1/tasks/{id}").HandlerFunc(DeleteTask)
|
|
||||||
r.Methods(http.MethodDelete).Path("/api/v1/tasks").HandlerFunc(DeleteTasks)
|
|
||||||
r.Methods(http.MethodGet).Path("/api/v1/tasks/{id}").HandlerFunc(GetTask)
|
|
||||||
r.Methods(http.MethodGet).Path("/api/v1/tasks").HandlerFunc(GetTasks)
|
|
||||||
r.Methods(http.MethodGet).Path("/api/v1/tasks/{id}/stats").HandlerFunc(GetTaskStats)
|
|
||||||
r.Methods(http.MethodGet).Path("/api/v1/config").HandlerFunc(GetConfig)
|
|
||||||
r.Methods(http.MethodPut).Path("/api/v1/config").HandlerFunc(PutConfig)
|
|
||||||
r.Methods(http.MethodPost).Path("/api/v1/extensions").HandlerFunc(InstallExtension)
|
|
||||||
r.Methods(http.MethodGet).Path("/api/v1/extensions").HandlerFunc(GetExtensions)
|
|
||||||
r.Methods(http.MethodGet).Path("/api/v1/extensions/{identity}").HandlerFunc(GetExtension)
|
|
||||||
r.Methods(http.MethodPut).Path("/api/v1/extensions/{identity}/settings").HandlerFunc(UpdateExtensionSettings)
|
|
||||||
r.Methods(http.MethodPut).Path("/api/v1/extensions/{identity}/switch").HandlerFunc(SwitchExtension)
|
|
||||||
r.Methods(http.MethodDelete).Path("/api/v1/extensions/{identity}").HandlerFunc(DeleteExtension)
|
|
||||||
r.Methods(http.MethodGet).Path("/api/v1/extensions/{identity}/upgrade").HandlerFunc(UpgradeCheckExtension)
|
|
||||||
r.Methods(http.MethodPost).Path("/api/v1/extensions/{identity}/upgrade").HandlerFunc(UpgradeExtension)
|
|
||||||
if ai.startCfg.WebEnable {
|
|
||||||
r.Path("/api/v1/proxy").HandlerFunc(DoProxy)
|
|
||||||
r.PathPrefix("/fs/tasks").Handler(http.FileServer(&taskFileSystem{
|
|
||||||
downloader: ai.downloader,
|
|
||||||
}))
|
|
||||||
r.PathPrefix("/fs/extensions").Handler(http.FileServer(&extensionFileSystem{
|
|
||||||
downloader: ai.downloader,
|
|
||||||
}))
|
|
||||||
r.PathPrefix("/").Handler(http.FileServer(http.FS(ai.startCfg.WebFS)))
|
|
||||||
}
|
|
||||||
|
|
||||||
r.Use(func(h http.Handler) http.Handler {
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
ctx := context.WithValue(r.Context(), instanceKey, ai)
|
|
||||||
h.ServeHTTP(w, r.WithContext(ctx))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
if httpCfg.ApiToken != "" || (ai.startCfg.WebEnable && ai.startCfg.WebBasicAuth != nil) {
|
|
||||||
r.Use(func(h http.Handler) http.Handler {
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if httpCfg.ApiToken != "" && r.Header.Get("X-Api-Token") == httpCfg.ApiToken {
|
|
||||||
h.ServeHTTP(w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if ai.startCfg.WebEnable && ai.startCfg.WebBasicAuth != nil {
|
|
||||||
if r.Header.Get("Authorization") == ai.startCfg.WebBasicAuth.Authorization() {
|
|
||||||
h.ServeHTTP(w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
w.Header().Set("WWW-Authenticate", "Basic realm=\"gopeed web\"")
|
|
||||||
}
|
|
||||||
WriteStatusJson(w, http.StatusUnauthorized, model.NewErrorResult[any]("unauthorized", model.CodeUnauthorized))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// recover panic
|
|
||||||
r.Use(func(h http.Handler) http.Handler {
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
defer func() {
|
|
||||||
if v := recover(); v != nil {
|
|
||||||
err := errors.WithStack(fmt.Errorf("%v", v))
|
|
||||||
ai.downloader.Logger.Error().Stack().Err(err).Msgf("http server panic: %s %s", r.Method, r.RequestURI)
|
|
||||||
WriteJson(w, model.NewErrorResult[any](err.Error(), model.CodeError))
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
h.ServeHTTP(w, r)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
srv := &http.Server{Handler: handlers.CORS(
|
|
||||||
handlers.AllowedHeaders([]string{"Content-Type", "X-Api-Token", "X-Target-Uri"}),
|
|
||||||
handlers.AllowedMethods([]string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"}),
|
|
||||||
handlers.AllowedOrigins([]string{"*"}),
|
|
||||||
)(r)}
|
|
||||||
|
|
||||||
listener, err := net.Listen("tcp", net.JoinHostPort(httpCfg.Host, fmt.Sprintf("%d", httpCfg.Port)))
|
|
||||||
if err != nil {
|
|
||||||
return 0, nil, err
|
|
||||||
}
|
|
||||||
ai.srv = srv
|
|
||||||
ai.listener = listener
|
|
||||||
|
|
||||||
return listener.Addr().(*net.TCPAddr).Port, func() {
|
|
||||||
if err := srv.Serve(listener); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
|
||||||
ai.downloader.Logger.Error().Err(err).Msg("http server listen error")
|
|
||||||
}
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getInstance(r *http.Request) *Instance {
|
|
||||||
return r.Context().Value(instanceKey).(*Instance)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Info(w http.ResponseWriter, r *http.Request) {
|
|
||||||
WriteJson(w, getInstance(r).Info())
|
|
||||||
}
|
|
||||||
|
|
||||||
func Resolve(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var req base.Request
|
|
||||||
if ReadJson(r, w, &req) {
|
|
||||||
WriteJson(w, getInstance(r).Resolve(&req))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func CreateTask(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var req model.CreateTask
|
|
||||||
if ReadJson(r, w, &req) {
|
|
||||||
WriteJson(w, getInstance(r).CreateTask(&req))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func CreateTaskBatch(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var req model.CreateTaskBatch
|
|
||||||
if ReadJson(r, w, &req) {
|
|
||||||
WriteJson(w, getInstance(r).CreateTaskBatch(&req))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func PauseTask(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var taskId string
|
|
||||||
if !parseTaskId(r, w, &taskId) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
WriteJson(w, getInstance(r).PauseTask(taskId))
|
|
||||||
}
|
|
||||||
|
|
||||||
func PauseTasks(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var filter download.TaskFilter
|
|
||||||
if !parseFilter(r, w, &filter) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
WriteJson(w, getInstance(r).PauseTasks(&filter))
|
|
||||||
}
|
|
||||||
|
|
||||||
func ContinueTask(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var taskId string
|
|
||||||
if !parseTaskId(r, w, &taskId) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
WriteJson(w, getInstance(r).ContinueTask(taskId))
|
|
||||||
}
|
|
||||||
|
|
||||||
func ContinueTasks(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var filter download.TaskFilter
|
|
||||||
if !parseFilter(r, w, &filter) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
WriteJson(w, getInstance(r).ContinueTasks(&filter))
|
|
||||||
}
|
|
||||||
|
|
||||||
func DeleteTask(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var taskId string
|
|
||||||
if !parseTaskId(r, w, &taskId) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
force := r.FormValue("force")
|
|
||||||
WriteJson(w, getInstance(r).DeleteTask(taskId, force == "true"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func DeleteTasks(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var filter download.TaskFilter
|
|
||||||
if !parseFilter(r, w, &filter) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
force := r.FormValue("force")
|
|
||||||
WriteJson(w, getInstance(r).DeleteTasks(&filter, force == "true"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetTask(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var taskId string
|
|
||||||
if !parseTaskId(r, w, &taskId) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
WriteJson(w, getInstance(r).GetTask(taskId))
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetTasks(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var filter download.TaskFilter
|
|
||||||
if !parseFilter(r, w, &filter) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
WriteJson(w, getInstance(r).GetTasks(&filter))
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetTaskStats(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var taskId string
|
|
||||||
if !parseTaskId(r, w, &taskId) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
WriteJson(w, getInstance(r).GetTaskStats(taskId))
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetConfig(w http.ResponseWriter, r *http.Request) {
|
|
||||||
WriteJson(w, getInstance(r).GetConfig())
|
|
||||||
}
|
|
||||||
|
|
||||||
func PutConfig(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var cfg base.DownloaderStoreConfig
|
|
||||||
if !ReadJson(r, w, &cfg) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
WriteJson(w, getInstance(r).PutConfig(&cfg))
|
|
||||||
}
|
|
||||||
|
|
||||||
func InstallExtension(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var req model.InstallExtension
|
|
||||||
if !ReadJson(r, w, &req) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
WriteJson(w, getInstance(r).InstallExtension(&req))
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetExtensions(w http.ResponseWriter, r *http.Request) {
|
|
||||||
WriteJson(w, getInstance(r).GetExtensions())
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetExtension(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var identity string
|
|
||||||
if !parseIdentity(r, w, &identity) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
WriteJson(w, getInstance(r).GetExtension(identity))
|
|
||||||
}
|
|
||||||
|
|
||||||
func UpdateExtensionSettings(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var identity string
|
|
||||||
if !parseIdentity(r, w, &identity) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var req model.UpdateExtensionSettings
|
|
||||||
if !ReadJson(r, w, &req) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
WriteJson(w, getInstance(r).UpdateExtensionSettings(identity, &req))
|
|
||||||
}
|
|
||||||
|
|
||||||
func SwitchExtension(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var identity string
|
|
||||||
if !parseIdentity(r, w, &identity) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var switchExtension model.SwitchExtension
|
|
||||||
if !ReadJson(r, w, &switchExtension) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
WriteJson(w, getInstance(r).SwitchExtension(identity, &switchExtension))
|
|
||||||
}
|
|
||||||
|
|
||||||
func DeleteExtension(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var identity string
|
|
||||||
if !parseIdentity(r, w, &identity) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
WriteJson(w, getInstance(r).DeleteExtension(identity))
|
|
||||||
}
|
|
||||||
|
|
||||||
func UpgradeCheckExtension(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var identity string
|
|
||||||
if !parseIdentity(r, w, &identity) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
WriteJson(w, getInstance(r).UpgradeCheckExtension(identity))
|
|
||||||
}
|
|
||||||
|
|
||||||
func UpgradeExtension(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var identity string
|
|
||||||
if !parseIdentity(r, w, &identity) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
WriteJson(w, getInstance(r).UpgradeExtension(identity))
|
|
||||||
}
|
|
||||||
|
|
||||||
func DoProxy(w http.ResponseWriter, r *http.Request) {
|
|
||||||
target := r.Header.Get("X-Target-Uri")
|
|
||||||
if target == "" {
|
|
||||||
writeError(w, "param invalid: X-Target-Uri")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
targetUrl, err := url.Parse(target)
|
|
||||||
if err != nil {
|
|
||||||
writeError(w, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
r.RequestURI = ""
|
|
||||||
r.URL = targetUrl
|
|
||||||
r.Host = targetUrl.Host
|
|
||||||
resp, err := http.DefaultClient.Do(r)
|
|
||||||
if err != nil {
|
|
||||||
writeError(w, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
for k, vv := range resp.Header {
|
|
||||||
for _, v := range vv {
|
|
||||||
w.Header().Set(k, v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
w.WriteHeader(resp.StatusCode)
|
|
||||||
buf, err := io.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
writeError(w, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
w.Write(buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
func parsePathParam(r *http.Request, w http.ResponseWriter, name string, value *string) bool {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
*value = vars[name]
|
|
||||||
if *value == "" {
|
|
||||||
WriteJson(w, model.NewErrorResult[any]("param invalid: id", model.CodeInvalidParam))
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseTaskId(r *http.Request, w http.ResponseWriter, taskId *string) bool {
|
|
||||||
return parsePathParam(r, w, "id", taskId)
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseIdentity(r *http.Request, w http.ResponseWriter, identity *string) bool {
|
|
||||||
return parsePathParam(r, w, "identity", identity)
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseFilter(r *http.Request, w http.ResponseWriter, filter *download.TaskFilter) bool {
|
|
||||||
if err := r.ParseForm(); err != nil {
|
|
||||||
WriteJson(w, model.NewErrorResult[any](err.Error()))
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
filter.ID = r.Form["id"]
|
|
||||||
filter.Status = convertStatues(r.Form["status"])
|
|
||||||
filter.NotStatus = convertStatues(r.Form["notStatus"])
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func convertStatues(statues []string) []base.Status {
|
|
||||||
result := make([]base.Status, 0)
|
|
||||||
for _, status := range statues {
|
|
||||||
result = append(result, base.Status(status))
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeError(w http.ResponseWriter, msg string) {
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
w.Write([]byte(msg))
|
|
||||||
}
|
|
||||||
|
|
||||||
func ReadJson(r *http.Request, w http.ResponseWriter, v any) bool {
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(v); err != nil {
|
|
||||||
WriteJson(w, model.NewErrorResult[any](err.Error()))
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func WriteJson(w http.ResponseWriter, v any) {
|
|
||||||
WriteStatusJson(w, http.StatusOK, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
func WriteStatusJson(w http.ResponseWriter, statusCode int, v any) {
|
|
||||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
|
||||||
w.WriteHeader(statusCode)
|
|
||||||
json.NewEncoder(w).Encode(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
func resolvePath(urlPath string, prefix string) (identity string, path string, err error) {
|
|
||||||
// remove prefix
|
|
||||||
clearPath := strings.TrimPrefix(urlPath, prefix)
|
|
||||||
// match extension identity, eg: /fs/extensions/identity/xxx
|
|
||||||
reg := regexp.MustCompile(`^/([^/]+)/(.*)$`)
|
|
||||||
if !reg.MatchString(clearPath) {
|
|
||||||
err = os.ErrNotExist
|
|
||||||
return
|
|
||||||
}
|
|
||||||
matched := reg.FindStringSubmatch(clearPath)
|
|
||||||
if len(matched) != 3 {
|
|
||||||
err = os.ErrNotExist
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return matched[1], matched[2], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle task file resource
|
|
||||||
type taskFileSystem struct {
|
|
||||||
downloader *download.Downloader
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *taskFileSystem) Open(name string) (http.File, error) {
|
|
||||||
// get extension identity
|
|
||||||
identity, path, err := resolvePath(name, "/fs/tasks")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
task := e.downloader.GetTask(identity)
|
|
||||||
if task == nil {
|
|
||||||
return nil, os.ErrNotExist
|
|
||||||
}
|
|
||||||
return os.Open(filepath.Join(task.Meta.RootDirPath(), path))
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle extension file resource
|
|
||||||
type extensionFileSystem struct {
|
|
||||||
downloader *download.Downloader
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *extensionFileSystem) Open(name string) (http.File, error) {
|
|
||||||
// get extension identity
|
|
||||||
identity, path, err := resolvePath(name, "/fs/extensions")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
extension, err := e.downloader.GetExtension(identity)
|
|
||||||
if err != nil {
|
|
||||||
return nil, os.ErrNotExist
|
|
||||||
}
|
|
||||||
extensionPath := e.downloader.ExtensionPath(extension)
|
|
||||||
return os.Open(filepath.Join(extensionPath, path))
|
|
||||||
}
|
|
||||||
@@ -15,6 +15,7 @@ const (
|
|||||||
HttpCodeOK = 200
|
HttpCodeOK = 200
|
||||||
HttpCodePartialContent = 206
|
HttpCodePartialContent = 206
|
||||||
|
|
||||||
|
HttpHeaderHost = "Host"
|
||||||
HttpHeaderRange = "Range"
|
HttpHeaderRange = "Range"
|
||||||
HttpHeaderAcceptRanges = "Accept-Ranges"
|
HttpHeaderAcceptRanges = "Accept-Ranges"
|
||||||
HttpHeaderContentLength = "Content-Length"
|
HttpHeaderContentLength = "Content-Length"
|
||||||
|
|||||||
@@ -171,7 +171,6 @@ type DownloaderStoreConfig struct {
|
|||||||
MaxRunning int `json:"maxRunning"` // MaxRunning is the max running download count
|
MaxRunning int `json:"maxRunning"` // MaxRunning is the max running download count
|
||||||
ProtocolConfig map[string]any `json:"protocolConfig"` // ProtocolConfig is special config for each protocol
|
ProtocolConfig map[string]any `json:"protocolConfig"` // ProtocolConfig is special config for each protocol
|
||||||
Extra map[string]any `json:"extra"`
|
Extra map[string]any `json:"extra"`
|
||||||
Http *DownloaderHttpConfig `json:"http"`
|
|
||||||
Proxy *DownloaderProxyConfig `json:"proxy"`
|
Proxy *DownloaderProxyConfig `json:"proxy"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,12 +181,6 @@ func (cfg *DownloaderStoreConfig) Init() *DownloaderStoreConfig {
|
|||||||
if cfg.ProtocolConfig == nil {
|
if cfg.ProtocolConfig == nil {
|
||||||
cfg.ProtocolConfig = make(map[string]any)
|
cfg.ProtocolConfig = make(map[string]any)
|
||||||
}
|
}
|
||||||
if cfg.Extra == nil {
|
|
||||||
cfg.Extra = make(map[string]any)
|
|
||||||
}
|
|
||||||
if cfg.Http == nil {
|
|
||||||
cfg.Http = &DownloaderHttpConfig{}
|
|
||||||
}
|
|
||||||
if cfg.Proxy == nil {
|
if cfg.Proxy == nil {
|
||||||
cfg.Proxy = &DownloaderProxyConfig{}
|
cfg.Proxy = &DownloaderProxyConfig{}
|
||||||
}
|
}
|
||||||
@@ -210,28 +203,12 @@ func (cfg *DownloaderStoreConfig) Merge(beforeCfg *DownloaderStoreConfig) *Downl
|
|||||||
if cfg.Extra == nil {
|
if cfg.Extra == nil {
|
||||||
cfg.Extra = beforeCfg.Extra
|
cfg.Extra = beforeCfg.Extra
|
||||||
}
|
}
|
||||||
if cfg.Http == nil {
|
|
||||||
cfg.Http = beforeCfg.Http
|
|
||||||
}
|
|
||||||
if cfg.Proxy == nil {
|
if cfg.Proxy == nil {
|
||||||
cfg.Proxy = beforeCfg.Proxy
|
cfg.Proxy = beforeCfg.Proxy
|
||||||
}
|
}
|
||||||
return cfg
|
return cfg
|
||||||
}
|
}
|
||||||
|
|
||||||
type DownloaderHttpConfig struct {
|
|
||||||
// Enable is the flag that enable http server
|
|
||||||
Enable bool `json:"Enable"`
|
|
||||||
// Host is the http server listen address.
|
|
||||||
Host string `json:"host"`
|
|
||||||
// Port is the http server listen port.
|
|
||||||
Port int `json:"port"`
|
|
||||||
// ApiToken is the auth token for http API
|
|
||||||
ApiToken string `json:"apiToken"`
|
|
||||||
// RunningPort is the http server real listen port.
|
|
||||||
RunningPort int `json:"runningPort"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type DownloaderProxyConfig struct {
|
type DownloaderProxyConfig struct {
|
||||||
Enable bool `json:"enable"`
|
Enable bool `json:"enable"`
|
||||||
// System is the flag that use system proxy
|
// System is the flag that use system proxy
|
||||||
|
|||||||
@@ -17,8 +17,6 @@ func TestDownloaderStoreConfig_Init(t *testing.T) {
|
|||||||
&DownloaderStoreConfig{
|
&DownloaderStoreConfig{
|
||||||
MaxRunning: 5,
|
MaxRunning: 5,
|
||||||
ProtocolConfig: map[string]any{},
|
ProtocolConfig: map[string]any{},
|
||||||
Extra: make(map[string]any),
|
|
||||||
Http: &DownloaderHttpConfig{},
|
|
||||||
Proxy: &DownloaderProxyConfig{},
|
Proxy: &DownloaderProxyConfig{},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -30,8 +28,6 @@ func TestDownloaderStoreConfig_Init(t *testing.T) {
|
|||||||
&DownloaderStoreConfig{
|
&DownloaderStoreConfig{
|
||||||
MaxRunning: 10,
|
MaxRunning: 10,
|
||||||
ProtocolConfig: map[string]any{},
|
ProtocolConfig: map[string]any{},
|
||||||
Extra: make(map[string]any),
|
|
||||||
Http: &DownloaderHttpConfig{},
|
|
||||||
Proxy: &DownloaderProxyConfig{},
|
Proxy: &DownloaderProxyConfig{},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -47,8 +43,6 @@ func TestDownloaderStoreConfig_Init(t *testing.T) {
|
|||||||
ProtocolConfig: map[string]any{
|
ProtocolConfig: map[string]any{
|
||||||
"key": "value",
|
"key": "value",
|
||||||
},
|
},
|
||||||
Extra: make(map[string]any),
|
|
||||||
Http: &DownloaderHttpConfig{},
|
|
||||||
Proxy: &DownloaderProxyConfig{},
|
Proxy: &DownloaderProxyConfig{},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -62,8 +56,6 @@ func TestDownloaderStoreConfig_Init(t *testing.T) {
|
|||||||
&DownloaderStoreConfig{
|
&DownloaderStoreConfig{
|
||||||
MaxRunning: 5,
|
MaxRunning: 5,
|
||||||
ProtocolConfig: map[string]any{},
|
ProtocolConfig: map[string]any{},
|
||||||
Extra: make(map[string]any),
|
|
||||||
Http: &DownloaderHttpConfig{},
|
|
||||||
Proxy: &DownloaderProxyConfig{
|
Proxy: &DownloaderProxyConfig{
|
||||||
Enable: true,
|
Enable: true,
|
||||||
},
|
},
|
||||||
|
|||||||
+15
-18
@@ -252,7 +252,7 @@ func (d *Downloader) setupFetcher(fm fetcher.FetcherManager, fetcher fetcher.Fet
|
|||||||
ctl.GetConfig = func(v any) {
|
ctl.GetConfig = func(v any) {
|
||||||
d.getProtocolConfig(fm.Name(), v)
|
d.getProtocolConfig(fm.Name(), v)
|
||||||
}
|
}
|
||||||
// get proxy config, task request proxy config has higher priority, then use global proxy config
|
// Get proxy config, task request proxy config has higher priority, then use global proxy config
|
||||||
ctl.GetProxy = func(requestProxy *base.RequestProxy) func(*gohttp.Request) (*url.URL, error) {
|
ctl.GetProxy = func(requestProxy *base.RequestProxy) func(*gohttp.Request) (*url.URL, error) {
|
||||||
if requestProxy == nil {
|
if requestProxy == nil {
|
||||||
return d.cfg.Proxy.ToHandler()
|
return d.cfg.Proxy.ToHandler()
|
||||||
@@ -390,7 +390,7 @@ func (d *Downloader) Pause(filter *TaskFilter) (err error) {
|
|||||||
return d.pauseAll()
|
return d.pauseAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
filter.NotStatus = []base.Status{base.DownloadStatusPause, base.DownloadStatusError, base.DownloadStatusDone}
|
filter.NotStatuses = []base.Status{base.DownloadStatusPause, base.DownloadStatusError, base.DownloadStatusDone}
|
||||||
pauseTasks := d.GetTasksByFilter(filter)
|
pauseTasks := d.GetTasksByFilter(filter)
|
||||||
if len(pauseTasks) == 0 {
|
if len(pauseTasks) == 0 {
|
||||||
return ErrTaskNotFound
|
return ErrTaskNotFound
|
||||||
@@ -430,7 +430,7 @@ func (d *Downloader) Continue(filter *TaskFilter) (err error) {
|
|||||||
return d.continueAll()
|
return d.continueAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
filter.NotStatus = []base.Status{base.DownloadStatusRunning, base.DownloadStatusDone}
|
filter.NotStatuses = []base.Status{base.DownloadStatusRunning, base.DownloadStatusDone}
|
||||||
continueTasks := d.GetTasksByFilter(filter)
|
continueTasks := d.GetTasksByFilter(filter)
|
||||||
if len(continueTasks) == 0 {
|
if len(continueTasks) == 0 {
|
||||||
return ErrTaskNotFound
|
return ErrTaskNotFound
|
||||||
@@ -527,7 +527,7 @@ func (d *Downloader) ContinueBatch(filter *TaskFilter) (err error) {
|
|||||||
|
|
||||||
func (d *Downloader) Delete(filter *TaskFilter, force bool) (err error) {
|
func (d *Downloader) Delete(filter *TaskFilter, force bool) (err error) {
|
||||||
if filter == nil || filter.IsEmpty() {
|
if filter == nil || filter.IsEmpty() {
|
||||||
return d.deleteAll(force)
|
return d.deleteAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteTasks := d.GetTasksByFilter(filter)
|
deleteTasks := d.GetTasksByFilter(filter)
|
||||||
@@ -572,27 +572,24 @@ func (d *Downloader) Delete(filter *TaskFilter, force bool) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Downloader) deleteAll(force bool) (err error) {
|
func (d *Downloader) deleteAll() (err error) {
|
||||||
deleteTasksPtr := make([]*Task, 0)
|
|
||||||
|
|
||||||
func() {
|
func() {
|
||||||
d.lock.Lock()
|
d.lock.Lock()
|
||||||
defer d.lock.Unlock()
|
defer d.lock.Unlock()
|
||||||
|
|
||||||
deleteTasksPtr = append(deleteTasksPtr, d.tasks...)
|
|
||||||
d.tasks = make([]*Task, 0)
|
d.tasks = make([]*Task, 0)
|
||||||
d.waitTasks = make([]*Task, 0)
|
d.waitTasks = make([]*Task, 0)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
for _, task := range deleteTasksPtr {
|
for _, task := range d.tasks {
|
||||||
if err = d.doDelete(task, force); err != nil {
|
if err = d.doDelete(task, true); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Downloader) GetTaskStats(id string) (sr any, err error) {
|
func (d *Downloader) Stats(id string) (sr any, err error) {
|
||||||
task := d.GetTask(id)
|
task := d.GetTask(id)
|
||||||
if task == nil {
|
if task == nil {
|
||||||
return sr, ErrTaskNotFound
|
return sr, ErrTaskNotFound
|
||||||
@@ -722,10 +719,10 @@ func (d *Downloader) GetTasksByFilter(filter *TaskFilter) []*Task {
|
|||||||
}
|
}
|
||||||
|
|
||||||
idMatch := func(task *Task) bool {
|
idMatch := func(task *Task) bool {
|
||||||
if len(filter.ID) == 0 {
|
if len(filter.IDs) == 0 {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
for _, id := range filter.ID {
|
for _, id := range filter.IDs {
|
||||||
if task.ID == id {
|
if task.ID == id {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -733,10 +730,10 @@ func (d *Downloader) GetTasksByFilter(filter *TaskFilter) []*Task {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
statusMatch := func(task *Task) bool {
|
statusMatch := func(task *Task) bool {
|
||||||
if len(filter.Status) == 0 {
|
if len(filter.Statuses) == 0 {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
for _, status := range filter.Status {
|
for _, status := range filter.Statuses {
|
||||||
if task.Status == status {
|
if task.Status == status {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -744,10 +741,10 @@ func (d *Downloader) GetTasksByFilter(filter *TaskFilter) []*Task {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
notStatusMatch := func(task *Task) bool {
|
notStatusMatch := func(task *Task) bool {
|
||||||
if len(filter.NotStatus) == 0 {
|
if len(filter.NotStatuses) == 0 {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
for _, status := range filter.NotStatus {
|
for _, status := range filter.NotStatuses {
|
||||||
if task.Status == status {
|
if task.Status == status {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -829,7 +826,7 @@ func (d *Downloader) watch(task *Task) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// When delete a not resolved task, need check if the task resource is nil
|
// When delete a not resolved task, need check if the task resource is nil
|
||||||
if task.Meta.Res == nil {
|
if task.Meta.Res == nil || d.GetTask(task.ID) == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -400,7 +400,7 @@ func TestDownloader_StoreAndRestore(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
time.Sleep(time.Millisecond * 1001)
|
time.Sleep(time.Millisecond * 1001)
|
||||||
err = downloader.Pause(&TaskFilter{ID: []string{id}})
|
err = downloader.Pause(&TaskFilter{IDs: []string{id}})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -424,7 +424,7 @@ func TestDownloader_StoreAndRestore(t *testing.T) {
|
|||||||
wg.Done()
|
wg.Done()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
err = downloader.Continue(&TaskFilter{ID: []string{id}})
|
err = downloader.Continue(&TaskFilter{IDs: []string{id}})
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -551,7 +551,7 @@ func TestDownloader_GetTasksByFilter(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("GetTasksByFilter ids", func(t *testing.T) {
|
t.Run("GetTasksByFilter ids", func(t *testing.T) {
|
||||||
tasks := downloader.GetTasksByFilter(&TaskFilter{
|
tasks := downloader.GetTasksByFilter(&TaskFilter{
|
||||||
ID: taskIds,
|
IDs: taskIds,
|
||||||
})
|
})
|
||||||
if len(tasks) != len(reqs) {
|
if len(tasks) != len(reqs) {
|
||||||
t.Errorf("GetTasksByFilter ids task got = %v, want %v", len(tasks), len(reqs))
|
t.Errorf("GetTasksByFilter ids task got = %v, want %v", len(tasks), len(reqs))
|
||||||
@@ -560,7 +560,7 @@ func TestDownloader_GetTasksByFilter(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("GetTasksByFilter match ids", func(t *testing.T) {
|
t.Run("GetTasksByFilter match ids", func(t *testing.T) {
|
||||||
tasks := downloader.GetTasksByFilter(&TaskFilter{
|
tasks := downloader.GetTasksByFilter(&TaskFilter{
|
||||||
ID: []string{taskIds[0]},
|
IDs: []string{taskIds[0]},
|
||||||
})
|
})
|
||||||
if len(tasks) != 1 {
|
if len(tasks) != 1 {
|
||||||
t.Errorf("GetTasksByFilter ids task got = %v, want %v", len(tasks), 1)
|
t.Errorf("GetTasksByFilter ids task got = %v, want %v", len(tasks), 1)
|
||||||
@@ -569,7 +569,7 @@ func TestDownloader_GetTasksByFilter(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("GetTasksByFilter not match ids", func(t *testing.T) {
|
t.Run("GetTasksByFilter not match ids", func(t *testing.T) {
|
||||||
tasks := downloader.GetTasksByFilter(&TaskFilter{
|
tasks := downloader.GetTasksByFilter(&TaskFilter{
|
||||||
ID: []string{"xxx"},
|
IDs: []string{"xxx"},
|
||||||
})
|
})
|
||||||
if len(tasks) != 0 {
|
if len(tasks) != 0 {
|
||||||
t.Errorf("GetTasksByFilter ids task got = %v, want %v", len(tasks), 0)
|
t.Errorf("GetTasksByFilter ids task got = %v, want %v", len(tasks), 0)
|
||||||
@@ -578,7 +578,7 @@ func TestDownloader_GetTasksByFilter(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("GetTasksByFilter status", func(t *testing.T) {
|
t.Run("GetTasksByFilter status", func(t *testing.T) {
|
||||||
tasks := downloader.GetTasksByFilter(&TaskFilter{
|
tasks := downloader.GetTasksByFilter(&TaskFilter{
|
||||||
Status: []base.Status{base.DownloadStatusDone},
|
Statuses: []base.Status{base.DownloadStatusDone},
|
||||||
})
|
})
|
||||||
if len(tasks) != len(reqs) {
|
if len(tasks) != len(reqs) {
|
||||||
t.Errorf("GetTasksByFilter status task got = %v, want %v", len(tasks), len(reqs))
|
t.Errorf("GetTasksByFilter status task got = %v, want %v", len(tasks), len(reqs))
|
||||||
@@ -587,7 +587,7 @@ func TestDownloader_GetTasksByFilter(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("GetTasksByFilter not match status", func(t *testing.T) {
|
t.Run("GetTasksByFilter not match status", func(t *testing.T) {
|
||||||
tasks := downloader.GetTasksByFilter(&TaskFilter{
|
tasks := downloader.GetTasksByFilter(&TaskFilter{
|
||||||
Status: []base.Status{base.DownloadStatusError},
|
Statuses: []base.Status{base.DownloadStatusError},
|
||||||
})
|
})
|
||||||
if len(tasks) != 0 {
|
if len(tasks) != 0 {
|
||||||
t.Errorf("GetTasksByFilter status task got = %v, want %v", len(tasks), 0)
|
t.Errorf("GetTasksByFilter status task got = %v, want %v", len(tasks), 0)
|
||||||
@@ -596,7 +596,7 @@ func TestDownloader_GetTasksByFilter(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("GetTasksByFilter match notStatus", func(t *testing.T) {
|
t.Run("GetTasksByFilter match notStatus", func(t *testing.T) {
|
||||||
tasks := downloader.GetTasksByFilter(&TaskFilter{
|
tasks := downloader.GetTasksByFilter(&TaskFilter{
|
||||||
NotStatus: []base.Status{base.DownloadStatusRunning, base.DownloadStatusPause},
|
NotStatuses: []base.Status{base.DownloadStatusRunning, base.DownloadStatusPause},
|
||||||
})
|
})
|
||||||
if len(tasks) != len(reqs) {
|
if len(tasks) != len(reqs) {
|
||||||
t.Errorf("GetTasksByFilter match notStatus task got = %v, want %v", len(tasks), len(reqs))
|
t.Errorf("GetTasksByFilter match notStatus task got = %v, want %v", len(tasks), len(reqs))
|
||||||
@@ -605,7 +605,7 @@ func TestDownloader_GetTasksByFilter(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("GetTasksByFilter not match notStatus", func(t *testing.T) {
|
t.Run("GetTasksByFilter not match notStatus", func(t *testing.T) {
|
||||||
tasks := downloader.GetTasksByFilter(&TaskFilter{
|
tasks := downloader.GetTasksByFilter(&TaskFilter{
|
||||||
NotStatus: []base.Status{base.DownloadStatusDone},
|
NotStatuses: []base.Status{base.DownloadStatusDone},
|
||||||
})
|
})
|
||||||
if len(tasks) != 0 {
|
if len(tasks) != 0 {
|
||||||
t.Errorf("GetTasksByFilter not match notStatus task got = %v, want %v", len(tasks), 0)
|
t.Errorf("GetTasksByFilter not match notStatus task got = %v, want %v", len(tasks), 0)
|
||||||
@@ -614,8 +614,8 @@ func TestDownloader_GetTasksByFilter(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("GetTasksByFilter match ids and status", func(t *testing.T) {
|
t.Run("GetTasksByFilter match ids and status", func(t *testing.T) {
|
||||||
tasks := downloader.GetTasksByFilter(&TaskFilter{
|
tasks := downloader.GetTasksByFilter(&TaskFilter{
|
||||||
ID: []string{taskIds[0]},
|
IDs: []string{taskIds[0]},
|
||||||
Status: []base.Status{base.DownloadStatusDone},
|
Statuses: []base.Status{base.DownloadStatusDone},
|
||||||
})
|
})
|
||||||
if len(tasks) != 1 {
|
if len(tasks) != 1 {
|
||||||
t.Errorf("GetTasksByFilter match ids and status task got = %v, want %v", len(tasks), 1)
|
t.Errorf("GetTasksByFilter match ids and status task got = %v, want %v", len(tasks), 1)
|
||||||
@@ -624,8 +624,8 @@ func TestDownloader_GetTasksByFilter(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("GetTasksByFilter not match ids and status", func(t *testing.T) {
|
t.Run("GetTasksByFilter not match ids and status", func(t *testing.T) {
|
||||||
tasks := downloader.GetTasksByFilter(&TaskFilter{
|
tasks := downloader.GetTasksByFilter(&TaskFilter{
|
||||||
ID: []string{taskIds[0]},
|
IDs: []string{taskIds[0]},
|
||||||
Status: []base.Status{base.DownloadStatusError},
|
Statuses: []base.Status{base.DownloadStatusError},
|
||||||
})
|
})
|
||||||
if len(tasks) != 0 {
|
if len(tasks) != 0 {
|
||||||
t.Errorf("GetTasksByFilter not match ids and status task got = %v, want %v", len(tasks), 0)
|
t.Errorf("GetTasksByFilter not match ids and status task got = %v, want %v", len(tasks), 0)
|
||||||
|
|||||||
@@ -702,38 +702,34 @@ type OnDoneContext struct {
|
|||||||
Task *Task `json:"task"`
|
Task *Task `json:"task"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ExtensionTask is a wrapper of Task, it's used to interact with extension scripts.
|
||||||
|
// Avoid extension scripts modifying task directly, use ExtensionTask to encapsulate task,
|
||||||
|
// only some fields can be modified, such as request info.
|
||||||
type ExtensionTask struct {
|
type ExtensionTask struct {
|
||||||
download *Downloader
|
download *Downloader
|
||||||
task *Task
|
|
||||||
|
|
||||||
Meta *ExtensionTaskMeta `json:"meta"`
|
*Task
|
||||||
}
|
|
||||||
|
|
||||||
// ExtensionTaskMeta restricts extension scripts to only modify request info
|
|
||||||
type ExtensionTaskMeta struct {
|
|
||||||
Req *base.Request `json:"req"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewExtensionTask(download *Downloader, task *Task) *ExtensionTask {
|
func NewExtensionTask(download *Downloader, task *Task) *ExtensionTask {
|
||||||
|
// restricts extension scripts to only modify request info
|
||||||
newTask := task.clone()
|
newTask := task.clone()
|
||||||
|
newTask.Meta.Req = task.Meta.Req
|
||||||
return &ExtensionTask{
|
return &ExtensionTask{
|
||||||
task: newTask,
|
|
||||||
download: download,
|
download: download,
|
||||||
Meta: &ExtensionTaskMeta{
|
Task: newTask,
|
||||||
Req: task.Meta.Req,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *ExtensionTask) Continue() error {
|
func (t *ExtensionTask) Continue() error {
|
||||||
return t.download.Continue(&TaskFilter{
|
return t.download.Continue(&TaskFilter{
|
||||||
ID: []string{t.task.ID},
|
IDs: []string{t.ID},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *ExtensionTask) Pause() error {
|
func (t *ExtensionTask) Pause() error {
|
||||||
return t.download.Pause(&TaskFilter{
|
return t.download.Pause(&TaskFilter{
|
||||||
ID: []string{t.task.ID},
|
IDs: []string{t.ID},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ func (t *Task) Name() string {
|
|||||||
return t.Meta.Res.Name
|
return t.Meta.Res.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
// get the name of the first file
|
// Get the name of the first file
|
||||||
return t.Meta.Res.Files[0].Name
|
return t.Meta.Res.Files[0].Name
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,13 +113,13 @@ func (t *Task) calcSpeed(speedArr []int64, downloaded int64, usedTime float64) i
|
|||||||
}
|
}
|
||||||
|
|
||||||
type TaskFilter struct {
|
type TaskFilter struct {
|
||||||
ID []string `json:"id"`
|
IDs []string
|
||||||
Status []base.Status `json:"status"`
|
Statuses []base.Status
|
||||||
NotStatus []base.Status `json:"notStatus"`
|
NotStatuses []base.Status
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *TaskFilter) IsEmpty() bool {
|
func (f *TaskFilter) IsEmpty() bool {
|
||||||
return len(f.ID) == 0 && len(f.Status) == 0 && len(f.NotStatus) == 0
|
return len(f.IDs) == 0 && len(f.Statuses) == 0 && len(f.NotStatuses) == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
type DownloaderConfig struct {
|
type DownloaderConfig struct {
|
||||||
|
|||||||
+389
@@ -0,0 +1,389 @@
|
|||||||
|
package rest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/GopeedLab/gopeed/pkg/base"
|
||||||
|
"github.com/GopeedLab/gopeed/pkg/download"
|
||||||
|
"github.com/GopeedLab/gopeed/pkg/rest/model"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"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,
|
||||||
|
"inDocker": base.InDocker == "true",
|
||||||
|
}
|
||||||
|
WriteJson(w, model.NewOkResult(info))
|
||||||
|
}
|
||||||
|
|
||||||
|
func Resolve(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req base.Request
|
||||||
|
if ReadJson(r, w, &req) {
|
||||||
|
rr, err := Downloader.Resolve(&req)
|
||||||
|
if err != nil {
|
||||||
|
WriteJson(w, model.NewErrorResult(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
WriteJson(w, model.NewOkResult(rr))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateTask(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req model.CreateTask
|
||||||
|
if ReadJson(r, w, &req) {
|
||||||
|
var (
|
||||||
|
taskId string
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if req.Rid != "" {
|
||||||
|
taskId, err = Downloader.Create(req.Rid, req.Opt)
|
||||||
|
} else if req.Req != nil {
|
||||||
|
taskId, err = Downloader.CreateDirect(req.Req, req.Opt)
|
||||||
|
} else {
|
||||||
|
WriteJson(w, model.NewErrorResult("param invalid: rid or req", model.CodeInvalidParam))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
WriteJson(w, model.NewErrorResult(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
WriteJson(w, model.NewOkResult(taskId))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
filter, errResult := parseIdFilter(r)
|
||||||
|
if errResult != nil {
|
||||||
|
WriteJson(w, errResult)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := Downloader.Pause(filter); err != nil {
|
||||||
|
WriteJson(w, model.NewErrorResult(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
WriteJson(w, model.NewNilResult())
|
||||||
|
}
|
||||||
|
|
||||||
|
func PauseTasks(w http.ResponseWriter, r *http.Request) {
|
||||||
|
filter, errResult := parseFilter(r)
|
||||||
|
if errResult != nil {
|
||||||
|
WriteJson(w, errResult)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := Downloader.Pause(filter); err != nil {
|
||||||
|
WriteJson(w, model.NewErrorResult(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
WriteJson(w, model.NewNilResult())
|
||||||
|
}
|
||||||
|
|
||||||
|
func ContinueTask(w http.ResponseWriter, r *http.Request) {
|
||||||
|
filter, errResult := parseIdFilter(r)
|
||||||
|
if errResult != nil {
|
||||||
|
WriteJson(w, errResult)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := Downloader.Continue(filter); err != nil {
|
||||||
|
WriteJson(w, model.NewErrorResult(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
WriteJson(w, model.NewNilResult())
|
||||||
|
}
|
||||||
|
|
||||||
|
func ContinueTasks(w http.ResponseWriter, r *http.Request) {
|
||||||
|
filter, errResult := parseFilter(r)
|
||||||
|
if errResult != nil {
|
||||||
|
WriteJson(w, errResult)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := Downloader.Continue(filter); err != nil {
|
||||||
|
WriteJson(w, model.NewErrorResult(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
WriteJson(w, model.NewNilResult())
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteTask(w http.ResponseWriter, r *http.Request) {
|
||||||
|
filter, errResult := parseIdFilter(r)
|
||||||
|
if errResult != nil {
|
||||||
|
WriteJson(w, errResult)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
force := r.FormValue("force")
|
||||||
|
|
||||||
|
if err := Downloader.Delete(filter, force == "true"); err != nil {
|
||||||
|
WriteJson(w, model.NewErrorResult(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
WriteJson(w, model.NewNilResult())
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteTasks(w http.ResponseWriter, r *http.Request) {
|
||||||
|
filter, errResult := parseFilter(r)
|
||||||
|
if errResult != nil {
|
||||||
|
WriteJson(w, errResult)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
force := r.FormValue("force")
|
||||||
|
|
||||||
|
if err := Downloader.Delete(filter, force == "true"); err != nil {
|
||||||
|
WriteJson(w, model.NewErrorResult(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
WriteJson(w, model.NewNilResult())
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetTask(w http.ResponseWriter, r *http.Request) {
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
taskId := vars["id"]
|
||||||
|
if taskId == "" {
|
||||||
|
WriteJson(w, model.NewErrorResult("param invalid: id", model.CodeInvalidParam))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
task := Downloader.GetTask(taskId)
|
||||||
|
if task == nil {
|
||||||
|
WriteJson(w, model.NewErrorResult("task not found", model.CodeTaskNotFound))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
WriteJson(w, model.NewOkResult(task))
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetTasks(w http.ResponseWriter, r *http.Request) {
|
||||||
|
filter, errResult := parseFilter(r)
|
||||||
|
if errResult != nil {
|
||||||
|
WriteJson(w, errResult)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks := Downloader.GetTasksByFilter(filter)
|
||||||
|
WriteJson(w, model.NewOkResult(tasks))
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetConfig(w http.ResponseWriter, r *http.Request) {
|
||||||
|
WriteJson(w, model.NewOkResult(getServerConfig()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func PutConfig(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var cfg base.DownloaderStoreConfig
|
||||||
|
if ReadJson(r, w, &cfg) {
|
||||||
|
if err := Downloader.PutConfig(&cfg); err != nil {
|
||||||
|
WriteJson(w, model.NewErrorResult(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
WriteJson(w, model.NewNilResult())
|
||||||
|
}
|
||||||
|
|
||||||
|
func InstallExtension(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req model.InstallExtension
|
||||||
|
if ReadJson(r, w, &req) {
|
||||||
|
var (
|
||||||
|
installedExt *download.Extension
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if req.DevMode {
|
||||||
|
installedExt, err = Downloader.InstallExtensionByFolder(req.URL, true)
|
||||||
|
} else {
|
||||||
|
installedExt, err = Downloader.InstallExtensionByGit(req.URL)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
WriteJson(w, model.NewErrorResult(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
WriteJson(w, model.NewOkResult(installedExt.Identity))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetExtensions(w http.ResponseWriter, r *http.Request) {
|
||||||
|
list := Downloader.GetExtensions()
|
||||||
|
WriteJson(w, model.NewOkResult(list))
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetExtension(w http.ResponseWriter, r *http.Request) {
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
identity := vars["identity"]
|
||||||
|
ext, err := Downloader.GetExtension(identity)
|
||||||
|
if err != nil {
|
||||||
|
WriteJson(w, model.NewErrorResult(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
WriteJson(w, model.NewOkResult(ext))
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateExtensionSettings(w http.ResponseWriter, r *http.Request) {
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
identity := vars["identity"]
|
||||||
|
var req model.UpdateExtensionSettings
|
||||||
|
if ReadJson(r, w, &req) {
|
||||||
|
if err := Downloader.UpdateExtensionSettings(identity, req.Settings); err != nil {
|
||||||
|
WriteJson(w, model.NewErrorResult(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
WriteJson(w, model.NewNilResult())
|
||||||
|
}
|
||||||
|
|
||||||
|
func SwitchExtension(w http.ResponseWriter, r *http.Request) {
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
identity := vars["identity"]
|
||||||
|
var switchExtension model.SwitchExtension
|
||||||
|
if ReadJson(r, w, &switchExtension) {
|
||||||
|
if err := Downloader.SwitchExtension(identity, switchExtension.Status); err != nil {
|
||||||
|
WriteJson(w, model.NewErrorResult(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
WriteJson(w, model.NewNilResult())
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteExtension(w http.ResponseWriter, r *http.Request) {
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
identity := vars["identity"]
|
||||||
|
if err := Downloader.DeleteExtension(identity); err != nil {
|
||||||
|
WriteJson(w, model.NewErrorResult(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
WriteJson(w, model.NewNilResult())
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateCheckExtension(w http.ResponseWriter, r *http.Request) {
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
identity := vars["identity"]
|
||||||
|
newVersion, err := Downloader.UpgradeCheckExtension(identity)
|
||||||
|
if err != nil {
|
||||||
|
WriteJson(w, model.NewErrorResult(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
WriteJson(w, model.NewOkResult(&model.UpdateCheckExtensionResp{
|
||||||
|
NewVersion: newVersion,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateExtension(w http.ResponseWriter, r *http.Request) {
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
identity := vars["identity"]
|
||||||
|
if err := Downloader.UpgradeExtension(identity); err != nil {
|
||||||
|
WriteJson(w, model.NewErrorResult(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
WriteJson(w, model.NewNilResult())
|
||||||
|
}
|
||||||
|
|
||||||
|
func DoProxy(w http.ResponseWriter, r *http.Request) {
|
||||||
|
target := r.Header.Get("X-Target-Uri")
|
||||||
|
if target == "" {
|
||||||
|
writeError(w, "param invalid: X-Target-Uri")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
targetUrl, err := url.Parse(target)
|
||||||
|
if err != nil {
|
||||||
|
writeError(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r.RequestURI = ""
|
||||||
|
r.URL = targetUrl
|
||||||
|
r.Host = targetUrl.Host
|
||||||
|
resp, err := http.DefaultClient.Do(r)
|
||||||
|
if err != nil {
|
||||||
|
writeError(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
for k, vv := range resp.Header {
|
||||||
|
for _, v := range vv {
|
||||||
|
w.Header().Set(k, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.WriteHeader(resp.StatusCode)
|
||||||
|
buf, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
writeError(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Write(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetStats(w http.ResponseWriter, r *http.Request) {
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
taskId := vars["id"]
|
||||||
|
if taskId == "" {
|
||||||
|
WriteJson(w, model.NewErrorResult("param invalid: id", model.CodeInvalidParam))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
statsResult, err := Downloader.Stats(taskId)
|
||||||
|
if err != nil {
|
||||||
|
writeError(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
WriteJson(w, model.NewOkResult(statsResult))
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseIdFilter(r *http.Request) (*download.TaskFilter, any) {
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
taskId := vars["id"]
|
||||||
|
if taskId == "" {
|
||||||
|
return nil, model.NewErrorResult("param invalid: id", model.CodeInvalidParam)
|
||||||
|
}
|
||||||
|
|
||||||
|
filter := &download.TaskFilter{
|
||||||
|
IDs: []string{taskId},
|
||||||
|
}
|
||||||
|
return filter, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseFilter(r *http.Request) (*download.TaskFilter, any) {
|
||||||
|
if err := r.ParseForm(); err != nil {
|
||||||
|
return nil, model.NewErrorResult(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
filter := &download.TaskFilter{
|
||||||
|
IDs: r.Form["id"],
|
||||||
|
Statuses: convertStatues(r.Form["status"]),
|
||||||
|
NotStatuses: convertStatues(r.Form["notStatus"]),
|
||||||
|
}
|
||||||
|
return filter, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertStatues(statues []string) []base.Status {
|
||||||
|
result := make([]base.Status, 0)
|
||||||
|
for _, status := range statues {
|
||||||
|
result = append(result, base.Status(status))
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeError(w http.ResponseWriter, msg string) {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
w.Write([]byte(msg))
|
||||||
|
}
|
||||||
|
|
||||||
|
func getServerConfig() *base.DownloaderStoreConfig {
|
||||||
|
cfg, _ := Downloader.GetConfig()
|
||||||
|
return cfg
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package rest
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Host string `json:"host"`
|
||||||
|
Port int `json:"port"`
|
||||||
|
}
|
||||||
@@ -13,6 +13,6 @@ type SwitchExtension struct {
|
|||||||
Status bool `json:"status"`
|
Status bool `json:"status"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UpgradeCheckExtensionResp struct {
|
type UpdateCheckExtensionResp struct {
|
||||||
NewVersion string `json:"newVersion"`
|
NewVersion string `json:"newVersion"`
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
package model
|
package model
|
||||||
|
|
||||||
import "encoding/json"
|
|
||||||
|
|
||||||
type RespCode int
|
type RespCode int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -22,15 +20,6 @@ type Result[T any] struct {
|
|||||||
Data T `json:"data"`
|
Data T `json:"data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Result[T]) Json() string {
|
|
||||||
buf, _ := json.Marshal(r)
|
|
||||||
return string(buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Result[T]) HasError() bool {
|
|
||||||
return r.Code != CodeOk
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewOkResult[T any](data T) *Result[T] {
|
func NewOkResult[T any](data T) *Result[T] {
|
||||||
return &Result[T]{
|
return &Result[T]{
|
||||||
Code: CodeOk,
|
Code: CodeOk,
|
||||||
@@ -44,14 +33,14 @@ func NewNilResult() *Result[any] {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewErrorResult[T any](msg string, code ...RespCode) *Result[T] {
|
func NewErrorResult(msg string, code ...RespCode) *Result[any] {
|
||||||
// if code is not provided, the default code is CodeError
|
// if code is not provided, the default code is CodeError
|
||||||
c := CodeError
|
c := CodeError
|
||||||
if len(code) > 0 {
|
if len(code) > 0 {
|
||||||
c = code[0]
|
c = code[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Result[T]{
|
return &Result[any]{
|
||||||
Code: c,
|
Code: c,
|
||||||
Msg: msg,
|
Msg: msg,
|
||||||
}
|
}
|
||||||
@@ -14,10 +14,13 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type StartConfig struct {
|
type StartConfig struct {
|
||||||
|
Network string `json:"network"`
|
||||||
|
Address string `json:"address"`
|
||||||
RefreshInterval int `json:"refreshInterval"`
|
RefreshInterval int `json:"refreshInterval"`
|
||||||
Storage Storage `json:"storage"`
|
Storage Storage `json:"storage"`
|
||||||
StorageDir string `json:"storageDir"`
|
StorageDir string `json:"storageDir"`
|
||||||
WhiteDownloadDirs []string `json:"whiteDownloadDirs"`
|
WhiteDownloadDirs []string `json:"whiteDownloadDirs"`
|
||||||
|
ApiToken string `json:"apiToken"`
|
||||||
DownloadConfig *base.DownloaderStoreConfig `json:"downloadConfig"`
|
DownloadConfig *base.DownloaderStoreConfig `json:"downloadConfig"`
|
||||||
|
|
||||||
ProductionMode bool
|
ProductionMode bool
|
||||||
@@ -28,6 +31,12 @@ type StartConfig struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *StartConfig) Init() *StartConfig {
|
func (cfg *StartConfig) Init() *StartConfig {
|
||||||
|
if cfg.Network == "" {
|
||||||
|
cfg.Network = "tcp"
|
||||||
|
}
|
||||||
|
if cfg.Address == "" {
|
||||||
|
cfg.Address = "127.0.0.1:0"
|
||||||
|
}
|
||||||
if cfg.RefreshInterval == 0 {
|
if cfg.RefreshInterval == 0 {
|
||||||
cfg.RefreshInterval = 350
|
cfg.RefreshInterval = 350
|
||||||
}
|
}
|
||||||
@@ -50,9 +59,3 @@ func (cfg *WebBasicAuth) Authorization() string {
|
|||||||
userId := cfg.Username + ":" + cfg.Password
|
userId := cfg.Username + ":" + cfg.Password
|
||||||
return "Basic " + base64.StdEncoding.EncodeToString([]byte(userId))
|
return "Basic " + base64.StdEncoding.EncodeToString([]byte(userId))
|
||||||
}
|
}
|
||||||
|
|
||||||
type HttpListenResult struct {
|
|
||||||
Host string `json:"host"`
|
|
||||||
// Port is the http server real listen port.
|
|
||||||
Port int `json:"port"`
|
|
||||||
}
|
|
||||||
File renamed without changes.
@@ -0,0 +1,241 @@
|
|||||||
|
package rest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"github.com/GopeedLab/gopeed/pkg/download"
|
||||||
|
"github.com/GopeedLab/gopeed/pkg/rest/model"
|
||||||
|
"github.com/GopeedLab/gopeed/pkg/util"
|
||||||
|
"github.com/gorilla/handlers"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
srv *http.Server
|
||||||
|
runningPort int
|
||||||
|
|
||||||
|
Downloader *download.Downloader
|
||||||
|
)
|
||||||
|
|
||||||
|
func Start(startCfg *model.StartConfig) (port int, err error) {
|
||||||
|
// avoid repeat start
|
||||||
|
if srv != nil {
|
||||||
|
return runningPort, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var listener net.Listener
|
||||||
|
srv, listener, err = BuildServer(startCfg)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
if err := srv.Serve(listener); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if addr, ok := listener.Addr().(*net.TCPAddr); ok {
|
||||||
|
port = addr.Port
|
||||||
|
runningPort = port
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func Stop() {
|
||||||
|
defer func() {
|
||||||
|
srv = nil
|
||||||
|
}()
|
||||||
|
|
||||||
|
if srv != nil {
|
||||||
|
if err := srv.Shutdown(context.TODO()); err != nil {
|
||||||
|
Downloader.Logger.Warn().Err(err).Msg("shutdown server failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if Downloader != nil {
|
||||||
|
if err := Downloader.Close(); err != nil {
|
||||||
|
Downloader.Logger.Warn().Err(err).Msg("close downloader failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BuildServer(startCfg *model.StartConfig) (*http.Server, net.Listener, error) {
|
||||||
|
if startCfg == nil {
|
||||||
|
startCfg = &model.StartConfig{}
|
||||||
|
}
|
||||||
|
startCfg.Init()
|
||||||
|
|
||||||
|
downloadCfg := &download.DownloaderConfig{
|
||||||
|
ProductionMode: startCfg.ProductionMode,
|
||||||
|
RefreshInterval: startCfg.RefreshInterval,
|
||||||
|
}
|
||||||
|
if startCfg.Storage == model.StorageBolt {
|
||||||
|
downloadCfg.Storage = download.NewBoltStorage(startCfg.StorageDir)
|
||||||
|
} else {
|
||||||
|
downloadCfg.Storage = download.NewMemStorage()
|
||||||
|
}
|
||||||
|
downloadCfg.StorageDir = startCfg.StorageDir
|
||||||
|
downloadCfg.Init()
|
||||||
|
Downloader = download.NewDownloader(downloadCfg)
|
||||||
|
if err := Downloader.Setup(); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if startCfg.Network == "unix" {
|
||||||
|
util.SafeRemove(startCfg.Address)
|
||||||
|
}
|
||||||
|
|
||||||
|
listener, err := net.Listen(startCfg.Network, startCfg.Address)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var r = mux.NewRouter()
|
||||||
|
r.Methods(http.MethodGet).Path("/api/v1/info").HandlerFunc(Info)
|
||||||
|
r.Methods(http.MethodPost).Path("/api/v1/resolve").HandlerFunc(Resolve)
|
||||||
|
r.Methods(http.MethodPost).Path("/api/v1/tasks").HandlerFunc(CreateTask)
|
||||||
|
r.Methods(http.MethodPost).Path("/api/v1/tasks/batch").HandlerFunc(CreateTaskBatch)
|
||||||
|
r.Methods(http.MethodPut).Path("/api/v1/tasks/{id}/pause").HandlerFunc(PauseTask)
|
||||||
|
r.Methods(http.MethodPut).Path("/api/v1/tasks/pause").HandlerFunc(PauseTasks)
|
||||||
|
r.Methods(http.MethodPut).Path("/api/v1/tasks/{id}/continue").HandlerFunc(ContinueTask)
|
||||||
|
r.Methods(http.MethodPut).Path("/api/v1/tasks/continue").HandlerFunc(ContinueTasks)
|
||||||
|
r.Methods(http.MethodDelete).Path("/api/v1/tasks/{id}").HandlerFunc(DeleteTask)
|
||||||
|
r.Methods(http.MethodDelete).Path("/api/v1/tasks").HandlerFunc(DeleteTasks)
|
||||||
|
r.Methods(http.MethodGet).Path("/api/v1/tasks/{id}").HandlerFunc(GetTask)
|
||||||
|
r.Methods(http.MethodGet).Path("/api/v1/tasks").HandlerFunc(GetTasks)
|
||||||
|
r.Methods(http.MethodGet).Path("/api/v1/tasks/{id}/stats").HandlerFunc(GetStats)
|
||||||
|
r.Methods(http.MethodGet).Path("/api/v1/config").HandlerFunc(GetConfig)
|
||||||
|
r.Methods(http.MethodPut).Path("/api/v1/config").HandlerFunc(PutConfig)
|
||||||
|
r.Methods(http.MethodPost).Path("/api/v1/extensions").HandlerFunc(InstallExtension)
|
||||||
|
r.Methods(http.MethodGet).Path("/api/v1/extensions").HandlerFunc(GetExtensions)
|
||||||
|
r.Methods(http.MethodGet).Path("/api/v1/extensions/{identity}").HandlerFunc(GetExtension)
|
||||||
|
r.Methods(http.MethodPut).Path("/api/v1/extensions/{identity}/settings").HandlerFunc(UpdateExtensionSettings)
|
||||||
|
r.Methods(http.MethodPut).Path("/api/v1/extensions/{identity}/switch").HandlerFunc(SwitchExtension)
|
||||||
|
r.Methods(http.MethodDelete).Path("/api/v1/extensions/{identity}").HandlerFunc(DeleteExtension)
|
||||||
|
r.Methods(http.MethodGet).Path("/api/v1/extensions/{identity}/update").HandlerFunc(UpdateCheckExtension)
|
||||||
|
r.Methods(http.MethodPost).Path("/api/v1/extensions/{identity}/update").HandlerFunc(UpdateExtension)
|
||||||
|
r.Path("/api/v1/proxy").HandlerFunc(DoProxy)
|
||||||
|
if startCfg.WebEnable {
|
||||||
|
r.PathPrefix("/fs/tasks").Handler(http.FileServer(new(taskFileSystem)))
|
||||||
|
r.PathPrefix("/fs/extensions").Handler(http.FileServer(new(extensionFileSystem)))
|
||||||
|
r.PathPrefix("/").Handler(http.FileServer(http.FS(startCfg.WebFS)))
|
||||||
|
}
|
||||||
|
|
||||||
|
if startCfg.ApiToken != "" || (startCfg.WebEnable && startCfg.WebBasicAuth != nil) {
|
||||||
|
r.Use(func(h http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if startCfg.ApiToken != "" && r.Header.Get("X-Api-Token") == startCfg.ApiToken {
|
||||||
|
h.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if startCfg.WebEnable && startCfg.WebBasicAuth != nil {
|
||||||
|
if r.Header.Get("Authorization") == startCfg.WebBasicAuth.Authorization() {
|
||||||
|
h.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Header().Set("WWW-Authenticate", "Basic realm=\"gopeed web\"")
|
||||||
|
}
|
||||||
|
WriteStatusJson(w, http.StatusUnauthorized, model.NewErrorResult("unauthorized", model.CodeUnauthorized))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// recover panic
|
||||||
|
r.Use(func(h http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
defer func() {
|
||||||
|
if v := recover(); v != nil {
|
||||||
|
err := errors.WithStack(fmt.Errorf("%v", v))
|
||||||
|
Downloader.Logger.Error().Stack().Err(err).Msgf("http server panic: %s %s", r.Method, r.RequestURI)
|
||||||
|
WriteJson(w, model.NewErrorResult(err.Error(), model.CodeError))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
h.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
srv = &http.Server{Handler: handlers.CORS(
|
||||||
|
handlers.AllowedHeaders([]string{"Content-Type", "X-Api-Token", "X-Target-Uri"}),
|
||||||
|
handlers.AllowedMethods([]string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"}),
|
||||||
|
handlers.AllowedOrigins([]string{"*"}),
|
||||||
|
)(r)}
|
||||||
|
return srv, listener, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolvePath(urlPath string, prefix string) (identity string, path string, err error) {
|
||||||
|
// remove prefix
|
||||||
|
clearPath := strings.TrimPrefix(urlPath, prefix)
|
||||||
|
// match extension identity, eg: /fs/extensions/identity/xxx
|
||||||
|
reg := regexp.MustCompile(`^/([^/]+)/(.*)$`)
|
||||||
|
if !reg.MatchString(clearPath) {
|
||||||
|
err = os.ErrNotExist
|
||||||
|
return
|
||||||
|
}
|
||||||
|
matched := reg.FindStringSubmatch(clearPath)
|
||||||
|
if len(matched) != 3 {
|
||||||
|
err = os.ErrNotExist
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return matched[1], matched[2], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle task file resource
|
||||||
|
type taskFileSystem struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *taskFileSystem) Open(name string) (http.File, error) {
|
||||||
|
// get extension identity
|
||||||
|
identity, path, err := resolvePath(name, "/fs/tasks")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
task := Downloader.GetTask(identity)
|
||||||
|
if task == nil {
|
||||||
|
return nil, os.ErrNotExist
|
||||||
|
}
|
||||||
|
return os.Open(filepath.Join(task.Meta.RootDirPath(), path))
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle extension file resource
|
||||||
|
type extensionFileSystem struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *extensionFileSystem) Open(name string) (http.File, error) {
|
||||||
|
// get extension identity
|
||||||
|
identity, path, err := resolvePath(name, "/fs/extensions")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
extension, err := Downloader.GetExtension(identity)
|
||||||
|
if err != nil {
|
||||||
|
return nil, os.ErrNotExist
|
||||||
|
}
|
||||||
|
extensionPath := Downloader.ExtensionPath(extension)
|
||||||
|
return os.Open(filepath.Join(extensionPath, path))
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadJson(r *http.Request, w http.ResponseWriter, v any) bool {
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(v); err != nil {
|
||||||
|
WriteJson(w, model.NewErrorResult(err.Error()))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func WriteJson(w http.ResponseWriter, v any) {
|
||||||
|
WriteStatusJson(w, http.StatusOK, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func WriteStatusJson(w http.ResponseWriter, statusCode int, v any) {
|
||||||
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
|
w.WriteHeader(statusCode)
|
||||||
|
json.NewEncoder(w).Encode(v)
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package api
|
package rest
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@@ -7,9 +7,9 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/GopeedLab/gopeed/internal/test"
|
"github.com/GopeedLab/gopeed/internal/test"
|
||||||
"github.com/GopeedLab/gopeed/pkg/api/model"
|
|
||||||
"github.com/GopeedLab/gopeed/pkg/base"
|
"github.com/GopeedLab/gopeed/pkg/base"
|
||||||
"github.com/GopeedLab/gopeed/pkg/download"
|
"github.com/GopeedLab/gopeed/pkg/download"
|
||||||
|
"github.com/GopeedLab/gopeed/pkg/rest/model"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -21,8 +21,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
httpInstance *Instance
|
restPort int
|
||||||
restPort int
|
|
||||||
|
|
||||||
taskReq = &base.Request{
|
taskReq = &base.Request{
|
||||||
Extra: map[string]any{
|
Extra: map[string]any{
|
||||||
@@ -91,7 +90,7 @@ func TestCreateTask(t *testing.T) {
|
|||||||
|
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
httpInstance.Listener(func(event *download.Event) {
|
Downloader.Listener(func(event *download.Event) {
|
||||||
if event.Key == download.EventKeyFinally {
|
if event.Key == download.EventKeyFinally {
|
||||||
wg.Done()
|
wg.Done()
|
||||||
}
|
}
|
||||||
@@ -118,7 +117,7 @@ func TestCreateDirectTask(t *testing.T) {
|
|||||||
doTest(func() {
|
doTest(func() {
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
httpInstance.Listener(func(event *download.Event) {
|
Downloader.Listener(func(event *download.Event) {
|
||||||
if event.Key == download.EventKeyFinally {
|
if event.Key == download.EventKeyFinally {
|
||||||
wg.Done()
|
wg.Done()
|
||||||
}
|
}
|
||||||
@@ -157,7 +156,7 @@ func TestCreateDirectTaskWithResoled(t *testing.T) {
|
|||||||
doTest(func() {
|
doTest(func() {
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
httpInstance.Listener(func(event *download.Event) {
|
Downloader.Listener(func(event *download.Event) {
|
||||||
if event.Key == download.EventKeyFinally {
|
if event.Key == download.EventKeyFinally {
|
||||||
wg.Done()
|
wg.Done()
|
||||||
}
|
}
|
||||||
@@ -181,7 +180,7 @@ func TestPauseAndContinueTask(t *testing.T) {
|
|||||||
doTest(func() {
|
doTest(func() {
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
httpInstance.Listener(func(event *download.Event) {
|
Downloader.Listener(func(event *download.Event) {
|
||||||
switch event.Key {
|
switch event.Key {
|
||||||
case download.EventKeyFinally:
|
case download.EventKeyFinally:
|
||||||
wg.Done()
|
wg.Done()
|
||||||
@@ -215,7 +214,10 @@ func TestPauseAndContinueTask(t *testing.T) {
|
|||||||
|
|
||||||
func TestPauseAllAndContinueALLTasks(t *testing.T) {
|
func TestPauseAllAndContinueALLTasks(t *testing.T) {
|
||||||
doTest(func() {
|
doTest(func() {
|
||||||
cfg := UnwrapResult(httpInstance.GetConfig())
|
cfg, err := Downloader.GetConfig()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
createAndPause := func() {
|
createAndPause := func() {
|
||||||
taskId := httpRequestCheckOk[string](http.MethodPost, "/api/v1/tasks", createReq)
|
taskId := httpRequestCheckOk[string](http.MethodPost, "/api/v1/tasks", createReq)
|
||||||
@@ -273,7 +275,7 @@ func TestDeleteAllTasks(t *testing.T) {
|
|||||||
|
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
wg.Add(taskCount)
|
wg.Add(taskCount)
|
||||||
httpInstance.Listener(func(event *download.Event) {
|
Downloader.Listener(func(event *download.Event) {
|
||||||
if event.Key == download.EventKeyFinally {
|
if event.Key == download.EventKeyFinally {
|
||||||
wg.Done()
|
wg.Done()
|
||||||
}
|
}
|
||||||
@@ -299,7 +301,7 @@ func TestDeleteTasksByStatues(t *testing.T) {
|
|||||||
|
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
wg.Add(taskCount)
|
wg.Add(taskCount)
|
||||||
httpInstance.Listener(func(event *download.Event) {
|
Downloader.Listener(func(event *download.Event) {
|
||||||
if event.Key == download.EventKeyFinally {
|
if event.Key == download.EventKeyFinally {
|
||||||
wg.Done()
|
wg.Done()
|
||||||
}
|
}
|
||||||
@@ -323,7 +325,7 @@ func TestGetTasks(t *testing.T) {
|
|||||||
doTest(func() {
|
doTest(func() {
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
httpInstance.Listener(func(event *download.Event) {
|
Downloader.Listener(func(event *download.Event) {
|
||||||
if event.Key == download.EventKeyFinally {
|
if event.Key == download.EventKeyFinally {
|
||||||
wg.Done()
|
wg.Done()
|
||||||
}
|
}
|
||||||
@@ -352,10 +354,7 @@ func TestGetAndPutConfig(t *testing.T) {
|
|||||||
cfg := httpRequestCheckOk[*base.DownloaderStoreConfig](http.MethodGet, "/api/v1/config", nil)
|
cfg := httpRequestCheckOk[*base.DownloaderStoreConfig](http.MethodGet, "/api/v1/config", nil)
|
||||||
cfg.DownloadDir = "./download"
|
cfg.DownloadDir = "./download"
|
||||||
cfg.Extra = map[string]any{
|
cfg.Extra = map[string]any{
|
||||||
"serverConfig": &struct {
|
"serverConfig": &Config{
|
||||||
Host string `json:"host"`
|
|
||||||
Port int `json:"port"`
|
|
||||||
}{
|
|
||||||
Host: "127.0.0.1",
|
Host: "127.0.0.1",
|
||||||
Port: 8080,
|
Port: 8080,
|
||||||
},
|
},
|
||||||
@@ -447,16 +446,16 @@ func TestDeleteExtension(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUpgradeCheckExtension(t *testing.T) {
|
func TestUpdateCheckExtension(t *testing.T) {
|
||||||
doTest(func() {
|
doTest(func() {
|
||||||
identity := httpRequestCheckOk[string](http.MethodPost, "/api/v1/extensions", installExtensionReq)
|
identity := httpRequestCheckOk[string](http.MethodPost, "/api/v1/extensions", installExtensionReq)
|
||||||
resp := httpRequestCheckOk[*model.UpgradeCheckExtensionResp](http.MethodGet, "/api/v1/extensions/"+identity+"/upgrade", nil)
|
resp := httpRequestCheckOk[*model.UpdateCheckExtensionResp](http.MethodGet, "/api/v1/extensions/"+identity+"/update", nil)
|
||||||
// no new version
|
// no new version
|
||||||
if resp.NewVersion != "" {
|
if resp.NewVersion != "" {
|
||||||
t.Errorf("UpgradeCheckExtension() got = %v, want %v", resp.NewVersion, "")
|
t.Errorf("UpdateCheckExtension() got = %v, want %v", resp.NewVersion, "")
|
||||||
}
|
}
|
||||||
// force update
|
// force update
|
||||||
httpRequestCheckOk[any](http.MethodPost, "/api/v1/extensions/"+identity+"/upgrade", nil)
|
httpRequestCheckOk[any](http.MethodPost, "/api/v1/extensions/"+identity+"/update", nil)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -507,19 +506,13 @@ func TestDoProxy(t *testing.T) {
|
|||||||
func TestApiToken(t *testing.T) {
|
func TestApiToken(t *testing.T) {
|
||||||
var cfg = &model.StartConfig{}
|
var cfg = &model.StartConfig{}
|
||||||
cfg.Init()
|
cfg.Init()
|
||||||
cfg.DownloadConfig = &base.DownloaderStoreConfig{
|
cfg.ApiToken = "123456"
|
||||||
Http: &base.DownloaderHttpConfig{
|
fileListener := doStart(cfg)
|
||||||
Enable: true,
|
|
||||||
ApiToken: "123456",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
httpInstance, _ = Create(cfg)
|
|
||||||
fileListener := doStart()
|
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := fileListener.Close(); err != nil {
|
if err := fileListener.Close(); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
httpInstance.Close()
|
Stop()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
status, _ := doHttpRequest0(http.MethodGet, "/api/v1/config", nil, nil)
|
status, _ := doHttpRequest0(http.MethodGet, "/api/v1/config", nil, nil)
|
||||||
@@ -528,7 +521,7 @@ func TestApiToken(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
status, _ = doHttpRequest0(http.MethodGet, "/api/v1/config", map[string]string{
|
status, _ = doHttpRequest0(http.MethodGet, "/api/v1/config", map[string]string{
|
||||||
"X-Api-Token": cfg.DownloadConfig.Http.ApiToken,
|
"X-Api-Token": cfg.ApiToken,
|
||||||
}, nil)
|
}, nil)
|
||||||
if status != http.StatusOK {
|
if status != http.StatusOK {
|
||||||
t.Errorf("TestApiToken() got = %v, want %v", status, http.StatusOK)
|
t.Errorf("TestApiToken() got = %v, want %v", status, http.StatusOK)
|
||||||
@@ -539,24 +532,18 @@ func TestApiToken(t *testing.T) {
|
|||||||
func TestAuthorization(t *testing.T) {
|
func TestAuthorization(t *testing.T) {
|
||||||
var cfg = &model.StartConfig{}
|
var cfg = &model.StartConfig{}
|
||||||
cfg.Init()
|
cfg.Init()
|
||||||
cfg.DownloadConfig = &base.DownloaderStoreConfig{
|
cfg.ApiToken = "123456"
|
||||||
Http: &base.DownloaderHttpConfig{
|
|
||||||
Enable: true,
|
|
||||||
ApiToken: "123456",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
cfg.WebEnable = true
|
cfg.WebEnable = true
|
||||||
cfg.WebBasicAuth = &model.WebBasicAuth{
|
cfg.WebBasicAuth = &model.WebBasicAuth{
|
||||||
Username: "admin",
|
Username: "admin",
|
||||||
Password: "123456",
|
Password: "123456",
|
||||||
}
|
}
|
||||||
httpInstance, _ = Create(cfg)
|
fileListener := doStart(cfg)
|
||||||
fileListener := doStart()
|
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := fileListener.Close(); err != nil {
|
if err := fileListener.Close(); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
httpInstance.Close()
|
Stop()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
status, _ := doHttpRequest0(http.MethodGet, "/api/v1/config", nil, nil)
|
status, _ := doHttpRequest0(http.MethodGet, "/api/v1/config", nil, nil)
|
||||||
@@ -572,7 +559,7 @@ func TestAuthorization(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
status, _ = doHttpRequest0(http.MethodGet, "/api/v1/config", map[string]string{
|
status, _ = doHttpRequest0(http.MethodGet, "/api/v1/config", map[string]string{
|
||||||
"X-Api-Token": cfg.DownloadConfig.Http.ApiToken,
|
"X-Api-Token": cfg.ApiToken,
|
||||||
}, nil)
|
}, nil)
|
||||||
if status != http.StatusOK {
|
if status != http.StatusOK {
|
||||||
t.Errorf("TestAuthorization() got = %v, want %v", status, http.StatusOK)
|
t.Errorf("TestAuthorization() got = %v, want %v", status, http.StatusOK)
|
||||||
@@ -586,23 +573,17 @@ func doTest(handler func()) {
|
|||||||
cfg.Storage = storage
|
cfg.Storage = storage
|
||||||
cfg.StorageDir = ".test_storage"
|
cfg.StorageDir = ".test_storage"
|
||||||
cfg.WebEnable = true
|
cfg.WebEnable = true
|
||||||
cfg.DownloadConfig = &base.DownloaderStoreConfig{
|
fileListener := doStart(cfg)
|
||||||
Http: &base.DownloaderHttpConfig{
|
|
||||||
Enable: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
httpInstance, _ = Create(cfg)
|
|
||||||
fileListener := doStart()
|
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := fileListener.Close(); err != nil {
|
if err := fileListener.Close(); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
httpInstance.StopHttp()
|
Stop()
|
||||||
httpInstance.Clear()
|
Downloader.Clear()
|
||||||
}()
|
}()
|
||||||
defer func() {
|
defer func() {
|
||||||
httpInstance.PauseTasks(nil)
|
Downloader.Pause(nil)
|
||||||
httpInstance.DeleteTasks(nil, true)
|
Downloader.Delete(nil, true)
|
||||||
os.RemoveAll(cfg.StorageDir)
|
os.RemoveAll(cfg.StorageDir)
|
||||||
}()
|
}()
|
||||||
taskReq.URL = "http://" + fileListener.Addr().String() + "/" + test.BuildName
|
taskReq.URL = "http://" + fileListener.Addr().String() + "/" + test.BuildName
|
||||||
@@ -612,19 +593,15 @@ func doTest(handler func()) {
|
|||||||
testFunc(model.StorageBolt)
|
testFunc(model.StorageBolt)
|
||||||
}
|
}
|
||||||
|
|
||||||
func doStart() net.Listener {
|
func doStart(cfg *model.StartConfig) net.Listener {
|
||||||
port := UnwrapResult(httpInstance.StartHttp())
|
port, err := Start(cfg)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
restPort = port
|
restPort = port
|
||||||
return test.StartTestFileServer()
|
return test.StartTestFileServer()
|
||||||
}
|
}
|
||||||
|
|
||||||
func UnwrapResult[T any](result *model.Result[T]) T {
|
|
||||||
if result.HasError() {
|
|
||||||
panic(result.Msg)
|
|
||||||
}
|
|
||||||
return result.Data
|
|
||||||
}
|
|
||||||
|
|
||||||
func doHttpRequest0(method string, path string, headers map[string]string, body any) (int, []byte) {
|
func doHttpRequest0(method string, path string, headers map[string]string, body any) (int, []byte) {
|
||||||
var reader io.Reader
|
var reader io.Reader
|
||||||
if body != nil {
|
if body != nil {
|
||||||
@@ -10,7 +10,6 @@ import (
|
|||||||
|
|
||||||
func ParseSchema(url string) string {
|
func ParseSchema(url string) string {
|
||||||
index := strings.Index(url, ":")
|
index := strings.Index(url, ":")
|
||||||
// if no schema or windows path like C:\a.txt, return FILE
|
|
||||||
if index == -1 || index == 1 {
|
if index == -1 || index == 1 {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,3 +68,6 @@ windows/flutter/generated_plugins.cmake
|
|||||||
# Hive database files
|
# Hive database files
|
||||||
database.hive
|
database.hive
|
||||||
database.lock
|
database.lock
|
||||||
|
|
||||||
|
assets/host/host
|
||||||
|
assets/host/host.exe
|
||||||
@@ -44,7 +44,7 @@ android {
|
|||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||||
applicationId "com.gopeed"
|
applicationId "com.gopeed.gopeed"
|
||||||
// You can update the following values to match your application needs.
|
// You can update the following values to match your application needs.
|
||||||
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration.
|
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration.
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="com.gopeed">
|
package="com.gopeed.gopeed">
|
||||||
<!-- The INTERNET permission is required for development. Specifically,
|
<!-- The INTERNET permission is required for development. Specifically,
|
||||||
the Flutter tool needs it to communicate with the running application
|
the Flutter tool needs it to communicate with the running application
|
||||||
to allow setting breakpoints, to provide hot reload, etc.
|
to allow setting breakpoints, to provide hot reload, etc.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="com.gopeed">
|
package="com.gopeed.gopeed">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package com.gopeed
|
package com.gopeed.gopeed
|
||||||
|
|
||||||
import androidx.annotation.NonNull
|
import androidx.annotation.NonNull
|
||||||
import com.gopeed.libgopeed.Libgopeed
|
import com.gopeed.libgopeed.Libgopeed
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="com.gopeed">
|
package="com.gopeed.gopeed">
|
||||||
<!-- The INTERNET permission is required for development. Specifically,
|
<!-- The INTERNET permission is required for development. Specifically,
|
||||||
the Flutter tool needs it to communicate with the running application
|
the Flutter tool needs it to communicate with the running application
|
||||||
to allow setting breakpoints, to provide hot reload, etc.
|
to allow setting breakpoints, to provide hot reload, etc.
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Whitespace-only changes.
@@ -74,8 +74,14 @@ typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;
|
|||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
extern __declspec(dllexport) char* Init(char* cfg);
|
|
||||||
extern __declspec(dllexport) char* Invoke(char* req);
|
/* Return type for Start */
|
||||||
|
struct Start_return {
|
||||||
|
GoInt r0;
|
||||||
|
char* r1;
|
||||||
|
};
|
||||||
|
extern struct Start_return Start(char* cfg);
|
||||||
|
extern void Stop();
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,13 @@ target 'Runner' do
|
|||||||
use_modular_headers!
|
use_modular_headers!
|
||||||
|
|
||||||
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
|
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
|
||||||
|
|
||||||
|
# share_handler addition start
|
||||||
|
target 'ShareExtension' do
|
||||||
|
inherit! :search_paths
|
||||||
|
pod "share_handler_ios_models", :path => ".symlinks/plugins/share_handler_ios/ios/Models"
|
||||||
|
end
|
||||||
|
# share_handler addition end
|
||||||
end
|
end
|
||||||
|
|
||||||
post_install do |installer|
|
post_install do |installer|
|
||||||
|
|||||||
@@ -7,6 +7,9 @@
|
|||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
|
0C585CBA2D41E28900FF2EC0 /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C585CB92D41E28900FF2EC0 /* ShareViewController.swift */; };
|
||||||
|
0C585CBD2D41E28900FF2EC0 /* Base in Resources */ = {isa = PBXBuildFile; fileRef = 0C585CBC2D41E28900FF2EC0 /* Base */; };
|
||||||
|
0C585CC12D41E28900FF2EC0 /* ShareExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 0C585CB72D41E28900FF2EC0 /* ShareExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||||
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
|
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
|
||||||
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
|
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
|
||||||
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
|
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
|
||||||
@@ -20,7 +23,28 @@
|
|||||||
DCA5B0FA95A8007ADEDACA1B /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 70BC8D37FAC641C7F20125E8 /* Pods_Runner.framework */; };
|
DCA5B0FA95A8007ADEDACA1B /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 70BC8D37FAC641C7F20125E8 /* Pods_Runner.framework */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
|
/* Begin PBXContainerItemProxy section */
|
||||||
|
0C585CBF2D41E28900FF2EC0 /* PBXContainerItemProxy */ = {
|
||||||
|
isa = PBXContainerItemProxy;
|
||||||
|
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
|
||||||
|
proxyType = 1;
|
||||||
|
remoteGlobalIDString = 0C585CB62D41E28900FF2EC0;
|
||||||
|
remoteInfo = ShareExtension;
|
||||||
|
};
|
||||||
|
/* End PBXContainerItemProxy section */
|
||||||
|
|
||||||
/* Begin PBXCopyFilesBuildPhase section */
|
/* Begin PBXCopyFilesBuildPhase section */
|
||||||
|
0C585CC22D41E28900FF2EC0 /* Embed Foundation Extensions */ = {
|
||||||
|
isa = PBXCopyFilesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
dstPath = "";
|
||||||
|
dstSubfolderSpec = 13;
|
||||||
|
files = (
|
||||||
|
0C585CC12D41E28900FF2EC0 /* ShareExtension.appex in Embed Foundation Extensions */,
|
||||||
|
);
|
||||||
|
name = "Embed Foundation Extensions";
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
|
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
|
||||||
isa = PBXCopyFilesBuildPhase;
|
isa = PBXCopyFilesBuildPhase;
|
||||||
buildActionMask = 8;
|
buildActionMask = 8;
|
||||||
@@ -35,7 +59,13 @@
|
|||||||
/* 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>"; };
|
029EF293EAA92EB3D18BBF19 /* libresolv.tbd */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libresolv.tbd; path = usr/lib/libresolv.tbd; sourceTree = "<group>"; };
|
||||||
|
0C585CB72D41E28900FF2EC0 /* ShareExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = ShareExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
0C585CB92D41E28900FF2EC0 /* ShareViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareViewController.swift; sourceTree = "<group>"; };
|
||||||
|
0C585CBC2D41E28900FF2EC0 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = "<group>"; };
|
||||||
|
0C585CBE2D41E28900FF2EC0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
|
0C585CC72D41E36800FF2EC0 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = "<group>"; };
|
||||||
|
0C585CC82D41E38D00FF2EC0 /* ShareExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ShareExtension.entitlements; 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>"; };
|
||||||
@@ -58,6 +88,13 @@
|
|||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
|
0C585CB42D41E28900FF2EC0 /* Frameworks */ = {
|
||||||
|
isa = PBXFrameworksBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
97C146EB1CF9000F007C117D /* Frameworks */ = {
|
97C146EB1CF9000F007C117D /* Frameworks */ = {
|
||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
@@ -72,6 +109,17 @@
|
|||||||
/* End PBXFrameworksBuildPhase section */
|
/* End PBXFrameworksBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXGroup section */
|
/* Begin PBXGroup section */
|
||||||
|
0C585CB82D41E28900FF2EC0 /* ShareExtension */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
0C585CC82D41E38D00FF2EC0 /* ShareExtension.entitlements */,
|
||||||
|
0C585CB92D41E28900FF2EC0 /* ShareViewController.swift */,
|
||||||
|
0C585CBB2D41E28900FF2EC0 /* MainInterface.storyboard */,
|
||||||
|
0C585CBE2D41E28900FF2EC0 /* Info.plist */,
|
||||||
|
);
|
||||||
|
path = ShareExtension;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
9740EEB11CF90186004384FC /* Flutter */ = {
|
9740EEB11CF90186004384FC /* Flutter */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -88,6 +136,7 @@
|
|||||||
children = (
|
children = (
|
||||||
9740EEB11CF90186004384FC /* Flutter */,
|
9740EEB11CF90186004384FC /* Flutter */,
|
||||||
97C146F01CF9000F007C117D /* Runner */,
|
97C146F01CF9000F007C117D /* Runner */,
|
||||||
|
0C585CB82D41E28900FF2EC0 /* ShareExtension */,
|
||||||
97C146EF1CF9000F007C117D /* Products */,
|
97C146EF1CF9000F007C117D /* Products */,
|
||||||
ADF7F433B4BC23D6C18AB78B /* Pods */,
|
ADF7F433B4BC23D6C18AB78B /* Pods */,
|
||||||
B4A1CBAC5AA50208793CFF34 /* Frameworks */,
|
B4A1CBAC5AA50208793CFF34 /* Frameworks */,
|
||||||
@@ -98,6 +147,7 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
97C146EE1CF9000F007C117D /* Runner.app */,
|
97C146EE1CF9000F007C117D /* Runner.app */,
|
||||||
|
0C585CB72D41E28900FF2EC0 /* ShareExtension.appex */,
|
||||||
);
|
);
|
||||||
name = Products;
|
name = Products;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -105,6 +155,7 @@
|
|||||||
97C146F01CF9000F007C117D /* Runner */ = {
|
97C146F01CF9000F007C117D /* Runner */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
0C585CC72D41E36800FF2EC0 /* Runner.entitlements */,
|
||||||
97C146FA1CF9000F007C117D /* Main.storyboard */,
|
97C146FA1CF9000F007C117D /* Main.storyboard */,
|
||||||
97C146FD1CF9000F007C117D /* Assets.xcassets */,
|
97C146FD1CF9000F007C117D /* Assets.xcassets */,
|
||||||
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
|
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
|
||||||
@@ -141,6 +192,23 @@
|
|||||||
/* End PBXGroup section */
|
/* End PBXGroup section */
|
||||||
|
|
||||||
/* Begin PBXNativeTarget section */
|
/* Begin PBXNativeTarget section */
|
||||||
|
0C585CB62D41E28900FF2EC0 /* ShareExtension */ = {
|
||||||
|
isa = PBXNativeTarget;
|
||||||
|
buildConfigurationList = 0C585CC62D41E28900FF2EC0 /* Build configuration list for PBXNativeTarget "ShareExtension" */;
|
||||||
|
buildPhases = (
|
||||||
|
0C585CB32D41E28900FF2EC0 /* Sources */,
|
||||||
|
0C585CB42D41E28900FF2EC0 /* Frameworks */,
|
||||||
|
0C585CB52D41E28900FF2EC0 /* Resources */,
|
||||||
|
);
|
||||||
|
buildRules = (
|
||||||
|
);
|
||||||
|
dependencies = (
|
||||||
|
);
|
||||||
|
name = ShareExtension;
|
||||||
|
productName = ShareExtension;
|
||||||
|
productReference = 0C585CB72D41E28900FF2EC0 /* ShareExtension.appex */;
|
||||||
|
productType = "com.apple.product-type.app-extension";
|
||||||
|
};
|
||||||
97C146ED1CF9000F007C117D /* Runner */ = {
|
97C146ED1CF9000F007C117D /* Runner */ = {
|
||||||
isa = PBXNativeTarget;
|
isa = PBXNativeTarget;
|
||||||
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
|
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
|
||||||
@@ -150,6 +218,7 @@
|
|||||||
97C146EA1CF9000F007C117D /* Sources */,
|
97C146EA1CF9000F007C117D /* Sources */,
|
||||||
97C146EB1CF9000F007C117D /* Frameworks */,
|
97C146EB1CF9000F007C117D /* Frameworks */,
|
||||||
97C146EC1CF9000F007C117D /* Resources */,
|
97C146EC1CF9000F007C117D /* Resources */,
|
||||||
|
0C585CC22D41E28900FF2EC0 /* Embed Foundation Extensions */,
|
||||||
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
||||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
||||||
DDA801E9D83E2E88398514D3 /* [CP] Embed Pods Frameworks */,
|
DDA801E9D83E2E88398514D3 /* [CP] Embed Pods Frameworks */,
|
||||||
@@ -157,6 +226,7 @@
|
|||||||
buildRules = (
|
buildRules = (
|
||||||
);
|
);
|
||||||
dependencies = (
|
dependencies = (
|
||||||
|
0C585CC02D41E28900FF2EC0 /* PBXTargetDependency */,
|
||||||
);
|
);
|
||||||
name = Runner;
|
name = Runner;
|
||||||
productName = Runner;
|
productName = Runner;
|
||||||
@@ -169,9 +239,13 @@
|
|||||||
97C146E61CF9000F007C117D /* Project object */ = {
|
97C146E61CF9000F007C117D /* Project object */ = {
|
||||||
isa = PBXProject;
|
isa = PBXProject;
|
||||||
attributes = {
|
attributes = {
|
||||||
|
LastSwiftUpdateCheck = 1540;
|
||||||
LastUpgradeCheck = 1430;
|
LastUpgradeCheck = 1430;
|
||||||
ORGANIZATIONNAME = "";
|
ORGANIZATIONNAME = "";
|
||||||
TargetAttributes = {
|
TargetAttributes = {
|
||||||
|
0C585CB62D41E28900FF2EC0 = {
|
||||||
|
CreatedOnToolsVersion = 15.4;
|
||||||
|
};
|
||||||
97C146ED1CF9000F007C117D = {
|
97C146ED1CF9000F007C117D = {
|
||||||
CreatedOnToolsVersion = 7.3.1;
|
CreatedOnToolsVersion = 7.3.1;
|
||||||
LastSwiftMigration = 1100;
|
LastSwiftMigration = 1100;
|
||||||
@@ -192,11 +266,20 @@
|
|||||||
projectRoot = "";
|
projectRoot = "";
|
||||||
targets = (
|
targets = (
|
||||||
97C146ED1CF9000F007C117D /* Runner */,
|
97C146ED1CF9000F007C117D /* Runner */,
|
||||||
|
0C585CB62D41E28900FF2EC0 /* ShareExtension */,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
/* End PBXProject section */
|
/* End PBXProject section */
|
||||||
|
|
||||||
/* Begin PBXResourcesBuildPhase section */
|
/* Begin PBXResourcesBuildPhase section */
|
||||||
|
0C585CB52D41E28900FF2EC0 /* Resources */ = {
|
||||||
|
isa = PBXResourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
0C585CBD2D41E28900FF2EC0 /* Base in Resources */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
97C146EC1CF9000F007C117D /* Resources */ = {
|
97C146EC1CF9000F007C117D /* Resources */ = {
|
||||||
isa = PBXResourcesBuildPhase;
|
isa = PBXResourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
@@ -284,6 +367,14 @@
|
|||||||
/* End PBXShellScriptBuildPhase section */
|
/* End PBXShellScriptBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXSourcesBuildPhase section */
|
/* Begin PBXSourcesBuildPhase section */
|
||||||
|
0C585CB32D41E28900FF2EC0 /* Sources */ = {
|
||||||
|
isa = PBXSourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
0C585CBA2D41E28900FF2EC0 /* ShareViewController.swift in Sources */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
97C146EA1CF9000F007C117D /* Sources */ = {
|
97C146EA1CF9000F007C117D /* Sources */ = {
|
||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
@@ -295,7 +386,23 @@
|
|||||||
};
|
};
|
||||||
/* End PBXSourcesBuildPhase section */
|
/* End PBXSourcesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXTargetDependency section */
|
||||||
|
0C585CC02D41E28900FF2EC0 /* PBXTargetDependency */ = {
|
||||||
|
isa = PBXTargetDependency;
|
||||||
|
target = 0C585CB62D41E28900FF2EC0 /* ShareExtension */;
|
||||||
|
targetProxy = 0C585CBF2D41E28900FF2EC0 /* PBXContainerItemProxy */;
|
||||||
|
};
|
||||||
|
/* End PBXTargetDependency section */
|
||||||
|
|
||||||
/* Begin PBXVariantGroup section */
|
/* Begin PBXVariantGroup section */
|
||||||
|
0C585CBB2D41E28900FF2EC0 /* MainInterface.storyboard */ = {
|
||||||
|
isa = PBXVariantGroup;
|
||||||
|
children = (
|
||||||
|
0C585CBC2D41E28900FF2EC0 /* Base */,
|
||||||
|
);
|
||||||
|
name = MainInterface.storyboard;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
|
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
|
||||||
isa = PBXVariantGroup;
|
isa = PBXVariantGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -315,6 +422,123 @@
|
|||||||
/* End PBXVariantGroup section */
|
/* End PBXVariantGroup section */
|
||||||
|
|
||||||
/* Begin XCBuildConfiguration section */
|
/* Begin XCBuildConfiguration section */
|
||||||
|
0C585CC32D41E28900FF2EC0 /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||||
|
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||||
|
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||||
|
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||||
|
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||||
|
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||||
|
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
DEVELOPMENT_TEAM = JH48DS925K;
|
||||||
|
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
INFOPLIST_FILE = ShareExtension/Info.plist;
|
||||||
|
INFOPLIST_KEY_CFBundleDisplayName = ShareExtension;
|
||||||
|
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 17.5;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
"@executable_path/../../Frameworks",
|
||||||
|
);
|
||||||
|
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||||
|
MTL_FAST_MATH = YES;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.gopeed.gopeed.ShareExtension;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SKIP_INSTALL = YES;
|
||||||
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
|
||||||
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
0C585CC42D41E28900FF2EC0 /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||||
|
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||||
|
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||||
|
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||||
|
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||||
|
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||||
|
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
DEVELOPMENT_TEAM = JH48DS925K;
|
||||||
|
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
INFOPLIST_FILE = ShareExtension/Info.plist;
|
||||||
|
INFOPLIST_KEY_CFBundleDisplayName = ShareExtension;
|
||||||
|
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 17.5;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
"@executable_path/../../Frameworks",
|
||||||
|
);
|
||||||
|
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
MTL_FAST_MATH = YES;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.gopeed.gopeed.ShareExtension;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SKIP_INSTALL = YES;
|
||||||
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
0C585CC52D41E28900FF2EC0 /* Profile */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||||
|
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||||
|
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||||
|
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||||
|
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||||
|
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||||
|
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
DEVELOPMENT_TEAM = JH48DS925K;
|
||||||
|
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
INFOPLIST_FILE = ShareExtension/Info.plist;
|
||||||
|
INFOPLIST_KEY_CFBundleDisplayName = ShareExtension;
|
||||||
|
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 17.5;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
"@executable_path/../../Frameworks",
|
||||||
|
);
|
||||||
|
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
MTL_FAST_MATH = YES;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.gopeed.gopeed.ShareExtension;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SKIP_INSTALL = YES;
|
||||||
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
};
|
||||||
|
name = Profile;
|
||||||
|
};
|
||||||
249021D3217E4FDB00AE95B9 /* Profile */ = {
|
249021D3217E4FDB00AE95B9 /* Profile */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
@@ -369,8 +593,10 @@
|
|||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
|
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
DEVELOPMENT_TEAM = JH48DS925K;
|
DEVELOPMENT_TEAM = JH48DS925K;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
@@ -379,7 +605,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.gopeed;
|
PRODUCT_BUNDLE_IDENTIFIER = com.gopeed.gopeed;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
@@ -498,8 +724,10 @@
|
|||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
|
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
|
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
DEVELOPMENT_TEAM = JH48DS925K;
|
DEVELOPMENT_TEAM = JH48DS925K;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
@@ -508,7 +736,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.gopeed;
|
PRODUCT_BUNDLE_IDENTIFIER = com.gopeed.gopeed;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
@@ -521,8 +749,10 @@
|
|||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
|
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
DEVELOPMENT_TEAM = JH48DS925K;
|
DEVELOPMENT_TEAM = JH48DS925K;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
@@ -531,7 +761,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.gopeed;
|
PRODUCT_BUNDLE_IDENTIFIER = com.gopeed.gopeed;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
@@ -542,6 +772,16 @@
|
|||||||
/* End XCBuildConfiguration section */
|
/* End XCBuildConfiguration section */
|
||||||
|
|
||||||
/* Begin XCConfigurationList section */
|
/* Begin XCConfigurationList section */
|
||||||
|
0C585CC62D41E28900FF2EC0 /* Build configuration list for PBXNativeTarget "ShareExtension" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
0C585CC32D41E28900FF2EC0 /* Debug */,
|
||||||
|
0C585CC42D41E28900FF2EC0 /* Release */,
|
||||||
|
0C585CC52D41E28900FF2EC0 /* Profile */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
|
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
|
||||||
isa = XCConfigurationList;
|
isa = XCConfigurationList;
|
||||||
buildConfigurations = (
|
buildConfigurations = (
|
||||||
|
|||||||
@@ -1,54 +1,106 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
|
||||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
|
||||||
<true/>
|
|
||||||
<key>CFBundleDevelopmentRegion</key>
|
|
||||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
|
||||||
<key>CFBundleDisplayName</key>
|
|
||||||
<string>Gopeed</string>
|
|
||||||
<key>CFBundleExecutable</key>
|
|
||||||
<string>$(EXECUTABLE_NAME)</string>
|
|
||||||
<key>CFBundleIdentifier</key>
|
|
||||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
|
||||||
<key>CFBundleInfoDictionaryVersion</key>
|
|
||||||
<string>6.0</string>
|
|
||||||
<key>CFBundlePackageType</key>
|
|
||||||
<string>APPL</string>
|
|
||||||
<key>CFBundleShortVersionString</key>
|
|
||||||
<string>$(FLUTTER_BUILD_NAME)</string>
|
|
||||||
<key>CFBundleSignature</key>
|
|
||||||
<string>????</string>
|
|
||||||
<key>CFBundleVersion</key>
|
|
||||||
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
|
||||||
<key>LSRequiresIPhoneOS</key>
|
|
||||||
<true/>
|
|
||||||
<key>NSAppTransportSecurity</key>
|
|
||||||
<dict>
|
<dict>
|
||||||
<key>NSAllowsArbitraryLoads</key>
|
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||||
<true/>
|
<true />
|
||||||
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||||
|
<key>CFBundleDisplayName</key>
|
||||||
|
<string>Gopeed</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>$(EXECUTABLE_NAME)</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>APPL</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>$(FLUTTER_BUILD_NAME)</string>
|
||||||
|
<key>CFBundleSignature</key>
|
||||||
|
<string>????</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||||
|
<key>LSRequiresIPhoneOS</key>
|
||||||
|
<true />
|
||||||
|
<key>NSAppTransportSecurity</key>
|
||||||
|
<dict>
|
||||||
|
<key>NSAllowsArbitraryLoads</key>
|
||||||
|
<true />
|
||||||
|
</dict>
|
||||||
|
<key>UILaunchStoryboardName</key>
|
||||||
|
<string>LaunchScreen</string>
|
||||||
|
<key>UIMainStoryboardFile</key>
|
||||||
|
<string>Main</string>
|
||||||
|
<key>UISupportedInterfaceOrientations</key>
|
||||||
|
<array>
|
||||||
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
|
</array>
|
||||||
|
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||||
|
<array>
|
||||||
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
|
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
|
</array>
|
||||||
|
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||||
|
<false />
|
||||||
|
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||||
|
<true />
|
||||||
|
|
||||||
|
<!-- Add for share_handler start -->
|
||||||
|
<!-- The 'NSUserActivityTypes' key is only needed if you plan to use the recordSentMessage API
|
||||||
|
allowing for conversations to show up as direct share suggestions -->
|
||||||
|
<key>NSUserActivityTypes</key>
|
||||||
|
<array>
|
||||||
|
<string>INSendMessageIntent</string>
|
||||||
|
</array>
|
||||||
|
|
||||||
|
<!-- Uncomment below lines if you want to use a custom group id rather than the default. Set it
|
||||||
|
in Build Settings -> User-Defined -->
|
||||||
|
<!-- <key>AppGroupId</key>
|
||||||
|
<string>$(CUSTOM_GROUP_ID)</string> -->
|
||||||
|
|
||||||
|
<key>CFBundleURLTypes</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleTypeRole</key>
|
||||||
|
<string>Editor</string>
|
||||||
|
<key>CFBundleURLSchemes</key>
|
||||||
|
<array>
|
||||||
|
<string>ShareMedia-$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
|
||||||
|
<key>NSPhotoLibraryUsageDescription</key>
|
||||||
|
<string>Photos can be shared to and used in this app</string>
|
||||||
|
|
||||||
|
<!-- Optional: Add/Customize for AirDrop support -->
|
||||||
|
<key>LSSupportsOpeningDocumentsInPlace</key>
|
||||||
|
<string>No</string>
|
||||||
|
<key>CFBundleDocumentTypes</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleTypeName</key>
|
||||||
|
<string>ShareHandler</string>
|
||||||
|
<key>LSHandlerRank</key>
|
||||||
|
<string>Alternate</string>
|
||||||
|
<key>LSItemContentTypes</key>
|
||||||
|
<array>
|
||||||
|
<string>public.file-url</string>
|
||||||
|
<string>public.image</string>
|
||||||
|
<string>public.text</string>
|
||||||
|
<string>public.movie</string>
|
||||||
|
<string>public.url</string>
|
||||||
|
<string>public.data</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
|
||||||
|
<!-- Add for share_handler end -->
|
||||||
</dict>
|
</dict>
|
||||||
<key>UILaunchStoryboardName</key>
|
|
||||||
<string>LaunchScreen</string>
|
|
||||||
<key>UIMainStoryboardFile</key>
|
|
||||||
<string>Main</string>
|
|
||||||
<key>UISupportedInterfaceOrientations</key>
|
|
||||||
<array>
|
|
||||||
<string>UIInterfaceOrientationPortrait</string>
|
|
||||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
|
||||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
|
||||||
</array>
|
|
||||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
|
||||||
<array>
|
|
||||||
<string>UIInterfaceOrientationPortrait</string>
|
|
||||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
|
||||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
|
||||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
|
||||||
</array>
|
|
||||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
|
||||||
<false/>
|
|
||||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
|
||||||
<true/>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
</plist>
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>com.apple.security.application-groups</key>
|
||||||
|
<array>
|
||||||
|
<string>group.com.gopeed.gopeed</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="j1y-V4-xli">
|
||||||
|
<dependencies>
|
||||||
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
|
||||||
|
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||||
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
|
</dependencies>
|
||||||
|
<scenes>
|
||||||
|
<!--Share View Controller-->
|
||||||
|
<scene sceneID="ceB-am-kn3">
|
||||||
|
<objects>
|
||||||
|
<viewController id="j1y-V4-xli" customClass="ShareViewController" customModuleProvider="target" sceneMemberID="viewController">
|
||||||
|
<view key="view" opaque="NO" contentMode="scaleToFill" id="wbc-yd-nQP">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
|
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
<viewLayoutGuide key="safeArea" id="1Xd-am-t49"/>
|
||||||
|
</view>
|
||||||
|
</viewController>
|
||||||
|
<placeholder placeholderIdentifier="IBFirstResponder" id="CEy-Cv-SGf" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||||
|
</objects>
|
||||||
|
</scene>
|
||||||
|
</scenes>
|
||||||
|
</document>
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<!-- Uncomment below lines if you want to use a custom group id rather than the default. Set it
|
||||||
|
in Build Settings -> User-Defined -->
|
||||||
|
<!-- <key>AppGroupId</key>
|
||||||
|
<string>$(CUSTOM_GROUP_ID)</string> -->
|
||||||
|
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||||
|
<key>NSExtension</key>
|
||||||
|
<dict>
|
||||||
|
<key>NSExtensionAttributes</key>
|
||||||
|
<dict>
|
||||||
|
<!-- Add supported message intent if you support sharing to a specific conversation - start -->
|
||||||
|
<key>IntentsSupported</key>
|
||||||
|
<array>
|
||||||
|
<string>INSendMessageIntent</string>
|
||||||
|
</array>
|
||||||
|
<!-- Add supported message intent if you support sharing to a specific conversation
|
||||||
|
(registered via the recordSentMessage api call) - end -->
|
||||||
|
<key>NSExtensionActivationRule</key>
|
||||||
|
<!-- Comment or delete the TRUEPREDICATE NSExtensionActivationRule that only works in
|
||||||
|
development mode -->
|
||||||
|
<!-- <string>TRUEPREDICATE</string> -->
|
||||||
|
<!-- Add a new NSExtensionActivationRule. The rule below will allow sharing one or more file
|
||||||
|
of any type, url, or text content, You can modify these rules to your liking for which types
|
||||||
|
of share content, as well as how many your app can handle -->
|
||||||
|
<string>SUBQUERY ( extensionItems, $extensionItem, SUBQUERY ( $extensionItem.attachments,
|
||||||
|
$attachment, ( ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.file-url"
|
||||||
|
|| ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.image" || ANY
|
||||||
|
$attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.text" || ANY
|
||||||
|
$attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.movie" || ANY
|
||||||
|
$attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.url" ) ).@count > 0 ).@count
|
||||||
|
> 0 </string>
|
||||||
|
<key>PHSupportedMediaTypes</key>
|
||||||
|
<array>
|
||||||
|
<string>Video</string>
|
||||||
|
<string>Image</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
<key>NSExtensionMainStoryboard</key>
|
||||||
|
<string>MainInterface</string>
|
||||||
|
<key>NSExtensionPointIdentifier</key>
|
||||||
|
<string>com.apple.share-services</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>com.apple.security.application-groups</key>
|
||||||
|
<array>
|
||||||
|
<string>group.com.gopeed.gopeed</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import share_handler_ios_models
|
||||||
|
|
||||||
|
class ShareViewController: ShareHandlerIosViewController {}
|
||||||
@@ -0,0 +1,227 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:dio/io.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:gopeed/api/model/extension.dart';
|
||||||
|
import 'package:gopeed/api/model/install_extension.dart';
|
||||||
|
import 'package:gopeed/api/model/switch_extension.dart';
|
||||||
|
|
||||||
|
import '../util/util.dart';
|
||||||
|
import 'model/create_task.dart';
|
||||||
|
import 'model/downloader_config.dart';
|
||||||
|
import 'model/request.dart';
|
||||||
|
import 'model/resolve_result.dart';
|
||||||
|
import 'model/result.dart';
|
||||||
|
import 'model/task.dart';
|
||||||
|
import 'model/update_extension_settings.dart';
|
||||||
|
import 'model/update_check_extension_resp.dart';
|
||||||
|
|
||||||
|
class _Client {
|
||||||
|
static _Client? _instance;
|
||||||
|
|
||||||
|
late Dio dio;
|
||||||
|
|
||||||
|
_Client._internal();
|
||||||
|
|
||||||
|
factory _Client(String network, String address, String apiToken) {
|
||||||
|
if (_instance == null) {
|
||||||
|
_instance = _Client._internal();
|
||||||
|
var dio = Dio();
|
||||||
|
final isUnixSocket = network == 'unix';
|
||||||
|
var baseUrl = 'http://127.0.0.1';
|
||||||
|
if (!isUnixSocket) {
|
||||||
|
if (Util.isWeb()) {
|
||||||
|
baseUrl = kDebugMode ? 'http://127.0.0.1:9999' : '';
|
||||||
|
} else {
|
||||||
|
baseUrl = 'http://$address';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dio.options.baseUrl = baseUrl;
|
||||||
|
dio.options.contentType = Headers.jsonContentType;
|
||||||
|
dio.options.sendTimeout = const Duration(seconds: 5);
|
||||||
|
dio.options.connectTimeout = const Duration(seconds: 5);
|
||||||
|
dio.options.receiveTimeout = const Duration(seconds: 60);
|
||||||
|
dio.interceptors.add(InterceptorsWrapper(onRequest: (options, handler) {
|
||||||
|
if (apiToken.isNotEmpty) {
|
||||||
|
options.headers['X-Api-Token'] = apiToken;
|
||||||
|
}
|
||||||
|
handler.next(options);
|
||||||
|
}));
|
||||||
|
|
||||||
|
_instance!.dio = dio;
|
||||||
|
if (isUnixSocket) {
|
||||||
|
(_instance!.dio.httpClientAdapter as IOHttpClientAdapter)
|
||||||
|
.createHttpClient = () {
|
||||||
|
final client = HttpClient();
|
||||||
|
client.connectionFactory =
|
||||||
|
(Uri uri, String? proxyHost, int? proxyPort) {
|
||||||
|
return Socket.startConnect(
|
||||||
|
InternetAddress(address, type: InternetAddressType.unix), 0);
|
||||||
|
};
|
||||||
|
return client;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return _instance!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TimeoutException implements Exception {
|
||||||
|
final String message;
|
||||||
|
|
||||||
|
TimeoutException(this.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
late _Client _client;
|
||||||
|
|
||||||
|
void init(String network, String address, String apiToken) {
|
||||||
|
_client = _Client(network, address, apiToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<T> _parse<T>(
|
||||||
|
Future<Response> Function() fetch,
|
||||||
|
T Function(dynamic json)? fromJsonT,
|
||||||
|
) async {
|
||||||
|
try {
|
||||||
|
var resp = await fetch();
|
||||||
|
fromJsonT ??= (json) => null as T;
|
||||||
|
final result = Result<T>.fromJson(resp.data, fromJsonT);
|
||||||
|
if (result.code == 0) {
|
||||||
|
return result.data as T;
|
||||||
|
} else {
|
||||||
|
throw Exception(result);
|
||||||
|
}
|
||||||
|
} on DioException catch (e) {
|
||||||
|
if (e.type == DioExceptionType.sendTimeout ||
|
||||||
|
e.type == DioExceptionType.receiveTimeout ||
|
||||||
|
e.type == DioExceptionType.connectionTimeout) {
|
||||||
|
throw TimeoutException("request timeout");
|
||||||
|
}
|
||||||
|
throw Exception(Result(code: 1000, msg: e.message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<ResolveResult> resolve(Request request) async {
|
||||||
|
return _parse<ResolveResult>(
|
||||||
|
() => _client.dio.post("/api/v1/resolve", data: request),
|
||||||
|
(data) => ResolveResult.fromJson(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String> createTask(CreateTask createTask) async {
|
||||||
|
return _parse<String>(
|
||||||
|
() => _client.dio.post("/api/v1/tasks", data: createTask),
|
||||||
|
(data) => data as String);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<Task>> getTasks(List<Status> statuses) async {
|
||||||
|
return _parse<List<Task>>(
|
||||||
|
() => _client.dio.get(
|
||||||
|
"/api/v1/tasks?${statuses.map((e) => "status=${e.name}").join("&")}"),
|
||||||
|
(data) => (data as List).map((e) => Task.fromJson(e)).toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> pauseTask(String id) async {
|
||||||
|
return _parse(() => _client.dio.put("/api/v1/tasks/$id/pause"), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> continueTask(String id) async {
|
||||||
|
return _parse(() => _client.dio.put("/api/v1/tasks/$id/continue"), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> pauseAllTasks(List<String>? ids) async {
|
||||||
|
return _parse(
|
||||||
|
() => _client.dio.put("/api/v1/tasks/pause", queryParameters: {
|
||||||
|
"id": ids,
|
||||||
|
}),
|
||||||
|
null);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> continueAllTasks(List<String>? ids) async {
|
||||||
|
return _parse(
|
||||||
|
() => _client.dio.put("/api/v1/tasks/continue", queryParameters: {
|
||||||
|
"id": ids,
|
||||||
|
}),
|
||||||
|
null);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> deleteTask(String id, bool force) async {
|
||||||
|
return _parse(
|
||||||
|
() => _client.dio.delete("/api/v1/tasks/$id?force=$force"), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> deleteTasks(List<String>? ids, bool force) async {
|
||||||
|
return _parse(
|
||||||
|
() => _client.dio.delete("/api/v1/tasks", queryParameters: {
|
||||||
|
"id": ids,
|
||||||
|
"force": force,
|
||||||
|
}),
|
||||||
|
null);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<DownloaderConfig> getConfig() async {
|
||||||
|
return _parse(() => _client.dio.get("/api/v1/config"),
|
||||||
|
(data) => DownloaderConfig.fromJson(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> putConfig(DownloaderConfig config) async {
|
||||||
|
return _parse(() => _client.dio.put("/api/v1/config", data: config), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> installExtension(InstallExtension installExtension) async {
|
||||||
|
return _parse(
|
||||||
|
() => _client.dio.post("/api/v1/extensions", data: installExtension),
|
||||||
|
null);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<Extension>> getExtensions() async {
|
||||||
|
return _parse<List<Extension>>(() => _client.dio.get("/api/v1/extensions"),
|
||||||
|
(data) => (data as List).map((e) => Extension.fromJson(e)).toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> updateExtensionSettings(
|
||||||
|
String identity, UpdateExtensionSettings updateExtensionSettings) async {
|
||||||
|
return _parse(
|
||||||
|
() => _client.dio.put("/api/v1/extensions/$identity/settings",
|
||||||
|
data: updateExtensionSettings),
|
||||||
|
null);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> switchExtension(
|
||||||
|
String identity, SwitchExtension switchExtension) async {
|
||||||
|
return _parse(
|
||||||
|
() => _client.dio
|
||||||
|
.put("/api/v1/extensions/$identity/switch", data: switchExtension),
|
||||||
|
null);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> deleteExtension(String identity) async {
|
||||||
|
return _parse(() => _client.dio.delete("/api/v1/extensions/$identity"), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<UpdateCheckExtensionResp> upgradeCheckExtension(String identity) async {
|
||||||
|
return _parse(() => _client.dio.get("/api/v1/extensions/$identity/update"),
|
||||||
|
(data) => UpdateCheckExtensionResp.fromJson(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> updateExtension(String identity) async {
|
||||||
|
return _parse(
|
||||||
|
() => _client.dio.post("/api/v1/extensions/$identity/update"), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Response<String>> proxyRequest<T>(String uri,
|
||||||
|
{data, Options? options}) async {
|
||||||
|
options ??= Options();
|
||||||
|
options.headers ??= {};
|
||||||
|
options.headers!["X-Target-Uri"] = uri;
|
||||||
|
|
||||||
|
// add timestamp to avoid cache
|
||||||
|
return _client.dio.request(
|
||||||
|
"/api/v1/proxy?t=${DateTime.now().millisecondsSinceEpoch}",
|
||||||
|
data: data,
|
||||||
|
options: options);
|
||||||
|
}
|
||||||
|
|
||||||
|
String join(String path) {
|
||||||
|
return "${_client.dio.options.baseUrl}/${Util.cleanPath(path)}";
|
||||||
|
}
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
class ApiException implements Exception {
|
|
||||||
final int code;
|
|
||||||
final String message;
|
|
||||||
|
|
||||||
ApiException(this.code, this.message);
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
import 'package:dio/dio.dart';
|
|
||||||
|
|
||||||
import '../api_exception.dart';
|
|
||||||
import '../model/result.dart';
|
|
||||||
|
|
||||||
mixin LibgopeedBootBase {
|
|
||||||
Dio createDio() {
|
|
||||||
Dio dio = Dio();
|
|
||||||
dio.options.contentType = Headers.jsonContentType;
|
|
||||||
dio.options.sendTimeout = const Duration(seconds: 5);
|
|
||||||
dio.options.connectTimeout = const Duration(seconds: 5);
|
|
||||||
dio.options.receiveTimeout = const Duration(seconds: 60);
|
|
||||||
return dio;
|
|
||||||
}
|
|
||||||
|
|
||||||
T handleResult<T>(
|
|
||||||
Result<T> result,
|
|
||||||
) {
|
|
||||||
if (result.code == 0) {
|
|
||||||
return result.data as T;
|
|
||||||
} else {
|
|
||||||
throw ApiException(result.code, result.msg ?? '');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,239 +0,0 @@
|
|||||||
import 'dart:async';
|
|
||||||
import 'dart:convert';
|
|
||||||
import 'dart:ffi';
|
|
||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:dio/dio.dart';
|
|
||||||
|
|
||||||
import '../../api/model/create_task.dart';
|
|
||||||
import '../../api/model/create_task_batch.dart';
|
|
||||||
import '../../api/model/downloader_config.dart';
|
|
||||||
import '../../api/model/extension.dart';
|
|
||||||
import '../../api/model/http_listen_result.dart';
|
|
||||||
import '../../api/model/info.dart';
|
|
||||||
import '../../api/model/install_extension.dart';
|
|
||||||
import '../../api/model/request.dart';
|
|
||||||
import '../../api/model/resolve_result.dart';
|
|
||||||
import '../../api/model/result.dart';
|
|
||||||
import '../../api/model/switch_extension.dart';
|
|
||||||
import '../../api/model/task.dart';
|
|
||||||
import '../../api/model/task_filter.dart';
|
|
||||||
import '../../api/model/update_check_extension_resp.dart';
|
|
||||||
import '../../api/model/update_extension_settings.dart';
|
|
||||||
import '../../util/extensions.dart';
|
|
||||||
import '../../util/util.dart';
|
|
||||||
import '../libgopeed_boot.dart';
|
|
||||||
import '../native/channel/libgopeed_channel.dart';
|
|
||||||
import '../native/ffi/libgopeed_bind.dart';
|
|
||||||
import '../native/ffi/libgopeed_ffi.dart';
|
|
||||||
import '../native/libgopeed_interface.dart';
|
|
||||||
import '../native/model/invoke_request.dart';
|
|
||||||
import '../native/model/start_config.dart';
|
|
||||||
import 'libgopeed_boot_base.dart';
|
|
||||||
|
|
||||||
LibgopeedBoot create() => LibgopeedBootNative();
|
|
||||||
|
|
||||||
class LibgopeedBootNative
|
|
||||||
with LibgopeedBootBase
|
|
||||||
implements LibgopeedBoot, LibgopeedApi {
|
|
||||||
late LibgopeedAbi _libgopeedAbi;
|
|
||||||
late Dio _dio;
|
|
||||||
|
|
||||||
LibgopeedBootNative() {
|
|
||||||
if (Util.isDesktop()) {
|
|
||||||
var libName = "libgopeed.";
|
|
||||||
if (Platform.isWindows) {
|
|
||||||
libName += "dll";
|
|
||||||
}
|
|
||||||
if (Platform.isMacOS) {
|
|
||||||
libName += "dylib";
|
|
||||||
}
|
|
||||||
if (Platform.isLinux) {
|
|
||||||
libName += "so";
|
|
||||||
}
|
|
||||||
_libgopeedAbi = LibgopeedFFi(LibgopeedBind(DynamicLibrary.open(libName)));
|
|
||||||
} else {
|
|
||||||
_libgopeedAbi = LibgopeedChannel();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<T> _invoke<T>(
|
|
||||||
String method,
|
|
||||||
List<dynamic> params,
|
|
||||||
T Function(dynamic json)? fromJsonT,
|
|
||||||
) async {
|
|
||||||
final invokeRequest = InvokeRequest(method: method, params: params);
|
|
||||||
final resp = await _libgopeedAbi.invoke(jsonEncode(invokeRequest));
|
|
||||||
final result =
|
|
||||||
Result<T>.fromJson(jsonDecode(resp), fromJsonT ??= (json) => null as T);
|
|
||||||
return handleResult(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<dynamic> _parseTaskFilter(TaskFilter? filter) {
|
|
||||||
if (filter == null) {
|
|
||||||
return [null];
|
|
||||||
}
|
|
||||||
return [filter];
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<LibgopeedApi> init(StartConfig cfg) async {
|
|
||||||
await _libgopeedAbi.init(jsonEncode(cfg.toJson()));
|
|
||||||
_dio = createDio();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<HttpListenResult> startHttp() async {
|
|
||||||
return _invoke<HttpListenResult>(
|
|
||||||
"StartHttp", [], (json) => HttpListenResult.fromJson(json));
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> stopHttp() async {
|
|
||||||
await _invoke<void>("StopHttp", [], null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> restartHttp() async {
|
|
||||||
await _invoke<void>("RestartHttp", [], null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<Info> info() async {
|
|
||||||
return _invoke<Info>("Info", [], (json) => Info.fromJson(json));
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<ResolveResult> resolve(Request request) async {
|
|
||||||
return _invoke<ResolveResult>(
|
|
||||||
"Resolve", [request], (json) => ResolveResult.fromJson(json));
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<String> createTask(CreateTask createTask) async {
|
|
||||||
return _invoke<String>(
|
|
||||||
"CreateTask", [createTask], (json) => json as String);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<String> createTaskBatch(CreateTaskBatch createTask) async {
|
|
||||||
return _invoke<String>(
|
|
||||||
"CreateTaskBatch", [createTask], (json) => json as String);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> pauseTask(String id) async {
|
|
||||||
await _invoke<void>("PauseTask", [id], null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> pauseTasks(TaskFilter? filter) async {
|
|
||||||
await _invoke<void>("PauseTasks", _parseTaskFilter(filter), null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> continueTask(String id) async {
|
|
||||||
await _invoke<void>("ContinueTask", [id], null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> continueTasks(TaskFilter? filter) async {
|
|
||||||
await _invoke<void>("ContinueTasks", _parseTaskFilter(filter), null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> deleteTask(String id, bool force) async {
|
|
||||||
await _invoke<void>("DeleteTask", [id, force], null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> deleteTasks(TaskFilter? filter, bool force) async {
|
|
||||||
await _invoke<void>(
|
|
||||||
"DeleteTasks",
|
|
||||||
_parseTaskFilter(filter).apply((it) {
|
|
||||||
it.add(force);
|
|
||||||
}),
|
|
||||||
null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<Task> getTask(String id) async {
|
|
||||||
return _invoke<Task>("GetTask", [id], (json) => Task.fromJson(json));
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<List<Task>> getTasks(TaskFilter? filter) async {
|
|
||||||
return _invoke<List<Task>>("GetTasks", _parseTaskFilter(filter),
|
|
||||||
(json) => (json as List).map((e) => Task.fromJson(e)).toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<Map<String, dynamic>> getTaskStats(String id) async {
|
|
||||||
return _invoke<Map<String, dynamic>>(
|
|
||||||
"GetTaskStats", [id], (json) => json as Map<String, dynamic>);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<DownloaderConfig> getConfig() async {
|
|
||||||
return _invoke<DownloaderConfig>(
|
|
||||||
"GetConfig", [], (json) => DownloaderConfig.fromJson(json));
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> putConfig(DownloaderConfig config) async {
|
|
||||||
await _invoke<void>("PutConfig", [config], null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> installExtension(InstallExtension installExtension) async {
|
|
||||||
await _invoke<void>("InstallExtension", [installExtension], null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<List<Extension>> getExtensions() async {
|
|
||||||
return _invoke<List<Extension>>("GetExtensions", [],
|
|
||||||
(json) => (json as List).map((e) => Extension.fromJson(e)).toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> updateExtensionSettings(
|
|
||||||
String identity, UpdateExtensionSettings updateExtensionSettings) async {
|
|
||||||
await _invoke<void>(
|
|
||||||
"UpdateExtensionSettings", [identity, updateExtensionSettings], null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> switchExtension(
|
|
||||||
String identity, SwitchExtension switchExtension) async {
|
|
||||||
await _invoke<void>("SwitchExtension", [identity, switchExtension], null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> deleteExtension(String identity) async {
|
|
||||||
await _invoke<void>("DeleteExtension", [identity], null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<UpdateCheckExtensionResp> upgradeCheckExtension(
|
|
||||||
String identity) async {
|
|
||||||
return _invoke<UpdateCheckExtensionResp>("UpgradeCheckExtension",
|
|
||||||
[identity], (json) => UpdateCheckExtensionResp.fromJson(json));
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> upgradeExtension(String identity) async {
|
|
||||||
await _invoke<void>("UpgradeExtension", [identity], null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> close() async {
|
|
||||||
await _invoke<void>("Close", [], null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<Response<String>> proxyRequest<T>(String uri,
|
|
||||||
{data, Options? options}) {
|
|
||||||
return _dio.request(uri, data: data, options: options);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,277 +0,0 @@
|
|||||||
import 'package:dio/dio.dart';
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
|
|
||||||
import '../../api/model/create_task.dart';
|
|
||||||
import '../../api/model/create_task_batch.dart';
|
|
||||||
import '../../api/model/downloader_config.dart';
|
|
||||||
import '../../api/model/extension.dart';
|
|
||||||
import '../../api/model/http_listen_result.dart';
|
|
||||||
import '../../api/model/info.dart';
|
|
||||||
import '../../api/model/install_extension.dart';
|
|
||||||
import '../../api/model/request.dart';
|
|
||||||
import '../../api/model/resolve_result.dart';
|
|
||||||
import '../../api/model/result.dart';
|
|
||||||
import '../../api/model/switch_extension.dart';
|
|
||||||
import '../../api/model/task.dart';
|
|
||||||
import '../../api/model/task_filter.dart';
|
|
||||||
import '../../api/model/update_check_extension_resp.dart';
|
|
||||||
import '../../api/model/update_extension_settings.dart';
|
|
||||||
import '../../util/extensions.dart';
|
|
||||||
import '../../util/util.dart';
|
|
||||||
import '../api_exception.dart';
|
|
||||||
import '../libgopeed_boot.dart';
|
|
||||||
import '../native/libgopeed_interface.dart';
|
|
||||||
import '../native/model/start_config.dart';
|
|
||||||
import 'libgopeed_boot_base.dart';
|
|
||||||
|
|
||||||
LibgopeedBoot create() => LibgopeedBootWeb();
|
|
||||||
|
|
||||||
class LibgopeedBootWeb
|
|
||||||
with LibgopeedBootBase
|
|
||||||
implements LibgopeedBoot, LibgopeedApi {
|
|
||||||
late Dio _dio;
|
|
||||||
static const String _apiPrefix = '/api/v1';
|
|
||||||
|
|
||||||
LibgopeedBootWeb();
|
|
||||||
|
|
||||||
Future<T> _fetch<T>(
|
|
||||||
Future<Response> Function() fetch,
|
|
||||||
T Function(dynamic json)? fromJsonT,
|
|
||||||
) async {
|
|
||||||
try {
|
|
||||||
var resp = await fetch();
|
|
||||||
fromJsonT ??= (json) => null as T;
|
|
||||||
final result = Result<T>.fromJson(resp.data, fromJsonT);
|
|
||||||
return handleResult(result);
|
|
||||||
} on DioException catch (e) {
|
|
||||||
if (e.type == DioExceptionType.sendTimeout ||
|
|
||||||
e.type == DioExceptionType.receiveTimeout ||
|
|
||||||
e.type == DioExceptionType.connectionTimeout) {
|
|
||||||
throw ApiException(1000, 'request timeout');
|
|
||||||
}
|
|
||||||
throw ApiException(1000, e.message ?? "");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, dynamic>? _parseTaskFilter(TaskFilter? filter) {
|
|
||||||
if (filter == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, dynamic> params = {};
|
|
||||||
if (filter.id != null) {
|
|
||||||
params['id'] = filter.id;
|
|
||||||
}
|
|
||||||
if (filter.status != null) {
|
|
||||||
params['status'] = filter.status;
|
|
||||||
}
|
|
||||||
if (filter.notStatus != null) {
|
|
||||||
params['notStatus'] = filter.notStatus;
|
|
||||||
}
|
|
||||||
if (params.isEmpty) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return params;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<LibgopeedApi> init(StartConfig cfg) async {
|
|
||||||
_dio = createDio();
|
|
||||||
_dio.options.baseUrl = kDebugMode ? 'http://127.0.0.1:9999' : '';
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<HttpListenResult> startHttp() async {
|
|
||||||
throw UnimplementedError();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> stopHttp() async {
|
|
||||||
throw UnimplementedError();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> restartHttp() async {
|
|
||||||
throw UnimplementedError();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<Info> info() {
|
|
||||||
return _fetch<Info>(
|
|
||||||
() => _dio.get("$_apiPrefix/info"), (data) => Info.fromJson(data));
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<ResolveResult> resolve(Request request) async {
|
|
||||||
return _fetch<ResolveResult>(
|
|
||||||
() => _dio.post("$_apiPrefix/resolve", data: request),
|
|
||||||
(data) => ResolveResult.fromJson(data));
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<String> createTask(CreateTask createTask) async {
|
|
||||||
return _fetch<String>(
|
|
||||||
() => _dio.post("$_apiPrefix/tasks", data: createTask),
|
|
||||||
(data) => data as String);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<String> createTaskBatch(CreateTaskBatch createTask) async {
|
|
||||||
return _fetch<String>(
|
|
||||||
() => _dio.post("$_apiPrefix/tasks", data: createTask),
|
|
||||||
(data) => data as String);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> pauseTask(String id) async {
|
|
||||||
await _fetch<void>(() => _dio.put("$_apiPrefix/tasks/$id/pause"), null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> pauseTasks(TaskFilter? filter) async {
|
|
||||||
await _fetch<void>(
|
|
||||||
() => _dio.put("$_apiPrefix/tasks/pause",
|
|
||||||
queryParameters: _parseTaskFilter(filter)),
|
|
||||||
null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> continueTask(String id) async {
|
|
||||||
await _fetch<void>(() => _dio.put("$_apiPrefix/tasks/$id/continue"), null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> continueTasks(TaskFilter? filter) async {
|
|
||||||
await _fetch<void>(
|
|
||||||
() => _dio.put("$_apiPrefix/tasks/continue",
|
|
||||||
queryParameters: _parseTaskFilter(filter)),
|
|
||||||
null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> deleteTask(String id, bool force) async {
|
|
||||||
await _fetch<void>(
|
|
||||||
() => _dio
|
|
||||||
.delete("$_apiPrefix/tasks/$id", queryParameters: {'force': force}),
|
|
||||||
null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> deleteTasks(TaskFilter? filter, bool force) async {
|
|
||||||
await _fetch<void>(
|
|
||||||
() => _dio.delete("$_apiPrefix/tasks",
|
|
||||||
queryParameters: _parseTaskFilter(filter)?.apply((it) {
|
|
||||||
it['force'] = force;
|
|
||||||
})),
|
|
||||||
null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<Task> getTask(String id) async {
|
|
||||||
return _fetch<Task>(
|
|
||||||
() => _dio.get("$_apiPrefix/tasks/$id"), (data) => Task.fromJson(data));
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<List<Task>> getTasks(TaskFilter? filter) async {
|
|
||||||
return _fetch<List<Task>>(
|
|
||||||
() => _dio.get("$_apiPrefix/tasks",
|
|
||||||
queryParameters: _parseTaskFilter(filter)),
|
|
||||||
(data) => (data as List).map((e) => Task.fromJson(e)).toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<Map<String, dynamic>> getTaskStats(String id) async {
|
|
||||||
return _fetch<Map<String, dynamic>>(
|
|
||||||
() => _dio.get("$_apiPrefix/tasks/$id/stats"),
|
|
||||||
(data) => data as Map<String, dynamic>);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<DownloaderConfig> getConfig() async {
|
|
||||||
return _fetch<DownloaderConfig>(() => _dio.get("$_apiPrefix/config"),
|
|
||||||
(data) => DownloaderConfig.fromJson(data));
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> putConfig(DownloaderConfig config) async {
|
|
||||||
await _fetch<void>(
|
|
||||||
() => _dio.put("$_apiPrefix/config", data: config), null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> installExtension(InstallExtension installExtension) async {
|
|
||||||
await _fetch<void>(
|
|
||||||
() => _dio.post("$_apiPrefix/extensions", data: installExtension),
|
|
||||||
null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<List<Extension>> getExtensions() async {
|
|
||||||
return _fetch<List<Extension>>(() => _dio.get("$_apiPrefix/extensions"),
|
|
||||||
(data) => (data as List).map((e) => Extension.fromJson(e)).toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> updateExtensionSettings(
|
|
||||||
String identity, UpdateExtensionSettings updateExtensionSettings) async {
|
|
||||||
await _fetch<void>(
|
|
||||||
() => _dio.put("$_apiPrefix/extensions/$identity/settings",
|
|
||||||
data: updateExtensionSettings),
|
|
||||||
null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> switchExtension(
|
|
||||||
String identity, SwitchExtension switchExtension) async {
|
|
||||||
await _fetch<void>(
|
|
||||||
() => _dio.put("$_apiPrefix/extensions/$identity/switch",
|
|
||||||
data: switchExtension),
|
|
||||||
null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> deleteExtension(String identity) async {
|
|
||||||
await _fetch<void>(
|
|
||||||
() => _dio.delete("$_apiPrefix/extensions/$identity"), null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<UpdateCheckExtensionResp> upgradeCheckExtension(
|
|
||||||
String identity) async {
|
|
||||||
return _fetch<UpdateCheckExtensionResp>(
|
|
||||||
() => _dio.get("$_apiPrefix/extensions/$identity/upgrade"),
|
|
||||||
(data) => UpdateCheckExtensionResp.fromJson(data));
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> upgradeExtension(String identity) async {
|
|
||||||
await _fetch<void>(
|
|
||||||
() => _dio.post("$_apiPrefix/extensions/$identity/upgrade"), null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> close() async {
|
|
||||||
throw UnimplementedError();
|
|
||||||
}
|
|
||||||
|
|
||||||
// To avoid CORS, use a backend proxy to forward the request
|
|
||||||
@override
|
|
||||||
Future<Response<String>> proxyRequest<T>(String uri,
|
|
||||||
{data, Options? options}) async {
|
|
||||||
options ??= Options();
|
|
||||||
options.headers ??= {};
|
|
||||||
options.headers!["X-Target-Uri"] = uri;
|
|
||||||
|
|
||||||
// add timestamp to avoid cache
|
|
||||||
return _dio.request(
|
|
||||||
"/api/v1/proxy?t=${DateTime.now().millisecondsSinceEpoch}",
|
|
||||||
data: data,
|
|
||||||
options: options);
|
|
||||||
}
|
|
||||||
|
|
||||||
String join(String path) {
|
|
||||||
return "${_dio.options.baseUrl}/${Util.cleanPath(path)}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
import "libgopeed_boot_stub.dart"
|
|
||||||
if (dart.library.html) 'entry/libgopeed_boot_browser.dart'
|
|
||||||
if (dart.library.io) 'entry/libgopeed_boot_native.dart';
|
|
||||||
import 'native/libgopeed_interface.dart';
|
|
||||||
|
|
||||||
abstract class LibgopeedBoot implements LibgopeedApiSingleton {
|
|
||||||
static LibgopeedApi? _instance;
|
|
||||||
|
|
||||||
static void singleton(LibgopeedApi instance) {
|
|
||||||
_instance ??= instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
static LibgopeedApi get instance {
|
|
||||||
return _instance!;
|
|
||||||
}
|
|
||||||
|
|
||||||
factory LibgopeedBoot() => create();
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
import 'package:gopeed/api/model/extension.dart';
|
|
||||||
import 'package:gopeed/api/model/request.dart';
|
|
||||||
import 'package:json_annotation/json_annotation.dart';
|
|
||||||
|
|
||||||
part 'create_task_batch.g.dart';
|
|
||||||
|
|
||||||
@JsonSerializable()
|
|
||||||
class CreateTaskBatch {
|
|
||||||
List<Request> reqs;
|
|
||||||
Option? opt;
|
|
||||||
|
|
||||||
CreateTaskBatch({
|
|
||||||
this.reqs = const [],
|
|
||||||
this.opt,
|
|
||||||
});
|
|
||||||
|
|
||||||
factory CreateTaskBatch.fromJson(Map<String, dynamic> json) =>
|
|
||||||
_$CreateTaskBatchFromJson(json);
|
|
||||||
Map<String, dynamic> toJson() => _$CreateTaskBatchToJson(this);
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
|
|
||||||
part of 'create_task_batch.dart';
|
|
||||||
|
|
||||||
// **************************************************************************
|
|
||||||
// JsonSerializableGenerator
|
|
||||||
// **************************************************************************
|
|
||||||
|
|
||||||
CreateTaskBatch _$CreateTaskBatchFromJson(Map<String, dynamic> json) =>
|
|
||||||
CreateTaskBatch(
|
|
||||||
reqs: (json['reqs'] as List<dynamic>?)
|
|
||||||
?.map((e) => Request.fromJson(e as Map<String, dynamic>))
|
|
||||||
.toList() ??
|
|
||||||
const [],
|
|
||||||
opt: json['opt'] == null
|
|
||||||
? null
|
|
||||||
: Option.fromJson(json['opt'] as Map<String, dynamic>),
|
|
||||||
);
|
|
||||||
|
|
||||||
Map<String, dynamic> _$CreateTaskBatchToJson(CreateTaskBatch instance) {
|
|
||||||
final val = <String, dynamic>{
|
|
||||||
'reqs': instance.reqs,
|
|
||||||
};
|
|
||||||
|
|
||||||
void writeNotNull(String key, dynamic value) {
|
|
||||||
if (value != null) {
|
|
||||||
val[key] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
writeNotNull('opt', instance.opt);
|
|
||||||
return val;
|
|
||||||
}
|
|
||||||
@@ -80,6 +80,7 @@ class ExtraConfig {
|
|||||||
String locale;
|
String locale;
|
||||||
bool lastDeleteTaskKeep;
|
bool lastDeleteTaskKeep;
|
||||||
bool defaultDirectDownload;
|
bool defaultDirectDownload;
|
||||||
|
bool defaultBtClient;
|
||||||
|
|
||||||
ExtraConfigBt bt = ExtraConfigBt();
|
ExtraConfigBt bt = ExtraConfigBt();
|
||||||
|
|
||||||
@@ -88,6 +89,7 @@ class ExtraConfig {
|
|||||||
this.locale = '',
|
this.locale = '',
|
||||||
this.lastDeleteTaskKeep = false,
|
this.lastDeleteTaskKeep = false,
|
||||||
this.defaultDirectDownload = false,
|
this.defaultDirectDownload = false,
|
||||||
|
this.defaultBtClient = true,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory ExtraConfig.fromJson(Map<String, dynamic>? json) =>
|
factory ExtraConfig.fromJson(Map<String, dynamic>? json) =>
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ ExtraConfig _$ExtraConfigFromJson(Map<String, dynamic> json) => ExtraConfig(
|
|||||||
locale: json['locale'] as String? ?? '',
|
locale: json['locale'] as String? ?? '',
|
||||||
lastDeleteTaskKeep: json['lastDeleteTaskKeep'] as bool? ?? false,
|
lastDeleteTaskKeep: json['lastDeleteTaskKeep'] as bool? ?? false,
|
||||||
defaultDirectDownload: json['defaultDirectDownload'] as bool? ?? false,
|
defaultDirectDownload: json['defaultDirectDownload'] as bool? ?? false,
|
||||||
|
defaultBtClient: json['defaultBtClient'] as bool? ?? true,
|
||||||
)..bt = ExtraConfigBt.fromJson(json['bt'] as Map<String, dynamic>);
|
)..bt = ExtraConfigBt.fromJson(json['bt'] as Map<String, dynamic>);
|
||||||
|
|
||||||
Map<String, dynamic> _$ExtraConfigToJson(ExtraConfig instance) =>
|
Map<String, dynamic> _$ExtraConfigToJson(ExtraConfig instance) =>
|
||||||
@@ -81,6 +82,7 @@ Map<String, dynamic> _$ExtraConfigToJson(ExtraConfig instance) =>
|
|||||||
'locale': instance.locale,
|
'locale': instance.locale,
|
||||||
'lastDeleteTaskKeep': instance.lastDeleteTaskKeep,
|
'lastDeleteTaskKeep': instance.lastDeleteTaskKeep,
|
||||||
'defaultDirectDownload': instance.defaultDirectDownload,
|
'defaultDirectDownload': instance.defaultDirectDownload,
|
||||||
|
'defaultBtClient': instance.defaultBtClient,
|
||||||
'bt': instance.bt.toJson(),
|
'bt': instance.bt.toJson(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
import 'package:json_annotation/json_annotation.dart';
|
|
||||||
|
|
||||||
part 'http_listen_result.g.dart';
|
|
||||||
|
|
||||||
@JsonSerializable()
|
|
||||||
class HttpListenResult {
|
|
||||||
String host;
|
|
||||||
int port;
|
|
||||||
|
|
||||||
HttpListenResult({
|
|
||||||
this.host = '',
|
|
||||||
this.port = 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
factory HttpListenResult.fromJson(Map<String, dynamic> json) =>
|
|
||||||
_$HttpListenResultFromJson(json);
|
|
||||||
Map<String, dynamic> toJson() => _$HttpListenResultToJson(this);
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
|
|
||||||
part of 'http_listen_result.dart';
|
|
||||||
|
|
||||||
// **************************************************************************
|
|
||||||
// JsonSerializableGenerator
|
|
||||||
// **************************************************************************
|
|
||||||
|
|
||||||
HttpListenResult _$HttpListenResultFromJson(Map<String, dynamic> json) =>
|
|
||||||
HttpListenResult(
|
|
||||||
host: json['host'] as String? ?? '',
|
|
||||||
port: (json['port'] as num?)?.toInt() ?? 0,
|
|
||||||
);
|
|
||||||
|
|
||||||
Map<String, dynamic> _$HttpListenResultToJson(HttpListenResult instance) =>
|
|
||||||
<String, dynamic>{
|
|
||||||
'host': instance.host,
|
|
||||||
'port': instance.port,
|
|
||||||
};
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
import 'package:json_annotation/json_annotation.dart';
|
|
||||||
|
|
||||||
part 'info.g.dart';
|
|
||||||
|
|
||||||
@JsonSerializable()
|
|
||||||
class Info {
|
|
||||||
String version;
|
|
||||||
String runtime;
|
|
||||||
String os;
|
|
||||||
String arch;
|
|
||||||
bool inDocker;
|
|
||||||
|
|
||||||
Info({
|
|
||||||
this.version = '',
|
|
||||||
this.runtime = '',
|
|
||||||
this.os = '',
|
|
||||||
this.arch = '',
|
|
||||||
this.inDocker = false,
|
|
||||||
});
|
|
||||||
|
|
||||||
factory Info.fromJson(Map<String, dynamic> json) => _$InfoFromJson(json);
|
|
||||||
Map<String, dynamic> toJson() => _$InfoToJson(this);
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
|
|
||||||
part of 'info.dart';
|
|
||||||
|
|
||||||
// **************************************************************************
|
|
||||||
// JsonSerializableGenerator
|
|
||||||
// **************************************************************************
|
|
||||||
|
|
||||||
Info _$InfoFromJson(Map<String, dynamic> json) => Info(
|
|
||||||
version: json['version'] as String? ?? '',
|
|
||||||
runtime: json['runtime'] as String? ?? '',
|
|
||||||
os: json['os'] as String? ?? '',
|
|
||||||
arch: json['arch'] as String? ?? '',
|
|
||||||
inDocker: json['inDocker'] as bool? ?? false,
|
|
||||||
);
|
|
||||||
|
|
||||||
Map<String, dynamic> _$InfoToJson(Info instance) => <String, dynamic>{
|
|
||||||
'version': instance.version,
|
|
||||||
'runtime': instance.runtime,
|
|
||||||
'os': instance.os,
|
|
||||||
'arch': instance.arch,
|
|
||||||
'inDocker': instance.inDocker,
|
|
||||||
};
|
|
||||||
@@ -10,8 +10,8 @@ class Options {
|
|||||||
Object? extra;
|
Object? extra;
|
||||||
|
|
||||||
Options({
|
Options({
|
||||||
required this.name,
|
this.name = '',
|
||||||
required this.path,
|
this.path = '',
|
||||||
this.selectFiles = const [],
|
this.selectFiles = const [],
|
||||||
this.extra,
|
this.extra,
|
||||||
});
|
});
|
||||||
@@ -24,12 +24,16 @@ class Options {
|
|||||||
|
|
||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
class OptsExtraHttp {
|
class OptsExtraHttp {
|
||||||
int connections = 0;
|
int connections;
|
||||||
bool autoTorrent = false;
|
bool autoTorrent;
|
||||||
|
|
||||||
OptsExtraHttp();
|
OptsExtraHttp({
|
||||||
|
this.connections = 0,
|
||||||
|
this.autoTorrent = false,
|
||||||
|
});
|
||||||
|
|
||||||
factory OptsExtraHttp.fromJson(Map<String, dynamic> json) =>
|
factory OptsExtraHttp.fromJson(Map<String, dynamic> json) =>
|
||||||
_$OptsExtraHttpFromJson(json);
|
_$OptsExtraHttpFromJson(json);
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => _$OptsExtraHttpToJson(this);
|
Map<String, dynamic> toJson() => _$OptsExtraHttpToJson(this);
|
||||||
}
|
}
|
||||||
@@ -7,8 +7,8 @@ part of 'options.dart';
|
|||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
Options _$OptionsFromJson(Map<String, dynamic> json) => Options(
|
Options _$OptionsFromJson(Map<String, dynamic> json) => Options(
|
||||||
name: json['name'] as String,
|
name: json['name'] as String? ?? '',
|
||||||
path: json['path'] as String,
|
path: json['path'] as String? ?? '',
|
||||||
selectFiles: (json['selectFiles'] as List<dynamic>?)
|
selectFiles: (json['selectFiles'] as List<dynamic>?)
|
||||||
?.map((e) => (e as num).toInt())
|
?.map((e) => (e as num).toInt())
|
||||||
.toList() ??
|
.toList() ??
|
||||||
@@ -34,9 +34,10 @@ Map<String, dynamic> _$OptionsToJson(Options instance) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
OptsExtraHttp _$OptsExtraHttpFromJson(Map<String, dynamic> json) =>
|
OptsExtraHttp _$OptsExtraHttpFromJson(Map<String, dynamic> json) =>
|
||||||
OptsExtraHttp()
|
OptsExtraHttp(
|
||||||
..connections = (json['connections'] as num).toInt()
|
connections: (json['connections'] as num?)?.toInt() ?? 0,
|
||||||
..autoTorrent = json['autoTorrent'] as bool;
|
autoTorrent: json['autoTorrent'] as bool? ?? false,
|
||||||
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$OptsExtraHttpToJson(OptsExtraHttp instance) =>
|
Map<String, dynamic> _$OptsExtraHttpToJson(OptsExtraHttp instance) =>
|
||||||
<String, dynamic>{
|
<String, dynamic>{
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ part 'request.g.dart';
|
|||||||
class Request {
|
class Request {
|
||||||
String url;
|
String url;
|
||||||
Object? extra;
|
Object? extra;
|
||||||
Map<String, String>? labels = {};
|
Map<String, String>? labels;
|
||||||
RequestProxy? proxy;
|
RequestProxy? proxy;
|
||||||
bool skipVerifyCert = false;
|
bool skipVerifyCert;
|
||||||
|
|
||||||
Request({
|
Request({
|
||||||
required this.url,
|
required this.url,
|
||||||
@@ -26,11 +26,15 @@ class Request {
|
|||||||
|
|
||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
class ReqExtraHttp {
|
class ReqExtraHttp {
|
||||||
String method = 'GET';
|
String method;
|
||||||
Map<String, String> header = {};
|
Map<String, String> header;
|
||||||
String body = '';
|
String body;
|
||||||
|
|
||||||
ReqExtraHttp();
|
ReqExtraHttp({
|
||||||
|
this.method = 'GET',
|
||||||
|
this.header = const {},
|
||||||
|
this.body = '',
|
||||||
|
});
|
||||||
|
|
||||||
factory ReqExtraHttp.fromJson(Map<String, dynamic> json) =>
|
factory ReqExtraHttp.fromJson(Map<String, dynamic> json) =>
|
||||||
_$ReqExtraHttpFromJson(json);
|
_$ReqExtraHttpFromJson(json);
|
||||||
@@ -40,9 +44,11 @@ class ReqExtraHttp {
|
|||||||
|
|
||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
class ReqExtraBt {
|
class ReqExtraBt {
|
||||||
List<String> trackers = [];
|
List<String> trackers;
|
||||||
|
|
||||||
ReqExtraBt();
|
ReqExtraBt({
|
||||||
|
this.trackers = const [],
|
||||||
|
});
|
||||||
|
|
||||||
factory ReqExtraBt.fromJson(Map<String, dynamic> json) =>
|
factory ReqExtraBt.fromJson(Map<String, dynamic> json) =>
|
||||||
_$ReqExtraBtFromJson(json);
|
_$ReqExtraBtFromJson(json);
|
||||||
@@ -58,15 +64,22 @@ enum RequestProxyMode {
|
|||||||
|
|
||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
class RequestProxy {
|
class RequestProxy {
|
||||||
RequestProxyMode mode = RequestProxyMode.follow;
|
RequestProxyMode mode;
|
||||||
String scheme = 'http';
|
String scheme;
|
||||||
String host = '';
|
String host;
|
||||||
String usr = '';
|
String usr;
|
||||||
String pwd = '';
|
String pwd;
|
||||||
|
|
||||||
RequestProxy();
|
RequestProxy({
|
||||||
|
this.mode = RequestProxyMode.follow,
|
||||||
|
this.scheme = 'http',
|
||||||
|
this.host = '',
|
||||||
|
this.usr = '',
|
||||||
|
this.pwd = '',
|
||||||
|
});
|
||||||
|
|
||||||
factory RequestProxy.fromJson(Map<String, dynamic> json) =>
|
factory RequestProxy.fromJson(Map<String, dynamic> json) =>
|
||||||
_$RequestProxyFromJson(json);
|
_$RequestProxyFromJson(json);
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => _$RequestProxyToJson(this);
|
Map<String, dynamic> toJson() => _$RequestProxyToJson(this);
|
||||||
}
|
}
|
||||||
@@ -36,10 +36,14 @@ Map<String, dynamic> _$RequestToJson(Request instance) {
|
|||||||
return val;
|
return val;
|
||||||
}
|
}
|
||||||
|
|
||||||
ReqExtraHttp _$ReqExtraHttpFromJson(Map<String, dynamic> json) => ReqExtraHttp()
|
ReqExtraHttp _$ReqExtraHttpFromJson(Map<String, dynamic> json) => ReqExtraHttp(
|
||||||
..method = json['method'] as String
|
method: json['method'] as String? ?? 'GET',
|
||||||
..header = Map<String, String>.from(json['header'] as Map)
|
header: (json['header'] as Map<String, dynamic>?)?.map(
|
||||||
..body = json['body'] as String;
|
(k, e) => MapEntry(k, e as String),
|
||||||
|
) ??
|
||||||
|
const {},
|
||||||
|
body: json['body'] as String? ?? '',
|
||||||
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$ReqExtraHttpToJson(ReqExtraHttp instance) =>
|
Map<String, dynamic> _$ReqExtraHttpToJson(ReqExtraHttp instance) =>
|
||||||
<String, dynamic>{
|
<String, dynamic>{
|
||||||
@@ -48,21 +52,26 @@ Map<String, dynamic> _$ReqExtraHttpToJson(ReqExtraHttp instance) =>
|
|||||||
'body': instance.body,
|
'body': instance.body,
|
||||||
};
|
};
|
||||||
|
|
||||||
ReqExtraBt _$ReqExtraBtFromJson(Map<String, dynamic> json) => ReqExtraBt()
|
ReqExtraBt _$ReqExtraBtFromJson(Map<String, dynamic> json) => ReqExtraBt(
|
||||||
..trackers =
|
trackers: (json['trackers'] as List<dynamic>?)
|
||||||
(json['trackers'] as List<dynamic>).map((e) => e as String).toList();
|
?.map((e) => e as String)
|
||||||
|
.toList() ??
|
||||||
|
const [],
|
||||||
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$ReqExtraBtToJson(ReqExtraBt instance) =>
|
Map<String, dynamic> _$ReqExtraBtToJson(ReqExtraBt instance) =>
|
||||||
<String, dynamic>{
|
<String, dynamic>{
|
||||||
'trackers': instance.trackers,
|
'trackers': instance.trackers,
|
||||||
};
|
};
|
||||||
|
|
||||||
RequestProxy _$RequestProxyFromJson(Map<String, dynamic> json) => RequestProxy()
|
RequestProxy _$RequestProxyFromJson(Map<String, dynamic> json) => RequestProxy(
|
||||||
..mode = $enumDecode(_$RequestProxyModeEnumMap, json['mode'])
|
mode: $enumDecodeNullable(_$RequestProxyModeEnumMap, json['mode']) ??
|
||||||
..scheme = json['scheme'] as String
|
RequestProxyMode.follow,
|
||||||
..host = json['host'] as String
|
scheme: json['scheme'] as String? ?? 'http',
|
||||||
..usr = json['usr'] as String
|
host: json['host'] as String? ?? '',
|
||||||
..pwd = json['pwd'] as String;
|
usr: json['usr'] as String? ?? '',
|
||||||
|
pwd: json['pwd'] as String? ?? '',
|
||||||
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$RequestProxyToJson(RequestProxy instance) =>
|
Map<String, dynamic> _$RequestProxyToJson(RequestProxy instance) =>
|
||||||
<String, dynamic>{
|
<String, dynamic>{
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
import 'package:gopeed/api/model/task.dart';
|
|
||||||
import 'package:json_annotation/json_annotation.dart';
|
|
||||||
|
|
||||||
part 'task_filter.g.dart';
|
|
||||||
|
|
||||||
@JsonSerializable()
|
|
||||||
class TaskFilter {
|
|
||||||
List<String>? id;
|
|
||||||
List<Status>? status;
|
|
||||||
List<Status>? notStatus;
|
|
||||||
|
|
||||||
TaskFilter({this.id, this.status, this.notStatus});
|
|
||||||
|
|
||||||
factory TaskFilter.fromJson(Map<String, dynamic> json) =>
|
|
||||||
_$TaskFilterFromJson(json);
|
|
||||||
Map<String, dynamic> toJson() => _$TaskFilterToJson(this);
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
|
|
||||||
part of 'task_filter.dart';
|
|
||||||
|
|
||||||
// **************************************************************************
|
|
||||||
// JsonSerializableGenerator
|
|
||||||
// **************************************************************************
|
|
||||||
|
|
||||||
TaskFilter _$TaskFilterFromJson(Map<String, dynamic> json) => TaskFilter(
|
|
||||||
id: (json['ids'] as List<dynamic>?)?.map((e) => e as String).toList(),
|
|
||||||
status: (json['statuses'] as List<dynamic>?)
|
|
||||||
?.map((e) => $enumDecode(_$StatusEnumMap, e))
|
|
||||||
.toList(),
|
|
||||||
notStatus: (json['notStatuses'] as List<dynamic>?)
|
|
||||||
?.map((e) => $enumDecode(_$StatusEnumMap, e))
|
|
||||||
.toList(),
|
|
||||||
);
|
|
||||||
|
|
||||||
Map<String, dynamic> _$TaskFilterToJson(TaskFilter instance) {
|
|
||||||
final val = <String, dynamic>{};
|
|
||||||
|
|
||||||
void writeNotNull(String key, dynamic value) {
|
|
||||||
if (value != null) {
|
|
||||||
val[key] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
writeNotNull('ids', instance.id);
|
|
||||||
writeNotNull(
|
|
||||||
'statuses', instance.status?.map((e) => _$StatusEnumMap[e]!).toList());
|
|
||||||
writeNotNull('notStatuses',
|
|
||||||
instance.notStatus?.map((e) => _$StatusEnumMap[e]!).toList());
|
|
||||||
return val;
|
|
||||||
}
|
|
||||||
|
|
||||||
const _$StatusEnumMap = {
|
|
||||||
Status.ready: 'ready',
|
|
||||||
Status.running: 'running',
|
|
||||||
Status.pause: 'pause',
|
|
||||||
Status.wait: 'wait',
|
|
||||||
Status.error: 'error',
|
|
||||||
Status.done: 'done',
|
|
||||||
};
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
import 'package:flutter/services.dart';
|
|
||||||
|
|
||||||
import '../libgopeed_interface.dart';
|
|
||||||
|
|
||||||
class LibgopeedChannel implements LibgopeedAbi {
|
|
||||||
static const _channel = MethodChannel('gopeed.com/libgopeed');
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> init(String cfg) async {
|
|
||||||
await _channel.invokeMethod<String>('init', {
|
|
||||||
'cfg': cfg,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<String> invoke(String params) async {
|
|
||||||
final result = await _channel.invokeMethod<String>('invoke', {
|
|
||||||
'params': params,
|
|
||||||
});
|
|
||||||
return result!;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
import 'dart:async';
|
|
||||||
import 'dart:ffi';
|
|
||||||
|
|
||||||
import 'package:ffi/ffi.dart';
|
|
||||||
|
|
||||||
import '../libgopeed_interface.dart';
|
|
||||||
import 'libgopeed_bind.dart';
|
|
||||||
|
|
||||||
class LibgopeedFFi implements LibgopeedAbi {
|
|
||||||
late LibgopeedBind _libgopeed;
|
|
||||||
|
|
||||||
LibgopeedFFi(LibgopeedBind libgopeed) {
|
|
||||||
_libgopeed = libgopeed;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> init(String cfg) {
|
|
||||||
final cfgPtr = cfg.toNativeUtf8();
|
|
||||||
try {
|
|
||||||
final result = _libgopeed.Init(cfgPtr.cast());
|
|
||||||
if (result != nullptr) {
|
|
||||||
throw Exception(
|
|
||||||
'Libgopeed init failed: ${result.cast<Utf8>().toDartString()}');
|
|
||||||
}
|
|
||||||
return Future.value();
|
|
||||||
} finally {
|
|
||||||
calloc.free(cfgPtr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<String> invoke(String params) {
|
|
||||||
final paramsPtr = params.toNativeUtf8();
|
|
||||||
try {
|
|
||||||
final result = _libgopeed.Invoke(paramsPtr.cast());
|
|
||||||
return Future.value(result.cast<Utf8>().toDartString());
|
|
||||||
} finally {
|
|
||||||
calloc.free(paramsPtr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
import 'package:dio/dio.dart';
|
|
||||||
|
|
||||||
import '../model/create_task.dart';
|
|
||||||
import '../model/create_task_batch.dart';
|
|
||||||
import '../model/downloader_config.dart';
|
|
||||||
import '../model/extension.dart';
|
|
||||||
import '../model/http_listen_result.dart';
|
|
||||||
import '../model/info.dart';
|
|
||||||
import '../model/install_extension.dart';
|
|
||||||
import '../model/request.dart';
|
|
||||||
import '../model/resolve_result.dart';
|
|
||||||
import '../model/switch_extension.dart';
|
|
||||||
import '../model/task.dart';
|
|
||||||
import '../model/task_filter.dart';
|
|
||||||
import '../model/update_check_extension_resp.dart';
|
|
||||||
import '../model/update_extension_settings.dart';
|
|
||||||
import 'model/start_config.dart';
|
|
||||||
|
|
||||||
abstract class LibgopeedAbi {
|
|
||||||
Future<void> init(String cfg);
|
|
||||||
Future<String> invoke(String params);
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract class LibgopeedApiSingleton {
|
|
||||||
Future<LibgopeedApi> init(StartConfig config);
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract class LibgopeedApi {
|
|
||||||
Future<HttpListenResult> startHttp();
|
|
||||||
|
|
||||||
Future<void> stopHttp();
|
|
||||||
|
|
||||||
Future<void> restartHttp();
|
|
||||||
|
|
||||||
Future<Info> info();
|
|
||||||
|
|
||||||
Future<ResolveResult> resolve(Request request);
|
|
||||||
|
|
||||||
Future<String> createTask(CreateTask createTask);
|
|
||||||
|
|
||||||
Future<String> createTaskBatch(CreateTaskBatch createTask);
|
|
||||||
|
|
||||||
Future<void> pauseTask(String id);
|
|
||||||
|
|
||||||
Future<void> pauseTasks(TaskFilter? filter);
|
|
||||||
|
|
||||||
Future<void> continueTask(String id);
|
|
||||||
|
|
||||||
Future<void> continueTasks(TaskFilter? filter);
|
|
||||||
|
|
||||||
Future<void> deleteTask(String id, bool force);
|
|
||||||
|
|
||||||
Future<void> deleteTasks(TaskFilter? filter, bool force);
|
|
||||||
|
|
||||||
Future<Task> getTask(String id);
|
|
||||||
|
|
||||||
Future<List<Task>> getTasks(TaskFilter? filter);
|
|
||||||
|
|
||||||
Future<Map<String, dynamic>> getTaskStats(String id);
|
|
||||||
|
|
||||||
Future<DownloaderConfig> getConfig();
|
|
||||||
|
|
||||||
Future<void> putConfig(DownloaderConfig config);
|
|
||||||
|
|
||||||
Future<void> installExtension(InstallExtension installExtension);
|
|
||||||
|
|
||||||
Future<List<Extension>> getExtensions();
|
|
||||||
|
|
||||||
Future<void> updateExtensionSettings(
|
|
||||||
String identity, UpdateExtensionSettings updateExtensionSettings);
|
|
||||||
|
|
||||||
Future<void> switchExtension(
|
|
||||||
String identity, SwitchExtension switchExtension);
|
|
||||||
|
|
||||||
Future<void> deleteExtension(String identity);
|
|
||||||
|
|
||||||
Future<UpdateCheckExtensionResp> upgradeCheckExtension(String identity);
|
|
||||||
|
|
||||||
Future<void> upgradeExtension(String identity);
|
|
||||||
|
|
||||||
Future<void> close();
|
|
||||||
|
|
||||||
Future<Response<String>> proxyRequest<T>(String uri,
|
|
||||||
{data, Options? options});
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
import 'package:json_annotation/json_annotation.dart';
|
|
||||||
|
|
||||||
part 'invoke_request.g.dart';
|
|
||||||
|
|
||||||
@JsonSerializable()
|
|
||||||
class InvokeRequest {
|
|
||||||
String method;
|
|
||||||
List<dynamic> params;
|
|
||||||
|
|
||||||
InvokeRequest({
|
|
||||||
this.method = '',
|
|
||||||
this.params = const [],
|
|
||||||
});
|
|
||||||
|
|
||||||
factory InvokeRequest.fromJson(Map<String, dynamic> json) =>
|
|
||||||
_$InvokeRequestFromJson(json);
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => _$InvokeRequestToJson(this);
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
|
|
||||||
part of 'invoke_request.dart';
|
|
||||||
|
|
||||||
// **************************************************************************
|
|
||||||
// JsonSerializableGenerator
|
|
||||||
// **************************************************************************
|
|
||||||
|
|
||||||
InvokeRequest _$InvokeRequestFromJson(Map<String, dynamic> json) =>
|
|
||||||
InvokeRequest(
|
|
||||||
method: json['method'] as String? ?? '',
|
|
||||||
params: (json['params'] as List<dynamic>?)
|
|
||||||
?.map((e) => e as String)
|
|
||||||
.toList() ??
|
|
||||||
const [],
|
|
||||||
);
|
|
||||||
|
|
||||||
Map<String, dynamic> _$InvokeRequestToJson(InvokeRequest instance) =>
|
|
||||||
<String, dynamic>{
|
|
||||||
'method': instance.method,
|
|
||||||
'params': instance.params,
|
|
||||||
};
|
|
||||||
@@ -9,13 +9,17 @@ import 'package:flutter_foreground_task/flutter_foreground_task.dart';
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:launch_at_startup/launch_at_startup.dart';
|
import 'package:launch_at_startup/launch_at_startup.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
import 'package:share_handler/share_handler.dart';
|
||||||
import 'package:tray_manager/tray_manager.dart';
|
import 'package:tray_manager/tray_manager.dart';
|
||||||
import 'package:uri_to_file/uri_to_file.dart';
|
import 'package:uri_to_file/uri_to_file.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
import 'package:window_manager/window_manager.dart';
|
import 'package:window_manager/window_manager.dart';
|
||||||
|
|
||||||
import '../../../../api/libgopeed_boot.dart';
|
import '../../../../api/api.dart';
|
||||||
import '../../../../api/model/downloader_config.dart';
|
import '../../../../api/model/downloader_config.dart';
|
||||||
|
import '../../../../api/model/request.dart';
|
||||||
|
import '../../../../core/common/start_config.dart';
|
||||||
|
import '../../../../core/libgopeed_boot.dart';
|
||||||
import '../../../../database/database.dart';
|
import '../../../../database/database.dart';
|
||||||
import '../../../../database/entity.dart';
|
import '../../../../database/entity.dart';
|
||||||
import '../../../../i18n/message.dart';
|
import '../../../../i18n/message.dart';
|
||||||
@@ -25,6 +29,8 @@ import '../../../../util/log_util.dart';
|
|||||||
import '../../../../util/package_info.dart';
|
import '../../../../util/package_info.dart';
|
||||||
import '../../../../util/util.dart';
|
import '../../../../util/util.dart';
|
||||||
import '../../../routes/app_pages.dart';
|
import '../../../routes/app_pages.dart';
|
||||||
|
import '../../create/dto/create_router_params.dart';
|
||||||
|
import '../../redirect/views/redirect_view.dart';
|
||||||
|
|
||||||
const unixSocketPath = 'gopeed.sock';
|
const unixSocketPath = 'gopeed.sock';
|
||||||
|
|
||||||
@@ -72,9 +78,12 @@ final allTrackerSubscribeUrlCdns = Map.fromIterable(allTrackerSubscribeUrls,
|
|||||||
});
|
});
|
||||||
|
|
||||||
class AppController extends GetxController with WindowListener, TrayListener {
|
class AppController extends GetxController with WindowListener, TrayListener {
|
||||||
|
static StartConfig? _defaultStartConfig;
|
||||||
|
|
||||||
final autoStartup = false.obs;
|
final autoStartup = false.obs;
|
||||||
|
final startConfig = StartConfig().obs;
|
||||||
|
final runningPort = 0.obs;
|
||||||
final downloaderConfig = DownloaderConfig().obs;
|
final downloaderConfig = DownloaderConfig().obs;
|
||||||
final httpRunningPort = 0.obs;
|
|
||||||
|
|
||||||
late AppLinks _appLinks;
|
late AppLinks _appLinks;
|
||||||
StreamSubscription<Uri>? _linkSubscription;
|
StreamSubscription<Uri>? _linkSubscription;
|
||||||
@@ -106,7 +115,7 @@ class AppController extends GetxController with WindowListener, TrayListener {
|
|||||||
void onClose() {
|
void onClose() {
|
||||||
_linkSubscription?.cancel();
|
_linkSubscription?.cancel();
|
||||||
trayManager.removeListener(this);
|
trayManager.removeListener(this);
|
||||||
LibgopeedBoot.instance.close();
|
LibgopeedBoot.instance.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -155,22 +164,46 @@ class AppController extends GetxController with WindowListener, TrayListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _initDeepLinks() async {
|
Future<void> _initDeepLinks() async {
|
||||||
// currently only support android
|
if (Util.isWeb()) {
|
||||||
if (!Util.isAndroid()) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_appLinks = AppLinks();
|
// Handle deep link
|
||||||
|
() async {
|
||||||
|
_appLinks = AppLinks();
|
||||||
|
|
||||||
// Handle link when app is in warm state (front or background)
|
// Handle link when app is in warm state (front or background)
|
||||||
_linkSubscription = _appLinks.uriLinkStream.listen((uri) async {
|
_linkSubscription = _appLinks.uriLinkStream.listen((uri) async {
|
||||||
await _toCreate(uri);
|
await _handleDeepLink(uri);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Check initial link if app was in cold state (terminated)
|
// Check initial link if app was in cold state (terminated)
|
||||||
final uri = await _appLinks.getInitialAppLink();
|
final uri = await _appLinks.getInitialLink();
|
||||||
if (uri != null) {
|
if (uri != null) {
|
||||||
await _toCreate(uri);
|
await _handleDeepLink(uri);
|
||||||
|
}
|
||||||
|
}();
|
||||||
|
|
||||||
|
// Handle shared media, e.g. shared link from browser
|
||||||
|
if (Util.isMobile()) {
|
||||||
|
() async {
|
||||||
|
final handler = ShareHandlerPlatform.instance;
|
||||||
|
|
||||||
|
handler.sharedMediaStream.listen((SharedMedia media) {
|
||||||
|
if (media.content?.isNotEmpty == true) {
|
||||||
|
final uri = Uri.parse(media.content!);
|
||||||
|
// content uri will be handled by the app_links plugin
|
||||||
|
if (uri.scheme != "content") {
|
||||||
|
_handleDeepLink(uri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
final media = await handler.getInitialSharedMedia();
|
||||||
|
if (media?.content?.isNotEmpty == true) {
|
||||||
|
_handleDeepLink(Uri.parse(media!.content!));
|
||||||
|
}
|
||||||
|
}();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -213,12 +246,11 @@ class AppController extends GetxController with WindowListener, TrayListener {
|
|||||||
),
|
),
|
||||||
MenuItem(
|
MenuItem(
|
||||||
label: "startAll".tr,
|
label: "startAll".tr,
|
||||||
onClick: (menuItem) async =>
|
onClick: (menuItem) async => {continueAllTasks(null)},
|
||||||
{LibgopeedBoot.instance.continueTasks(null)},
|
|
||||||
),
|
),
|
||||||
MenuItem(
|
MenuItem(
|
||||||
label: "pauseAll".tr,
|
label: "pauseAll".tr,
|
||||||
onClick: (menuItem) async => {LibgopeedBoot.instance.pauseTasks(null)},
|
onClick: (menuItem) async => {pauseAllTasks(null)},
|
||||||
),
|
),
|
||||||
MenuItem(
|
MenuItem(
|
||||||
label: 'setting'.tr,
|
label: 'setting'.tr,
|
||||||
@@ -243,7 +275,7 @@ class AppController extends GetxController with WindowListener, TrayListener {
|
|||||||
label: 'exit'.tr,
|
label: 'exit'.tr,
|
||||||
onClick: (menuItem) async {
|
onClick: (menuItem) async {
|
||||||
try {
|
try {
|
||||||
await LibgopeedBoot.instance.close();
|
await LibgopeedBoot.instance.stop();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.w("libgopeed stop fail", e);
|
logger.w("libgopeed stop fail", e);
|
||||||
}
|
}
|
||||||
@@ -300,18 +332,78 @@ class AppController extends GetxController with WindowListener, TrayListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _toCreate(Uri uri) async {
|
Future<void> _handleDeepLink(Uri uri) async {
|
||||||
final path = (uri.scheme == "magnet" ||
|
if (uri.scheme == "gopeed") {
|
||||||
uri.scheme == "http" ||
|
if (uri.path == "/create") {
|
||||||
uri.scheme == "https")
|
final params = uri.queryParameters["params"];
|
||||||
? uri.toString()
|
if (params?.isNotEmpty == true) {
|
||||||
: (await toFile(uri.toString())).path;
|
final paramsJson =
|
||||||
await Get.rootDelegate.offAndToNamed(Routes.CREATE, arguments: path);
|
String.fromCharCodes(base64Decode(base64.normalize(params!)));
|
||||||
|
Get.rootDelegate.offAndToNamed(Routes.REDIRECT,
|
||||||
|
arguments: RedirectArgs(Routes.CREATE,
|
||||||
|
arguments:
|
||||||
|
CreateRouterParams.fromJson(jsonDecode(paramsJson))));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Get.rootDelegate.offAndToNamed(Routes.CREATE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Get.rootDelegate.offAndToNamed(Routes.HOME);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String path;
|
||||||
|
if (uri.scheme == "magnet" ||
|
||||||
|
uri.scheme == "http" ||
|
||||||
|
uri.scheme == "https") {
|
||||||
|
path = uri.toString();
|
||||||
|
} else if (uri.scheme == "file") {
|
||||||
|
path = Util.isWindows() ? uri.path.substring(1) : uri.path;
|
||||||
|
} else {
|
||||||
|
path = (await toFile(uri.toString())).path;
|
||||||
|
}
|
||||||
|
Get.rootDelegate.offAndToNamed(Routes.REDIRECT,
|
||||||
|
arguments: RedirectArgs(Routes.CREATE,
|
||||||
|
arguments: CreateRouterParams(req: Request(url: path))));
|
||||||
|
}
|
||||||
|
|
||||||
|
String runningAddress() {
|
||||||
|
if (startConfig.value.network == 'unix') {
|
||||||
|
return startConfig.value.address;
|
||||||
|
}
|
||||||
|
return '${startConfig.value.address.split(':').first}:$runningPort';
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<StartConfig> _initDefaultStartConfig() async {
|
||||||
|
if (_defaultStartConfig != null) {
|
||||||
|
return _defaultStartConfig!;
|
||||||
|
}
|
||||||
|
_defaultStartConfig = StartConfig();
|
||||||
|
if (!Util.supportUnixSocket()) {
|
||||||
|
// not support unix socket, use tcp
|
||||||
|
_defaultStartConfig!.network = "tcp";
|
||||||
|
_defaultStartConfig!.address = "127.0.0.1:0";
|
||||||
|
} else {
|
||||||
|
_defaultStartConfig!.network = "unix";
|
||||||
|
_defaultStartConfig!.address =
|
||||||
|
"${(await getTemporaryDirectory()).path}/$unixSocketPath";
|
||||||
|
}
|
||||||
|
_defaultStartConfig!.apiToken = '';
|
||||||
|
return _defaultStartConfig!;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<StartConfig> loadStartConfig() async {
|
||||||
|
final defaultCfg = await _initDefaultStartConfig();
|
||||||
|
final saveCfg = Database.instance.getStartConfig();
|
||||||
|
startConfig.value.network = saveCfg?.network ?? defaultCfg.network;
|
||||||
|
startConfig.value.address = saveCfg?.address ?? defaultCfg.address;
|
||||||
|
startConfig.value.apiToken = saveCfg?.apiToken ?? defaultCfg.apiToken;
|
||||||
|
return startConfig.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<DownloaderConfig> loadDownloaderConfig() async {
|
Future<DownloaderConfig> loadDownloaderConfig() async {
|
||||||
try {
|
try {
|
||||||
downloaderConfig.value = await LibgopeedBoot.instance.getConfig();
|
downloaderConfig.value = await getConfig();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.w("load downloader config fail", e, StackTrace.current);
|
logger.w("load downloader config fail", e, StackTrace.current);
|
||||||
downloaderConfig.value = DownloaderConfig();
|
downloaderConfig.value = DownloaderConfig();
|
||||||
@@ -344,7 +436,7 @@ class AppController extends GetxController with WindowListener, TrayListener {
|
|||||||
});
|
});
|
||||||
refreshTrackers();
|
refreshTrackers();
|
||||||
|
|
||||||
await LibgopeedBoot.instance.putConfig(downloaderConfig.value);
|
await saveConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
refreshTrackers() {
|
refreshTrackers() {
|
||||||
@@ -373,7 +465,7 @@ class AppController extends GetxController with WindowListener, TrayListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<List<String>> _fetchTrackers(String subscribeUrl) async {
|
Future<List<String>> _fetchTrackers(String subscribeUrl) async {
|
||||||
final resp = await LibgopeedBoot.instance.proxyRequest(subscribeUrl);
|
final resp = await proxyRequest(subscribeUrl);
|
||||||
if (resp.statusCode != 200) {
|
if (resp.statusCode != 200) {
|
||||||
throw Exception(
|
throw Exception(
|
||||||
'Failed to get trackers, status code: ${resp.statusCode}');
|
'Failed to get trackers, status code: ${resp.statusCode}');
|
||||||
@@ -430,4 +522,12 @@ class AppController extends GetxController with WindowListener, TrayListener {
|
|||||||
args: ['--${Args.flagHidden}']);
|
args: ['--${Args.flagHidden}']);
|
||||||
autoStartup.value = await launchAtStartup.isEnabled();
|
autoStartup.value = await launchAtStartup.isEnabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> saveConfig() async {
|
||||||
|
Database.instance.saveStartConfig(StartConfigEntity(
|
||||||
|
network: startConfig.value.network,
|
||||||
|
address: startConfig.value.address,
|
||||||
|
apiToken: startConfig.value.apiToken));
|
||||||
|
await putConfig(downloaderConfig.value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -9,7 +9,6 @@ import '../../app/controllers/app_controller.dart';
|
|||||||
|
|
||||||
class CreateController extends GetxController
|
class CreateController extends GetxController
|
||||||
with GetSingleTickerProviderStateMixin {
|
with GetSingleTickerProviderStateMixin {
|
||||||
// final files = [].obs;
|
|
||||||
final RxList fileInfos = [].obs;
|
final RxList fileInfos = [].obs;
|
||||||
final RxList openedFolders = [].obs;
|
final RxList openedFolders = [].obs;
|
||||||
final selectedIndexes = <int>[].obs;
|
final selectedIndexes = <int>[].obs;
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
|
|
||||||
|
import '../../../../api/model/options.dart';
|
||||||
|
import '../../../../api/model/request.dart';
|
||||||
|
|
||||||
|
part 'create_router_params.g.dart';
|
||||||
|
|
||||||
|
@JsonSerializable(explicitToJson: true)
|
||||||
|
class CreateRouterParams {
|
||||||
|
Request? req;
|
||||||
|
Options? opt;
|
||||||
|
|
||||||
|
CreateRouterParams({
|
||||||
|
this.req,
|
||||||
|
this.opt,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory CreateRouterParams.fromJson(
|
||||||
|
Map<String, dynamic> json,
|
||||||
|
) =>
|
||||||
|
_$CreateRouterParamsFromJson(json);
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => _$CreateRouterParamsToJson(this);
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'create_router_params.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonSerializableGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
CreateRouterParams _$CreateRouterParamsFromJson(Map<String, dynamic> json) =>
|
||||||
|
CreateRouterParams(
|
||||||
|
req: json['req'] == null
|
||||||
|
? null
|
||||||
|
: Request.fromJson(json['req'] as Map<String, dynamic>),
|
||||||
|
opt: json['opt'] == null
|
||||||
|
? null
|
||||||
|
: Options.fromJson(json['opt'] as Map<String, dynamic>),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$CreateRouterParamsToJson(CreateRouterParams instance) {
|
||||||
|
final val = <String, dynamic>{};
|
||||||
|
|
||||||
|
void writeNotNull(String key, dynamic value) {
|
||||||
|
if (value != null) {
|
||||||
|
val[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
writeNotNull('req', instance.req?.toJson());
|
||||||
|
writeNotNull('opt', instance.opt?.toJson());
|
||||||
|
return val;
|
||||||
|
}
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
import 'package:autoscale_tabbarview/autoscale_tabbarview.dart';
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:contentsize_tabbarview/contentsize_tabbarview.dart';
|
||||||
import 'package:desktop_drop/desktop_drop.dart';
|
import 'package:desktop_drop/desktop_drop.dart';
|
||||||
import 'package:file_picker/file_picker.dart';
|
import 'package:file_picker/file_picker.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@@ -7,11 +9,12 @@ import 'package:get/get.dart';
|
|||||||
import 'package:path/path.dart' as path;
|
import 'package:path/path.dart' as path;
|
||||||
import 'package:rounded_loading_button_plus/rounded_loading_button.dart';
|
import 'package:rounded_loading_button_plus/rounded_loading_button.dart';
|
||||||
|
|
||||||
import '../../../../api/libgopeed_boot.dart';
|
import '../../../../api/api.dart';
|
||||||
import '../../../../api/model/create_task.dart';
|
import '../../../../api/model/create_task.dart';
|
||||||
import '../../../../api/model/options.dart';
|
import '../../../../api/model/options.dart';
|
||||||
import '../../../../api/model/request.dart';
|
import '../../../../api/model/request.dart';
|
||||||
import '../../../../api/model/resolve_result.dart';
|
import '../../../../api/model/resolve_result.dart';
|
||||||
|
import '../../../../api/model/task.dart';
|
||||||
import '../../../../database/database.dart';
|
import '../../../../database/database.dart';
|
||||||
import '../../../../util/input_formatter.dart';
|
import '../../../../util/input_formatter.dart';
|
||||||
import '../../../../util/message.dart';
|
import '../../../../util/message.dart';
|
||||||
@@ -19,10 +22,11 @@ import '../../../../util/util.dart';
|
|||||||
import '../../../routes/app_pages.dart';
|
import '../../../routes/app_pages.dart';
|
||||||
import '../../../views/compact_checkbox.dart';
|
import '../../../views/compact_checkbox.dart';
|
||||||
import '../../../views/directory_selector.dart';
|
import '../../../views/directory_selector.dart';
|
||||||
import '../../../views/file_list_view.dart';
|
import '../../../views/file_tree_view.dart';
|
||||||
import '../../app/controllers/app_controller.dart';
|
import '../../app/controllers/app_controller.dart';
|
||||||
import '../../history/views/history_view.dart';
|
import '../../history/views/history_view.dart';
|
||||||
import '../controllers/create_controller.dart';
|
import '../controllers/create_controller.dart';
|
||||||
|
import '../dto/create_router_params.dart';
|
||||||
|
|
||||||
class CreateView extends GetView<CreateController> {
|
class CreateView extends GetView<CreateController> {
|
||||||
final _confirmFormKey = GlobalKey<FormState>();
|
final _confirmFormKey = GlobalKey<FormState>();
|
||||||
@@ -36,9 +40,20 @@ class CreateView extends GetView<CreateController> {
|
|||||||
final _proxyPortController = TextEditingController();
|
final _proxyPortController = TextEditingController();
|
||||||
final _proxyUsrController = TextEditingController();
|
final _proxyUsrController = TextEditingController();
|
||||||
final _proxyPwdController = TextEditingController();
|
final _proxyPwdController = TextEditingController();
|
||||||
final _httpUaController = TextEditingController();
|
final _httpHeaderControllers = [
|
||||||
final _httpCookieController = TextEditingController();
|
(
|
||||||
final _httpRefererController = TextEditingController();
|
name: TextEditingController(text: "User-Agent"),
|
||||||
|
value: TextEditingController()
|
||||||
|
),
|
||||||
|
(
|
||||||
|
name: TextEditingController(text: "Cookie"),
|
||||||
|
value: TextEditingController()
|
||||||
|
),
|
||||||
|
(
|
||||||
|
name: TextEditingController(text: "Referer"),
|
||||||
|
value: TextEditingController()
|
||||||
|
),
|
||||||
|
];
|
||||||
final _btTrackerController = TextEditingController();
|
final _btTrackerController = TextEditingController();
|
||||||
|
|
||||||
final _availableSchemes = ["http:", "https:", "magnet:"];
|
final _availableSchemes = ["http:", "https:", "magnet:"];
|
||||||
@@ -60,32 +75,91 @@ class CreateView extends GetView<CreateController> {
|
|||||||
_pathController.text = appController.downloaderConfig.value.downloadDir;
|
_pathController.text = appController.downloaderConfig.value.downloadDir;
|
||||||
}
|
}
|
||||||
|
|
||||||
final String? filePath = Get.rootDelegate.arguments();
|
final CreateRouterParams? routerParams = Get.rootDelegate.arguments();
|
||||||
if (_urlController.text.isEmpty) {
|
if (routerParams?.req?.url.isNotEmpty ?? false) {
|
||||||
if (filePath?.isNotEmpty ?? false) {
|
// get url from route arguments
|
||||||
// get file path from route arguments
|
final url = routerParams!.req!.url;
|
||||||
_urlController.text = filePath!;
|
_urlController.text = url;
|
||||||
_urlController.selection = TextSelection.fromPosition(
|
_urlController.selection = TextSelection.fromPosition(
|
||||||
TextPosition(offset: _urlController.text.length));
|
TextPosition(offset: _urlController.text.length));
|
||||||
} else {
|
final uppercaseUrl = url.toUpperCase();
|
||||||
// read clipboard
|
Protocol? protocol;
|
||||||
Clipboard.getData('text/plain').then((value) {
|
if (uppercaseUrl.startsWith("HTTP:") ||
|
||||||
if (value?.text?.isNotEmpty ?? false) {
|
uppercaseUrl.startsWith("HTTPS:")) {
|
||||||
if (_availableSchemes
|
protocol = Protocol.http;
|
||||||
.where((e) =>
|
|
||||||
value!.text!.startsWith(e) ||
|
|
||||||
value.text!.startsWith(e.toUpperCase()))
|
|
||||||
.isNotEmpty) {
|
|
||||||
_urlController.text = value!.text!;
|
|
||||||
_urlController.selection = TextSelection.fromPosition(
|
|
||||||
TextPosition(offset: _urlController.text.length));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
recognizeMagnetUri(value!.text!);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
if (uppercaseUrl.startsWith("MAGNET:") ||
|
||||||
|
uppercaseUrl.endsWith(".TORRENT")) {
|
||||||
|
protocol = Protocol.bt;
|
||||||
|
}
|
||||||
|
if (protocol != null) {
|
||||||
|
final extraHandlers = {
|
||||||
|
Protocol.http: () {
|
||||||
|
final reqExtra = ReqExtraHttp.fromJson(
|
||||||
|
jsonDecode(jsonEncode(routerParams.req!.extra)));
|
||||||
|
_httpHeaderControllers.clear();
|
||||||
|
reqExtra.header.forEach((key, value) {
|
||||||
|
_httpHeaderControllers.add(
|
||||||
|
(
|
||||||
|
name: TextEditingController(text: key),
|
||||||
|
value: TextEditingController(text: value),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
_skipVerifyCertController.value = routerParams.req!.skipVerifyCert;
|
||||||
|
},
|
||||||
|
Protocol.bt: () {
|
||||||
|
final reqExtra = ReqExtraBt.fromJson(
|
||||||
|
jsonDecode(jsonEncode(routerParams.req!.extra)));
|
||||||
|
_btTrackerController.text = reqExtra.trackers.join("\n");
|
||||||
|
},
|
||||||
|
};
|
||||||
|
if (routerParams.req?.extra != null) {
|
||||||
|
extraHandlers[protocol]?.call();
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle options
|
||||||
|
if (routerParams.opt != null) {
|
||||||
|
_renameController.text = routerParams.opt!.name;
|
||||||
|
_pathController.text = routerParams.opt!.path;
|
||||||
|
|
||||||
|
final optionsHandlers = {
|
||||||
|
Protocol.http: () {
|
||||||
|
final opt = routerParams.opt!;
|
||||||
|
_renameController.text = opt.name;
|
||||||
|
_pathController.text = opt.path;
|
||||||
|
if (opt.extra != null) {
|
||||||
|
final optsExtraHttp =
|
||||||
|
OptsExtraHttp.fromJson(jsonDecode(jsonEncode(opt.extra)));
|
||||||
|
_connectionsController.text =
|
||||||
|
optsExtraHttp.connections.toString();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Protocol.bt: null,
|
||||||
|
};
|
||||||
|
if (routerParams.opt?.extra != null) {
|
||||||
|
optionsHandlers[protocol]?.call();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (_urlController.text.isEmpty) {
|
||||||
|
// read clipboard
|
||||||
|
Clipboard.getData('text/plain').then((value) {
|
||||||
|
if (value?.text?.isNotEmpty ?? false) {
|
||||||
|
if (_availableSchemes
|
||||||
|
.where((e) =>
|
||||||
|
value!.text!.startsWith(e) ||
|
||||||
|
value.text!.startsWith(e.toUpperCase()))
|
||||||
|
.isNotEmpty) {
|
||||||
|
_urlController.text = value!.text!;
|
||||||
|
_urlController.selection = TextSelection.fromPosition(
|
||||||
|
TextPosition(offset: _urlController.text.length));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
recognizeMagnetUri(value!.text!);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
@@ -436,56 +510,105 @@ class CreateView extends GetView<CreateController> {
|
|||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
AutoScaleTabBarView(
|
DefaultTabController(
|
||||||
controller: controller.advancedTabController,
|
length: 2,
|
||||||
children: [
|
child: ContentSizeTabBarView(
|
||||||
Column(
|
controller:
|
||||||
children: [
|
controller.advancedTabController,
|
||||||
TextFormField(
|
children: [
|
||||||
controller: _httpUaController,
|
Column(
|
||||||
decoration: const InputDecoration(
|
children: [
|
||||||
labelText: 'User-Agent',
|
..._httpHeaderControllers.map((e) {
|
||||||
)),
|
return Row(
|
||||||
TextFormField(
|
children: [
|
||||||
controller: _httpCookieController,
|
Flexible(
|
||||||
decoration: const InputDecoration(
|
child: TextFormField(
|
||||||
labelText: 'Cookie',
|
controller: e.name,
|
||||||
)),
|
decoration: InputDecoration(
|
||||||
TextFormField(
|
hintText:
|
||||||
controller: _httpRefererController,
|
'httpHeaderName'.tr,
|
||||||
decoration: const InputDecoration(
|
),
|
||||||
labelText: 'Referer',
|
),
|
||||||
)),
|
),
|
||||||
Padding(
|
const Padding(
|
||||||
padding:
|
padding: EdgeInsets.only(
|
||||||
const EdgeInsets.only(top: 10),
|
left: 10)),
|
||||||
child: CompactCheckbox(
|
Flexible(
|
||||||
label: 'skipVerifyCert'.tr,
|
child: TextFormField(
|
||||||
value:
|
controller: e.value,
|
||||||
_skipVerifyCertController.value,
|
decoration: InputDecoration(
|
||||||
onChanged: (bool? value) {
|
hintText:
|
||||||
_skipVerifyCertController.value =
|
'httpHeaderValue'.tr,
|
||||||
value ?? false;
|
),
|
||||||
},
|
),
|
||||||
textStyle: const TextStyle(
|
),
|
||||||
color: Colors.grey,
|
const Padding(
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
left: 10)),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.add),
|
||||||
|
onPressed: () {
|
||||||
|
_httpHeaderControllers.add(
|
||||||
|
(
|
||||||
|
name:
|
||||||
|
TextEditingController(),
|
||||||
|
value:
|
||||||
|
TextEditingController(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
controller.showAdvanced
|
||||||
|
.update((val) => val);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon:
|
||||||
|
const Icon(Icons.remove),
|
||||||
|
onPressed: () {
|
||||||
|
if (_httpHeaderControllers
|
||||||
|
.length <=
|
||||||
|
1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_httpHeaderControllers
|
||||||
|
.remove(e);
|
||||||
|
controller.showAdvanced
|
||||||
|
.update((val) => val);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
Padding(
|
||||||
|
padding:
|
||||||
|
const EdgeInsets.only(top: 10),
|
||||||
|
child: CompactCheckbox(
|
||||||
|
label: 'skipVerifyCert'.tr,
|
||||||
|
value: _skipVerifyCertController
|
||||||
|
.value,
|
||||||
|
onChanged: (bool? value) {
|
||||||
|
_skipVerifyCertController
|
||||||
|
.value = value ?? false;
|
||||||
|
},
|
||||||
|
textStyle: const TextStyle(
|
||||||
|
color: Colors.grey,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
),
|
Column(
|
||||||
Column(
|
children: [
|
||||||
children: [
|
TextFormField(
|
||||||
TextFormField(
|
controller: _btTrackerController,
|
||||||
controller: _btTrackerController,
|
maxLines: 5,
|
||||||
maxLines: 5,
|
decoration: InputDecoration(
|
||||||
decoration: InputDecoration(
|
labelText: 'Trackers',
|
||||||
labelText: 'Trackers',
|
hintText: 'addTrackerHit'.tr,
|
||||||
hintText: 'addTrackerHit'.tr,
|
)),
|
||||||
)),
|
],
|
||||||
],
|
)
|
||||||
)
|
],
|
||||||
],
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
).paddingOnly(top: 16),
|
).paddingOnly(top: 16),
|
||||||
@@ -594,7 +717,7 @@ class CreateView extends GetView<CreateController> {
|
|||||||
final isDirect = controller.directDownload.value || isMultiLine;
|
final isDirect = controller.directDownload.value || isMultiLine;
|
||||||
if (isDirect) {
|
if (isDirect) {
|
||||||
await Future.wait(urls.map((url) {
|
await Future.wait(urls.map((url) {
|
||||||
return LibgopeedBoot.instance.createTask(CreateTask(
|
return createTask(CreateTask(
|
||||||
req: Request(
|
req: Request(
|
||||||
url: url,
|
url: url,
|
||||||
extra: parseReqExtra(url),
|
extra: parseReqExtra(url),
|
||||||
@@ -610,7 +733,7 @@ class CreateView extends GetView<CreateController> {
|
|||||||
}));
|
}));
|
||||||
Get.rootDelegate.offNamed(Routes.TASK);
|
Get.rootDelegate.offNamed(Routes.TASK);
|
||||||
} else {
|
} else {
|
||||||
final rr = await LibgopeedBoot.instance.resolve(Request(
|
final rr = await resolve(Request(
|
||||||
url: submitUrl,
|
url: submitUrl,
|
||||||
extra: parseReqExtra(_urlController.text),
|
extra: parseReqExtra(_urlController.text),
|
||||||
proxy: parseProxy(),
|
proxy: parseProxy(),
|
||||||
@@ -644,12 +767,10 @@ class CreateView extends GetView<CreateController> {
|
|||||||
if (controller.showAdvanced.value) {
|
if (controller.showAdvanced.value) {
|
||||||
switch (controller.advancedTabController.index) {
|
switch (controller.advancedTabController.index) {
|
||||||
case 0:
|
case 0:
|
||||||
final header = {
|
final header = Map<String, String>.fromEntries(_httpHeaderControllers
|
||||||
"User-Agent": _httpUaController.text,
|
.map((e) => MapEntry(e.name.text, e.value.text)));
|
||||||
"Cookie": _httpCookieController.text,
|
header.removeWhere(
|
||||||
"Referer": _httpRefererController.text,
|
(key, value) => key.trim().isEmpty || value.trim().isEmpty);
|
||||||
};
|
|
||||||
header.removeWhere((key, value) => value.trim().isEmpty);
|
|
||||||
if (header.isNotEmpty) {
|
if (header.isNotEmpty) {
|
||||||
reqExtra = ReqExtraHttp()..header = header;
|
reqExtra = ReqExtraHttp()..header = header;
|
||||||
}
|
}
|
||||||
@@ -698,7 +819,13 @@ class CreateView extends GetView<CreateController> {
|
|||||||
child: Form(
|
child: Form(
|
||||||
key: createFormKey,
|
key: createFormKey,
|
||||||
autovalidateMode: AutovalidateMode.always,
|
autovalidateMode: AutovalidateMode.always,
|
||||||
child: FileListView(files: rr.res.files)),
|
child: FileTreeView(
|
||||||
|
files: rr.res.files,
|
||||||
|
initialValues: rr.res.files.asMap().keys.toList(),
|
||||||
|
onSelectionChanged: (List<int> values) {
|
||||||
|
controller.selectedIndexes.value = values;
|
||||||
|
},
|
||||||
|
)),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -741,28 +868,23 @@ class CreateView extends GetView<CreateController> {
|
|||||||
await Future.wait(
|
await Future.wait(
|
||||||
controller.selectedIndexes.map((index) {
|
controller.selectedIndexes.map((index) {
|
||||||
final file = rr.res.files[index];
|
final file = rr.res.files[index];
|
||||||
return LibgopeedBoot.instance.createTask(
|
return createTask(CreateTask(
|
||||||
CreateTask(
|
req: file.req!..proxy = parseProxy(),
|
||||||
req: file.req!..proxy = parseProxy(),
|
opt: Options(
|
||||||
opt: Options(
|
name: file.name,
|
||||||
name: file.name,
|
path: path.join(_pathController.text,
|
||||||
path: path.join(
|
rr.res.name, file.path),
|
||||||
_pathController.text,
|
selectFiles: [],
|
||||||
rr.res.name,
|
extra: optExtra)));
|
||||||
file.path),
|
|
||||||
selectFiles: [],
|
|
||||||
extra: optExtra)));
|
|
||||||
}));
|
}));
|
||||||
} else {
|
} else {
|
||||||
await LibgopeedBoot.instance.createTask(
|
await createTask(CreateTask(
|
||||||
CreateTask(
|
rid: rr.id,
|
||||||
rid: rr.id,
|
opt: Options(
|
||||||
opt: Options(
|
name: _renameController.text,
|
||||||
name: _renameController.text,
|
path: _pathController.text,
|
||||||
path: _pathController.text,
|
selectFiles: controller.selectedIndexes,
|
||||||
selectFiles:
|
extra: optExtra)));
|
||||||
controller.selectedIndexes,
|
|
||||||
extra: optExtra)));
|
|
||||||
}
|
}
|
||||||
Get.back();
|
Get.back();
|
||||||
Get.rootDelegate.offNamed(Routes.TASK);
|
Get.rootDelegate.offNamed(Routes.TASK);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
import 'package:gopeed/api/api.dart';
|
||||||
|
|
||||||
import '../../../../api/libgopeed_boot.dart';
|
|
||||||
import '../../../../api/model/extension.dart';
|
import '../../../../api/model/extension.dart';
|
||||||
|
|
||||||
class ExtensionController extends GetxController {
|
class ExtensionController extends GetxController {
|
||||||
@@ -17,13 +17,12 @@ class ExtensionController extends GetxController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> load() async {
|
Future<void> load() async {
|
||||||
extensions.value = await LibgopeedBoot.instance.getExtensions();
|
extensions.value = await getExtensions();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> checkUpdate() async {
|
Future<void> checkUpdate() async {
|
||||||
for (final ext in extensions) {
|
for (final ext in extensions) {
|
||||||
final resp =
|
final resp = await upgradeCheckExtension(ext.identity);
|
||||||
await LibgopeedBoot.instance.upgradeCheckExtension(ext.identity);
|
|
||||||
if (resp.newVersion.isNotEmpty) {
|
if (resp.newVersion.isNotEmpty) {
|
||||||
updateFlags[ext.identity] = resp.newVersion;
|
updateFlags[ext.identity] = resp.newVersion;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,13 +11,11 @@ import 'package:path/path.dart' as path;
|
|||||||
import 'package:rounded_loading_button_plus/rounded_loading_button.dart';
|
import 'package:rounded_loading_button_plus/rounded_loading_button.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
import '../../../../api/entry/libgopeed_boot_web.dart';
|
import '../../../../api/api.dart';
|
||||||
import '../../../../api/libgopeed_boot.dart';
|
|
||||||
import '../../../../api/model/extension.dart';
|
import '../../../../api/model/extension.dart';
|
||||||
import '../../../../api/model/install_extension.dart';
|
import '../../../../api/model/install_extension.dart';
|
||||||
import '../../../../api/model/switch_extension.dart';
|
import '../../../../api/model/switch_extension.dart';
|
||||||
import '../../../../api/model/update_extension_settings.dart';
|
import '../../../../api/model/update_extension_settings.dart';
|
||||||
import '../../../../util/mac_secure_util.dart';
|
|
||||||
import '../../../../util/message.dart';
|
import '../../../../util/message.dart';
|
||||||
import '../../../../util/util.dart';
|
import '../../../../util/util.dart';
|
||||||
import '../../../views/icon_button_loading.dart';
|
import '../../../views/icon_button_loading.dart';
|
||||||
@@ -79,9 +77,8 @@ class ExtensionView extends GetView<ExtensionController> {
|
|||||||
}
|
}
|
||||||
_installBtnController.start();
|
_installBtnController.start();
|
||||||
try {
|
try {
|
||||||
await LibgopeedBoot.instance.installExtension(
|
await installExtension(InstallExtension(
|
||||||
InstallExtension(
|
url: _installUrlController.text));
|
||||||
url: _installUrlController.text));
|
|
||||||
Get.snackbar('tip'.tr, 'extensionInstallSuccess'.tr);
|
Get.snackbar('tip'.tr, 'extensionInstallSuccess'.tr);
|
||||||
await controller.load();
|
await controller.load();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -98,9 +95,8 @@ class ExtensionView extends GetView<ExtensionController> {
|
|||||||
var dir =
|
var dir =
|
||||||
await FilePicker.platform.getDirectoryPath();
|
await FilePicker.platform.getDirectoryPath();
|
||||||
if (dir != null) {
|
if (dir != null) {
|
||||||
MacSecureUtil.saveBookmark(dir);
|
|
||||||
try {
|
try {
|
||||||
await LibgopeedBoot.instance.installExtension(
|
await installExtension(
|
||||||
InstallExtension(devMode: true, url: dir));
|
InstallExtension(devMode: true, url: dir));
|
||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'tip'.tr, 'extensionInstallSuccess'.tr);
|
'tip'.tr, 'extensionInstallSuccess'.tr);
|
||||||
@@ -135,10 +131,8 @@ class ExtensionView extends GetView<ExtensionController> {
|
|||||||
)
|
)
|
||||||
: Util.isWeb()
|
: Util.isWeb()
|
||||||
? Image.network(
|
? Image.network(
|
||||||
(LibgopeedBoot.instance
|
join(
|
||||||
as LibgopeedBootWeb)
|
'/fs/extensions/${extension.identity}/${extension.icon}'),
|
||||||
.join(
|
|
||||||
'/fs/extensions/${extension.identity}/${extension.icon}'),
|
|
||||||
width: 48,
|
width: 48,
|
||||||
height: 48,
|
height: 48,
|
||||||
)
|
)
|
||||||
@@ -159,9 +153,9 @@ class ExtensionView extends GetView<ExtensionController> {
|
|||||||
value: !extension.disabled,
|
value: !extension.disabled,
|
||||||
onChanged: (value) async {
|
onChanged: (value) async {
|
||||||
try {
|
try {
|
||||||
await LibgopeedBoot.instance
|
await switchExtension(
|
||||||
.switchExtension(extension.identity,
|
extension.identity,
|
||||||
SwitchExtension(status: value));
|
SwitchExtension(status: value));
|
||||||
await controller.load();
|
await controller.load();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
showErrorMessage(e);
|
showErrorMessage(e);
|
||||||
@@ -358,11 +352,10 @@ class ExtensionView extends GetView<ExtensionController> {
|
|||||||
try {
|
try {
|
||||||
confrimController.start();
|
confrimController.start();
|
||||||
if (formKey.currentState?.saveAndValidate() == true) {
|
if (formKey.currentState?.saveAndValidate() == true) {
|
||||||
await LibgopeedBoot.instance
|
await updateExtensionSettings(
|
||||||
.updateExtensionSettings(
|
extension.identity,
|
||||||
extension.identity,
|
UpdateExtensionSettings(
|
||||||
UpdateExtensionSettings(
|
settings: formKey.currentState!.value));
|
||||||
settings: formKey.currentState!.value));
|
|
||||||
await controller.load();
|
await controller.load();
|
||||||
Get.back();
|
Get.back();
|
||||||
}
|
}
|
||||||
@@ -459,8 +452,7 @@ class ExtensionView extends GetView<ExtensionController> {
|
|||||||
),
|
),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
try {
|
try {
|
||||||
await LibgopeedBoot.instance
|
await deleteExtension(extension.identity);
|
||||||
.deleteExtension(extension.identity);
|
|
||||||
await controller.load();
|
await controller.load();
|
||||||
Get.back();
|
Get.back();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -493,19 +485,18 @@ class ExtensionView extends GetView<ExtensionController> {
|
|||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
confrimController.start();
|
confrimController.start();
|
||||||
try {
|
try {
|
||||||
await LibgopeedBoot.instance
|
await updateExtension(extension.identity);
|
||||||
.upgradeExtension(extension.identity);
|
|
||||||
await controller.load();
|
await controller.load();
|
||||||
controller.updateFlags.remove(extension.identity);
|
controller.updateFlags.remove(extension.identity);
|
||||||
Get.back();
|
Get.back();
|
||||||
showMessage('tip'.tr, 'extensionUpgradeSuccess'.tr);
|
showMessage('tip'.tr, 'extensionUpdateSuccess'.tr);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
showErrorMessage(e);
|
showErrorMessage(e);
|
||||||
} finally {
|
} finally {
|
||||||
confrimController.stop();
|
confrimController.stop();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: Text('newVersionUpgrade'.tr),
|
child: Text('newVersionUpdate'.tr),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
));
|
));
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
|
import '../controllers/redirect_controller.dart';
|
||||||
|
|
||||||
|
class RedirectBinding extends Bindings {
|
||||||
|
@override
|
||||||
|
void dependencies() {
|
||||||
|
Get.lazyPut<RedirectController>(
|
||||||
|
() => RedirectController(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
|
class RedirectController extends GetxController {}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
|
import '../controllers/redirect_controller.dart';
|
||||||
|
|
||||||
|
class RedirectArgs {
|
||||||
|
final String page;
|
||||||
|
final dynamic arguments;
|
||||||
|
|
||||||
|
RedirectArgs(this.page, {this.arguments});
|
||||||
|
}
|
||||||
|
|
||||||
|
class RedirectView extends GetView<RedirectController> {
|
||||||
|
const RedirectView({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final redirectArgs = Get.rootDelegate.arguments() as RedirectArgs;
|
||||||
|
// Waiting for previous page controller to delete, avoid deleting controller that route page after redirect
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||||
|
await Future.delayed(const Duration(milliseconds: 350));
|
||||||
|
Get.rootDelegate
|
||||||
|
.offAndToNamed(redirectArgs.page, arguments: redirectArgs.arguments);
|
||||||
|
});
|
||||||
|
return const SizedBox();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
import 'package:gopeed/api/api.dart';
|
||||||
import '../../../../api/libgopeed_boot.dart';
|
|
||||||
|
|
||||||
class SettingController extends GetxController {
|
class SettingController extends GetxController {
|
||||||
final tapStatues = <String, bool>{}.obs;
|
final tapStatues = <String, bool>{}.obs;
|
||||||
@@ -29,13 +28,12 @@ class SettingController extends GetxController {
|
|||||||
void fetchLatestVersion() async {
|
void fetchLatestVersion() async {
|
||||||
String? releaseDataStr;
|
String? releaseDataStr;
|
||||||
try {
|
try {
|
||||||
releaseDataStr = (await LibgopeedBoot.instance.proxyRequest(
|
releaseDataStr = (await proxyRequest(
|
||||||
"https://api.github.com/repos/GopeedLab/gopeed/releases/latest"))
|
"https://api.github.com/repos/GopeedLab/gopeed/releases/latest"))
|
||||||
.data;
|
.data;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
releaseDataStr = (await LibgopeedBoot.instance
|
releaseDataStr =
|
||||||
.proxyRequest("https://gopeed.com/api/release"))
|
(await proxyRequest("https://gopeed.com/api/release")).data;
|
||||||
.data;
|
|
||||||
}
|
}
|
||||||
if (releaseDataStr == null) {
|
if (releaseDataStr == null) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import 'package:intl/intl.dart';
|
|||||||
import 'package:launch_at_startup/launch_at_startup.dart';
|
import 'package:launch_at_startup/launch_at_startup.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
import '../../../../api/libgopeed_boot.dart';
|
|
||||||
import '../../../../api/model/downloader_config.dart';
|
import '../../../../api/model/downloader_config.dart';
|
||||||
import '../../../../i18n/message.dart';
|
import '../../../../i18n/message.dart';
|
||||||
import '../../../../util/input_formatter.dart';
|
import '../../../../util/input_formatter.dart';
|
||||||
@@ -18,6 +17,7 @@ import '../../../../util/locale_manager.dart';
|
|||||||
import '../../../../util/log_util.dart';
|
import '../../../../util/log_util.dart';
|
||||||
import '../../../../util/message.dart';
|
import '../../../../util/message.dart';
|
||||||
import '../../../../util/package_info.dart';
|
import '../../../../util/package_info.dart';
|
||||||
|
import '../../../../util/scheme_register/scheme_register.dart';
|
||||||
import '../../../../util/util.dart';
|
import '../../../../util/util.dart';
|
||||||
import '../../../views/check_list_view.dart';
|
import '../../../views/check_list_view.dart';
|
||||||
import '../../../views/directory_selector.dart';
|
import '../../../views/directory_selector.dart';
|
||||||
@@ -36,6 +36,7 @@ class SettingView extends GetView<SettingController> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final appController = Get.find<AppController>();
|
final appController = Get.find<AppController>();
|
||||||
final downloaderCfg = appController.downloaderConfig;
|
final downloaderCfg = appController.downloaderConfig;
|
||||||
|
final startCfg = appController.startConfig;
|
||||||
|
|
||||||
Timer? timer;
|
Timer? timer;
|
||||||
Future<bool> debounceSave(
|
Future<bool> debounceSave(
|
||||||
@@ -51,8 +52,8 @@ class SettingView extends GetView<SettingController> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LibgopeedBoot.instance
|
appController
|
||||||
.putConfig(downloaderCfg.value)
|
.saveConfig()
|
||||||
.then((_) => completer.complete(true))
|
.then((_) => completer.complete(true))
|
||||||
.onError(completer.completeError);
|
.onError(completer.completeError);
|
||||||
if (needRestart) {
|
if (needRestart) {
|
||||||
@@ -111,7 +112,7 @@ class SettingView extends GetView<SettingController> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
final buildDefaultDirectDownload =
|
final buildDefaultDirectDownload =
|
||||||
_buildConfigItem('defaultDirectDownload'.tr, () {
|
_buildConfigItem('defaultDirectDownload', () {
|
||||||
return appController.downloaderConfig.value.extra.defaultDirectDownload
|
return appController.downloaderConfig.value.extra.defaultDirectDownload
|
||||||
? 'on'.tr
|
? 'on'.tr
|
||||||
: 'off'.tr;
|
: 'off'.tr;
|
||||||
@@ -161,7 +162,7 @@ class SettingView extends GetView<SettingController> {
|
|||||||
// Currently auto startup only support Windows and Linux
|
// Currently auto startup only support Windows and Linux
|
||||||
final buildAutoStartup = !Util.isWindows() && !Util.isLinux()
|
final buildAutoStartup = !Util.isWindows() && !Util.isLinux()
|
||||||
? () => null
|
? () => null
|
||||||
: _buildConfigItem('launchAtStartup'.tr, () {
|
: _buildConfigItem('launchAtStartup', () {
|
||||||
return appController.autoStartup.value ? 'on'.tr : 'off'.tr;
|
return appController.autoStartup.value ? 'on'.tr : 'off'.tr;
|
||||||
}, (Key key) {
|
}, (Key key) {
|
||||||
return Container(
|
return Container(
|
||||||
@@ -229,8 +230,9 @@ class SettingView extends GetView<SettingController> {
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
final buildHttpUseServerCtime = _buildConfigItem('useServerCtime'.tr,
|
final buildHttpUseServerCtime = _buildConfigItem(
|
||||||
() => httpConfig.useServerCtime ? 'on'.tr : 'off'.tr, (Key key) {
|
'useServerCtime', () => httpConfig.useServerCtime ? 'on'.tr : 'off'.tr,
|
||||||
|
(Key key) {
|
||||||
return Container(
|
return Container(
|
||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
child: Switch(
|
child: Switch(
|
||||||
@@ -420,6 +422,37 @@ class SettingView extends GetView<SettingController> {
|
|||||||
].where((e) => e != null).map((e) => e!).toList(),
|
].where((e) => e != null).map((e) => e!).toList(),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
final buildBtDefaultClientConfig = !Util.isWindows()
|
||||||
|
? () => null
|
||||||
|
: _buildConfigItem('setAsDefaultBtClient', () {
|
||||||
|
return appController.downloaderConfig.value.extra.defaultBtClient
|
||||||
|
? 'on'.tr
|
||||||
|
: 'off'.tr;
|
||||||
|
}, (Key key) {
|
||||||
|
return Container(
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
child: Switch(
|
||||||
|
value:
|
||||||
|
appController.downloaderConfig.value.extra.defaultBtClient,
|
||||||
|
onChanged: (bool value) async {
|
||||||
|
try {
|
||||||
|
if (value) {
|
||||||
|
registerDefaultTorrentClient();
|
||||||
|
} else {
|
||||||
|
unregisterDefaultTorrentClient();
|
||||||
|
}
|
||||||
|
appController.downloaderConfig.update((val) {
|
||||||
|
val!.extra.defaultBtClient = value;
|
||||||
|
});
|
||||||
|
await debounceSave();
|
||||||
|
} catch (e) {
|
||||||
|
showErrorMessage(e);
|
||||||
|
logger.e('register default torrent client fail', e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
// ui config items start
|
// ui config items start
|
||||||
final buildTheme = _buildConfigItem(
|
final buildTheme = _buildConfigItem(
|
||||||
@@ -530,7 +563,7 @@ class SettingView extends GetView<SettingController> {
|
|||||||
launchUrl(Uri.parse('https://gopeed.com'),
|
launchUrl(Uri.parse('https://gopeed.com'),
|
||||||
mode: LaunchMode.externalApplication);
|
mode: LaunchMode.externalApplication);
|
||||||
},
|
},
|
||||||
child: Text('newVersionUpgrade'.tr),
|
child: Text('newVersionUpdate'.tr),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
));
|
));
|
||||||
@@ -637,7 +670,7 @@ class SettingView extends GetView<SettingController> {
|
|||||||
final portController = TextEditingController(text: port);
|
final portController = TextEditingController(text: port);
|
||||||
updateAddress() async {
|
updateAddress() async {
|
||||||
final newAddress = '${ipController.text}:${portController.text}';
|
final newAddress = '${ipController.text}:${portController.text}';
|
||||||
if (newAddress != proxy.host) {
|
if (newAddress != startCfg.value.address) {
|
||||||
proxy.host = newAddress;
|
proxy.host = newAddress;
|
||||||
|
|
||||||
await debounceSave();
|
await debounceSave();
|
||||||
@@ -718,152 +751,152 @@ class SettingView extends GetView<SettingController> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// advanced config API items start
|
// advanced config API items start
|
||||||
// final buildApiProtocol = _buildConfigItem(
|
final buildApiProtocol = _buildConfigItem(
|
||||||
// 'protocol',
|
'protocol',
|
||||||
// () => startCfg.value.host == 'tcp'
|
() => startCfg.value.network == 'tcp'
|
||||||
// ? 'TCP ${startCfg.value.address}'
|
? 'TCP ${startCfg.value.address}'
|
||||||
// : 'Unix',
|
: 'Unix',
|
||||||
// (Key key) {
|
(Key key) {
|
||||||
// final items = <Widget>[
|
final items = <Widget>[
|
||||||
// SizedBox(
|
SizedBox(
|
||||||
// width: 80,
|
width: 80,
|
||||||
// child: DropdownButtonFormField<String>(
|
child: DropdownButtonFormField<String>(
|
||||||
// value: startCfg.value.host,
|
value: startCfg.value.network,
|
||||||
// onChanged: Util.isDesktop()
|
onChanged: Util.isDesktop()
|
||||||
// ? (value) async {
|
? (value) async {
|
||||||
// startCfg.update((val) {
|
startCfg.update((val) {
|
||||||
// val!.host = value!;
|
val!.network = value!;
|
||||||
// });
|
});
|
||||||
|
|
||||||
// await debounceSave(needRestart: true);
|
await debounceSave(needRestart: true);
|
||||||
// }
|
}
|
||||||
// : null,
|
: null,
|
||||||
// items: [
|
items: [
|
||||||
// const DropdownMenuItem<String>(
|
const DropdownMenuItem<String>(
|
||||||
// value: 'tcp',
|
value: 'tcp',
|
||||||
// child: Text('TCP'),
|
child: Text('TCP'),
|
||||||
// ),
|
),
|
||||||
// Util.supportUnixSocket()
|
Util.supportUnixSocket()
|
||||||
// ? const DropdownMenuItem<String>(
|
? const DropdownMenuItem<String>(
|
||||||
// value: 'unix',
|
value: 'unix',
|
||||||
// child: Text('Unix'),
|
child: Text('Unix'),
|
||||||
// )
|
)
|
||||||
// : null,
|
: null,
|
||||||
// ].where((e) => e != null).map((e) => e!).toList(),
|
].where((e) => e != null).map((e) => e!).toList(),
|
||||||
// ),
|
),
|
||||||
// )
|
)
|
||||||
// ];
|
];
|
||||||
// if (Util.isDesktop() && startCfg.value.host == 'tcp') {
|
if (Util.isDesktop() && startCfg.value.network == 'tcp') {
|
||||||
// final arr = startCfg.value.address.split(':');
|
final arr = startCfg.value.address.split(':');
|
||||||
// var ip = '127.0.0.1';
|
var ip = '127.0.0.1';
|
||||||
// var port = '0';
|
var port = '0';
|
||||||
// if (arr.length > 1) {
|
if (arr.length > 1) {
|
||||||
// ip = arr[0];
|
ip = arr[0];
|
||||||
// port = arr[1];
|
port = arr[1];
|
||||||
// }
|
}
|
||||||
|
|
||||||
// final ipController = TextEditingController(text: ip);
|
final ipController = TextEditingController(text: ip);
|
||||||
// final portController = TextEditingController(text: port);
|
final portController = TextEditingController(text: port);
|
||||||
// updateAddress() async {
|
updateAddress() async {
|
||||||
// if (ipController.text.isEmpty || portController.text.isEmpty) {
|
if (ipController.text.isEmpty || portController.text.isEmpty) {
|
||||||
// return;
|
return;
|
||||||
// }
|
}
|
||||||
// final newAddress = '${ipController.text}:${portController.text}';
|
final newAddress = '${ipController.text}:${portController.text}';
|
||||||
// if (newAddress != startCfg.value.address) {
|
if (newAddress != startCfg.value.address) {
|
||||||
// startCfg.value.address = newAddress;
|
startCfg.value.address = newAddress;
|
||||||
|
|
||||||
// final saved = await debounceSave(
|
final saved = await debounceSave(
|
||||||
// check: () async {
|
check: () async {
|
||||||
// // Check if address already in use
|
// Check if address already in use
|
||||||
// final configIp = ipController.text;
|
final configIp = ipController.text;
|
||||||
// final configPort = int.parse(portController.text);
|
final configPort = int.parse(portController.text);
|
||||||
// if (configPort == 0) {
|
if (configPort == 0) {
|
||||||
// return '';
|
return '';
|
||||||
// }
|
}
|
||||||
// try {
|
try {
|
||||||
// final socket = await Socket.connect(configIp, configPort,
|
final socket = await Socket.connect(configIp, configPort,
|
||||||
// timeout: const Duration(seconds: 3));
|
timeout: const Duration(seconds: 3));
|
||||||
// socket.close();
|
socket.close();
|
||||||
// return 'portInUse'
|
return 'portInUse'
|
||||||
// .trParams({'port': configPort.toString()});
|
.trParams({'port': configPort.toString()});
|
||||||
// } catch (e) {
|
} catch (e) {
|
||||||
// return '';
|
return '';
|
||||||
// }
|
}
|
||||||
// },
|
},
|
||||||
// needRestart: true);
|
needRestart: true);
|
||||||
|
|
||||||
// // If save failed, restore the old address
|
// If save failed, restore the old address
|
||||||
// if (!saved) {
|
if (!saved) {
|
||||||
// // final oldAddress =
|
final oldAddress =
|
||||||
// // (await appController.loadStartConfig()).address;
|
(await appController.loadStartConfig()).address;
|
||||||
// // startCfg.update((val) async {
|
startCfg.update((val) async {
|
||||||
// // val!.address = oldAddress;
|
val!.address = oldAddress;
|
||||||
// // });
|
});
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
// ipController.addListener(updateAddress);
|
ipController.addListener(updateAddress);
|
||||||
// portController.addListener(updateAddress);
|
portController.addListener(updateAddress);
|
||||||
// items.addAll([
|
items.addAll([
|
||||||
// const Padding(padding: EdgeInsets.only(left: 20)),
|
const Padding(padding: EdgeInsets.only(left: 20)),
|
||||||
// Flexible(
|
Flexible(
|
||||||
// child: TextFormField(
|
child: TextFormField(
|
||||||
// controller: ipController,
|
controller: ipController,
|
||||||
// decoration: const InputDecoration(
|
decoration: const InputDecoration(
|
||||||
// labelText: 'IP',
|
labelText: 'IP',
|
||||||
// contentPadding: EdgeInsets.all(0.0),
|
contentPadding: EdgeInsets.all(0.0),
|
||||||
// ),
|
),
|
||||||
// keyboardType: TextInputType.number,
|
keyboardType: TextInputType.number,
|
||||||
// inputFormatters: [
|
inputFormatters: [
|
||||||
// FilteringTextInputFormatter.allow(RegExp('[0-9.]')),
|
FilteringTextInputFormatter.allow(RegExp('[0-9.]')),
|
||||||
// ],
|
],
|
||||||
// ),
|
),
|
||||||
// ),
|
),
|
||||||
// const Padding(padding: EdgeInsets.only(left: 10)),
|
const Padding(padding: EdgeInsets.only(left: 10)),
|
||||||
// Flexible(
|
Flexible(
|
||||||
// child: TextFormField(
|
child: TextFormField(
|
||||||
// controller: portController,
|
controller: portController,
|
||||||
// decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
// labelText: 'port'.tr,
|
labelText: 'port'.tr,
|
||||||
// contentPadding: const EdgeInsets.all(0.0),
|
contentPadding: const EdgeInsets.all(0.0),
|
||||||
// ),
|
),
|
||||||
// keyboardType: TextInputType.number,
|
keyboardType: TextInputType.number,
|
||||||
// inputFormatters: [
|
inputFormatters: [
|
||||||
// FilteringTextInputFormatter.digitsOnly,
|
FilteringTextInputFormatter.digitsOnly,
|
||||||
// NumericalRangeFormatter(min: 0, max: 65535),
|
NumericalRangeFormatter(min: 0, max: 65535),
|
||||||
// ],
|
],
|
||||||
// ),
|
),
|
||||||
// ),
|
),
|
||||||
// ]);
|
]);
|
||||||
// }
|
}
|
||||||
|
|
||||||
// return Form(
|
return Form(
|
||||||
// child: Row(
|
child: Row(
|
||||||
// children: items,
|
children: items,
|
||||||
// ),
|
),
|
||||||
// );
|
);
|
||||||
// },
|
},
|
||||||
// );
|
);
|
||||||
// final buildApiToken = _buildConfigItem('apiToken',
|
final buildApiToken = _buildConfigItem('apiToken',
|
||||||
// () => startCfg.value.apiToken.isEmpty ? 'notSet'.tr : 'set'.tr,
|
() => startCfg.value.apiToken.isEmpty ? 'notSet'.tr : 'set'.tr,
|
||||||
// (Key key) {
|
(Key key) {
|
||||||
// final apiTokenController =
|
final apiTokenController =
|
||||||
// TextEditingController(text: startCfg.value.apiToken);
|
TextEditingController(text: startCfg.value.apiToken);
|
||||||
// apiTokenController.addListener(() async {
|
apiTokenController.addListener(() async {
|
||||||
// if (apiTokenController.text != startCfg.value.apiToken) {
|
if (apiTokenController.text != startCfg.value.apiToken) {
|
||||||
// startCfg.value.apiToken = apiTokenController.text;
|
startCfg.value.apiToken = apiTokenController.text;
|
||||||
|
|
||||||
// await debounceSave(needRestart: true);
|
await debounceSave(needRestart: true);
|
||||||
// }
|
}
|
||||||
// });
|
});
|
||||||
// return TextField(
|
return TextField(
|
||||||
// key: key,
|
key: key,
|
||||||
// obscureText: true,
|
obscureText: true,
|
||||||
// controller: apiTokenController,
|
controller: apiTokenController,
|
||||||
// focusNode: FocusNode(),
|
focusNode: FocusNode(),
|
||||||
// );
|
);
|
||||||
// });
|
});
|
||||||
|
|
||||||
// advanced config log items start
|
// advanced config log items start
|
||||||
buildLogsDir() {
|
buildLogsDir() {
|
||||||
@@ -947,6 +980,7 @@ class SettingView extends GetView<SettingController> {
|
|||||||
buildBtTrackerSubscribeUrls(),
|
buildBtTrackerSubscribeUrls(),
|
||||||
buildBtTrackers(),
|
buildBtTrackers(),
|
||||||
buildBtSeedConfig(),
|
buildBtSeedConfig(),
|
||||||
|
buildBtDefaultClientConfig(),
|
||||||
]),
|
]),
|
||||||
)),
|
)),
|
||||||
Text('ui'.tr),
|
Text('ui'.tr),
|
||||||
@@ -993,16 +1027,16 @@ class SettingView extends GetView<SettingController> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
children: _addDivider([buildProxy()]),
|
children: _addDivider([buildProxy()]),
|
||||||
)),
|
)),
|
||||||
// const Text('API'),
|
const Text('API'),
|
||||||
// Card(
|
Card(
|
||||||
// child: Column(
|
child: Column(
|
||||||
// children: _addDivider([
|
children: _addDivider([
|
||||||
// buildApiProtocol(),
|
buildApiProtocol(),
|
||||||
// Util.isDesktop() && startCfg.value.host == 'tcp'
|
Util.isDesktop() && startCfg.value.network == 'tcp'
|
||||||
// ? buildApiToken()
|
? buildApiToken()
|
||||||
// : null,
|
: null,
|
||||||
// ]),
|
]),
|
||||||
// )),
|
)),
|
||||||
Text('developer'.tr),
|
Text('developer'.tr),
|
||||||
Card(
|
Card(
|
||||||
child: Column(
|
child: Column(
|
||||||
|
|||||||
Loaded 100 of 169 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user