mirror of
https://github.com/GopeedLab/gopeed.git
synced 2026-11-13 11:53:41 +00:00
Compare commits
260
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1c2e86a991 | ||
|
|
c78dc5f18f | ||
|
|
8ea5abb5c3 | ||
|
|
d1a4067dfa | ||
|
|
a1928d202f | ||
|
|
7376e85895 | ||
|
|
eb79dd5ac5 | ||
|
|
5b945b16c1 | ||
|
|
80e60f4190 | ||
|
|
3087796ef5 | ||
|
|
99de0aaf9b | ||
|
|
6bea61cf0a | ||
|
|
6c3553805e | ||
|
|
adc7c7766d | ||
|
|
15d0296307 | ||
|
|
32d2e46118 | ||
|
|
abe3cec13d | ||
|
|
9249613601 | ||
|
|
5bafb40599 | ||
|
|
5968d3c14a | ||
|
|
fcb54d3acc | ||
|
|
e2c4567ed0 | ||
|
|
c3b28c7034 | ||
|
|
32061200a5 | ||
|
|
19aad9bc9c | ||
|
|
2d0cc1c877 | ||
|
|
dc94a1004c | ||
|
|
1385f62fb0 | ||
|
|
34fc886662 | ||
|
|
c651263747 | ||
|
|
96a4526bc8 | ||
|
|
97cf8ff0c5 | ||
|
|
70c16195ef | ||
|
|
eb9d966855 | ||
|
|
0e3ca912a7 | ||
|
|
b3e372326f | ||
|
|
0100f0131d | ||
|
|
e4c3b449a4 | ||
|
|
aaa8cd6406 | ||
|
|
328422b3b8 | ||
|
|
17cce89ef1 | ||
|
|
d9c5ca339f | ||
|
|
1e08ecd8db | ||
|
|
34bcbd9571 | ||
|
|
09bdb5584a | ||
|
|
36f5c9569e | ||
|
|
3d9edc5133 | ||
|
|
ed08c05532 | ||
|
|
9bd8befcb8 | ||
|
|
7ba87e48fc | ||
|
|
af5f739630 | ||
|
|
5558cac263 | ||
|
|
4128e718e9 | ||
|
|
6d017b18f5 | ||
|
|
35eb089178 | ||
|
|
b5f78f0fbe | ||
|
|
628d73f90d | ||
|
|
7d2d84d844 | ||
|
|
1cbe58d760 | ||
|
|
c77a4094a3 | ||
|
|
33f6325bf7 | ||
|
|
701ea59035 | ||
|
|
e77441f0de | ||
|
|
dd46e7a3eb | ||
|
|
f42838810a | ||
|
|
7fdba12376 | ||
|
|
20130ae6d0 | ||
|
|
4598e9b351 | ||
|
|
2e639e0fef | ||
|
|
dbbbc204fb | ||
|
|
caab121cc2 | ||
|
|
c637b36094 | ||
|
|
38ba901956 | ||
|
|
8bae1a5850 | ||
|
|
56375c0edb | ||
|
|
5b69472ac8 | ||
|
|
dbc9c4c121 | ||
|
|
c0e24ce869 | ||
|
|
74c24ad860 | ||
|
|
9b1ed15fea | ||
|
|
a875a9258e | ||
|
|
0d2ac10af8 | ||
|
|
978b8bc7cb | ||
|
|
50c3713c90 | ||
|
|
2639b6bc65 | ||
|
|
8b29ceb151 | ||
|
|
e1c14ad9f8 | ||
|
|
e4f86097e8 | ||
|
|
0515ce6081 | ||
|
|
048554df4d | ||
|
|
2d02486ca6 | ||
|
|
3523f5855a | ||
|
|
aa9577a2e0 | ||
|
|
efc9dcca0b | ||
|
|
b062336c09 | ||
|
|
baf88a5cb7 | ||
|
|
e208014d34 | ||
|
|
3ecedd60fd | ||
|
|
6fd68e4752 | ||
|
|
c2910b0e8c | ||
|
|
8ed51787ad | ||
|
|
2e5fd3017c | ||
|
|
027d29e834 | ||
|
|
1a3c8c026b | ||
|
|
17cb59c606 | ||
|
|
e3828c2982 | ||
|
|
8cef0bb727 | ||
|
|
21ec1e4539 | ||
|
|
dda44cc770 | ||
|
|
78911b2aa9 | ||
|
|
4935dcd0e6 | ||
|
|
e8fe6ad550 | ||
|
|
db4003ad4f | ||
|
|
0a4be165a5 | ||
|
|
c62867e3ef | ||
|
|
6df4959845 | ||
|
|
e0b18013be | ||
|
|
d205e3b372 | ||
|
|
719daf054b | ||
|
|
a5f72a22c3 | ||
|
|
bf5c7655f2 | ||
|
|
691c2f496e | ||
|
|
9ef3405e43 | ||
|
|
215faa0bf0 | ||
|
|
695da7ea87 | ||
|
|
7c6c361d30 | ||
|
|
872ba0ba87 | ||
|
|
93c14dcee5 | ||
|
|
1af0b2175c | ||
|
|
ee81ebffef | ||
|
|
dc00fd2543 | ||
|
|
11015c7be1 | ||
|
|
57befd2c03 | ||
|
|
35156ed968 | ||
|
|
ee6c36174f | ||
|
|
fdfd95555d | ||
|
|
7ec824edda | ||
|
|
1f8b1be669 | ||
|
|
b0bcfeae34 | ||
|
|
10b346a326 | ||
|
|
a85af39b35 | ||
|
|
576f0d2e24 | ||
|
|
95eb3a427c | ||
|
|
e9442edd94 | ||
|
|
77e59f7376 | ||
|
|
135fba1b54 | ||
|
|
ff813e3bc9 | ||
|
|
babfe79023 | ||
|
|
89ac52bebd | ||
|
|
15e1c2f2d5 | ||
|
|
203f0ccc82 | ||
|
|
2422bd2c66 | ||
|
|
0306a1ed6b | ||
|
|
8a94650370 | ||
|
|
837a193e96 | ||
|
|
fcf0746f9a | ||
|
|
9eb466ccd3 | ||
|
|
c54f22613e | ||
|
|
3e1cfa0a27 | ||
|
|
9d22b49e41 | ||
|
|
1c6df55975 | ||
|
|
237616ded2 | ||
|
|
cebdda564e | ||
|
|
08ffb51946 | ||
|
|
8378f51f8e | ||
|
|
978d786f99 | ||
|
|
8b997fa7f2 | ||
|
|
745afb600c | ||
|
|
b45b840afb | ||
|
|
2a7a136203 | ||
|
|
dfb8958999 | ||
|
|
1568f4d6ff | ||
|
|
12ebceecb0 | ||
|
|
8cfe46cb71 | ||
|
|
2f1b929bcd | ||
|
|
2144cd8c3a | ||
|
|
080b34d070 | ||
|
|
de73d2a29f | ||
|
|
9de2b765fb | ||
|
|
be75b50521 | ||
|
|
39aec19471 | ||
|
|
9884001d54 | ||
|
|
29c834a070 | ||
|
|
b16f348676 | ||
|
|
7b46c2a2f6 | ||
|
|
1716d8b33e | ||
|
|
855f0b4002 | ||
|
|
ee0d081b83 | ||
|
|
d2f91a49ad | ||
|
|
fb6f889ddb | ||
|
|
0e6f1013cb | ||
|
|
fd10b18707 | ||
|
|
f423cf5b6d | ||
|
|
37b54b0c0f | ||
|
|
cd74c895d3 | ||
|
|
450027a8b0 | ||
|
|
48821a519e | ||
|
|
423ee5c3d1 | ||
|
|
96c8945ac8 | ||
|
|
b2b714ae88 | ||
|
|
2793ff696e | ||
|
|
2f81c5ef04 | ||
|
|
2eed470d5c | ||
|
|
34f8df1999 | ||
|
|
e901e08534 | ||
|
|
56561ba764 | ||
|
|
cf60e5a60a | ||
|
|
bf8e24a6af | ||
|
|
ff4e6f448c | ||
|
|
dccf080fbb | ||
|
|
9d7094133d | ||
|
|
ea6277805c | ||
|
|
3ad3060d71 | ||
|
|
c1545adeb2 | ||
|
|
9659176752 | ||
|
|
4371345c7d | ||
|
|
655f9d99c7 | ||
|
|
a9c6705bd3 | ||
|
|
46e0c6258f | ||
|
|
7c596c6b91 | ||
|
|
5e92379aa2 | ||
|
|
8a47b6cfb5 | ||
|
|
97eca4ec9c | ||
|
|
b0a7a5569a | ||
|
|
5073ae6166 | ||
|
|
81c6fcf15d | ||
|
|
2c6f821c4b | ||
|
|
236423fe3c | ||
|
|
a8136afc31 | ||
|
|
4d4d3b446a | ||
|
|
c4a30dc4c2 | ||
|
|
0fc133e46d | ||
|
|
c6aa83194f | ||
|
|
e292926697 | ||
|
|
8100bafdf9 | ||
|
|
e0582b5442 | ||
|
|
034137322f | ||
|
|
43378c7dc8 | ||
|
|
30b7189d59 | ||
|
|
bbd553d910 | ||
|
|
3996574b95 | ||
|
|
bd585309bf | ||
|
|
4eda5a3e76 | ||
|
|
d3c5dafd3e | ||
|
|
936e193c20 | ||
|
|
01501ca857 | ||
|
|
4763ff129f | ||
|
|
bce32eb976 | ||
|
|
c383c109a8 | ||
|
|
86a1a2f981 | ||
|
|
ede6a9834d | ||
|
|
359f28bb1f | ||
|
|
caf9bf265b | ||
|
|
046729bfaa | ||
|
|
f150ec23c4 | ||
|
|
eee6112be5 | ||
|
|
79a93d5b50 | ||
|
|
74d0e8e0bc | ||
|
|
bdce3c84a3 | ||
|
|
871185e7ea |
No files matched your search
@@ -0,0 +1,27 @@
|
|||||||
|
## Donate
|
||||||
|
|
||||||
|
Thank you for your support! 🙏
|
||||||
|
|
||||||
|
<div style="text-align: center;">
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>微信</th>
|
||||||
|
<th>支付宝</th>
|
||||||
|
<th>PayPal</th>
|
||||||
|
<th>Ko-fi</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><img src="/.donate/qr/wechatpay.jpg" alt="wechatpay" width="300" /></td>
|
||||||
|
<td><img src="/.donate/qr/alipay.jpg" alt="alipay" width="300" /></td>
|
||||||
|
<td><a href="https://paypal.me/monkeyWie" target="_blank">Click Me</a></td>
|
||||||
|
<td><a href="https://ko-fi.com/gopeed" target="_blank">Click Me</a></td>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
### Crypto
|
||||||
|
|
||||||
|
- **BTC**: 35jYkPN8bgkh7AWjt3B1VgMQHqtGYcvF6p
|
||||||
|
- **ETH:** 0x928a7fd2398c941Ac6713704e606Fa414595e3A0
|
||||||
|
- **Matic:** 0x928a7fd2398c941Ac6713704e606Fa414595e3A0
|
||||||
|
- **BNB:** 0x928a7fd2398c941Ac6713704e606Fa414595e3A0
|
||||||
|
- **USDT(ERC20)**: 0x928a7fd2398c941Ac6713704e606Fa414595e3A0
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 81 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 76 KiB |
@@ -0,0 +1,15 @@
|
|||||||
|
# These are supported funding model platforms
|
||||||
|
|
||||||
|
#github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||||
|
#patreon: # Replace with a single Patreon username
|
||||||
|
#open_collective: # Replace with a single Open Collective username
|
||||||
|
#ko_fi: # Replace with a single Ko-fi username
|
||||||
|
#tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||||
|
#community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||||
|
#liberapay: # Replace with a single Liberapay username
|
||||||
|
#issuehunt: # Replace with a single IssueHunt username
|
||||||
|
#otechie: # Replace with a single Otechie username
|
||||||
|
#custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
||||||
|
# github: monkeyWie
|
||||||
|
ko_fi: gopeed
|
||||||
|
custom: https://github.com/GopeedLab/gopeed/blob/main/.donate/index.md#donate
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<!--
|
||||||
|
Please use the following template to create an issue.
|
||||||
|
-->
|
||||||
|
### Description(required)
|
||||||
|
### App Version(required)
|
||||||
|
### OS Version(required)
|
||||||
|
### Snapshots
|
||||||
|
### Log
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
name-template: 'v$NEXT_PATCH_VERSION 🌈'
|
||||||
|
tag-template: 'v$NEXT_PATCH_VERSION'
|
||||||
|
categories:
|
||||||
|
- title: '🆕 Features'
|
||||||
|
labels:
|
||||||
|
- 'feat'
|
||||||
|
- 'feature'
|
||||||
|
- 'enhancement'
|
||||||
|
- title: '🐛 Bug Fixes'
|
||||||
|
labels:
|
||||||
|
- 'fix'
|
||||||
|
- 'bugfix'
|
||||||
|
- 'bug'
|
||||||
|
- title: '🧰 Maintenance'
|
||||||
|
label: 'chore'
|
||||||
|
- title: '📖 Document'
|
||||||
|
label: 'doc'
|
||||||
|
- title: '🚀 CI/CD'
|
||||||
|
label: 'ci'
|
||||||
|
change-template: '- $TITLE @$AUTHOR (#$NUMBER)'
|
||||||
|
version-resolver:
|
||||||
|
major:
|
||||||
|
labels:
|
||||||
|
- 'major'
|
||||||
|
minor:
|
||||||
|
labels:
|
||||||
|
- 'minor'
|
||||||
|
patch:
|
||||||
|
labels:
|
||||||
|
- 'patch'
|
||||||
|
default: patch
|
||||||
|
template: |
|
||||||
|
## Changes
|
||||||
|
|
||||||
|
$CHANGES
|
||||||
@@ -0,0 +1,370 @@
|
|||||||
|
name: build
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
env:
|
||||||
|
GO_VERSION: 1.19
|
||||||
|
FLUTTER_VERSION: 3.10.6
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
get-release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
tag_name: ${{ steps.get-release.outputs.tag_name }}
|
||||||
|
upload_url: ${{ steps.get-release.outputs.upload_url }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: monkeyWie/get-latest-release@v1
|
||||||
|
id: get-release
|
||||||
|
with:
|
||||||
|
myToken: ${{ github.token }}
|
||||||
|
|
||||||
|
build-windows:
|
||||||
|
runs-on: windows-latest
|
||||||
|
needs: [get-release]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: actions/setup-go@v4
|
||||||
|
with:
|
||||||
|
go-version: ${{ env.GO_VERSION }}
|
||||||
|
- uses: subosito/flutter-action@v2
|
||||||
|
with:
|
||||||
|
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||||
|
- name: Build
|
||||||
|
env:
|
||||||
|
VERSION: ${{ needs.get-release.outputs.tag_name }}
|
||||||
|
run: |
|
||||||
|
Invoke-WebRequest -Uri "https://github.com/jrsoftware/issrc/raw/main/Files/Languages/Unofficial/ChineseSimplified.isl" -OutFile "ChineseSimplified.isl"
|
||||||
|
mv ChineseSimplified.isl "C:\Program Files (x86)\Inno Setup 6\Languages\"
|
||||||
|
|
||||||
|
go build -tags nosqlite -ldflags="-w -s" -buildmode=c-shared -o ui/flutter/windows/libgopeed.dll github.com/GopeedLab/gopeed/bind/desktop
|
||||||
|
cd ui/flutter
|
||||||
|
flutter build windows
|
||||||
|
$mingw = "C:\ProgramData\Chocolatey\lib\mingw\tools\install\mingw64\bin"
|
||||||
|
$system = "C:\Windows\System32"
|
||||||
|
$release = "build\windows\runner\Release\"
|
||||||
|
cp $mingw\libstdc++-6.dll $release
|
||||||
|
cp $mingw\libgcc_s_seh-1.dll $release
|
||||||
|
cp $mingw\libwinpthread-1.dll $release
|
||||||
|
cp $system\msvcp140.dll $release
|
||||||
|
cp $system\vcruntime140.dll $release
|
||||||
|
cp $system\vcruntime140_1.dll $release
|
||||||
|
|
||||||
|
New-Item -Path build\windows\Output -ItemType Directory
|
||||||
|
Compress-Archive -Path "$release*" -DestinationPath "build\windows\Output\Gopeed-$env:VERSION-windows-amd64-portable.zip"
|
||||||
|
|
||||||
|
cd build/windows
|
||||||
|
echo @"
|
||||||
|
; Script generated by the Inno Setup Script Wizard.
|
||||||
|
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
|
||||||
|
|
||||||
|
#define MyAppName "Gopeed"
|
||||||
|
#define MyAppVersion "$($env:VERSION.substring(1))"
|
||||||
|
#define MyAppPublisher "monkeyWie"
|
||||||
|
#define MyAppURL "https://gopeed.com"
|
||||||
|
#define MyAppExeName "gopeed.exe"
|
||||||
|
|
||||||
|
[Setup]
|
||||||
|
; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications.
|
||||||
|
; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
|
||||||
|
AppId={{5960F34D-1E42-402C-8C85-DE2FF24CBAE4}
|
||||||
|
AppName={#MyAppName}
|
||||||
|
AppVersion={#MyAppVersion}
|
||||||
|
;AppVerName={#MyAppName} {#MyAppVersion}
|
||||||
|
AppPublisher={#MyAppPublisher}
|
||||||
|
AppPublisherURL={#MyAppURL}
|
||||||
|
AppSupportURL={#MyAppURL}
|
||||||
|
AppUpdatesURL={#MyAppURL}
|
||||||
|
DefaultDirName={autopf}\gopeed
|
||||||
|
DisableProgramGroupPage=yes
|
||||||
|
LicenseFile=..\..\..\..\LICENSE
|
||||||
|
; Remove the following line to run in administrative install mode (install for all users.)
|
||||||
|
PrivilegesRequired=lowest
|
||||||
|
OutputBaseFilename=gopeed
|
||||||
|
SetupIconFile=..\..\assets\icon\icon.ico
|
||||||
|
Compression=lzma
|
||||||
|
SolidCompression=yes
|
||||||
|
WizardStyle=modern
|
||||||
|
LanguageDetectionMethod=uilanguage
|
||||||
|
ShowLanguageDialog=yes
|
||||||
|
|
||||||
|
[Languages]
|
||||||
|
Name: "english"; MessagesFile: "compiler:Default.isl"
|
||||||
|
Name: "chinesesimplified"; MessagesFile: "compiler:Languages\ChineseSimplified.isl"
|
||||||
|
|
||||||
|
[Tasks]
|
||||||
|
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
|
||||||
|
|
||||||
|
[Files]
|
||||||
|
Source: ".\runner\Release\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs
|
||||||
|
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
|
||||||
|
|
||||||
|
[UninstallDelete]
|
||||||
|
Type: filesandordirs; Name: "{app}"
|
||||||
|
|
||||||
|
[Icons]
|
||||||
|
Name: "{autoprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
|
||||||
|
Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
|
||||||
|
|
||||||
|
[Run]
|
||||||
|
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent
|
||||||
|
"@ > setup.iss
|
||||||
|
iscc.exe setup.iss
|
||||||
|
mv "Output\gopeed.exe" "Output\Gopeed-$env:VERSION-windows-amd64.exe"
|
||||||
|
Compress-Archive -Path "Output\Gopeed-$env:VERSION-windows-amd64.exe" -DestinationPath "Output\Gopeed-$env:VERSION-windows-amd64.zip"
|
||||||
|
Remove-Item -Path "Output\Gopeed-$env:VERSION-windows-amd64.exe"
|
||||||
|
- name: Upload
|
||||||
|
uses: shogo82148/actions-upload-release-asset@v1
|
||||||
|
with:
|
||||||
|
upload_url: ${{ needs.get-release.outputs.upload_url }}
|
||||||
|
asset_path: ui/flutter/build/windows/Output/*
|
||||||
|
overwrite: true
|
||||||
|
build-macos-arm64-lib:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [get-release]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: actions/setup-go@v4
|
||||||
|
with:
|
||||||
|
go-version: ${{ env.GO_VERSION }}
|
||||||
|
- name: Build
|
||||||
|
run: |
|
||||||
|
docker pull techknowlogick/xgo:go-1.19.x
|
||||||
|
go install src.techknowlogick.com/xgo@latest
|
||||||
|
xgo --targets=darwin/arm64 -tags="nosqlite" -ldflags="-w -s" -buildmode=c-shared -pkg=bind/desktop -out=libgopeed .
|
||||||
|
mv libgopeed-*.dylib libgopeed.dylib
|
||||||
|
- uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: macos-arm64-lib
|
||||||
|
path: libgopeed.dylib
|
||||||
|
build-macos:
|
||||||
|
runs-on: macos-latest
|
||||||
|
needs: [get-release, build-macos-arm64-lib]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: actions/setup-go@v4
|
||||||
|
with:
|
||||||
|
go-version: ${{ env.GO_VERSION }}
|
||||||
|
- uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: 16
|
||||||
|
- uses: subosito/flutter-action@v2
|
||||||
|
with:
|
||||||
|
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||||
|
- name: Install appdmg
|
||||||
|
run: npm install -g appdmg
|
||||||
|
- uses: actions/download-artifact@v3
|
||||||
|
with:
|
||||||
|
name: macos-arm64-lib
|
||||||
|
path: ui/flutter/lib-arm64
|
||||||
|
- name: Build
|
||||||
|
env:
|
||||||
|
VERSION: ${{ needs.get-release.outputs.tag_name }}
|
||||||
|
run: |
|
||||||
|
PROJECT_DIR=$(pwd)
|
||||||
|
|
||||||
|
# amd64 lib
|
||||||
|
go build -tags nosqlite -ldflags="-w -s" -buildmode=c-shared -o ui/flutter/lib-amd64/libgopeed.dylib github.com/GopeedLab/gopeed/bind/desktop
|
||||||
|
# universal binary
|
||||||
|
mkdir -p ui/flutter/macos/Frameworks
|
||||||
|
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
|
||||||
|
cd ui/flutter/macos/Frameworks
|
||||||
|
lipo -create -output libgopeed.dylib amd64-lib arm64-lib
|
||||||
|
|
||||||
|
cd $PROJECT_DIR/ui/flutter
|
||||||
|
flutter build macos
|
||||||
|
cd build/macos/Build/Products/Release
|
||||||
|
cat>appdmg.json<<EOF
|
||||||
|
{
|
||||||
|
"title": "Gopeed",
|
||||||
|
"icon": "Gopeed.app/Contents/Resources/AppIcon.icns",
|
||||||
|
"contents": [
|
||||||
|
{ "x": 448, "y": 344, "type": "link", "path": "/Applications" },
|
||||||
|
{ "x": 192, "y": 344, "type": "file", "path": "Gopeed.app" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
mkdir dist
|
||||||
|
appdmg appdmg.json dist/Gopeed-$VERSION-macos.dmg
|
||||||
|
- name: Upload
|
||||||
|
uses: shogo82148/actions-upload-release-asset@v1
|
||||||
|
with:
|
||||||
|
upload_url: ${{ needs.get-release.outputs.upload_url }}
|
||||||
|
asset_path: ui/flutter/build/macos/Build/Products/Release/dist/*
|
||||||
|
overwrite: true
|
||||||
|
build-linux:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [get-release]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: actions/setup-go@v4
|
||||||
|
with:
|
||||||
|
go-version: ${{ env.GO_VERSION }}
|
||||||
|
- uses: subosito/flutter-action@v2
|
||||||
|
with:
|
||||||
|
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||||
|
- run: |
|
||||||
|
sudo apt-get update -y
|
||||||
|
sudo apt-get install -y ninja-build libgtk-3-dev libayatana-appindicator3-dev
|
||||||
|
- name: Build
|
||||||
|
env:
|
||||||
|
VERSION: ${{ needs.get-release.outputs.tag_name }}
|
||||||
|
run: |
|
||||||
|
go build -tags nosqlite -ldflags="-w -s" -buildmode=c-shared -o ui/flutter/linux/bundle/lib/libgopeed.so github.com/GopeedLab/gopeed/bind/desktop
|
||||||
|
cd ui/flutter
|
||||||
|
flutter build linux
|
||||||
|
mkdir -p debian/gui
|
||||||
|
cp assets/icon/icon_1024.png debian/gui/gopeed.png
|
||||||
|
cat>debian/debian.yaml<<EOF
|
||||||
|
flutter_app:
|
||||||
|
command: gopeed
|
||||||
|
arch: x64
|
||||||
|
parent: /usr/local/lib
|
||||||
|
control:
|
||||||
|
Package: gopeed
|
||||||
|
Version: ${VERSION:1}
|
||||||
|
Architecture: amd64
|
||||||
|
Essential: no
|
||||||
|
Priority: optional
|
||||||
|
Depends:
|
||||||
|
Maintainer: monkeyWie
|
||||||
|
Description: High speed downloader that supports all platforms.
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat>debian/gui/gopeed.desktop<<EOF
|
||||||
|
[Desktop Entry]
|
||||||
|
Version=${VERSION:1}
|
||||||
|
Name=Gopeed
|
||||||
|
GenericName=Gopeed
|
||||||
|
Comment=High speed downloader that supports all platforms.
|
||||||
|
Terminal=false
|
||||||
|
Type=Application
|
||||||
|
Categories=Utility
|
||||||
|
Keywords=Flutter;Downloader;
|
||||||
|
EOF
|
||||||
|
|
||||||
|
dart pub global activate flutter_to_debian
|
||||||
|
flutter_to_debian
|
||||||
|
mv debian/packages/gopeed*.deb debian/packages/Gopeed-$VERSION-linux-amd64.deb
|
||||||
|
- name: Upload
|
||||||
|
uses: shogo82148/actions-upload-release-asset@v1
|
||||||
|
with:
|
||||||
|
upload_url: ${{ needs.get-release.outputs.upload_url }}
|
||||||
|
asset_path: ui/flutter/debian/packages/*
|
||||||
|
overwrite: true
|
||||||
|
build-android:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [get-release]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: actions/setup-go@v4
|
||||||
|
with:
|
||||||
|
go-version: ${{ env.GO_VERSION }}
|
||||||
|
- uses: actions/setup-java@v3
|
||||||
|
with:
|
||||||
|
distribution: "zulu"
|
||||||
|
java-version: "11"
|
||||||
|
- uses: subosito/flutter-action@v2
|
||||||
|
with:
|
||||||
|
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||||
|
- name: Build
|
||||||
|
env:
|
||||||
|
VERSION: ${{ needs.get-release.outputs.tag_name }}
|
||||||
|
APK_KEYSTORE : ${{ secrets.APK_KEYSTORE }}
|
||||||
|
APK_KEY_PASSWORD : ${{ secrets.APK_KEY_PASSWORD }}
|
||||||
|
APK_STORE_PASSWORD : ${{ secrets.APK_STORE_PASSWORD }}
|
||||||
|
run: |
|
||||||
|
go install golang.org/x/mobile/cmd/gomobile@latest
|
||||||
|
go get golang.org/x/mobile/bind
|
||||||
|
gomobile init
|
||||||
|
gomobile bind -tags nosqlite -ldflags="-w -s" -o ui/flutter/android/app/libs/libgopeed.aar -target=android -androidapi 19 -javapkg=com.gopeed github.com/GopeedLab/gopeed/bind/mobile
|
||||||
|
cd ui/flutter
|
||||||
|
echo $APK_KEYSTORE | base64 -di > android/app/upload-keystore.jks
|
||||||
|
flutter build apk
|
||||||
|
mkdir dist
|
||||||
|
cp build/app/outputs/flutter-apk/app-release.apk dist/Gopeed-$VERSION-android.apk
|
||||||
|
- name: Upload
|
||||||
|
uses: shogo82148/actions-upload-release-asset@v1
|
||||||
|
with:
|
||||||
|
upload_url: ${{ needs.get-release.outputs.upload_url }}
|
||||||
|
asset_path: ui/flutter/dist/*
|
||||||
|
overwrite: true
|
||||||
|
build-web:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [get-release]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: actions/setup-go@v4
|
||||||
|
with:
|
||||||
|
go-version: ${{ env.GO_VERSION }}
|
||||||
|
- uses: subosito/flutter-action@v2
|
||||||
|
with:
|
||||||
|
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||||
|
- name: Build
|
||||||
|
env:
|
||||||
|
VERSION: ${{ needs.get-release.outputs.tag_name }}
|
||||||
|
run: |
|
||||||
|
cd ui/flutter
|
||||||
|
flutter build web --web-renderer html
|
||||||
|
cd ../../
|
||||||
|
cp -r ui/flutter/build/web cmd/web/dist
|
||||||
|
mkdir -p dist/zip
|
||||||
|
goos_arr=(windows darwin linux)
|
||||||
|
goarch_arr=(386 amd64 arm64)
|
||||||
|
export CGO_ENABLED=0
|
||||||
|
for goos in "${goos_arr[@]}"; do
|
||||||
|
for goarch in "${goarch_arr[@]}"; do
|
||||||
|
goos_name=$goos
|
||||||
|
if [ $goos = "darwin" ]; then
|
||||||
|
goos_name="macos"
|
||||||
|
fi
|
||||||
|
name=gopeed-web-$VERSION-$goos_name-$goarch
|
||||||
|
dir="dist/$name/"
|
||||||
|
(GOOS=$goos GOARCH=$goarch go build -tags nosqlite,web -ldflags="-s -w" -o $dir github.com/GopeedLab/gopeed/cmd/web \
|
||||||
|
&& cd $dir \
|
||||||
|
&& file=$(ls -AU | head -1) \
|
||||||
|
&& mkdir $name \
|
||||||
|
&& mv $file $name/$(echo $file | sed -e "s/web/gopeed/g") \
|
||||||
|
&& zip -r ../zip/$name.zip * \
|
||||||
|
&& cd ../..) \
|
||||||
|
|| true
|
||||||
|
done
|
||||||
|
done
|
||||||
|
- name: Upload
|
||||||
|
uses: shogo82148/actions-upload-release-asset@v1
|
||||||
|
with:
|
||||||
|
upload_url: ${{ needs.get-release.outputs.upload_url }}
|
||||||
|
asset_path: dist/zip/*
|
||||||
|
overwrite: true
|
||||||
|
build-docker:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [get-release]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v2
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v2
|
||||||
|
- name: Login to DockerHub
|
||||||
|
uses: docker/login-action@v1
|
||||||
|
with:
|
||||||
|
username: liwei2633
|
||||||
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
|
- name: Build and push
|
||||||
|
uses: docker/build-push-action@v2
|
||||||
|
env:
|
||||||
|
VERSION: ${{ needs.get-release.outputs.tag_name }}
|
||||||
|
with:
|
||||||
|
push: true
|
||||||
|
platforms: |
|
||||||
|
linux/386
|
||||||
|
linux/amd64
|
||||||
|
linux/arm64
|
||||||
|
tags: |
|
||||||
|
liwei2633/gopeed:latest
|
||||||
|
liwei2633/gopeed:${{ env.VERSION }}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
name: release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
paths:
|
||||||
|
- "bind/**"
|
||||||
|
- "cmd/**"
|
||||||
|
- "internal/**"
|
||||||
|
- "pkg/**"
|
||||||
|
- "ui/**"
|
||||||
|
- ".github/workflows/release.yml"
|
||||||
|
- ".github/release-drafter.yml"
|
||||||
|
- "go.mod"
|
||||||
|
- "go.sum"
|
||||||
|
- "Dockerfile"
|
||||||
|
|
||||||
|
env:
|
||||||
|
GO_VERSION: 1.19
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
tag_name: ${{ steps.create_release.outputs.tag_name }}
|
||||||
|
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: actions/setup-go@v4
|
||||||
|
with:
|
||||||
|
go-version: ${{ env.GO_VERSION }}
|
||||||
|
- uses: release-drafter/release-drafter@v5
|
||||||
|
id: create_release
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
name: test
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
paths:
|
||||||
|
- "bind/**"
|
||||||
|
- "cmd/**"
|
||||||
|
- "internal/**"
|
||||||
|
- "pkg/**"
|
||||||
|
- "ui/**"
|
||||||
|
- ".github/workflows/test.yml"
|
||||||
|
- "go.mod"
|
||||||
|
- "go.sum"
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
paths:
|
||||||
|
- "bind/**"
|
||||||
|
- "cmd/**"
|
||||||
|
- "internal/**"
|
||||||
|
- "pkg/**"
|
||||||
|
- "ui/**"
|
||||||
|
- ".github/workflows/test.yml"
|
||||||
|
- "go.mod"
|
||||||
|
- "go.sum"
|
||||||
|
|
||||||
|
env:
|
||||||
|
GO_VERSION: 1.19
|
||||||
|
FLUTTER_VERSION: 3.10.6
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
# lint:
|
||||||
|
# name: Lint
|
||||||
|
# runs-on: ubuntu-latest
|
||||||
|
# steps:
|
||||||
|
# - uses: actions/setup-go@v2
|
||||||
|
# with:
|
||||||
|
# go-version: '^1.19'
|
||||||
|
# - uses: actions/checkout@v2
|
||||||
|
# - name: Lint Go Code
|
||||||
|
# run: |
|
||||||
|
# export PATH=$PATH:$(go env GOPATH)/bin # temporary fix. See https://github.com/actions/setup-go/issues/14
|
||||||
|
# go get -u golang.org/x/lint/golint
|
||||||
|
# golint -set_exit_status ./...
|
||||||
|
test:
|
||||||
|
name: Test check
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: actions/setup-go@v4
|
||||||
|
with:
|
||||||
|
go-version: ${{ env.GO_VERSION }}
|
||||||
|
- name: Run Unit tests.
|
||||||
|
run: go test $(go list ./... | grep -v /cmd/ | grep -v /bind/ ) -v -covermode=count -coverprofile=coverage.txt
|
||||||
|
- uses: codecov/codecov-action@v3
|
||||||
|
with:
|
||||||
|
files: ./coverage.txt
|
||||||
|
build:
|
||||||
|
name: Build check
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
# needs: [lint, test]
|
||||||
|
needs: [test]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: actions/setup-go@v4
|
||||||
|
with:
|
||||||
|
go-version: ${{ env.GO_VERSION }}
|
||||||
|
- uses: subosito/flutter-action@v2
|
||||||
|
with:
|
||||||
|
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||||
|
- run: |
|
||||||
|
sudo apt-get update -y
|
||||||
|
sudo apt-get install -y ninja-build libgtk-3-dev libayatana-appindicator3-dev
|
||||||
|
- name: Build
|
||||||
|
run: |
|
||||||
|
go build -tags nosqlite -ldflags="-w -s" -buildmode=c-shared -o ui/flutter/linux/bundle/lib/libgopeed.so github.com/GopeedLab/gopeed/bind/desktop
|
||||||
|
cd ui/flutter
|
||||||
|
flutter build linux
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
name: 'translator'
|
||||||
|
|
||||||
|
on:
|
||||||
|
issues:
|
||||||
|
types: [opened, edited]
|
||||||
|
issue_comment:
|
||||||
|
types: [created, edited]
|
||||||
|
discussion:
|
||||||
|
types: [created, edited]
|
||||||
|
discussion_comment:
|
||||||
|
types: [created, edited]
|
||||||
|
pull_request_target:
|
||||||
|
types: [opened, edited]
|
||||||
|
pull_request_review_comment:
|
||||||
|
types: [created, edited]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
translate:
|
||||||
|
permissions:
|
||||||
|
issues: write
|
||||||
|
discussions: write
|
||||||
|
pull-requests: write
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: lizheming/[email protected]
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
IS_MODIFY_TITLE: true
|
||||||
|
APPEND_TRANSLATION: true
|
||||||
+14
-2
@@ -11,6 +11,18 @@
|
|||||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||||
*.out
|
*.out
|
||||||
|
|
||||||
debug
|
|
||||||
|
|
||||||
.idea/
|
.idea/
|
||||||
|
|
||||||
|
bin/
|
||||||
|
|
||||||
|
.torrent.db
|
||||||
|
.torrent.db-shm
|
||||||
|
.torrent.db-wal
|
||||||
|
|
||||||
|
*.data
|
||||||
|
*.db
|
||||||
|
*.log
|
||||||
|
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
dist/
|
||||||
Vendored
-17
@@ -1,17 +0,0 @@
|
|||||||
{
|
|
||||||
// Use IntelliSense to learn about possible attributes.
|
|
||||||
// Hover to view descriptions of existing attributes.
|
|
||||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
|
||||||
"version": "0.2.0",
|
|
||||||
"configurations": [
|
|
||||||
{
|
|
||||||
"name": "Launch",
|
|
||||||
"type": "go",
|
|
||||||
"request": "launch",
|
|
||||||
"mode": "auto",
|
|
||||||
"program": "${fileDirname}",
|
|
||||||
"env": {},
|
|
||||||
"args": []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
Vendored
-9
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"go.useLanguageServer": true,
|
|
||||||
"[go]": {
|
|
||||||
"editor.formatOnSave": true,
|
|
||||||
"editor.codeActionsOnSave": {
|
|
||||||
"source.organizeImports": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
# Gopeed contributors guide
|
||||||
|
|
||||||
|
Firstly, thank you for your interest in contributing to Gopeed. This guide will help you better
|
||||||
|
participate in the development of Gopeed.
|
||||||
|
|
||||||
|
## Branch description
|
||||||
|
|
||||||
|
This project only has one main branch, namely the `main` branch. If you want to participate in the
|
||||||
|
development of Gopeed, please fork this project first, and then develop in your fork project. After
|
||||||
|
development is completed, submit a PR to this project and merge it into the `main` branch.
|
||||||
|
|
||||||
|
## Local development
|
||||||
|
|
||||||
|
It is recommended to develop and debug through the web. First, start the backend service, and start
|
||||||
|
it by the command line `go run cmd/api/main.go`, the default port of the service is `9999`, and then
|
||||||
|
start the front-end flutter project in `debug` mode to run.
|
||||||
|
|
||||||
|
## Translation
|
||||||
|
|
||||||
|
The internationalization files of Gopeed are located in the `ui/flutter/assets/locales` directory.
|
||||||
|
You only need to add the corresponding language file in this directory.
|
||||||
|
|
||||||
|
|
||||||
|
Generate locales after you edit locales:
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
get generate locales
|
||||||
|
```
|
||||||
|
|
||||||
|
Please refer to `en_us.dart` for translation.
|
||||||
|
|
||||||
|
## flutter development
|
||||||
|
|
||||||
|
Don't forget to run`dart format ./ui/flutter`before you commit to keep your code in standard dart format
|
||||||
|
|
||||||
|
Turn on build_runner watcher if you want to edit api/models:
|
||||||
|
|
||||||
|
```
|
||||||
|
flutter pub run build_runner watch
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
get-cli commands usages:
|
||||||
|
|
||||||
|
```
|
||||||
|
create:
|
||||||
|
controller: Generate controller
|
||||||
|
page: Use to generate pages
|
||||||
|
view: Generate view
|
||||||
|
generate:
|
||||||
|
locales: Generate translation file from json files
|
||||||
|
model: generate Class model from json
|
||||||
|
```
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
# Gopeed コントリビューターガイド
|
||||||
|
|
||||||
|
まず最初に、Gopeed への貢献に興味を持っていただきありがとうございます。このガイドは、あなたが Gopeed の
|
||||||
|
開発に参加するための手助けとなるでしょう。
|
||||||
|
|
||||||
|
## ブランチの説明
|
||||||
|
|
||||||
|
このプロジェクトのメインブランチは `main` ブランチのみです。Gopeed の開発に参加したい場合は、
|
||||||
|
まずこのプロジェクトをフォークし、フォークしたプロジェクトで開発を行ってください。開発が完了したら、
|
||||||
|
このプロジェクトに PR を提出し、`main` ブランチにマージしてください。
|
||||||
|
|
||||||
|
## ローカル開発
|
||||||
|
|
||||||
|
開発およびデバッグはウェブ上で行うことを推奨する。まずバックエンドのサービスを起動し、
|
||||||
|
コマンドライン `go run cmd/api/main.go` で起動する。サービスのデフォルトポートは `9999` で、
|
||||||
|
次にフロントエンドの flutter プロジェクトを `debug` モードで起動して実行します。
|
||||||
|
|
||||||
|
## 翻訳
|
||||||
|
|
||||||
|
Gopeed の国際化ファイルは `ui/flutter/assets/locales` ディレクトリにあります。
|
||||||
|
このディレクトリに対応する言語ファイルを追加するだけでよいです。
|
||||||
|
|
||||||
|
|
||||||
|
ロケール編集後にロケールを生成:
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
get generate locales
|
||||||
|
```
|
||||||
|
|
||||||
|
翻訳については `en_us.dart` を参照してください。
|
||||||
|
|
||||||
|
## flutter での開発
|
||||||
|
|
||||||
|
コミットする前に `dart format ./ui/flutter` を実行し、コードを標準の dart フォーマットにしておくことを忘れないでください
|
||||||
|
|
||||||
|
api/models を編集したい場合は build_runner watcher をオンにします:
|
||||||
|
|
||||||
|
```
|
||||||
|
flutter pub run build_runner watch
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
get-cli コマンドの使用法:
|
||||||
|
|
||||||
|
```
|
||||||
|
create:
|
||||||
|
controller: Generate controller
|
||||||
|
page: Use to generate pages
|
||||||
|
view: Generate view
|
||||||
|
generate:
|
||||||
|
locales: Generate translation file from json files
|
||||||
|
model: generate Class model from json
|
||||||
|
```
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
# Gopeed 贡献指南
|
||||||
|
|
||||||
|
首先感谢您对贡献代码感兴趣,这份指南将帮助您更好的参与到 Gopeed 的开发中来。
|
||||||
|
|
||||||
|
## 分支说明
|
||||||
|
|
||||||
|
本项目只有一个主分支,即 `main` 分支,如果您想要参与到 Gopeed 的开发中来,请先 fork 本项目,然后在您的 fork 项目中进行开发,开发完成后再向本项目提交
|
||||||
|
PR,合并到 `main` 分支。
|
||||||
|
|
||||||
|
## 本地开发
|
||||||
|
|
||||||
|
建议通过 web 端进行开发调试,首先启动后端服务,通过命令行 `go run cmd/api/main.go` 启动 ,服务启动默认端口为 `9999`,然后以 `debug` 模式启动前端
|
||||||
|
flutter 项目即可运行。
|
||||||
|
|
||||||
|
## 翻译
|
||||||
|
|
||||||
|
Gopeed 的国际化文件位于 `ui/flutter/assets/locales` 目录下,只需要在该目录下添加对应的语言文件即可。
|
||||||
|
|
||||||
|
编辑locales后请运行以下命令:
|
||||||
|
|
||||||
|
```
|
||||||
|
get generate locales
|
||||||
|
```
|
||||||
|
|
||||||
|
请注意以 `en_US.json` 为参照进行翻译。
|
||||||
|
|
||||||
|
## flutter开发
|
||||||
|
|
||||||
|
每次提交前请务必`dart format ./ui/flutter`
|
||||||
|
|
||||||
|
如果要编辑api/models,请打开build_runner watcher:
|
||||||
|
|
||||||
|
```
|
||||||
|
flutter pub run build_runner watch
|
||||||
|
```
|
||||||
|
|
||||||
|
适用get-cli 命令:
|
||||||
|
|
||||||
|
```
|
||||||
|
create:
|
||||||
|
controller: Generate controller
|
||||||
|
page: Use to generate pages
|
||||||
|
view: Generate view
|
||||||
|
generate:
|
||||||
|
locales: Generate translation file from json files
|
||||||
|
model: generate Class model from json
|
||||||
|
```
|
||||||
|
|
||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
FROM instrumentisto/flutter:3.10.6 AS flutter
|
||||||
|
WORKDIR /app
|
||||||
|
COPY ./ui/flutter/pubspec.yaml ./ui/flutter/pubspec.lock ./
|
||||||
|
RUN flutter pub get
|
||||||
|
COPY ./ui/flutter ./
|
||||||
|
RUN flutter build web --web-renderer html
|
||||||
|
|
||||||
|
FROM golang:1.19.3 AS go
|
||||||
|
WORKDIR /app
|
||||||
|
COPY ./go.mod ./go.sum ./
|
||||||
|
RUN go mod download
|
||||||
|
COPY . .
|
||||||
|
COPY --from=flutter /app/build/web ./cmd/web/dist
|
||||||
|
RUN CGO_ENABLED=0 go build -tags nosqlite,web -ldflags="-s -w" -o dist/gopeed github.com/GopeedLab/gopeed/cmd/web
|
||||||
|
|
||||||
|
FROM alpine:3.14.2
|
||||||
|
LABEL maintainer="monkeyWie"
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=go /app/dist/gopeed ./
|
||||||
|
EXPOSE 9999
|
||||||
|
ENTRYPOINT ["./gopeed"]
|
||||||
@@ -1,20 +1,169 @@
|
|||||||
## gopeed
|
[](https://github.com/GopeedLab/gopeed/actions?query=workflow%3Atest)
|
||||||
支持多协议(HTTP、BitTorrent)下载的客户端,提供命令行、RESTful API、WebSocket、Go类库方式来使用。
|
[](https://codecov.io/gh/GopeedLab/gopeed)
|
||||||
|
[](https://github.com/GopeedLab/gopeed/releases)
|
||||||
|
[](https://github.com/GopeedLab/gopeed/releases)
|
||||||
|
[](https://github.com/GopeedLab/gopeed/blob/main/.donate/index.md#donate)
|
||||||
|
[](https://discord.gg/ZUJqJrwCGB)
|
||||||
|
|
||||||
## TODO
|

|
||||||
- [x] HTTP下载实现
|
|
||||||
- [ ] BitTorrent下载实现
|
|
||||||
- [x] .torrent文件解析
|
|
||||||
- [x] tracker协议实现
|
|
||||||
- [x] peer wire protocol协议实现
|
|
||||||
- [ ] DHT协议实现
|
|
||||||
- [ ] 磁力链接支持
|
|
||||||
- [ ] uTP协议实现
|
|
||||||
- [ ] 下载接口抽象(不关心具体协议)
|
|
||||||
- [ ] 支持自定义配置
|
|
||||||
- [ ] 限速功能实现
|
|
||||||
- [ ] 命令行工具提供
|
|
||||||
- [ ] RESTful服务提供
|
|
||||||
|
|
||||||
## 参与
|
[English](/README.md) | [中文](/README_zh-CN.md) | [日本語](/README_ja-JP.md)
|
||||||
由于项目目前还未定型,代码可能随时有大的调整,所以暂不接受PR,当然如果有什么好的想法可以在issue区提出来。
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
Gopeed (full name Go Speed), a high-speed downloader developed by `Golang` + `Flutter`, supports (HTTP, BitTorrent, Magnet) protocol, and supports all platforms.
|
||||||
|
|
||||||
|
Visit ✈ [Official Website](https://gopeed.com) | 📖 [Develop Docs](https://docs.gopeed.com)
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
**Supported platforms**
|
||||||
|
|
||||||
|
- [x] windows
|
||||||
|
- [x] macos
|
||||||
|
- [x] linux
|
||||||
|
- [x] android
|
||||||
|
- [ ] ios
|
||||||
|
- [x] web
|
||||||
|
- [x] docker
|
||||||
|
|
||||||
|
[Download](https://github.com/GopeedLab/gopeed/releases/latest)
|
||||||
|
|
||||||
|
> Tips: If the macos open failed, please execute the `xattr -d com.apple.quarantine /Applications/Gopeed.app` command in the terminal
|
||||||
|
|
||||||
|
### Command tool
|
||||||
|
|
||||||
|
use `go install`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go install github.com/GopeedLab/gopeed/cmd/gopeed@latest
|
||||||
|
```
|
||||||
|
|
||||||
|
### Docker
|
||||||
|
|
||||||
|
#### Start directly
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run -d -p 9999:9999 -v /path/to/download:/root/Downloads liwei2633/gopeed
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Using Docker Compose
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Access Gopeed
|
||||||
|
|
||||||
|
When the docker container is running, you can access the web page through `http://localhost:9999`.
|
||||||
|
|
||||||
|
## Donate
|
||||||
|
|
||||||
|
If you like this project, please consider [donating](/.donate/index.md#donate) to support the development of this project, thank you!
|
||||||
|
|
||||||
|
## Showcase
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
This project is divided into two parts, the front end uses `flutter`, the back end uses `Golang`, and the two sides communicate through the `http` protocol. On the unix system, `unix socket` is used, and on the windows system, `tcp` protocol is used.
|
||||||
|
|
||||||
|
> The front code is located in the `ui/flutter` directory.
|
||||||
|
|
||||||
|
### Environment
|
||||||
|
|
||||||
|
1. Golang 1.19+
|
||||||
|
2. Flutter 3.7+
|
||||||
|
|
||||||
|
### Clone
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone [email protected]:GopeedLab/gopeed.git
|
||||||
|
```
|
||||||
|
|
||||||
|
### Contributing
|
||||||
|
|
||||||
|
Please refer to [CONTRIBUTING.md](/CONTRIBUTING.md)
|
||||||
|
|
||||||
|
### Build
|
||||||
|
|
||||||
|
#### Desktop
|
||||||
|
|
||||||
|
First, you need to configure the environment according to the official [Flutter desktop website documention](https://docs.flutter.dev/development/platform-integration/desktop), then you will need to ensure the cgo environment is set up accordingly. For detailed instructions on setting up the cgo environment, please refer to relevant resources available online.
|
||||||
|
|
||||||
|
command:
|
||||||
|
|
||||||
|
- windows
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go build -tags nosqlite -ldflags="-w -s" -buildmode=c-shared -o ui/flutter/windows/libgopeed.dll github.com/GopeedLab/gopeed/bind/desktop
|
||||||
|
cd ui/flutter
|
||||||
|
flutter build windows
|
||||||
|
```
|
||||||
|
|
||||||
|
- macos
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go build -tags nosqlite -ldflags="-w -s" -buildmode=c-shared -o ui/flutter/macos/Frameworks/libgopeed.dylib github.com/GopeedLab/gopeed/bind/desktop
|
||||||
|
cd ui/flutter
|
||||||
|
flutter build macos
|
||||||
|
```
|
||||||
|
|
||||||
|
- linux
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go build -tags nosqlite -ldflags="-w -s" -buildmode=c-shared -o ui/flutter/linux/bundle/lib/libgopeed.so github.com/GopeedLab/gopeed/bind/desktop
|
||||||
|
cd ui/flutter
|
||||||
|
flutter build linux
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Mobile
|
||||||
|
|
||||||
|
Same as before, you also need to prepare the `cgo` environment, and then install `gomobile`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go install golang.org/x/mobile/cmd/gomobile@latest
|
||||||
|
go get golang.org/x/mobile/bind
|
||||||
|
gomobile init
|
||||||
|
```
|
||||||
|
|
||||||
|
command:
|
||||||
|
|
||||||
|
- android
|
||||||
|
|
||||||
|
```bash
|
||||||
|
gomobile bind -tags nosqlite -ldflags="-w -s" -o ui/flutter/android/app/libs/libgopeed.aar -target=android -androidapi 19 -javapkg=com.gopeed github.com/GopeedLab/gopeed/bind/mobile
|
||||||
|
cd ui/flutter
|
||||||
|
flutter build apk
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Web
|
||||||
|
|
||||||
|
command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd ui/flutter
|
||||||
|
flutter build web
|
||||||
|
cd ../../
|
||||||
|
rm -rf cmd/web/dist
|
||||||
|
cp -r ui/flutter/build/web cmd/web/dist
|
||||||
|
go build -tags nosqlite,web -ldflags="-s -w" -o bin/ github.com/GopeedLab/gopeed/cmd/web
|
||||||
|
go run cmd/web/main.go
|
||||||
|
```
|
||||||
|
|
||||||
|
## Thanks
|
||||||
|
|
||||||
|
### Contributors
|
||||||
|
|
||||||
|
<a href="https://github.com/GopeedLab/gopeed/graphs/contributors">
|
||||||
|
<img src="https://contrib.rocks/image?repo=GopeedLab/gopeed" />
|
||||||
|
</a>
|
||||||
|
|
||||||
|
### JetBrains
|
||||||
|
|
||||||
|
[](https://www.jetbrains.com/?from=gopeed)
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
[GPLv3](LICENSE)
|
||||||
+169
@@ -0,0 +1,169 @@
|
|||||||
|
[](https://github.com/GopeedLab/gopeed/actions?query=workflow%3Atest)
|
||||||
|
[](https://codecov.io/gh/GopeedLab/gopeed)
|
||||||
|
[](https://github.com/GopeedLab/gopeed/releases)
|
||||||
|
[](https://github.com/GopeedLab/gopeed/releases)
|
||||||
|
[](https://github.com/GopeedLab/gopeed/blob/main/.donate/index.md#donate)
|
||||||
|
[](https://discord.gg/ZUJqJrwCGB)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
[English](/README.md) | [中文](/README_zh-CN.md) | [日本語](/README_ja-JP.md)
|
||||||
|
|
||||||
|
## はじめに
|
||||||
|
|
||||||
|
Gopeed (正式名 Go Speed) は `Golang` + `Flutter` によって開発された高速ダウンローダーで、(HTTP、BitTorrent、Magnet) プロトコルをサポートし、すべてのプラットフォームをサポートします。
|
||||||
|
|
||||||
|
見て下さい ✈ [公式ウェブサイト](https://gopeed.com) | 📖 [開発ドキュメント](https://docs.gopeed.com)
|
||||||
|
|
||||||
|
## インストール
|
||||||
|
|
||||||
|
**サポートするプラットフォーム**
|
||||||
|
|
||||||
|
- [x] windows
|
||||||
|
- [x] macos
|
||||||
|
- [x] linux
|
||||||
|
- [x] android
|
||||||
|
- [ ] ios
|
||||||
|
- [x] web
|
||||||
|
- [x] docker
|
||||||
|
|
||||||
|
[ダウンロード](https://github.com/GopeedLab/gopeed/releases/latest)
|
||||||
|
|
||||||
|
> ヒント: macos を開くのに失敗した場合は、ターミナルで `xattr -d com.apple.quarantine /Applications/Gopeed.app` コマンドを実行してください
|
||||||
|
|
||||||
|
### コマンドツール
|
||||||
|
|
||||||
|
`go install` を使用:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go install github.com/GopeedLab/gopeed/cmd/gopeed@latest
|
||||||
|
```
|
||||||
|
|
||||||
|
### Docker
|
||||||
|
|
||||||
|
#### 直接始める
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run -d -p 9999:9999 -v /path/to/download:/root/Downloads liwei2633/gopeed
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Docker Compose の使用
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Gopeed へのアクセス
|
||||||
|
|
||||||
|
docker コンテナが起動しているときは、`http://localhost:9999` からウェブページにアクセスできます。
|
||||||
|
|
||||||
|
## 寄付
|
||||||
|
|
||||||
|
もしこのプロジェクトがお気に召しましたら、このプロジェクトの発展を支援するために[寄付](/.donate/index.md#donate)をご検討ください!
|
||||||
|
|
||||||
|
## ショーケース
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## 開発
|
||||||
|
|
||||||
|
このプロジェクトは二つの部分に分かれており、フロントエンドでは `flutter` を、バックエンドでは `Golang` を使用し、両者は `http` プロトコルで通信する。ユニックスシステムでは `unix socket` を、ウィンドウズシステムでは `tcp` プロトコルを使用します。
|
||||||
|
|
||||||
|
> フロントコードは `ui/flutter` ディレクトリにあります。
|
||||||
|
|
||||||
|
### 環境
|
||||||
|
|
||||||
|
1. Go 言語 1.19+
|
||||||
|
2. Flutter 3.7+
|
||||||
|
|
||||||
|
### クローン
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone [email protected]:GopeedLab/gopeed.git
|
||||||
|
```
|
||||||
|
|
||||||
|
### コントリビュート
|
||||||
|
|
||||||
|
[CONTRIBUTING.md](/CONTRIBUTING_ja-JP.md) をご参照ください
|
||||||
|
|
||||||
|
### ビルド
|
||||||
|
|
||||||
|
#### デスクトップ
|
||||||
|
|
||||||
|
まず、[flutter デスクトップ公式サイトドキュメント](https://docs.flutter.dev/development/platform-integration/desktop)に従って環境を設定し、自分で検索できる `cgo` 環境を用意します。
|
||||||
|
|
||||||
|
コマンド:
|
||||||
|
|
||||||
|
- windows
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go build -tags nosqlite -ldflags="-w -s" -buildmode=c-shared -o ui/flutter/windows/libgopeed.dll github.com/GopeedLab/gopeed/bind/desktop
|
||||||
|
cd ui/flutter
|
||||||
|
flutter build windows
|
||||||
|
```
|
||||||
|
|
||||||
|
- macos
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go build -tags nosqlite -ldflags="-w -s" -buildmode=c-shared -o ui/flutter/macos/Frameworks/libgopeed.dylib github.com/GopeedLab/gopeed/bind/desktop
|
||||||
|
cd ui/flutter
|
||||||
|
flutter build macos
|
||||||
|
```
|
||||||
|
|
||||||
|
- linux
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go build -tags nosqlite -ldflags="-w -s" -buildmode=c-shared -o ui/flutter/linux/bundle/lib/libgopeed.so github.com/GopeedLab/gopeed/bind/desktop
|
||||||
|
cd ui/flutter
|
||||||
|
flutter build linux
|
||||||
|
```
|
||||||
|
|
||||||
|
#### モバイル
|
||||||
|
|
||||||
|
先ほどと同じように、`cgo` 環境を準備し、`gomobile` をインストールする必要があります:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go install golang.org/x/mobile/cmd/gomobile@latest
|
||||||
|
go get golang.org/x/mobile/bind
|
||||||
|
gomobile init
|
||||||
|
```
|
||||||
|
|
||||||
|
コマンド:
|
||||||
|
|
||||||
|
- android
|
||||||
|
|
||||||
|
```bash
|
||||||
|
gomobile bind -tags nosqlite -ldflags="-w -s" -o ui/flutter/android/app/libs/libgopeed.aar -target=android -androidapi 19 -javapkg=com.gopeed github.com/GopeedLab/gopeed/bind/mobile
|
||||||
|
cd ui/flutter
|
||||||
|
flutter build apk
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Web
|
||||||
|
|
||||||
|
コマンド:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd ui/flutter
|
||||||
|
flutter build web
|
||||||
|
cd ../../
|
||||||
|
rm -rf cmd/web/dist
|
||||||
|
cp -r ui/flutter/build/web cmd/web/dist
|
||||||
|
go build -tags nosqlite,web -ldflags="-s -w" -o bin/ github.com/GopeedLab/gopeed/cmd/web
|
||||||
|
go run cmd/web/main.go
|
||||||
|
```
|
||||||
|
|
||||||
|
## 感謝
|
||||||
|
|
||||||
|
### コントリビューター
|
||||||
|
|
||||||
|
<a href="https://github.com/GopeedLab/gopeed/graphs/contributors">
|
||||||
|
<img src="https://contrib.rocks/image?repo=GopeedLab/gopeed" />
|
||||||
|
</a>
|
||||||
|
|
||||||
|
### JetBrains
|
||||||
|
|
||||||
|
[](https://www.jetbrains.com/?from=gopeed)
|
||||||
|
|
||||||
|
## ライセンス
|
||||||
|
|
||||||
|
[GPLv3](LICENSE)
|
||||||
+169
@@ -0,0 +1,169 @@
|
|||||||
|
[](https://github.com/GopeedLab/gopeed/actions?query=workflow%3Atest)
|
||||||
|
[](https://codecov.io/gh/GopeedLab/gopeed)
|
||||||
|
[](https://github.com/GopeedLab/gopeed/releases)
|
||||||
|
[](https://github.com/GopeedLab/gopeed/releases)
|
||||||
|
[](https://github.com/GopeedLab/gopeed/blob/main/.donate/index.md#donate)
|
||||||
|
[](https://discord.gg/ZUJqJrwCGB)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
[English](/README.md) | [中文](/README_zh-CN.md) | [日本語](/README_ja-JP.md)
|
||||||
|
|
||||||
|
## 介绍
|
||||||
|
|
||||||
|
Gopeed(全称 Go Speed),是一款由`Golang`+`Flutter`开发的高速下载器,支持(HTTP、BitTorrent、Magnet)协议下载,并且支持全平台使用。
|
||||||
|
|
||||||
|
访问 ✈ [官方网站](https://gopeed.com/zh-CN) | 📖 [开发文档](https://docs.gopeed.com/zh/)
|
||||||
|
|
||||||
|
## 安装
|
||||||
|
|
||||||
|
**已支持平台**
|
||||||
|
|
||||||
|
- [x] windows
|
||||||
|
- [x] macos
|
||||||
|
- [x] linux
|
||||||
|
- [x] android
|
||||||
|
- [ ] ios
|
||||||
|
- [x] web
|
||||||
|
- [x] docker
|
||||||
|
|
||||||
|
[前往下载](https://github.com/GopeedLab/gopeed/releases/latest)
|
||||||
|
|
||||||
|
> 注:macos 版本运行如果提示损坏,请在终端执行 `xattr -d com.apple.quarantine /Applications/Gopeed.app` 命令
|
||||||
|
|
||||||
|
### 命令行工具
|
||||||
|
|
||||||
|
使用`go install`安装:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go install github.com/GopeedLab/gopeed/cmd/gopeed@latest
|
||||||
|
```
|
||||||
|
|
||||||
|
### Docker
|
||||||
|
|
||||||
|
#### 直接运行
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run -d -p 9999:9999 -v /path/to/download:/root/Downloads liwei2633/gopeed
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 使用 Docker Compose
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 访问服务
|
||||||
|
|
||||||
|
当 docker 容器运行时,可以通过 `http://localhost:9999` 访问 web 页面。
|
||||||
|
|
||||||
|
## 打赏
|
||||||
|
|
||||||
|
如果觉得项目对你有帮助,请考虑[打赏](/.donate/index.md#donate)以支持这个项目的发展,非常感谢!
|
||||||
|
|
||||||
|
## 界面展示
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## 开发
|
||||||
|
|
||||||
|
本项目分为前端和后端两个部分,前端使用`flutter`,后端使用`Golang`,两边通过`http`协议进行通讯,在 unix 系统下,使用的是`unix socket`,在 windows 系统下,使用的是`tcp`协议。
|
||||||
|
|
||||||
|
> 前端代码位于`ui/flutter`目录下。
|
||||||
|
|
||||||
|
### 环境要求
|
||||||
|
|
||||||
|
1. Golang 1.19+
|
||||||
|
2. Flutter 3.7+
|
||||||
|
|
||||||
|
### 克隆项目
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone [email protected]:GopeedLab/gopeed.git
|
||||||
|
```
|
||||||
|
|
||||||
|
### 贡献代码
|
||||||
|
|
||||||
|
请参考[贡献指南](CONTRIBUTING_zh-CN.md)
|
||||||
|
|
||||||
|
### 编译
|
||||||
|
|
||||||
|
#### 桌面端
|
||||||
|
|
||||||
|
首先需要按照[flutter desktop 官网文档](https://docs.flutter.dev/development/platform-integration/desktop)进行环境配置,然后需要准备好`cgo`环境,具体可以自行搜索。
|
||||||
|
|
||||||
|
构建命令:
|
||||||
|
|
||||||
|
- windows
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go build -tags nosqlite -ldflags="-w -s" -buildmode=c-shared -o ui/flutter/windows/libgopeed.dll github.com/GopeedLab/gopeed/bind/desktop
|
||||||
|
cd ui/flutter
|
||||||
|
flutter build windows
|
||||||
|
```
|
||||||
|
|
||||||
|
- macos
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go build -tags nosqlite -ldflags="-w -s" -buildmode=c-shared -o ui/flutter/macos/Frameworks/libgopeed.dylib github.com/GopeedLab/gopeed/bind/desktop
|
||||||
|
cd ui/flutter
|
||||||
|
flutter build macos
|
||||||
|
```
|
||||||
|
|
||||||
|
- linux
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go build -tags nosqlite -ldflags="-w -s" -buildmode=c-shared -o ui/flutter/linux/bundle/lib/libgopeed.so github.com/GopeedLab/gopeed/bind/desktop
|
||||||
|
cd ui/flutter
|
||||||
|
flutter build linux
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 移动端
|
||||||
|
|
||||||
|
同样的也是需要准备好`cgo`环境,接着安装`gomobile`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go install golang.org/x/mobile/cmd/gomobile@latest
|
||||||
|
go get golang.org/x/mobile/bind
|
||||||
|
gomobile init
|
||||||
|
```
|
||||||
|
|
||||||
|
构建命令:
|
||||||
|
|
||||||
|
- android
|
||||||
|
|
||||||
|
```bash
|
||||||
|
gomobile bind -tags nosqlite -ldflags="-w -s" -o ui/flutter/android/app/libs/libgopeed.aar -target=android -androidapi 19 -javapkg=com.gopeed github.com/GopeedLab/gopeed/bind/mobile
|
||||||
|
cd ui/flutter
|
||||||
|
flutter build apk
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Web 端
|
||||||
|
|
||||||
|
构建命令:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd ui/flutter
|
||||||
|
flutter build web
|
||||||
|
cd ../../
|
||||||
|
rm -rf cmd/web/dist
|
||||||
|
cp -r ui/flutter/build/web cmd/web/dist
|
||||||
|
go build -tags nosqlite,web -ldflags="-s -w" -o bin/ github.com/GopeedLab/gopeed/cmd/web
|
||||||
|
go run cmd/web/main.go
|
||||||
|
```
|
||||||
|
|
||||||
|
## 感谢
|
||||||
|
|
||||||
|
### 贡献者
|
||||||
|
|
||||||
|
<a href="https://github.com/GopeedLab/gopeed/graphs/contributors">
|
||||||
|
<img src="https://contrib.rocks/image?repo=GopeedLab/gopeed" />
|
||||||
|
</a>
|
||||||
|
|
||||||
|
### JetBrains
|
||||||
|
|
||||||
|
[](https://www.jetbrains.com/?from=gopeed)
|
||||||
|
|
||||||
|
## 开源许可
|
||||||
|
|
||||||
|
基于 [GPLv3](LICENSE) 协议开源。
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg height="105" viewBox="0 0 105 105" width="105" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><linearGradient id="a" gradientUnits="userSpaceOnUse" x1="99.03" x2="61.54" y1="59.18" y2="90.91"><stop offset="0" stop-color="#087cfa"/><stop offset=".02" stop-color="#0d7bfa"/><stop offset=".37" stop-color="#5566f9"/><stop offset=".66" stop-color="#8a57f8"/><stop offset=".88" stop-color="#ab4ef7"/><stop offset="1" stop-color="#b74af7"/></linearGradient><linearGradient id="b" x1="37.54" x2="60.33" xlink:href="#a" y1="34.26" y2="8.33"/><linearGradient id="c" gradientUnits="userSpaceOnUse" x1="17.84" x2="87.09" y1="88.36" y2="43.86"><stop offset="0" stop-color="#087cfa"/><stop offset=".1" stop-color="#1598d3"/><stop offset=".22" stop-color="#23b6aa"/><stop offset=".35" stop-color="#2dcc8b"/><stop offset=".46" stop-color="#35dd74"/><stop offset=".57" stop-color="#39e767"/><stop offset=".67" stop-color="#3bea62"/></linearGradient><path d="m89.06 41.63 11.44 25.37-19.58 33.5-12.06-29.28z" fill="url(#a)"/><path d="m68.86 71.22 12.06 29.28-30.34-10.43z" fill="#b74af7"/><path d="m72.13 31.61-6.22-27.11h-34.49l-26.92 41.18 7.79 18.92-7.79 17.27 55.75-47.43z" fill="url(#b)"/><path d="m100.5 24.88-40.25 9.56-55.75 47.43 35.96 18.63 28.4-29.28z" fill="url(#c)"/><path d="m22.5 22.5h60v60h-60z"/><g fill="#fff"><path d="m28.13 71.25h22.5v3.75h-22.5z"/><path d="m26.44 42.81v-.07a12.7 12.7 0 0 1 12.97-12.74 13.62 13.62 0 0 1 9.73 3.39l-3.43 4.14a9.13 9.13 0 0 0 -6.47-2.53c-4 0-7.11 3.5-7.11 7.7v.07c0 4.53 3.11 7.85 7.5 7.85a8.67 8.67 0 0 0 5.12-1.49v-3.56h-5.48v-4.7h10.73v10.74a16.07 16.07 0 0 1 -10.57 3.93c-7.69 0-12.99-5.41-12.99-12.73z"/><path d="m52.3 42.81v-.07a12.79 12.79 0 0 1 13.19-12.74c7.63 0 13.11 5.69 13.11 12.72v.07a12.79 12.79 0 0 1 -13.18 12.8c-7.64-.05-13.12-5.74-13.12-12.78zm20.61 0v-.07a7.51 7.51 0 0 0 -7.49-7.74 7.4 7.4 0 0 0 -7.42 7.67v.07a7.52 7.52 0 0 0 7.5 7.78 7.4 7.4 0 0 0 7.41-7.71z"/></g></svg>
|
||||||
|
After Width: | Height: | Size: 1.9 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 29 KiB |
@@ -0,0 +1,33 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/GopeedLab/gopeed/pkg/base"
|
||||||
|
"github.com/GopeedLab/gopeed/pkg/download"
|
||||||
|
"github.com/GopeedLab/gopeed/pkg/protocol/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
finallyCh := make(chan error)
|
||||||
|
_, err := download.Boot().
|
||||||
|
URL("https://www.baidu.com/index.html").
|
||||||
|
Listener(func(event *download.Event) {
|
||||||
|
if event.Key == download.EventKeyFinally {
|
||||||
|
finallyCh <- event.Err
|
||||||
|
}
|
||||||
|
}).
|
||||||
|
Create(&base.Options{
|
||||||
|
Extra: http.OptsExtra{
|
||||||
|
Connections: 8,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
err = <-finallyCh
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("download fail:%v\n", err)
|
||||||
|
} else {
|
||||||
|
fmt.Println("download success")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "C"
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"github.com/GopeedLab/gopeed/pkg/rest"
|
||||||
|
"github.com/GopeedLab/gopeed/pkg/rest/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {}
|
||||||
|
|
||||||
|
//export Start
|
||||||
|
func Start(cfg *C.char) (int, *C.char) {
|
||||||
|
var config model.StartConfig
|
||||||
|
if err := json.Unmarshal([]byte(C.GoString(cfg)), &config); err != nil {
|
||||||
|
return 0, C.CString(err.Error())
|
||||||
|
}
|
||||||
|
realPort, err := rest.Start(&config)
|
||||||
|
if err != nil {
|
||||||
|
return 0, C.CString(err.Error())
|
||||||
|
}
|
||||||
|
return realPort, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//export Stop
|
||||||
|
func Stop() {
|
||||||
|
rest.Stop()
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package libgopeed
|
||||||
|
|
||||||
|
// #cgo LDFLAGS: -static-libstdc++
|
||||||
|
import "C"
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"github.com/GopeedLab/gopeed/pkg/rest"
|
||||||
|
"github.com/GopeedLab/gopeed/pkg/rest/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Start(cfg string) (int, error) {
|
||||||
|
var config model.StartConfig
|
||||||
|
if err := json.Unmarshal([]byte(cfg), &config); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return rest.Start(&config)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Stop() {
|
||||||
|
rest.Stop()
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
"github.com/GopeedLab/gopeed/cmd"
|
||||||
|
"github.com/GopeedLab/gopeed/pkg/rest/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
cfg := &model.StartConfig{
|
||||||
|
Network: "tcp",
|
||||||
|
Address: "127.0.0.1:9999",
|
||||||
|
Storage: model.StorageBolt,
|
||||||
|
}
|
||||||
|
cmd.Start(cfg)
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
|
||||||
|
_______ ______ .______ _______ _______ _______
|
||||||
|
/ _____| / __ \ | _ \ | ____|| ____|| \
|
||||||
|
| | __ | | | | | |_) | | |__ | |__ | .--. |
|
||||||
|
| | |_ | | | | | | ___/ | __| | __| | | | |
|
||||||
|
| |__| | | `--' | | | | |____ | |____ | '--' |
|
||||||
|
\______| \______/ | _| |_______||_______||_______/
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
type args struct {
|
||||||
|
url string
|
||||||
|
connections *int
|
||||||
|
dir *string
|
||||||
|
}
|
||||||
|
|
||||||
|
func parse() *args {
|
||||||
|
dir, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
var args args
|
||||||
|
args.connections = flag.Int("C", 16, "Concurrent connections.")
|
||||||
|
args.dir = flag.String("D", dir, "Store directory.")
|
||||||
|
flag.Parse()
|
||||||
|
t := flag.Args()
|
||||||
|
if len(t) > 0 {
|
||||||
|
args.url = t[0]
|
||||||
|
} else {
|
||||||
|
gPrintln("missing url parameter, for example: gopeed https://www.google.com or gopeed bt.torrent or gopeed magnet:?xt=urn:btih:...")
|
||||||
|
gPrintln("try 'gopeed -h' for more information")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
return &args
|
||||||
|
}
|
||||||
|
|
||||||
|
func gPrint(msg string) {
|
||||||
|
fmt.Print("gopeed: " + msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func gPrintln(msg string) {
|
||||||
|
gPrint(msg + "\n")
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/GopeedLab/gopeed/pkg/base"
|
||||||
|
"github.com/GopeedLab/gopeed/pkg/download"
|
||||||
|
"github.com/GopeedLab/gopeed/pkg/protocol/http"
|
||||||
|
"github.com/GopeedLab/gopeed/pkg/util"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
const progressWidth = 20
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
args := parse()
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(1)
|
||||||
|
_, err := download.Boot().
|
||||||
|
URL(args.url).
|
||||||
|
Listener(func(event *download.Event) {
|
||||||
|
if event.Key == download.EventKeyProgress {
|
||||||
|
printProgress(event.Task, "downloading...")
|
||||||
|
}
|
||||||
|
if event.Key == download.EventKeyFinally {
|
||||||
|
var title string
|
||||||
|
if event.Err != nil {
|
||||||
|
title = "fail"
|
||||||
|
} else {
|
||||||
|
title = "complete"
|
||||||
|
}
|
||||||
|
printProgress(event.Task, title)
|
||||||
|
fmt.Println()
|
||||||
|
if event.Err != nil {
|
||||||
|
fmt.Printf("reason: %s", event.Err.Error())
|
||||||
|
} else {
|
||||||
|
fmt.Printf("saving path: %s", *args.dir)
|
||||||
|
}
|
||||||
|
wg.Done()
|
||||||
|
}
|
||||||
|
}).
|
||||||
|
Create(&base.Options{
|
||||||
|
Path: *args.dir,
|
||||||
|
Extra: http.OptsExtra{Connections: *args.connections},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
lastLineLen = 0
|
||||||
|
sb = new(strings.Builder)
|
||||||
|
)
|
||||||
|
|
||||||
|
func printProgress(task *download.Task, title string) {
|
||||||
|
rate := float64(task.Progress.Downloaded) / float64(task.Meta.Res.Size)
|
||||||
|
completeWidth := int(progressWidth * rate)
|
||||||
|
speed := util.ByteFmt(task.Progress.Speed)
|
||||||
|
totalSize := util.ByteFmt(task.Meta.Res.Size)
|
||||||
|
sb.WriteString(fmt.Sprintf("\r%s [", title))
|
||||||
|
for i := 0; i < progressWidth; i++ {
|
||||||
|
if i < completeWidth {
|
||||||
|
sb.WriteString("■")
|
||||||
|
} else {
|
||||||
|
sb.WriteString("□")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sb.WriteString(fmt.Sprintf("] %.1f%% %s/s %s", rate*100, speed, totalSize))
|
||||||
|
if lastLineLen != 0 {
|
||||||
|
paddingLen := lastLineLen - sb.Len()
|
||||||
|
if paddingLen > 0 {
|
||||||
|
sb.WriteString(strings.Repeat(" ", paddingLen))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lastLineLen = sb.Len()
|
||||||
|
fmt.Print(sb.String())
|
||||||
|
sb.Reset()
|
||||||
|
}
|
||||||
-28
@@ -1,28 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/monkeyWie/gopeed-core/download"
|
|
||||||
"github.com/monkeyWie/gopeed-core/download/common"
|
|
||||||
"github.com/monkeyWie/gopeed-core/download/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
downloader := buildDownloader()
|
|
||||||
res, err := downloader.Resolve(&common.Request{
|
|
||||||
URL: "http://192.168.200.163:8088/docker-compose",
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
err = downloader.Create(res, &common.Options{
|
|
||||||
Path: "E:\\test\\gopeed\\http",
|
|
||||||
Connections: 4,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildDownloader() *download.Downloader {
|
|
||||||
return download.NewDownloader(&http.Fetcher{BaseFetcher: &common.BaseFetcher{}})
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
"fmt"
|
||||||
|
"github.com/GopeedLab/gopeed/pkg/rest"
|
||||||
|
"github.com/GopeedLab/gopeed/pkg/rest/model"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed banner.txt
|
||||||
|
var banner string
|
||||||
|
|
||||||
|
func Start(cfg *model.StartConfig) {
|
||||||
|
fmt.Println(banner)
|
||||||
|
srv, listener, err := rest.BuildServer(cfg)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
downloadCfg, err := rest.Downloader.GetConfig()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if downloadCfg.FirstLoad {
|
||||||
|
// Set default download dir to user download dir
|
||||||
|
userDir, err := os.UserHomeDir()
|
||||||
|
if err == nil {
|
||||||
|
downloadDir := filepath.Join(userDir, "Downloads")
|
||||||
|
downloadCfg.DownloadDir = downloadDir
|
||||||
|
rest.Downloader.PutConfig(downloadCfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
fmt.Printf("Server start success on http://%s\n", listener.Addr().String())
|
||||||
|
if err := srv.Serve(listener); err != nil && err != http.ErrServerClosed {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
//go:build web
|
||||||
|
// +build web
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"embed"
|
||||||
|
"github.com/GopeedLab/gopeed/cmd"
|
||||||
|
"github.com/GopeedLab/gopeed/pkg/rest/model"
|
||||||
|
"io/fs"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed dist/*
|
||||||
|
var dist embed.FS
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
sub, err := fs.Sub(dist, "dist")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
cfg := &model.StartConfig{
|
||||||
|
Network: "tcp",
|
||||||
|
Address: "0.0.0.0:9999",
|
||||||
|
Storage: model.StorageBolt,
|
||||||
|
WebEnable: true,
|
||||||
|
WebFS: sub,
|
||||||
|
}
|
||||||
|
cmd.Start(cfg)
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
version: '3'
|
||||||
|
|
||||||
|
services:
|
||||||
|
gopeed:
|
||||||
|
container_name: gopeed
|
||||||
|
ports:
|
||||||
|
- 9999:9999 # HTTP port (host:container)
|
||||||
|
image: liwei2633/gopeed
|
||||||
|
volumes:
|
||||||
|
- ~/Downloads:/root/Downloads # replace with your prefer path
|
||||||
|
restart: unless-stopped
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
package common
|
|
||||||
|
|
||||||
import "errors"
|
|
||||||
|
|
||||||
type Status int
|
|
||||||
|
|
||||||
const (
|
|
||||||
DownloadStatusReady Status = iota
|
|
||||||
DownloadStatusStart
|
|
||||||
DownloadStatusPause
|
|
||||||
DownloadStatusError
|
|
||||||
DownloadStatusDone
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
HttpCodeOK = 200
|
|
||||||
HttpCodePartialContent = 206
|
|
||||||
|
|
||||||
HttpHeaderRange = "Range"
|
|
||||||
HttpHeaderContentLength = "Content-Length"
|
|
||||||
HttpHeaderContentRange = "Content-Range"
|
|
||||||
HttpHeaderContentDisposition = "Content-Disposition"
|
|
||||||
|
|
||||||
HttpHeaderRangeFormat = "bytes=%d-%d"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
PauseErr = errors.New("pause")
|
|
||||||
DeleteErr = errors.New("delete")
|
|
||||||
)
|
|
||||||
@@ -1,149 +0,0 @@
|
|||||||
package common
|
|
||||||
|
|
||||||
import (
|
|
||||||
"golang.org/x/net/proxy"
|
|
||||||
"net"
|
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// 下载请求
|
|
||||||
type Request struct {
|
|
||||||
// 下载链接
|
|
||||||
URL string
|
|
||||||
// 附加信息
|
|
||||||
Extra interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 资源信息
|
|
||||||
type Resource struct {
|
|
||||||
Req *Request
|
|
||||||
// 资源总大小
|
|
||||||
Size int64
|
|
||||||
// 是否支持断点下载
|
|
||||||
Range bool
|
|
||||||
// 资源所包含的文件列表
|
|
||||||
Files []*FileInfo
|
|
||||||
}
|
|
||||||
|
|
||||||
type Options struct {
|
|
||||||
// 保存文件名
|
|
||||||
Name string
|
|
||||||
// 保存目录
|
|
||||||
Path string
|
|
||||||
// 并发连接数
|
|
||||||
Connections int
|
|
||||||
}
|
|
||||||
|
|
||||||
type FileInfo struct {
|
|
||||||
Name string
|
|
||||||
Path string
|
|
||||||
Size int64
|
|
||||||
}
|
|
||||||
|
|
||||||
type Process interface {
|
|
||||||
Start() error
|
|
||||||
Pause() error
|
|
||||||
Continue() error
|
|
||||||
Delete() error
|
|
||||||
}
|
|
||||||
|
|
||||||
type FileCreator interface {
|
|
||||||
Create(name string, size int64) (file *os.File, err error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Controller struct {
|
|
||||||
Files map[string]*os.File
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewController() *Controller {
|
|
||||||
return &Controller{Files: make(map[string]*os.File)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Controller) Touch(name string, size int64) (file *os.File, err error) {
|
|
||||||
if size > 0 {
|
|
||||||
err := os.Truncate(name, size)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
file, err = os.OpenFile(name, os.O_RDWR, 0666)
|
|
||||||
} else {
|
|
||||||
file, err = os.Create(name)
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
c.Files[name] = file
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Controller) Open(name string) (file *os.File, err error) {
|
|
||||||
file, err = os.OpenFile(name, os.O_RDWR, 0666)
|
|
||||||
if err == nil {
|
|
||||||
c.Files[name] = file
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Controller) Write(name string, offset int64, buf []byte) (int, error) {
|
|
||||||
return c.Files[name].WriteAt(buf, offset)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Controller) Close(name string) error {
|
|
||||||
err := c.Files[name].Close()
|
|
||||||
delete(c.Files, name)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Controller) ContextDialer() (proxy.Dialer, error) {
|
|
||||||
// return proxy.SOCKS5("tpc", "127.0.0.1:9999", nil, nil)
|
|
||||||
var dialer proxy.Dialer
|
|
||||||
return &DialerWarp{dialer: dialer}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type DialerWarp struct {
|
|
||||||
dialer proxy.Dialer
|
|
||||||
}
|
|
||||||
|
|
||||||
type ConnWarp struct {
|
|
||||||
conn net.Conn
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ConnWarp) Read(b []byte) (n int, err error) {
|
|
||||||
return c.conn.Read(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ConnWarp) Write(b []byte) (n int, err error) {
|
|
||||||
return c.conn.Write(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ConnWarp) Close() error {
|
|
||||||
return c.conn.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ConnWarp) LocalAddr() net.Addr {
|
|
||||||
return c.conn.LocalAddr()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ConnWarp) RemoteAddr() net.Addr {
|
|
||||||
return c.conn.RemoteAddr()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ConnWarp) SetDeadline(t time.Time) error {
|
|
||||||
return c.conn.SetDeadline(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ConnWarp) SetReadDeadline(t time.Time) error {
|
|
||||||
return c.conn.SetReadDeadline(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ConnWarp) SetWriteDeadline(t time.Time) error {
|
|
||||||
return c.conn.SetWriteDeadline(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DialerWarp) Dial(network, addr string) (c net.Conn, err error) {
|
|
||||||
conn, err := d.dialer.Dial(network, addr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &ConnWarp{conn: conn}, nil
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
package common
|
|
||||||
|
|
||||||
// 对应协议的下载支持
|
|
||||||
type Fetcher interface {
|
|
||||||
BaseFetch
|
|
||||||
// 支持的协议列表
|
|
||||||
Protocols() []string
|
|
||||||
// 解析请求
|
|
||||||
Resolve(req *Request) (*Resource, error)
|
|
||||||
// 创建任务
|
|
||||||
Create(res *Resource, opts *Options) (Process, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type BaseFetch interface {
|
|
||||||
InitCtl(ctl *Controller)
|
|
||||||
GetCtl() *Controller
|
|
||||||
}
|
|
||||||
|
|
||||||
type BaseFetcher struct {
|
|
||||||
ctl *Controller
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *BaseFetcher) InitCtl(ctl *Controller) {
|
|
||||||
b.ctl = ctl
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *BaseFetcher) GetCtl() *Controller {
|
|
||||||
return b.ctl
|
|
||||||
}
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
package download
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/monkeyWie/gopeed-core/download/common"
|
|
||||||
"net/url"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type TaskInfo struct {
|
|
||||||
ID string
|
|
||||||
Res *common.Resource
|
|
||||||
Opts *common.Options
|
|
||||||
Status common.Status
|
|
||||||
Files map[string]*common.FileInfo
|
|
||||||
|
|
||||||
process common.Process
|
|
||||||
}
|
|
||||||
|
|
||||||
type Downloader struct {
|
|
||||||
fetches map[string]common.Fetcher
|
|
||||||
tasks map[string]*TaskInfo
|
|
||||||
*common.Controller
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewDownloader(fetchers ...common.Fetcher) *Downloader {
|
|
||||||
d := &Downloader{Controller: common.NewController()}
|
|
||||||
d.fetches = make(map[string]common.Fetcher)
|
|
||||||
for _, f := range fetchers {
|
|
||||||
for _, p := range f.Protocols() {
|
|
||||||
d.fetches[strings.ToUpper(p)] = f
|
|
||||||
}
|
|
||||||
f.InitCtl(d.Controller)
|
|
||||||
}
|
|
||||||
d.tasks = make(map[string]*TaskInfo)
|
|
||||||
return d
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Downloader) getFetcher(URL string) (common.Fetcher, error) {
|
|
||||||
url, err := url.Parse(URL)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if fetch, ok := d.fetches[strings.ToUpper(url.Scheme)]; ok {
|
|
||||||
return fetch, nil
|
|
||||||
}
|
|
||||||
return nil, errors.New("unsupported protocol")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Downloader) Resolve(req *common.Request) (*common.Resource, error) {
|
|
||||||
fetcher, err := d.getFetcher(req.URL)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return fetcher.Resolve(req)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Downloader) Create(res *common.Resource, opts *common.Options) error {
|
|
||||||
fetcher, err := d.getFetcher(res.Req.URL)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
process, err := fetcher.Create(res, opts)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
id := uuid.New().String()
|
|
||||||
d.tasks[id] = &TaskInfo{
|
|
||||||
ID: id,
|
|
||||||
Res: res,
|
|
||||||
Opts: opts,
|
|
||||||
Status: common.DownloadStatusReady,
|
|
||||||
process: process,
|
|
||||||
}
|
|
||||||
return process.Start()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Downloader) Pause(id string) {
|
|
||||||
d.tasks[id].process.Pause()
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
package download
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/monkeyWie/gopeed-core/download/common"
|
|
||||||
"github.com/monkeyWie/gopeed-core/download/http"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestDownloader_Resolve(t *testing.T) {
|
|
||||||
downloader := buildDownloader()
|
|
||||||
res, err := downloader.Resolve(&common.Request{
|
|
||||||
URL: "http://192.168.200.163:8088/docker-compose",
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
fmt.Println(res)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDownloader_Create(t *testing.T) {
|
|
||||||
downloader := buildDownloader()
|
|
||||||
res, err := downloader.Resolve(&common.Request{
|
|
||||||
URL: "http://192.168.200.163:8088/docker-compose",
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
err = downloader.Create(res, &common.Options{
|
|
||||||
Path: "E:\\test\\gopeed\\http",
|
|
||||||
Connections: 4,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildDownloader() *Downloader {
|
|
||||||
return NewDownloader(&http.Fetcher{BaseFetcher: &common.BaseFetcher{}})
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
package file_allocate
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
type FileAllocate interface {
|
|
||||||
Allocate(name string, size int64) (file *os.File, err error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type DefaultFileAllocator struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *DefaultFileAllocator) Allocate(name string, size int64) (file *os.File, err error) {
|
|
||||||
err = os.Truncate(name, size)
|
|
||||||
file, err = os.OpenFile(name, os.O_RDWR, 0644)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
package file_allocate
|
|
||||||
|
|
||||||
import "os"
|
|
||||||
|
|
||||||
type PreFileAllocator struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PreFileAllocator) Allocate(name string, size int64) (file *os.File, err error) {
|
|
||||||
err = os.Truncate(name, size)
|
|
||||||
file, err = os.OpenFile(name, os.O_RDWR, 0644)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
@@ -1,167 +0,0 @@
|
|||||||
package http
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"github.com/monkeyWie/gopeed-core/download/common"
|
|
||||||
"github.com/monkeyWie/gopeed-core/download/http/model"
|
|
||||||
"io/ioutil"
|
|
||||||
"mime"
|
|
||||||
"net/http"
|
|
||||||
"net/http/cookiejar"
|
|
||||||
"net/url"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type RequestError struct {
|
|
||||||
Code int
|
|
||||||
Msg string
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRequestError(code int, msg string) *RequestError {
|
|
||||||
return &RequestError{Code: code, Msg: msg}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (re *RequestError) Error() string {
|
|
||||||
return fmt.Sprintf("http request fail,code:%d", re.Code)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Fetcher struct {
|
|
||||||
*common.BaseFetcher
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewFetcher() *Fetcher {
|
|
||||||
return &Fetcher{BaseFetcher: &common.BaseFetcher{}}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *Fetcher) Protocols() []string {
|
|
||||||
return []string{"HTTP", "HTTPS"}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *Fetcher) Resolve(req *common.Request) (*common.Resource, error) {
|
|
||||||
httpReq, err := BuildRequest(req)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
client := BuildClient()
|
|
||||||
// 只访问一个字节,测试资源是否支持Range请求
|
|
||||||
httpReq.Header.Set(common.HttpHeaderRange, fmt.Sprintf(common.HttpHeaderRangeFormat, 0, 0))
|
|
||||||
httpResp, err := client.Do(httpReq)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// 拿到响应头就关闭,不用加defer
|
|
||||||
httpResp.Body.Close()
|
|
||||||
res := &common.Resource{
|
|
||||||
Req: req,
|
|
||||||
Range: false,
|
|
||||||
Files: []*common.FileInfo{},
|
|
||||||
}
|
|
||||||
if common.HttpCodePartialContent == httpResp.StatusCode {
|
|
||||||
// 返回206响应码表示支持断点下载
|
|
||||||
res.Range = true
|
|
||||||
// 解析资源大小: bytes 0-1000/1001 => 1001
|
|
||||||
contentTotal := subLastSlash(httpResp.Header.Get(common.HttpHeaderContentRange))
|
|
||||||
if contentTotal != "" {
|
|
||||||
parse, err := strconv.ParseInt(contentTotal, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
res.Size = parse
|
|
||||||
}
|
|
||||||
} else if common.HttpCodeOK == httpResp.StatusCode {
|
|
||||||
// 返回200响应码,不支持断点下载,通过Content-Length头获取文件大小,获取不到的话可能是chunked编码
|
|
||||||
contentLength := httpResp.Header.Get(common.HttpHeaderContentLength)
|
|
||||||
if contentLength != "" {
|
|
||||||
parse, err := strconv.ParseInt(contentLength, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
res.Size = parse
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return nil, NewRequestError(httpResp.StatusCode, httpResp.Status)
|
|
||||||
}
|
|
||||||
file := &common.FileInfo{
|
|
||||||
Size: res.Size,
|
|
||||||
}
|
|
||||||
contentDisposition := httpResp.Header.Get(common.HttpHeaderContentDisposition)
|
|
||||||
if contentDisposition != "" {
|
|
||||||
_, params, _ := mime.ParseMediaType(contentDisposition)
|
|
||||||
filename := params["filename"]
|
|
||||||
if filename != "" {
|
|
||||||
file.Name = filename
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Get file name by URL
|
|
||||||
if file.Name == "" {
|
|
||||||
parse, err := url.Parse(req.URL)
|
|
||||||
if err == nil {
|
|
||||||
// e.g. /files/test.txt => test.txt
|
|
||||||
file.Name = subLastSlash(parse.Path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// unknown file name
|
|
||||||
if file.Name == "" {
|
|
||||||
file.Name = "unknown"
|
|
||||||
}
|
|
||||||
res.Files = append(res.Files, file)
|
|
||||||
return res, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *Fetcher) Create(res *common.Resource, opts *common.Options) (common.Process, error) {
|
|
||||||
if opts.Connections != 1 && !res.Range {
|
|
||||||
opts.Connections = 1
|
|
||||||
}
|
|
||||||
return NewProcess(h, res, opts), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func subLastSlash(str string) string {
|
|
||||||
if str == "" {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
index := strings.LastIndex(str, "/")
|
|
||||||
if index != -1 {
|
|
||||||
return str[index+1:]
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func BuildClient() *http.Client {
|
|
||||||
// Cookie handle
|
|
||||||
jar, _ := cookiejar.New(nil)
|
|
||||||
return &http.Client{
|
|
||||||
Jar: jar,
|
|
||||||
Timeout: time.Second * 10,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BuildRequest(req *common.Request) (*http.Request, error) {
|
|
||||||
url, err := url.Parse(req.URL)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
httpReq := &http.Request{
|
|
||||||
URL: url,
|
|
||||||
Header: map[string][]string{},
|
|
||||||
}
|
|
||||||
|
|
||||||
if req.Extra != nil {
|
|
||||||
if extra, ok := req.Extra.(model.Extra); ok {
|
|
||||||
if extra.Method != "" {
|
|
||||||
httpReq.Method = extra.Method
|
|
||||||
} else {
|
|
||||||
httpReq.Method = http.MethodGet
|
|
||||||
}
|
|
||||||
if len(extra.Header) > 0 {
|
|
||||||
for k, v := range extra.Header {
|
|
||||||
httpReq.Header[k] = []string{v}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
httpReq.Body = ioutil.NopCloser(bytes.NewBufferString(extra.Body))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return httpReq, nil
|
|
||||||
}
|
|
||||||
@@ -1,307 +0,0 @@
|
|||||||
package http
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/md5"
|
|
||||||
"encoding/hex"
|
|
||||||
"fmt"
|
|
||||||
"github.com/monkeyWie/gopeed-core/download/common"
|
|
||||||
"io"
|
|
||||||
"math/rand"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
const testDir = "./"
|
|
||||||
const buildSize = 200 * 1024 * 1024
|
|
||||||
const buildName = "build.data"
|
|
||||||
const buildFile = testDir + buildName
|
|
||||||
const downloadName = "download.data"
|
|
||||||
const downloadFile = testDir + downloadName
|
|
||||||
|
|
||||||
func TestFetcher_Resolve(t *testing.T) {
|
|
||||||
testResolve(startTestFileServer, &common.Resource{
|
|
||||||
Size: buildSize,
|
|
||||||
Range: true,
|
|
||||||
Files: []*common.FileInfo{
|
|
||||||
{
|
|
||||||
Name: buildName,
|
|
||||||
Size: buildSize,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}, t)
|
|
||||||
testResolve(startTestChunkedServer, &common.Resource{
|
|
||||||
Size: 0,
|
|
||||||
Range: false,
|
|
||||||
Files: []*common.FileInfo{
|
|
||||||
{
|
|
||||||
Name: buildName,
|
|
||||||
Size: 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func testResolve(startTestServer func() net.Listener, want *common.Resource, t *testing.T) {
|
|
||||||
listener := startTestServer()
|
|
||||||
defer listener.Close()
|
|
||||||
fetcher := NewFetcher()
|
|
||||||
res, err := fetcher.Resolve(&common.Request{
|
|
||||||
URL: "http://" + listener.Addr().String() + "/" + buildName,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
res.Req = nil
|
|
||||||
if !reflect.DeepEqual(want, res) {
|
|
||||||
t.Errorf("Resolve error = %v, want %v", res, want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFetcher_DownloadNormal(t *testing.T) {
|
|
||||||
listener := startTestFileServer()
|
|
||||||
defer listener.Close()
|
|
||||||
// 正常下载
|
|
||||||
downloadNormal(listener, 1, t)
|
|
||||||
downloadNormal(listener, 5, t)
|
|
||||||
downloadNormal(listener, 8, t)
|
|
||||||
downloadNormal(listener, 16, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFetcher_DownloadContinue(t *testing.T) {
|
|
||||||
listener := startTestFileServer()
|
|
||||||
defer listener.Close()
|
|
||||||
// 暂停继续
|
|
||||||
downloadContinue(listener, 1, t)
|
|
||||||
fmt.Println("d1")
|
|
||||||
downloadContinue(listener, 5, t)
|
|
||||||
fmt.Println("d5")
|
|
||||||
downloadContinue(listener, 8, t)
|
|
||||||
fmt.Println("d8")
|
|
||||||
downloadContinue(listener, 16, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFetcher_DownloadChunked(t *testing.T) {
|
|
||||||
listener := startTestChunkedServer()
|
|
||||||
defer listener.Close()
|
|
||||||
// chunked编码下载
|
|
||||||
//downloadNormal(listener, 1, t)
|
|
||||||
downloadContinue(listener, 1, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFetcher_DownloadRetry(t *testing.T) {
|
|
||||||
listener := startTestRetryServer()
|
|
||||||
defer listener.Close()
|
|
||||||
// chunked编码下载
|
|
||||||
downloadNormal(listener, 1, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFetcher_DownloadError(t *testing.T) {
|
|
||||||
listener := startTestErrorServer()
|
|
||||||
defer listener.Close()
|
|
||||||
// chunked编码下载
|
|
||||||
downloadError(listener, 1, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func startTestFileServer() net.Listener {
|
|
||||||
return startTestServer(func() http.Handler {
|
|
||||||
return http.FileServer(http.Dir(testDir))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func startTestChunkedServer() net.Listener {
|
|
||||||
return startTestServer(func() http.Handler {
|
|
||||||
mux := http.NewServeMux()
|
|
||||||
mux.HandleFunc("/"+buildName, func(writer http.ResponseWriter, request *http.Request) {
|
|
||||||
file, err := os.Open(buildFile)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
io.Copy(writer, file)
|
|
||||||
})
|
|
||||||
return mux
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func startTestRetryServer() net.Listener {
|
|
||||||
counter := 0
|
|
||||||
return startTestServer(func() http.Handler {
|
|
||||||
mux := http.NewServeMux()
|
|
||||||
mux.HandleFunc("/"+buildName, func(writer http.ResponseWriter, request *http.Request) {
|
|
||||||
counter++
|
|
||||||
if counter != 1 && counter < 5 {
|
|
||||||
writer.WriteHeader(500)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
file, err := os.Open(buildFile)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
io.Copy(writer, file)
|
|
||||||
})
|
|
||||||
return mux
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func startTestErrorServer() net.Listener {
|
|
||||||
counter := 0
|
|
||||||
return startTestServer(func() http.Handler {
|
|
||||||
mux := http.NewServeMux()
|
|
||||||
mux.HandleFunc("/"+buildName, func(writer http.ResponseWriter, request *http.Request) {
|
|
||||||
counter++
|
|
||||||
if counter != 1 {
|
|
||||||
writer.WriteHeader(500)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return mux
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func startTestServer(serverHandle func() http.Handler) net.Listener {
|
|
||||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
file, err := os.Create(buildFile)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
// 随机生成一个文件
|
|
||||||
l := int64(8192)
|
|
||||||
buf := make([]byte, l)
|
|
||||||
size := int64(0)
|
|
||||||
for {
|
|
||||||
_, err := rand.Read(buf)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
if size+l >= buildSize {
|
|
||||||
file.WriteAt(buf[0:buildSize-size], size)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
file.WriteAt(buf, size)
|
|
||||||
size += l
|
|
||||||
}
|
|
||||||
server := &http.Server{}
|
|
||||||
server.Handler = serverHandle()
|
|
||||||
go server.Serve(listener)
|
|
||||||
|
|
||||||
return &delFileListener{
|
|
||||||
File: file,
|
|
||||||
Listener: listener,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type delFileListener struct {
|
|
||||||
*os.File
|
|
||||||
net.Listener
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *delFileListener) Close() error {
|
|
||||||
defer func() {
|
|
||||||
c.File.Close()
|
|
||||||
if err := ifExistAndRemove(c.File.Name()); err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
}
|
|
||||||
if err := ifExistAndRemove(downloadFile); err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
return c.Listener.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func ifExistAndRemove(name string) error {
|
|
||||||
if _, err := os.Stat(name); !os.IsNotExist(err) {
|
|
||||||
return os.Remove(name)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func downloadReady(listener net.Listener, connections int, t *testing.T) common.Process {
|
|
||||||
fetcher := NewFetcher()
|
|
||||||
fetcher.InitCtl(common.NewController())
|
|
||||||
res, err := fetcher.Resolve(&common.Request{
|
|
||||||
URL: "http://" + listener.Addr().String() + "/" + buildName,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
process, err := fetcher.Create(res, &common.Options{
|
|
||||||
Name: downloadName,
|
|
||||||
Path: testDir,
|
|
||||||
Connections: connections,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
return process
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func downloadNormal(listener net.Listener, connections int, t *testing.T) {
|
|
||||||
process := downloadReady(listener, connections, t)
|
|
||||||
err := process.Start()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
want := fileMd5(buildFile)
|
|
||||||
got := fileMd5(downloadFile)
|
|
||||||
if want != got {
|
|
||||||
t.Errorf("Download error = %v, want %v", got, want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func downloadContinue(listener net.Listener, connections int, t *testing.T) {
|
|
||||||
process := downloadReady(listener, connections, t)
|
|
||||||
go func() {
|
|
||||||
err := process.Start()
|
|
||||||
if err != nil && err != common.PauseErr {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
time.Sleep(time.Millisecond * 200)
|
|
||||||
if err := process.Pause(); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
time.Sleep(time.Millisecond * 200)
|
|
||||||
if err := process.Continue(); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
want := fileMd5(buildFile)
|
|
||||||
got := fileMd5(downloadFile)
|
|
||||||
if want != got {
|
|
||||||
t.Errorf("Download error = %v, want %v", got, want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func downloadError(listener net.Listener, connections int, t *testing.T) {
|
|
||||||
process := downloadReady(listener, connections, t)
|
|
||||||
err := process.Start()
|
|
||||||
if err == nil {
|
|
||||||
t.Errorf("Download error = %v, want %v", err, nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func fileMd5(filePath string) string {
|
|
||||||
file, err := os.Open(filePath)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
// Tell the program to call the following function when the current function returns
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
// Open a new hash interface to write to
|
|
||||||
hash := md5.New()
|
|
||||||
|
|
||||||
// Copy the file in the hash interface and check for any error
|
|
||||||
if _, err := io.Copy(hash, file); err != nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return hex.EncodeToString(hash.Sum(nil))
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
package model
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/monkeyWie/gopeed-core/download/common"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Chunk struct {
|
|
||||||
Status common.Status
|
|
||||||
Begin int64
|
|
||||||
End int64
|
|
||||||
Downloaded int64
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewChunk(begin int64, end int64) *Chunk {
|
|
||||||
return &Chunk{
|
|
||||||
Status: common.DownloadStatusReady,
|
|
||||||
Begin: begin,
|
|
||||||
End: end,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
package model
|
|
||||||
|
|
||||||
type Extra struct {
|
|
||||||
Method string
|
|
||||||
Header map[string]string
|
|
||||||
Body string
|
|
||||||
}
|
|
||||||
@@ -1,245 +0,0 @@
|
|||||||
package http
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/monkeyWie/gopeed-core/download/common"
|
|
||||||
"github.com/monkeyWie/gopeed-core/download/http/model"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"path/filepath"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Process struct {
|
|
||||||
fetcher *Fetcher
|
|
||||||
|
|
||||||
res *common.Resource
|
|
||||||
opts *common.Options
|
|
||||||
status common.Status
|
|
||||||
clients []*http.Response
|
|
||||||
chunks []*model.Chunk
|
|
||||||
|
|
||||||
pauseCond *sync.Cond
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewProcess(fetcher *Fetcher, res *common.Resource, opts *common.Options) *Process {
|
|
||||||
return &Process{
|
|
||||||
fetcher: fetcher,
|
|
||||||
res: res,
|
|
||||||
opts: opts,
|
|
||||||
status: common.DownloadStatusReady,
|
|
||||||
|
|
||||||
pauseCond: sync.NewCond(&sync.Mutex{}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Process) Start() error {
|
|
||||||
ctl := p.fetcher.GetCtl()
|
|
||||||
// 创建文件
|
|
||||||
name := p.name()
|
|
||||||
_, err := ctl.Touch(name, p.res.Size)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer p.close()
|
|
||||||
p.status = common.DownloadStatusStart
|
|
||||||
if p.res.Range {
|
|
||||||
// 每个连接平均需要下载的分块大小
|
|
||||||
chunkSize := p.res.Size / int64(p.opts.Connections)
|
|
||||||
p.chunks = make([]*model.Chunk, p.opts.Connections)
|
|
||||||
p.clients = make([]*http.Response, p.opts.Connections)
|
|
||||||
for i := 0; i < p.opts.Connections; i++ {
|
|
||||||
var (
|
|
||||||
begin = chunkSize * int64(i)
|
|
||||||
end int64
|
|
||||||
)
|
|
||||||
if i == p.opts.Connections-1 {
|
|
||||||
// 最后一个分块需要保证把文件下载完
|
|
||||||
end = p.res.Size
|
|
||||||
} else {
|
|
||||||
end = begin + chunkSize
|
|
||||||
}
|
|
||||||
chunk := model.NewChunk(begin, end)
|
|
||||||
p.chunks[i] = chunk
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 只支持单连接下载
|
|
||||||
p.chunks = make([]*model.Chunk, 1)
|
|
||||||
p.clients = make([]*http.Response, 1)
|
|
||||||
p.chunks[0] = model.NewChunk(0, 0)
|
|
||||||
}
|
|
||||||
return p.fetch()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Process) Pause() error {
|
|
||||||
p.pauseCond.L.Lock()
|
|
||||||
defer p.pauseCond.L.Unlock()
|
|
||||||
if common.DownloadStatusStart != p.status {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
p.status = common.DownloadStatusPause
|
|
||||||
p.stop()
|
|
||||||
// 释放锁,并等待下载结束
|
|
||||||
p.pauseCond.Wait()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Process) Continue() error {
|
|
||||||
if func() bool {
|
|
||||||
p.pauseCond.L.Lock()
|
|
||||||
defer p.pauseCond.L.Unlock()
|
|
||||||
if common.DownloadStatusPause != p.status && common.DownloadStatusError != p.status {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
p.status = common.DownloadStatusStart
|
|
||||||
return false
|
|
||||||
}() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
ctl = p.fetcher.GetCtl()
|
|
||||||
name = p.name()
|
|
||||||
)
|
|
||||||
_, err := ctl.Open(name)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer ctl.Close(name)
|
|
||||||
return p.fetch()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Process) Delete() error {
|
|
||||||
panic("implement me")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Process) close() error {
|
|
||||||
return p.fetcher.GetCtl().Close(p.name())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Process) name() string {
|
|
||||||
// 创建文件
|
|
||||||
var filename = p.opts.Name
|
|
||||||
if filename == "" {
|
|
||||||
filename = p.res.Files[0].Name
|
|
||||||
}
|
|
||||||
return filepath.Join(p.opts.Path, filename)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Process) fetch() error {
|
|
||||||
errCh := make(chan error, p.opts.Connections)
|
|
||||||
defer close(errCh)
|
|
||||||
|
|
||||||
for i := 0; i < p.opts.Connections; i++ {
|
|
||||||
go func(i int) {
|
|
||||||
errCh <- p.fetchChunk(i, p.name(), p.chunks[i])
|
|
||||||
}(i)
|
|
||||||
}
|
|
||||||
|
|
||||||
var retErr error
|
|
||||||
for i := 0; i < p.opts.Connections; i++ {
|
|
||||||
err := <-errCh
|
|
||||||
if err != nil && retErr == nil {
|
|
||||||
retErr = err
|
|
||||||
// 有一个连接失败就立即终止下载
|
|
||||||
if retErr != common.PauseErr {
|
|
||||||
func() {
|
|
||||||
p.pauseCond.L.Lock()
|
|
||||||
defer p.pauseCond.L.Unlock()
|
|
||||||
p.stop()
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
p.pauseCond.Signal()
|
|
||||||
return retErr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Process) stop() {
|
|
||||||
if len(p.clients) > 0 {
|
|
||||||
for _, client := range p.clients {
|
|
||||||
fmt.Println("client close")
|
|
||||||
if client != nil {
|
|
||||||
client.Body.Close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Process) fetchChunk(index int, name string, chunk *model.Chunk) (err error) {
|
|
||||||
httpReq, err := BuildRequest(p.res.Req)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var (
|
|
||||||
client = BuildClient()
|
|
||||||
buf = make([]byte, 8192)
|
|
||||||
)
|
|
||||||
// 重试5次
|
|
||||||
for i := 0; i < 5; i++ {
|
|
||||||
var (
|
|
||||||
resp *http.Response
|
|
||||||
retry bool
|
|
||||||
)
|
|
||||||
if p.res.Range {
|
|
||||||
httpReq.Header.Set(common.HttpHeaderRange,
|
|
||||||
fmt.Sprintf(common.HttpHeaderRangeFormat, chunk.Begin+chunk.Downloaded, chunk.End))
|
|
||||||
} else {
|
|
||||||
chunk.Downloaded = 0
|
|
||||||
}
|
|
||||||
err = func() error {
|
|
||||||
p.pauseCond.L.Lock()
|
|
||||||
defer p.pauseCond.L.Unlock()
|
|
||||||
if p.status == common.DownloadStatusPause {
|
|
||||||
return common.PauseErr
|
|
||||||
}
|
|
||||||
resp, err = client.Do(httpReq)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
p.clients[index] = resp
|
|
||||||
if resp.StatusCode != common.HttpCodeOK && resp.StatusCode != common.HttpCodePartialContent {
|
|
||||||
err = NewRequestError(resp.StatusCode, resp.Status)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}()
|
|
||||||
if err != nil {
|
|
||||||
if err == common.PauseErr {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// 请求失败重试
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
retry, err = func() (bool, error) {
|
|
||||||
defer resp.Body.Close()
|
|
||||||
for {
|
|
||||||
n, err := resp.Body.Read(buf)
|
|
||||||
if n > 0 {
|
|
||||||
_, err := p.fetcher.GetCtl().Write(name, chunk.Begin+chunk.Downloaded, buf[:n])
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
chunk.Downloaded += int64(n)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
if err != io.EOF {
|
|
||||||
return true, err
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false, nil
|
|
||||||
}()
|
|
||||||
if err == nil || !retry {
|
|
||||||
// 下载成功,跳出重试
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
p.chunks[index].Status = common.DownloadStatusError
|
|
||||||
} else {
|
|
||||||
p.chunks[index].Status = common.DownloadStatusDone
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
@@ -1,12 +1,81 @@
|
|||||||
module github.com/monkeyWie/gopeed-core
|
module github.com/GopeedLab/gopeed
|
||||||
|
|
||||||
go 1.13
|
go 1.19
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/RoaringBitmap/roaring v0.4.21
|
github.com/anacrolix/missinggo/v2 v2.7.2-0.20230527121029-a582b4f397b9
|
||||||
github.com/cenkalti/mse v0.0.0-20140930130441-6ef65f170972
|
github.com/anacrolix/torrent v1.52.3
|
||||||
github.com/google/uuid v1.1.1
|
github.com/gorilla/handlers v1.5.1
|
||||||
github.com/marksamman/bencode v0.0.0-20150821143521-dc84f26e086e
|
github.com/gorilla/mux v1.8.0
|
||||||
github.com/sirupsen/logrus v1.4.2
|
github.com/matoous/go-nanoid/v2 v2.0.0
|
||||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381
|
github.com/virtuald/go-paniclog v0.0.0-20190812204905-43a7fa316459
|
||||||
|
go.etcd.io/bbolt v1.3.6
|
||||||
|
golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde
|
||||||
|
)
|
||||||
|
|
||||||
|
replace github.com/anacrolix/torrent => github.com/monkeyWie/torrent v1.52.4-alpha
|
||||||
|
|
||||||
|
require (
|
||||||
|
crawshaw.io/sqlite v0.3.3-0.20220618202545-d1964889ea3c // indirect
|
||||||
|
github.com/RoaringBitmap/roaring v1.2.3 // indirect
|
||||||
|
github.com/ajwerner/btree v0.0.0-20211221152037-f427b3e689c0 // indirect
|
||||||
|
github.com/alecthomas/atomic v0.1.0-alpha2 // indirect
|
||||||
|
github.com/anacrolix/chansync v0.3.0 // indirect
|
||||||
|
github.com/anacrolix/dht/v2 v2.19.2-0.20221121215055-066ad8494444 // indirect
|
||||||
|
github.com/anacrolix/envpprof v1.2.1 // indirect
|
||||||
|
github.com/anacrolix/generics v0.0.0-20230428105757-683593396d68 // indirect
|
||||||
|
github.com/anacrolix/go-libutp v1.2.0 // indirect
|
||||||
|
github.com/anacrolix/log v0.14.0 // indirect
|
||||||
|
github.com/anacrolix/missinggo v1.3.0 // indirect
|
||||||
|
github.com/anacrolix/missinggo/perf v1.0.0 // indirect
|
||||||
|
github.com/anacrolix/mmsg v1.0.0 // indirect
|
||||||
|
github.com/anacrolix/multiless v0.3.0 // indirect
|
||||||
|
github.com/anacrolix/stm v0.4.0 // indirect
|
||||||
|
github.com/anacrolix/sync v0.4.0 // indirect
|
||||||
|
github.com/anacrolix/upnp v0.1.3-0.20220123035249-922794e51c96 // indirect
|
||||||
|
github.com/anacrolix/utp v0.1.0 // indirect
|
||||||
|
github.com/bahlo/generic-list-go v0.2.0 // indirect
|
||||||
|
github.com/benbjohnson/immutable v0.3.0 // indirect
|
||||||
|
github.com/bits-and-blooms/bitset v1.2.2 // indirect
|
||||||
|
github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 // indirect
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/dustin/go-humanize v1.0.0 // indirect
|
||||||
|
github.com/edsrzf/mmap-go v1.1.0 // indirect
|
||||||
|
github.com/felixge/httpsnoop v1.0.1 // indirect
|
||||||
|
github.com/go-logr/logr v1.2.3 // indirect
|
||||||
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
|
github.com/google/btree v1.1.2 // indirect
|
||||||
|
github.com/google/uuid v1.3.0 // indirect
|
||||||
|
github.com/gorilla/websocket v1.5.0 // indirect
|
||||||
|
github.com/huandu/xstrings v1.3.2 // indirect
|
||||||
|
github.com/lispad/go-generics-tools v1.1.0 // indirect
|
||||||
|
github.com/mschoch/smat v0.2.0 // indirect
|
||||||
|
github.com/pion/datachannel v1.5.2 // indirect
|
||||||
|
github.com/pion/dtls/v2 v2.2.4 // indirect
|
||||||
|
github.com/pion/ice/v2 v2.2.6 // indirect
|
||||||
|
github.com/pion/interceptor v0.1.11 // indirect
|
||||||
|
github.com/pion/logging v0.2.2 // indirect
|
||||||
|
github.com/pion/mdns v0.0.5 // indirect
|
||||||
|
github.com/pion/randutil v0.1.0 // indirect
|
||||||
|
github.com/pion/rtcp v1.2.9 // indirect
|
||||||
|
github.com/pion/rtp v1.7.13 // indirect
|
||||||
|
github.com/pion/sctp v1.8.2 // indirect
|
||||||
|
github.com/pion/sdp/v3 v3.0.5 // indirect
|
||||||
|
github.com/pion/srtp/v2 v2.0.9 // indirect
|
||||||
|
github.com/pion/stun v0.3.5 // indirect
|
||||||
|
github.com/pion/transport v0.13.1 // indirect
|
||||||
|
github.com/pion/transport/v2 v2.0.0 // indirect
|
||||||
|
github.com/pion/turn/v2 v2.0.8 // indirect
|
||||||
|
github.com/pion/udp v0.1.4 // indirect
|
||||||
|
github.com/pion/webrtc/v3 v3.1.42 // indirect
|
||||||
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
|
github.com/rs/dnscache v0.0.0-20211102005908-e0241e321417 // indirect
|
||||||
|
github.com/tidwall/btree v1.6.0 // indirect
|
||||||
|
go.opentelemetry.io/otel v1.8.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/trace v1.8.0 // indirect
|
||||||
|
golang.org/x/crypto v0.5.0 // indirect
|
||||||
|
golang.org/x/exp v0.0.0-20220613132600-b0d781184e0d // indirect
|
||||||
|
golang.org/x/net v0.7.0 // indirect
|
||||||
|
golang.org/x/sys v0.5.0 // indirect
|
||||||
|
golang.org/x/time v0.0.0-20220609170525-579cf78fd858 // indirect
|
||||||
)
|
)
|
||||||
Loaded 50 of 334 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user