mirror of
https://github.com/GopeedLab/gopeed.git
synced 2026-11-11 10:54:08 +00:00
Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bfad91dcab | ||
|
|
61a959adda | ||
|
|
f4b839fd11 | ||
|
|
969aeb5e0a | ||
|
|
68d42eb397 |
No files matched your search
+34
-22
@@ -43,22 +43,22 @@ template: |
|
|||||||
<td rowspan="2">🪟 Windows</td>
|
<td rowspan="2">🪟 Windows</td>
|
||||||
<td><code>EXE</code></td>
|
<td><code>EXE</code></td>
|
||||||
<td>amd64</td>
|
<td>amd64</td>
|
||||||
<td><a href="https://github.com/GopeedLab/gopeed/releases/download/v$NEXT_PATCH_VERSION/Gopeed-v$NEXT_PATCH_VERSION-windows-amd64.zip">📥</a></td>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-v$NEXT_PATCH_VERSION-windows-amd64.zip">📥</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><code>Portable</code></td>
|
<td><code>Portable</code></td>
|
||||||
<td>amd64</td>
|
<td>amd64</td>
|
||||||
<td><a href="https://github.com/GopeedLab/gopeed/releases/download/v$NEXT_PATCH_VERSION/Gopeed-v$NEXT_PATCH_VERSION-windows-amd64-portable.zip">📥</a></td>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-v$NEXT_PATCH_VERSION-windows-amd64-portable.zip">📥</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>🍎 MacOS</td>
|
<td>🍎 MacOS</td>
|
||||||
<td><code>DMG</code></td>
|
<td><code>DMG</code></td>
|
||||||
<td>universal</td>
|
<td>universal</td>
|
||||||
<td><a href="https://github.com/GopeedLab/gopeed/releases/download/v$NEXT_PATCH_VERSION/Gopeed-v$NEXT_PATCH_VERSION-macos.dmg">📥</a></td>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-v$NEXT_PATCH_VERSION-macos.dmg">📥</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td rowspan="6">🐧 Linux</td>
|
<td rowspan="6">🐧 Linux</td>
|
||||||
<td><code>Flathub</code></td>
|
<td><code>Flathub</code></td
|
||||||
<td>amd64</td>
|
<td>amd64</td>
|
||||||
<td><a href="https://flathub.org/apps/com.gopeed.Gopeed">📥</a></td>
|
<td><a href="https://flathub.org/apps/com.gopeed.Gopeed">📥</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -70,32 +70,44 @@ template: |
|
|||||||
<tr>
|
<tr>
|
||||||
<td rowspan="2"><code>DEB</code></td>
|
<td rowspan="2"><code>DEB</code></td>
|
||||||
<td>amd64</td>
|
<td>amd64</td>
|
||||||
<td><a href="https://github.com/GopeedLab/gopeed/releases/download/v$NEXT_PATCH_VERSION/Gopeed-v$NEXT_PATCH_VERSION-linux-amd64.deb">📥</a></td>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-v$NEXT_PATCH_VERSION-linux-amd64.deb">📥</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>arm64</td>
|
<td>arm64</td>
|
||||||
<td><a href="https://github.com/GopeedLab/gopeed/releases/download/v$NEXT_PATCH_VERSION/Gopeed-v$NEXT_PATCH_VERSION-linux-arm64.deb">📥</a></td>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-v$NEXT_PATCH_VERSION-linux-arm64.deb">📥</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td rowspan="2"><code>AppImage</code></td>
|
<td rowspan="2"><code>AppImage</code></td>
|
||||||
<td>amd64</td>
|
<td>amd64</td>
|
||||||
<td><a href="https://github.com/GopeedLab/gopeed/releases/download/v$NEXT_PATCH_VERSION/Gopeed-v$NEXT_PATCH_VERSION-linux-amd64.AppImage">📥</a></td>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-v$NEXT_PATCH_VERSION-linux-amd64.AppImage">📥</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>arm64</td>
|
<td>arm64</td>
|
||||||
<td><a href="https://github.com/GopeedLab/gopeed/releases/download/v$NEXT_PATCH_VERSION/Gopeed-v$NEXT_PATCH_VERSION-linux-arm64.AppImage">📥</a></td>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-v$NEXT_PATCH_VERSION-linux-arm64.AppImage">📥</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>🤖 Android</td>
|
<td rowspan="4">🤖 Android</td>
|
||||||
<td><code>APK</code></td>
|
<td rowspan="4"><code>APK</code></td>
|
||||||
<td>universal</td>
|
<td>universal</td>
|
||||||
<td><a href="https://github.com/GopeedLab/gopeed/releases/download/v$NEXT_PATCH_VERSION/Gopeed-v$NEXT_PATCH_VERSION-android.apk">📥</a></td>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-v$NEXT_PATCH_VERSION-android.apk">📥</a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>armeabi-v7a</td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-v$NEXT_PATCH_VERSION-android-armeabi-v7a.apk">📥</a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>arm64-v8a</td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-v$NEXT_PATCH_VERSION-android-arm64-v8a.apk">📥</a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>x86_64</td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-v$NEXT_PATCH_VERSION-android-x86_64.apk">📥</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>📱 iOS</td>
|
<td>📱 iOS</td>
|
||||||
<td><code>IPA</code></td>
|
<td><code>IPA</code></td>
|
||||||
<td>universal</td>
|
<td>universal</td>
|
||||||
<td><a href="https://github.com/GopeedLab/gopeed/releases/download/v$NEXT_PATCH_VERSION/Gopeed-v$NEXT_PATCH_VERSION-ios.ipa">📥</a></td>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-v$NEXT_PATCH_VERSION-ios.ipa">📥</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>🐳 Docker</td>
|
<td>🐳 Docker</td>
|
||||||
@@ -107,47 +119,47 @@ template: |
|
|||||||
<td rowspan="2">💾 Qnap</td>
|
<td rowspan="2">💾 Qnap</td>
|
||||||
<td rowspan="2"><code>QPKG</code></td>
|
<td rowspan="2"><code>QPKG</code></td>
|
||||||
<td>amd64</td>
|
<td>amd64</td>
|
||||||
<td><a href="https://github.com/GopeedLab/gopeed/releases/download/v$NEXT_PATCH_VERSION/gopeed-v$NEXT_PATCH_VERSION-qnap-amd64.qpkg">📥</a></td>
|
<td><a href="https://gopeed.com/api/download?tpl=gopeed-v$NEXT_PATCH_VERSION-qnap-amd64.qpkg">📥</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>arm64</td>
|
<td>arm64</td>
|
||||||
<td><a href="https://github.com/GopeedLab/gopeed/releases/download/v$NEXT_PATCH_VERSION/gopeed-v$NEXT_PATCH_VERSION-qnap-arm64.qpkg">📥</a></td>
|
<td><a href="https://gopeed.com/api/download?tpl=gopeed-v$NEXT_PATCH_VERSION-qnap-arm64.qpkg">📥</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td rowspan="8">🌐 Web</td>
|
<td rowspan="8">🌐 Web</td>
|
||||||
<td rowspan="3"><code>Windows</code></td>
|
<td rowspan="3"><code>Windows</code></td>
|
||||||
<td>amd64</td>
|
<td>amd64</td>
|
||||||
<td><a href="https://github.com/GopeedLab/gopeed/releases/download/v$NEXT_PATCH_VERSION/gopeed-web-v$NEXT_PATCH_VERSION-windows-amd64.zip">📥</a></td>
|
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-v$NEXT_PATCH_VERSION-windows-amd64.zip">📥</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>arm64</td>
|
<td>arm64</td>
|
||||||
<td><a href="https://github.com/GopeedLab/gopeed/releases/download/v$NEXT_PATCH_VERSION/gopeed-web-v$NEXT_PATCH_VERSION-windows-arm64.zip">📥</a></td>
|
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-v$NEXT_PATCH_VERSION-windows-arm64.zip">📥</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>386</td>
|
<td>386</td>
|
||||||
<td><a href="https://github.com/GopeedLab/gopeed/releases/download/v$NEXT_PATCH_VERSION/gopeed-web-v$NEXT_PATCH_VERSION-windows-386.zip">📥</a></td>
|
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-v$NEXT_PATCH_VERSION-windows-386.zip">📥</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td rowspan="2"><code>MacOS</code></td>
|
<td rowspan="2"><code>MacOS</code></td>
|
||||||
<td>amd64</td>
|
<td>amd64</td>
|
||||||
<td><a href="https://github.com/GopeedLab/gopeed/releases/download/v$NEXT_PATCH_VERSION/gopeed-web-v$NEXT_PATCH_VERSION-macos-amd64.zip">📥</a></td>
|
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-v$NEXT_PATCH_VERSION-macos-amd64.zip">📥</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>arm64</td>
|
<td>arm64</td>
|
||||||
<td><a href="https://github.com/GopeedLab/gopeed/releases/download/v$NEXT_PATCH_VERSION/gopeed-web-v$NEXT_PATCH_VERSION-macos-arm64.zip">📥</a></td>
|
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-v$NEXT_PATCH_VERSION-macos-arm64.zip">📥</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td rowspan="3"><code>Linux</code></td>
|
<td rowspan="3"><code>Linux</code></td>
|
||||||
<td>amd64</td>
|
<td>amd64</td>
|
||||||
<td><a href="https://github.com/GopeedLab/gopeed/releases/download/v$NEXT_PATCH_VERSION/gopeed-web-v$NEXT_PATCH_VERSION-linux-amd64.zip">📥</a></td>
|
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-v$NEXT_PATCH_VERSION-linux-amd64.zip">📥</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>arm64</td>
|
<td>arm64</td>
|
||||||
<td><a href="https://github.com/GopeedLab/gopeed/releases/download/v$NEXT_PATCH_VERSION/gopeed-web-v$NEXT_PATCH_VERSION-linux-arm64.zip">📥</a></td>
|
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-v$NEXT_PATCH_VERSION-linux-arm64.zip">📥</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>386</td>
|
<td>386</td>
|
||||||
<td><a href="https://github.com/GopeedLab/gopeed/releases/download/v$NEXT_PATCH_VERSION/gopeed-web-v$NEXT_PATCH_VERSION-linux-386.zip">📥</a></td>
|
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-v$NEXT_PATCH_VERSION-linux-386.zip">📥</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@@ -236,7 +236,7 @@ jobs:
|
|||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ ubuntu-22.04, ubuntu-22.04-arm ]
|
os: [ubuntu-22.04, ubuntu-22.04-arm]
|
||||||
needs: [get-release]
|
needs: [get-release]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
@@ -270,7 +270,7 @@ jobs:
|
|||||||
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/exec/host github.com/GopeedLab/gopeed/cmd/host
|
go build -ldflags="-w -s" -o ui/flutter/assets/exec/host github.com/GopeedLab/gopeed/cmd/host
|
||||||
go build -ldflags="-w -s" -o ui/flutter/assets/exec/updater github.com/GopeedLab/gopeed/cmd/updater
|
go build -ldflags="-w -s" -o ui/flutter/assets/exec/updater github.com/GopeedLab/gopeed/cmd/updater
|
||||||
|
|
||||||
cd ui/flutter
|
cd ui/flutter
|
||||||
cat << EOF > distribute_options.yaml
|
cat << EOF > distribute_options.yaml
|
||||||
output: dist/
|
output: dist/
|
||||||
@@ -295,13 +295,13 @@ jobs:
|
|||||||
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 release --name linux
|
flutter_distributor release --name linux
|
||||||
cd dist/*
|
cd dist/*
|
||||||
|
|
||||||
# Get current running architecture, x86_64 -> amd64 and aarch64 -> arm64
|
# Get current running architecture, x86_64 -> amd64 and aarch64 -> arm64
|
||||||
ARCH="amd64"
|
ARCH="amd64"
|
||||||
if [[ "${{ matrix.os }}" == *-arm ]]; then
|
if [[ "${{ matrix.os }}" == *-arm ]]; then
|
||||||
ARCH="arm64"
|
ARCH="arm64"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
mv gopeed-*-linux.AppImage Gopeed-v${VERSION}-linux-${ARCH}.AppImage
|
mv gopeed-*-linux.AppImage Gopeed-v${VERSION}-linux-${ARCH}.AppImage
|
||||||
mv gopeed-*-linux.deb Gopeed-v${VERSION}-linux-${ARCH}.deb
|
mv gopeed-*-linux.deb Gopeed-v${VERSION}-linux-${ARCH}.deb
|
||||||
- name: Upload
|
- name: Upload
|
||||||
@@ -472,8 +472,12 @@ jobs:
|
|||||||
cd ui/flutter
|
cd ui/flutter
|
||||||
echo $APK_KEYSTORE | base64 -di > android/app/upload-keystore.jks
|
echo $APK_KEYSTORE | base64 -di > android/app/upload-keystore.jks
|
||||||
flutter build apk --dart-define="UPDATE_CHANNEL=androidApk"
|
flutter build apk --dart-define="UPDATE_CHANNEL=androidApk"
|
||||||
|
flutter build apk --split-per-abi --dart-define="UPDATE_CHANNEL=androidApk"
|
||||||
mkdir dist
|
mkdir dist
|
||||||
cp build/app/outputs/flutter-apk/app-release.apk dist/Gopeed-v$VERSION-android.apk
|
cp build/app/outputs/flutter-apk/app-release.apk dist/Gopeed-v$VERSION-android.apk
|
||||||
|
cp build/app/outputs/flutter-apk/app-armeabi-v7a-release.apk dist/Gopeed-v$VERSION-android-armeabi-v7a.apk
|
||||||
|
cp build/app/outputs/flutter-apk/app-arm64-v8a-release.apk dist/Gopeed-v$VERSION-android-arm64-v8a.apk
|
||||||
|
cp build/app/outputs/flutter-apk/app-x86_64-release.apk dist/Gopeed-v$VERSION-android-x86_64.apk
|
||||||
- name: Upload
|
- name: Upload
|
||||||
uses: shogo82148/actions-upload-release-asset@v1
|
uses: shogo82148/actions-upload-release-asset@v1
|
||||||
with:
|
with:
|
||||||
@@ -529,7 +533,6 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
VERSION: ${{ needs.get-release.outputs.version }}
|
VERSION: ${{ needs.get-release.outputs.version }}
|
||||||
run: |
|
run: |
|
||||||
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 --no-web-resources-cdn
|
flutter build web --no-web-resources-cdn
|
||||||
cd ../../
|
cd ../../
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
go-version: ${{ env.GO_VERSION }}
|
go-version: ${{ env.GO_VERSION }}
|
||||||
- name: Run Unit tests.
|
- name: Run Unit tests.
|
||||||
run: go test $(go list ./... | grep -v /cmd/ | grep -v /bind/ ) -v -covermode=count -coverprofile=coverage.txt
|
run: go test $(go list ./... | grep -v /cmd/ | grep -v /bind/) -v -covermode=count -coverprofile=coverage.txt
|
||||||
- uses: codecov/codecov-action@v4
|
- uses: codecov/codecov-action@v4
|
||||||
with:
|
with:
|
||||||
files: ./coverage.txt
|
files: ./coverage.txt
|
||||||
|
|||||||
@@ -70,10 +70,22 @@ Visit ✈ [Official Website](https://gopeed.com) | 📖 [Official Docs](https://
|
|||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-arm64.AppImage">📥</a></td>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-arm64.AppImage">📥</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>🤖 Android</td>
|
<td rowspan="4">🤖 Android</td>
|
||||||
<td><code>APK</code></td>
|
<td rowspan="4"><code>APK</code></td>
|
||||||
<td>universal</td>
|
<td>universal</td>
|
||||||
<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-android.apk">📥</a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>armeabi-v7a</td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android-armeabi-v7a.apk">📥</a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>arm64-v8a</td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android-arm64-v8a.apk">📥</a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>x86_64</td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android-x86_64.apk">📥</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>📱 iOS</td>
|
<td>📱 iOS</td>
|
||||||
|
|||||||
+14
-3
@@ -70,10 +70,22 @@ Gopeed (正式名 Go Speed) は `Golang` + `Flutter` によって開発された
|
|||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-arm64.AppImage">📥</a></td>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-arm64.AppImage">📥</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>🤖 Android</td>
|
<td rowspan="4">🤖 Android</td>
|
||||||
<td><code>APK</code></td>
|
<td rowspan="4"><code>APK</code></td>
|
||||||
<td>universal</td>
|
<td>universal</td>
|
||||||
<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-android.apk">📥</a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>armeabi-v7a</td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android-armeabi-v7a.apk">📥</a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>arm64-v8a</td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android-arm64-v8a.apk">📥</a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>x86_64</td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android-x86_64.apk">📥</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>📱 iOS</td>
|
<td>📱 iOS</td>
|
||||||
@@ -135,7 +147,6 @@ Gopeed (正式名 Go Speed) は `Golang` + `Flutter` によって開発された
|
|||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
インストールについての詳細は、[インストール](https://docs.gopeed.com/install.html)を参照してください。
|
インストールについての詳細は、[インストール](https://docs.gopeed.com/install.html)を参照してください。
|
||||||
|
|
||||||
### 🛠️ コマンドツール
|
### 🛠️ コマンドツール
|
||||||
|
|||||||
+14
-3
@@ -70,10 +70,22 @@ Truy cập ✈ [Trang web chính thức](https://gopeed.com) | 📖 [Tài liệu
|
|||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-arm64.AppImage">📥</a></td>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-arm64.AppImage">📥</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>🤖 Android</td>
|
<td rowspan="4">🤖 Android</td>
|
||||||
<td><code>APK</code></td>
|
<td rowspan="4"><code>APK</code></td>
|
||||||
<td>universal</td>
|
<td>universal</td>
|
||||||
<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-android.apk">📥</a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>armeabi-v7a</td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android-armeabi-v7a.apk">📥</a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>arm64-v8a</td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android-arm64-v8a.apk">📥</a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>x86_64</td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android-x86_64.apk">📥</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>📱 iOS</td>
|
<td>📱 iOS</td>
|
||||||
@@ -135,7 +147,6 @@ Truy cập ✈ [Trang web chính thức](https://gopeed.com) | 📖 [Tài liệu
|
|||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</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)
|
||||||
|
|
||||||
### 🛠️ Công cụ lệnh
|
### 🛠️ Công cụ lệnh
|
||||||
|
|||||||
+14
-3
@@ -70,10 +70,22 @@ Gopeed(全称 Go Speed),直译过来中文名叫做`够快下载器`(不
|
|||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-arm64.AppImage">📥</a></td>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-arm64.AppImage">📥</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>🤖 Android</td>
|
<td rowspan="4">🤖 Android</td>
|
||||||
<td><code>APK</code></td>
|
<td rowspan="4"><code>APK</code></td>
|
||||||
<td>universal</td>
|
<td>universal</td>
|
||||||
<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-android.apk">📥</a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>armeabi-v7a</td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android-armeabi-v7a.apk">📥</a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>arm64-v8a</td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android-arm64-v8a.apk">📥</a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>x86_64</td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android-x86_64.apk">📥</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>📱 iOS</td>
|
<td>📱 iOS</td>
|
||||||
@@ -135,7 +147,6 @@ Gopeed(全称 Go Speed),直译过来中文名叫做`够快下载器`(不
|
|||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
更多关于安装的内容请参考[安装文档](https://docs.gopeed.com/zh/install.html)
|
更多关于安装的内容请参考[安装文档](https://docs.gopeed.com/zh/install.html)
|
||||||
|
|
||||||
### 🛠️ 命令行工具
|
### 🛠️ 命令行工具
|
||||||
|
|||||||
+14
-2
@@ -70,10 +70,22 @@ Gopeed(全稱 Go Speed),是一款使用`Golang`+`Flutter`編寫的高速
|
|||||||
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-arm64.AppImage">📥</a></td>
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-arm64.AppImage">📥</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>🤖 Android</td>
|
<td rowspan="4">🤖 Android</td>
|
||||||
<td><code>APK</code></td>
|
<td rowspan="4"><code>APK</code></td>
|
||||||
<td>universal</td>
|
<td>universal</td>
|
||||||
<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-android.apk">📥</a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>armeabi-v7a</td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android-armeabi-v7a.apk">📥</a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>arm64-v8a</td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android-arm64-v8a.apk">📥</a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>x86_64</td>
|
||||||
|
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android-x86_64.apk">📥</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>📱 iOS</td>
|
<td>📱 iOS</td>
|
||||||
|
|||||||
@@ -51,6 +51,11 @@ type chunk struct {
|
|||||||
retryTimes int
|
retryTimes int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// get remain to download bytes
|
||||||
|
func (c *chunk) remain() int64 {
|
||||||
|
return c.End - c.Begin + 1 - c.Downloaded
|
||||||
|
}
|
||||||
|
|
||||||
func newChunk(begin int64, end int64) *chunk {
|
func newChunk(begin int64, end int64) *chunk {
|
||||||
return &chunk{
|
return &chunk{
|
||||||
Begin: begin,
|
Begin: begin,
|
||||||
@@ -319,7 +324,7 @@ func (f *Fetcher) fetchChunk(index int, ctx context.Context) (err error) {
|
|||||||
// retry until all remain chunks failed
|
// retry until all remain chunks failed
|
||||||
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.remain() <= 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
// if all chunks failed, return
|
// if all chunks failed, return
|
||||||
@@ -364,15 +369,30 @@ 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
|
||||||
}
|
}
|
||||||
|
chunk.failed = false
|
||||||
reader := NewTimeoutReader(resp.Body, readTimeout)
|
reader := NewTimeoutReader(resp.Body, readTimeout)
|
||||||
for {
|
for {
|
||||||
n, err := reader.Read(buf)
|
n, err := reader.Read(buf)
|
||||||
if n > 0 {
|
if n > 0 {
|
||||||
|
finished := false
|
||||||
|
if f.meta.Res.Range {
|
||||||
|
remain := chunk.remain()
|
||||||
|
// If downloaded bytes exceed the remain bytes, only write remain bytes
|
||||||
|
if remain < int64(n) {
|
||||||
|
n = int(remain)
|
||||||
|
finished = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_, 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 {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
chunk.Downloaded += int64(n)
|
chunk.Downloaded += int64(n)
|
||||||
|
|
||||||
|
if finished {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
|
|||||||
@@ -176,6 +176,14 @@ func TestFetcher_DownloadResponseBodyReadTimeout(t *testing.T) {
|
|||||||
downloadError(listener, 4, t)
|
downloadError(listener, 4, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFetcher_DownloadOnBugFileServer(t *testing.T) {
|
||||||
|
listener := test.StartTestRangeBugServer()
|
||||||
|
defer listener.Close()
|
||||||
|
|
||||||
|
downloadNormal(listener, 1, t)
|
||||||
|
downloadNormal(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()
|
||||||
|
|||||||
+87
-57
@@ -180,68 +180,98 @@ func StartTestLimitServer(maxConnections int32, delay int64) net.Listener {
|
|||||||
writer.WriteHeader(403)
|
writer.WriteHeader(403)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
rangeFileHandle(
|
||||||
r := request.Header.Get("Range")
|
writer,
|
||||||
if r == "" {
|
request,
|
||||||
writer.Header().Set("Content-Length", fmt.Sprintf("%d", BuildSize))
|
nil,
|
||||||
writer.WriteHeader(200)
|
func(file *os.File, n int64) {
|
||||||
(writer.(http.Flusher)).Flush()
|
slowCopyN(sl, writer, file, n, delay)
|
||||||
|
},
|
||||||
file, err := os.Open(BuildFile)
|
)
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
slowCopy(sl, writer, file, delay)
|
|
||||||
} else {
|
|
||||||
// split range
|
|
||||||
s := strings.Split(r, "=")
|
|
||||||
if len(s) != 2 {
|
|
||||||
writer.WriteHeader(400)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
s = strings.Split(s[1], "-")
|
|
||||||
if len(s) != 2 {
|
|
||||||
writer.WriteHeader(400)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
start, err := strconv.ParseInt(s[0], 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
writer.WriteHeader(400)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
end, err := strconv.ParseInt(s[1], 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
writer.WriteHeader(400)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if start < 0 || end < 0 || start > end {
|
|
||||||
writer.WriteHeader(400)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if end >= BuildSize {
|
|
||||||
end = BuildSize - 1
|
|
||||||
}
|
|
||||||
writer.Header().Set("Content-Length", fmt.Sprintf("%d", end-start+1))
|
|
||||||
writer.Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", start, end, BuildSize))
|
|
||||||
writer.Header().Set("Accept-Ranges", "bytes")
|
|
||||||
writer.WriteHeader(206)
|
|
||||||
(writer.(http.Flusher)).Flush()
|
|
||||||
|
|
||||||
file, err := os.Open(BuildFile)
|
|
||||||
if err != nil {
|
|
||||||
writer.WriteHeader(500)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
file.Seek(start, 0)
|
|
||||||
slowCopyN(sl, writer, file, end-start+1, delay)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
return mux
|
return mux
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StartTestRangeBugServer simulate bug server:
|
||||||
|
// Don't follow Range request rules, always return more data than range, e.g. Range: bytes=0-100, return 150 bytes
|
||||||
|
func StartTestRangeBugServer() net.Listener {
|
||||||
|
return startTestServer(func(sl *shutdownListener) http.Handler {
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
mux.HandleFunc("/"+BuildName, func(writer http.ResponseWriter, request *http.Request) {
|
||||||
|
rangeFileHandle(
|
||||||
|
writer,
|
||||||
|
request,
|
||||||
|
func(end int64) int64 {
|
||||||
|
var bugEnd = end
|
||||||
|
if end != 0 {
|
||||||
|
bugEnd = end + 50
|
||||||
|
if bugEnd >= BuildSize {
|
||||||
|
bugEnd = end
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bugEnd
|
||||||
|
},
|
||||||
|
func(file *os.File, n int64) {
|
||||||
|
io.CopyN(writer, file, n)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
return mux
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func rangeFileHandle(writer http.ResponseWriter, request *http.Request, modifyEnd func(end int64) int64, iocpN func(file *os.File, n int64)) {
|
||||||
|
r := request.Header.Get("Range")
|
||||||
|
// split range
|
||||||
|
s := strings.Split(r, "=")
|
||||||
|
if len(s) != 2 {
|
||||||
|
writer.WriteHeader(400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s = strings.Split(s[1], "-")
|
||||||
|
if len(s) != 2 {
|
||||||
|
writer.WriteHeader(400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
start, err := strconv.ParseInt(s[0], 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
writer.WriteHeader(400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
end, err := strconv.ParseInt(s[1], 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
writer.WriteHeader(400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if start < 0 || end < 0 || start > end {
|
||||||
|
writer.WriteHeader(400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if end >= BuildSize {
|
||||||
|
end = BuildSize - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if modifyEnd != nil {
|
||||||
|
end = modifyEnd(end)
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.Header().Set("Content-Length", fmt.Sprintf("%d", end-start+1))
|
||||||
|
writer.Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", start, end, BuildSize))
|
||||||
|
writer.Header().Set("Accept-Ranges", "bytes")
|
||||||
|
writer.WriteHeader(206)
|
||||||
|
(writer.(http.Flusher)).Flush()
|
||||||
|
|
||||||
|
file, err := os.Open(BuildFile)
|
||||||
|
if err != nil {
|
||||||
|
writer.WriteHeader(500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
file.Seek(start, 0)
|
||||||
|
iocpN(file, end-start+1)
|
||||||
|
}
|
||||||
|
|
||||||
// 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(sl *shutdownListener, 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)
|
||||||
|
|||||||
@@ -208,7 +208,7 @@ func (d *Downloader) Setup() error {
|
|||||||
if task.Status == base.DownloadStatusRunning {
|
if task.Status == base.DownloadStatusRunning {
|
||||||
downloadDataChanged = current != task.Progress.Downloaded
|
downloadDataChanged = current != task.Progress.Downloaded
|
||||||
task.Progress.Used = task.timer.Used()
|
task.Progress.Used = task.timer.Used()
|
||||||
task.Progress.Speed = task.calcSpeed(task.speedArr, current-task.Progress.Downloaded, tick)
|
task.Progress.Speed = task.updateSpeed(current-task.Progress.Downloaded, tick)
|
||||||
task.Progress.Downloaded = current
|
task.Progress.Downloaded = current
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,7 +217,7 @@ func (d *Downloader) Setup() error {
|
|||||||
uploader := task.fetcher.(fetcher.Uploader)
|
uploader := task.fetcher.(fetcher.Uploader)
|
||||||
currentUploaded := uploader.UploadedBytes()
|
currentUploaded := uploader.UploadedBytes()
|
||||||
uploadDataChanged = currentUploaded != task.Progress.Uploaded
|
uploadDataChanged = currentUploaded != task.Progress.Uploaded
|
||||||
task.Progress.UploadSpeed = task.calcSpeed(task.uploadSpeedArr, currentUploaded-task.Progress.Uploaded, tick)
|
task.Progress.UploadSpeed = task.updateUploadSpeed(currentUploaded-task.Progress.Uploaded, tick)
|
||||||
task.Progress.Uploaded = currentUploaded
|
task.Progress.Uploaded = currentUploaded
|
||||||
}
|
}
|
||||||
d.emit(EventKeyProgress, task)
|
d.emit(EventKeyProgress, task)
|
||||||
|
|||||||
+17
-6
@@ -100,16 +100,27 @@ func (t *Task) clone() *Task {
|
|||||||
return util.DeepClone(t)
|
return util.DeepClone(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Task) calcSpeed(speedArr []int64, downloaded int64, usedTime float64) int64 {
|
func (t *Task) updateSpeed(downloaded int64, usedTime float64) int64 {
|
||||||
speedArr = append(speedArr, downloaded)
|
return calcSpeed(&t.speedArr, downloaded, usedTime)
|
||||||
if len(speedArr) > 6 {
|
}
|
||||||
speedArr = speedArr[1:]
|
|
||||||
|
func (t *Task) updateUploadSpeed(downloaded int64, usedTime float64) int64 {
|
||||||
|
return calcSpeed(&t.uploadSpeedArr, downloaded, usedTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
func calcSpeed(speedArr *[]int64, downloaded int64, usedTime float64) int64 {
|
||||||
|
*speedArr = append(*speedArr, downloaded)
|
||||||
|
// Record last 5 seconds of download speed to calculate the average speed
|
||||||
|
if len(*speedArr) > int(5.0/usedTime) {
|
||||||
|
*speedArr = (*speedArr)[1:]
|
||||||
}
|
}
|
||||||
|
|
||||||
var total int64
|
var total int64
|
||||||
for _, v := range speedArr {
|
for _, v := range *speedArr {
|
||||||
total += v
|
total += v
|
||||||
}
|
}
|
||||||
return int64(float64(total) / float64(len(speedArr)) / usedTime)
|
|
||||||
|
return int64(float64(total) / float64(len(*speedArr)) / usedTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
type TaskFilter struct {
|
type TaskFilter struct {
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package rest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"compress/gzip"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type gzipResponseWriter struct {
|
||||||
|
io.Writer
|
||||||
|
http.ResponseWriter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g gzipResponseWriter) Write(b []byte) (int, error) {
|
||||||
|
return g.Writer.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func gzipMiddleware(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Encoding", "gzip")
|
||||||
|
w.Header().Add("Vary", "Accept-Encoding")
|
||||||
|
|
||||||
|
gz := gzip.NewWriter(w)
|
||||||
|
defer gz.Close()
|
||||||
|
|
||||||
|
next.ServeHTTP(gzipResponseWriter{Writer: gz, ResponseWriter: w}, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
+81
-5
@@ -10,12 +10,14 @@ import (
|
|||||||
"github.com/gorilla/handlers"
|
"github.com/gorilla/handlers"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
"io/fs"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -126,16 +128,28 @@ func BuildServer(startCfg *model.StartConfig) (*http.Server, net.Listener, error
|
|||||||
if startCfg.WebEnable {
|
if startCfg.WebEnable {
|
||||||
r.PathPrefix("/fs/tasks").Handler(http.FileServer(new(taskFileSystem)))
|
r.PathPrefix("/fs/tasks").Handler(http.FileServer(new(taskFileSystem)))
|
||||||
r.PathPrefix("/fs/extensions").Handler(http.FileServer(new(extensionFileSystem)))
|
r.PathPrefix("/fs/extensions").Handler(http.FileServer(new(extensionFileSystem)))
|
||||||
r.PathPrefix("/").Handler(http.FileServer(http.FS(startCfg.WebFS)))
|
r.PathPrefix("/").Handler(gzipMiddleware(http.FileServer(newEmbedCacheFileSystem(http.FS(startCfg.WebFS)))))
|
||||||
}
|
}
|
||||||
|
|
||||||
if startCfg.ApiToken != "" || (startCfg.WebEnable && startCfg.WebBasicAuth != nil) {
|
enableApiToken := startCfg.ApiToken != ""
|
||||||
|
enableBasicAuth := startCfg.WebEnable && startCfg.WebBasicAuth != nil
|
||||||
|
if enableApiToken || enableBasicAuth {
|
||||||
r.Use(func(h http.Handler) http.Handler {
|
r.Use(func(h http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if startCfg.ApiToken != "" && r.Header.Get("X-Api-Token") == startCfg.ApiToken {
|
if enableApiToken {
|
||||||
h.ServeHTTP(w, r)
|
apiTokenHeader := r.Header["X-Api-Token"]
|
||||||
return
|
// If api token header is set, only check api token ignore basic auth
|
||||||
|
if len(apiTokenHeader) > 0 {
|
||||||
|
if apiTokenHeader[0] == startCfg.ApiToken {
|
||||||
|
h.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteStatusJson(w, http.StatusUnauthorized, model.NewErrorResult("unauthorized", model.CodeUnauthorized))
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if startCfg.WebEnable && startCfg.WebBasicAuth != nil {
|
if startCfg.WebEnable && startCfg.WebBasicAuth != nil {
|
||||||
if r.Header.Get("Authorization") == startCfg.WebBasicAuth.Authorization() {
|
if r.Header.Get("Authorization") == startCfg.WebBasicAuth.Authorization() {
|
||||||
h.ServeHTTP(w, r)
|
h.ServeHTTP(w, r)
|
||||||
@@ -222,6 +236,68 @@ func (e *extensionFileSystem) Open(name string) (http.File, error) {
|
|||||||
return os.Open(filepath.Join(extensionPath, path))
|
return os.Open(filepath.Join(extensionPath, path))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type embedCacheFileSystem struct {
|
||||||
|
fs http.FileSystem
|
||||||
|
lastModTime time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func newEmbedCacheFileSystem(fs http.FileSystem) *embedCacheFileSystem {
|
||||||
|
efs := &embedCacheFileSystem{
|
||||||
|
fs: fs,
|
||||||
|
lastModTime: time.Now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
exe, err := os.Executable()
|
||||||
|
if err != nil {
|
||||||
|
return efs
|
||||||
|
}
|
||||||
|
|
||||||
|
fi, err := os.Stat(exe)
|
||||||
|
if err != nil {
|
||||||
|
return efs
|
||||||
|
}
|
||||||
|
|
||||||
|
efs.lastModTime = fi.ModTime()
|
||||||
|
return efs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *embedCacheFileSystem) Open(name string) (http.File, error) {
|
||||||
|
file, err := e.fs.Open(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &embedFile{
|
||||||
|
File: file,
|
||||||
|
lastModTime: e.lastModTime,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type embedFile struct {
|
||||||
|
http.File
|
||||||
|
lastModTime time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
type embedFileInfo struct {
|
||||||
|
fs.FileInfo
|
||||||
|
lastModTime time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *embedFileInfo) ModTime() time.Time {
|
||||||
|
return e.lastModTime
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *embedFile) Stat() (fs.FileInfo, error) {
|
||||||
|
fi, err := e.File.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &embedFileInfo{
|
||||||
|
FileInfo: fi,
|
||||||
|
lastModTime: e.lastModTime,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func ReadJson(r *http.Request, w http.ResponseWriter, v any) bool {
|
func ReadJson(r *http.Request, w http.ResponseWriter, v any) bool {
|
||||||
if err := json.NewDecoder(r.Body).Decode(v); err != nil {
|
if err := json.NewDecoder(r.Body).Decode(v); err != nil {
|
||||||
WriteJson(w, model.NewErrorResult(err.Error()))
|
WriteJson(w, model.NewErrorResult(err.Error()))
|
||||||
|
|||||||
+96
-1
@@ -14,6 +14,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -478,6 +479,66 @@ func TestFsExtensionFail(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestWebFsEnhance(t *testing.T) {
|
||||||
|
indexHtml := `
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>index</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>index</h1>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`
|
||||||
|
webDistPath := "dist"
|
||||||
|
os.MkdirAll("dist", os.ModePerm)
|
||||||
|
if err := os.WriteFile(filepath.Join(webDistPath, "index.html"), []byte(indexHtml), os.ModePerm); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(webDistPath)
|
||||||
|
|
||||||
|
doTest0(func(cfg *model.StartConfig) {
|
||||||
|
cfg.WebFS = os.DirFS(webDistPath)
|
||||||
|
}, func() {
|
||||||
|
// First request no cache
|
||||||
|
code, header, _ := doHttpRequest1(http.MethodGet, "/index.html", map[string]string{
|
||||||
|
"Accept-Encoding": "gzip",
|
||||||
|
}, nil)
|
||||||
|
if code != http.StatusOK {
|
||||||
|
t.Errorf("TestWebFsEnhance() got = %v, want %v", code, http.StatusOK)
|
||||||
|
}
|
||||||
|
// Check header last-modified
|
||||||
|
if _, ok := header["Last-Modified"]; !ok {
|
||||||
|
t.Errorf("TestWebFsEnhance() missing key = %v", "Last-Modified")
|
||||||
|
}
|
||||||
|
// Check gzip compress
|
||||||
|
if _, ok := header["Content-Encoding"]; !ok || header["Content-Encoding"] != "gzip" {
|
||||||
|
t.Errorf("TestWebFsEnhance() no gzip compress")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request with If-Modified-Since
|
||||||
|
ifModifiedSince := header["Last-Modified"]
|
||||||
|
code, _, _ = doHttpRequest1(http.MethodGet, "/index.html", map[string]string{
|
||||||
|
"If-Modified-Since": ifModifiedSince,
|
||||||
|
}, nil)
|
||||||
|
if code != http.StatusNotModified {
|
||||||
|
t.Errorf("TestWebFsEnhance() got = %v, want %v", code, http.StatusNotModified)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request with un gzip
|
||||||
|
code, header, _ = doHttpRequest1(http.MethodGet, "/index.html?t=123", nil, nil)
|
||||||
|
if code != http.StatusOK {
|
||||||
|
t.Errorf("TestWebFsEnhance() got = %v, want %v", code, http.StatusOK)
|
||||||
|
}
|
||||||
|
// Check no gzip compress
|
||||||
|
if _, ok := header["Content-Encoding"]; ok && header["Content-Encoding"] == "gzip" {
|
||||||
|
t.Errorf("TestWebFsEnhance() has gzip compress")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestDoProxy(t *testing.T) {
|
func TestDoProxy(t *testing.T) {
|
||||||
doTest(func() {
|
doTest(func() {
|
||||||
code, respBody := doHttpRequest0(http.MethodGet, "/api/v1/proxy", map[string]string{
|
code, respBody := doHttpRequest0(http.MethodGet, "/api/v1/proxy", map[string]string{
|
||||||
@@ -564,15 +625,38 @@ func TestAuthorization(t *testing.T) {
|
|||||||
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
status, _ = doHttpRequest0(http.MethodGet, "/api/v1/config", map[string]string{
|
||||||
|
"Authorization": cfg.WebBasicAuth.Authorization(),
|
||||||
|
"X-Api-Token": cfg.ApiToken,
|
||||||
|
}, nil)
|
||||||
|
if status != http.StatusOK {
|
||||||
|
t.Errorf("TestAuthorization() got = %v, want %v", status, http.StatusOK)
|
||||||
|
}
|
||||||
|
|
||||||
|
status, _ = doHttpRequest0(http.MethodGet, "/api/v1/config", map[string]string{
|
||||||
|
"Authorization": cfg.WebBasicAuth.Authorization(),
|
||||||
|
"X-Api-Token": "",
|
||||||
|
}, nil)
|
||||||
|
if status != http.StatusUnauthorized {
|
||||||
|
t.Errorf("TestAuthorization() got = %v, want %v", status, http.StatusUnauthorized)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func doTest(handler func()) {
|
func doTest(handler func()) {
|
||||||
|
doTest0(nil, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func doTest0(onStart func(cfg *model.StartConfig), handler func()) {
|
||||||
testFunc := func(storage model.Storage) {
|
testFunc := func(storage model.Storage) {
|
||||||
var cfg = &model.StartConfig{}
|
var cfg = &model.StartConfig{}
|
||||||
cfg.Init()
|
cfg.Init()
|
||||||
cfg.Storage = storage
|
cfg.Storage = storage
|
||||||
cfg.StorageDir = ".test_storage"
|
cfg.StorageDir = ".test_storage"
|
||||||
cfg.WebEnable = true
|
cfg.WebEnable = true
|
||||||
|
if onStart != nil {
|
||||||
|
onStart(cfg)
|
||||||
|
}
|
||||||
fileListener := doStart(cfg)
|
fileListener := doStart(cfg)
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := fileListener.Close(); err != nil {
|
if err := fileListener.Close(); err != nil {
|
||||||
@@ -603,6 +687,11 @@ func doStart(cfg *model.StartConfig) net.Listener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
||||||
|
r1, _, r3 := doHttpRequest1(method, path, headers, body)
|
||||||
|
return r1, r3
|
||||||
|
}
|
||||||
|
|
||||||
|
func doHttpRequest1(method string, path string, headers map[string]string, body any) (int, map[string]string, []byte) {
|
||||||
var reader io.Reader
|
var reader io.Reader
|
||||||
if body != nil {
|
if body != nil {
|
||||||
buf, _ := json.Marshal(body)
|
buf, _ := json.Marshal(body)
|
||||||
@@ -623,12 +712,18 @@ func doHttpRequest0(method string, path string, headers map[string]string, body
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
defer response.Body.Close()
|
defer response.Body.Close()
|
||||||
|
|
||||||
|
respHeader := make(map[string]string)
|
||||||
|
for k, vv := range response.Header {
|
||||||
|
respHeader[k] = vv[0]
|
||||||
|
}
|
||||||
|
|
||||||
respBody, err := io.ReadAll(response.Body)
|
respBody, err := io.ReadAll(response.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.StatusCode, respBody
|
return response.StatusCode, respHeader, respBody
|
||||||
}
|
}
|
||||||
|
|
||||||
func doHttpRequest[T any](method string, path string, headers map[string]string, body any) (int, *model.Result[T]) {
|
func doHttpRequest[T any](method string, path string, headers map[string]string, body any) (int, *model.Result[T]) {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
|
|||||||
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
||||||
# Read more about iOS versioning at
|
# Read more about iOS versioning at
|
||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||||
version: 1.6.10+1
|
version: 1.6.11+1
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=3.0.0 <4.0.0"
|
sdk: ">=3.0.0 <4.0.0"
|
||||||
|
|||||||
Reference in new issue
Block a user