mirror of
https://github.com/GopeedLab/gopeed.git
synced 2026-11-10 02:20:28 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4736a91904 | ||
|
|
0a4d74a513 | ||
|
|
a06549a11a | ||
|
|
2cb93a03e2 | ||
|
|
81dfc073a6 | ||
|
|
427921ccab | ||
|
|
f4876c8a0e | ||
|
|
63296f6950 | ||
|
|
4b4c03fd90 | ||
|
|
91dfd78b77 | ||
|
|
35a3625b11 | ||
|
|
dc9b501e35 | ||
|
|
467ada5500 | ||
|
|
85fe92a378 | ||
|
|
983d9ad09e | ||
|
|
d9f757aaf0 |
No files matched your search
+57
-23
@@ -4,17 +4,17 @@ on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
platform:
|
||||
description: "Build platform"
|
||||
description: 'Build platform'
|
||||
required: true
|
||||
default: "all"
|
||||
default: 'all'
|
||||
test:
|
||||
description: "Test mode"
|
||||
description: 'Test mode'
|
||||
required: true
|
||||
default: "false"
|
||||
|
||||
env:
|
||||
GO_VERSION: "1.23"
|
||||
FLUTTER_VERSION: "3.24.3"
|
||||
FLUTTER_VERSION: "3.16.8"
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
build-windows:
|
||||
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'windows' }}
|
||||
runs-on: windows-latest
|
||||
needs: [get-release]
|
||||
needs: [ get-release ]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-go@v4
|
||||
@@ -137,7 +137,7 @@ jobs:
|
||||
build-macos-arm64-lib:
|
||||
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'macos' }}
|
||||
runs-on: macos-latest
|
||||
needs: [get-release]
|
||||
needs: [ get-release ]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-go@v4
|
||||
@@ -155,7 +155,7 @@ jobs:
|
||||
build-macos:
|
||||
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'macos' }}
|
||||
runs-on: macos-13
|
||||
needs: [get-release, build-macos-arm64-lib]
|
||||
needs: [ get-release, build-macos-arm64-lib ]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-go@v4
|
||||
@@ -214,7 +214,7 @@ jobs:
|
||||
build-linux:
|
||||
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'linux' }}
|
||||
runs-on: ubuntu-latest
|
||||
needs: [get-release]
|
||||
needs: [ get-release ]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-go@v4
|
||||
@@ -252,7 +252,7 @@ jobs:
|
||||
build-snap:
|
||||
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'snap' }}
|
||||
runs-on: ubuntu-latest
|
||||
needs: [get-release]
|
||||
needs: [ get-release ]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-go@v4
|
||||
@@ -268,31 +268,31 @@ jobs:
|
||||
run: |
|
||||
go build -tags nosqlite -ldflags="-w -s -X github.com/GopeedLab/gopeed/pkg/base.Version=$VERSION" -buildmode=c-shared -o ui/flutter/linux/bundle/lib/libgopeed.so github.com/GopeedLab/gopeed/bind/desktop
|
||||
cd ui/flutter
|
||||
|
||||
|
||||
sudo snap install snapcraft --classic
|
||||
mkdir -p snap/gui
|
||||
cp assets/icon/icon.svg snap/gui/gopeed.svg
|
||||
|
||||
|
||||
cat>snap/snapcraft.yaml<<EOF
|
||||
name: gopeed
|
||||
version: $VERSION
|
||||
summary: A modern download manager
|
||||
description: High speed downloader that supports all platforms.
|
||||
|
||||
|
||||
confinement: strict
|
||||
base: core22
|
||||
grade: stable
|
||||
|
||||
|
||||
architectures:
|
||||
- build-on: amd64
|
||||
build-for: amd64
|
||||
|
||||
|
||||
slots:
|
||||
dbus-gopeed:
|
||||
interface: dbus
|
||||
bus: session
|
||||
name: com.gopeed.gopeed
|
||||
|
||||
|
||||
apps:
|
||||
gopeed:
|
||||
command: bin/gopeed
|
||||
@@ -345,10 +345,10 @@ jobs:
|
||||
mkdir -p \$CRAFT_PART_INSTALL/bin/
|
||||
cp -r build/linux/*/release/bundle/* \$CRAFT_PART_INSTALL/bin/
|
||||
EOF
|
||||
|
||||
|
||||
cp linux/assets/com.gopeed.Gopeed.desktop snap/gui/gopeed.desktop
|
||||
sed -i 's/Icon=com.gopeed.Gopeed/Icon=\${SNAP}\/meta\/gui\/gopeed.svg/g' snap/gui/gopeed.desktop
|
||||
|
||||
|
||||
snapcraft --use-lxd
|
||||
|
||||
# Snapcraft login
|
||||
@@ -357,7 +357,7 @@ jobs:
|
||||
build-android:
|
||||
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'android' }}
|
||||
runs-on: ubuntu-latest
|
||||
needs: [get-release]
|
||||
needs: [ get-release ]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-go@v4
|
||||
@@ -366,7 +366,7 @@ jobs:
|
||||
- uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: "zulu"
|
||||
java-version: "17"
|
||||
java-version: "11"
|
||||
- uses: subosito/flutter-action@v2
|
||||
with:
|
||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||
@@ -395,7 +395,7 @@ jobs:
|
||||
build-ios:
|
||||
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'ios' }}
|
||||
runs-on: macos-latest
|
||||
needs: [get-release]
|
||||
needs: [ get-release ]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-go@v4
|
||||
@@ -428,7 +428,7 @@ jobs:
|
||||
build-web:
|
||||
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'web' }}
|
||||
runs-on: ubuntu-latest
|
||||
needs: [get-release]
|
||||
needs: [ get-release ]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-go@v4
|
||||
@@ -441,12 +441,13 @@ jobs:
|
||||
env:
|
||||
VERSION: ${{ needs.get-release.outputs.version }}
|
||||
run: |
|
||||
VERSION=${VERSION:-$(date +%y%m%d%H%M)} # Use current time as version if not specified
|
||||
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
|
||||
@@ -468,6 +469,39 @@ jobs:
|
||||
|| true
|
||||
done
|
||||
done
|
||||
sudo apt update -y
|
||||
sudo apt install -y pv python2 bsdmainutils
|
||||
wget -O qdk2_0.32.bionic_amd64.deb "https://github.com/qnap-dev/qdk2/releases/download/v0.32/qdk2_0.32.bionic_amd64.deb"
|
||||
dpkg -X qdk2_0.32.bionic_amd64.deb qdk2 # Direct installs will fail due to missing dependencies!
|
||||
[[ -d qdk2 ]] || exit 1
|
||||
export PATH=$(pwd)/qdk2/usr/bin:$(pwd)/qdk2/usr/share/qdk2/QDK/bin:${PATH}
|
||||
wget -O Gopeed.template.tar.gz "https://github.com/GopeedLab/QpkgBuild/raw/refs/heads/master/template/Gopeed.template.tar.gz"
|
||||
tar -zxf Gopeed.template.tar.gz
|
||||
[[ -d Gopeed ]] || exit 1
|
||||
goos=linux
|
||||
goarch_arr=(amd64 arm64)
|
||||
for goarch in "${goarch_arr[@]}"; do
|
||||
qarch=x86_64
|
||||
[[ "${goarch}" == "arm64" ]] && qarch=arm_64
|
||||
name=gopeed-web-v${VERSION}-${goos}-${goarch}
|
||||
cp dist/${name}/${name}/* Gopeed/${qarch}/
|
||||
done
|
||||
cd Gopeed
|
||||
sed -i -e 's/__QPKG_VER__/${VERSION}/g' qpkg.cfg
|
||||
qbuild || exit 1
|
||||
for f in build/*; do echo ">>> build=[${f}]"; done
|
||||
goos=qnap
|
||||
goarch_arr=(amd64 arm64)
|
||||
for goarch in "${goarch_arr[@]}"; do
|
||||
qarch=x86_64
|
||||
[[ "${goarch}" == "arm64" ]] && qarch=arm_64
|
||||
sname=Gopeed_${VERSION}_${qarch}.qpkg
|
||||
dname=gopeed-${goos}-v${VERSION}-${goarch}.qpkg
|
||||
echo "sname=${sname}, dname=${dname}"
|
||||
[[ -f build/${sname} ]] && cp -ra build/${sname} ../dist/zip/${dname}
|
||||
done
|
||||
for f in ../dist/zip/*; do echo ">>> dist=[${f}]"; done
|
||||
cd ..
|
||||
- name: Upload
|
||||
uses: shogo82148/actions-upload-release-asset@v1
|
||||
with:
|
||||
@@ -477,7 +511,7 @@ jobs:
|
||||
build-docker:
|
||||
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'docker' }}
|
||||
runs-on: ubuntu-latest
|
||||
needs: [get-release]
|
||||
needs: [ get-release ]
|
||||
steps:
|
||||
- name: Remove unnecessary files
|
||||
run: |
|
||||
|
||||
@@ -28,7 +28,7 @@ on:
|
||||
|
||||
env:
|
||||
GO_VERSION: "1.23"
|
||||
FLUTTER_VERSION: "3.24.3"
|
||||
FLUTTER_VERSION: "3.16.8"
|
||||
|
||||
jobs:
|
||||
# lint:
|
||||
@@ -73,17 +73,17 @@ jobs:
|
||||
- uses: subosito/flutter-action@v2
|
||||
with:
|
||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||
- if: runner.os == 'windows-latest'
|
||||
- if: runner.os == 'windows'
|
||||
run: |
|
||||
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
|
||||
- if: runner.os == 'macos-latest'
|
||||
- if: runner.os == 'macOS'
|
||||
run: |
|
||||
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
|
||||
- if: runner.os == 'ubuntu-latest'
|
||||
- if: runner.os == 'Linux'
|
||||
run: |
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install -y ninja-build libgtk-3-dev libayatana-appindicator3-dev
|
||||
|
||||
+2
-3
@@ -8,14 +8,13 @@ RUN CGO_ENABLED=0 go build -tags nosqlite,web \
|
||||
-ldflags="-s -w -X github.com/GopeedLab/gopeed/pkg/base.Version=$VERSION -X github.com/GopeedLab/gopeed/pkg/base.InDocker=true" \
|
||||
-o dist/gopeed github.com/GopeedLab/gopeed/cmd/web
|
||||
|
||||
FROM alpine:3.14.2
|
||||
FROM alpine:3.18
|
||||
LABEL maintainer="monkeyWie"
|
||||
WORKDIR /app
|
||||
COPY --from=go /app/dist/gopeed ./
|
||||
COPY entrypoint.sh ./entrypoint.sh
|
||||
RUN apk update && \
|
||||
apk upgrade --no-cache && \
|
||||
apk add --no-cache bash su-exec; \
|
||||
apk add --no-cache su-exec ; \
|
||||
chmod +x ./entrypoint.sh && \
|
||||
rm -rf /var/cache/apk/*
|
||||
VOLUME ["/app/storage"]
|
||||
|
||||
@@ -115,7 +115,7 @@ This project is divided into two parts, the front end uses `flutter`, the back e
|
||||
### Environment
|
||||
|
||||
1. Golang 1.23+
|
||||
2. Flutter 3.24+
|
||||
2. Flutter 3.16+
|
||||
|
||||
### Clone
|
||||
|
||||
|
||||
+1
-1
@@ -103,7 +103,7 @@ Gopeed (正式名 Go Speed) は `Golang` + `Flutter` によって開発された
|
||||
### 環境
|
||||
|
||||
1. Go 言語 1.23+
|
||||
2. Flutter 3.24+
|
||||
2. Flutter 3.16+
|
||||
|
||||
### クローン
|
||||
|
||||
|
||||
+1
-1
@@ -111,7 +111,7 @@ Dự án này được chia thành hai phần, phần giao diện sử dụng `f
|
||||
### Environment
|
||||
|
||||
1. Golang 1.23+
|
||||
2. Flutter 3.24+
|
||||
2. Flutter 3.16+
|
||||
|
||||
### Clone
|
||||
|
||||
|
||||
+1
-1
@@ -115,7 +115,7 @@ Gopeed 还提供了浏览器扩展用于接管浏览器下载,支持 Chrome、
|
||||
### 环境要求
|
||||
|
||||
1. Golang 1.23+
|
||||
2. Flutter 3.24+
|
||||
2. Flutter 3.16+
|
||||
|
||||
### 克隆项目
|
||||
|
||||
|
||||
+2
-2
@@ -86,7 +86,7 @@ Gopeed(全稱 Go Speed),是一款使用`Golang`+`Flutter`編寫的高速
|
||||
|
||||
更多關於安裝的內容請參考[安裝文檔](https://docs.gopeed.com/zh/install.html)
|
||||
|
||||
### 使用 CLI 安裝
|
||||
### 使用CLI安裝
|
||||
|
||||
使用`go install`安裝:
|
||||
|
||||
@@ -111,7 +111,7 @@ go install github.com/GopeedLab/gopeed/cmd/gopeed@latest
|
||||
### 開發環境
|
||||
|
||||
1. Golang 1.23+
|
||||
2. Flutter 3.24+
|
||||
2. Flutter 3.16+
|
||||
|
||||
### 克隆項目
|
||||
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package bind
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"github.com/GopeedLab/gopeed/pkg/api"
|
||||
"github.com/GopeedLab/gopeed/pkg/api/model"
|
||||
)
|
||||
|
||||
// Global singleton instance
|
||||
var instance *api.Instance
|
||||
|
||||
func Init(config *model.StartConfig) (err error) {
|
||||
if instance != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
config.ProductionMode = true
|
||||
instance, err = api.Create(config)
|
||||
return
|
||||
}
|
||||
|
||||
func Invoke(request *api.Request) string {
|
||||
if instance == nil {
|
||||
return BuildResult(errors.New("instance not initialized"))
|
||||
}
|
||||
return jsonEncode(api.Invoke(instance, request))
|
||||
}
|
||||
|
||||
func BuildResult(data any) string {
|
||||
if err, ok := data.(error); ok {
|
||||
buf, _ := json.Marshal(model.NewErrorResult[any](err.Error()))
|
||||
return string(buf)
|
||||
}
|
||||
return jsonEncode(model.NewOkResult(data))
|
||||
}
|
||||
|
||||
func jsonEncode(data any) string {
|
||||
buf, _ := json.Marshal(data)
|
||||
return string(buf)
|
||||
}
|
||||
+18
-13
@@ -3,27 +3,32 @@ package main
|
||||
import "C"
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/GopeedLab/gopeed/pkg/rest"
|
||||
"github.com/GopeedLab/gopeed/pkg/rest/model"
|
||||
"github.com/GopeedLab/gopeed/bind"
|
||||
"github.com/GopeedLab/gopeed/pkg/api"
|
||||
"github.com/GopeedLab/gopeed/pkg/api/model"
|
||||
)
|
||||
|
||||
func main() {}
|
||||
|
||||
//export Start
|
||||
func Start(cfg *C.char) (int, *C.char) {
|
||||
//export Init
|
||||
func Init(cfg *C.char) *C.char {
|
||||
var config model.StartConfig
|
||||
if err := json.Unmarshal([]byte(C.GoString(cfg)), &config); err != nil {
|
||||
return 0, C.CString(err.Error())
|
||||
return C.CString(err.Error())
|
||||
}
|
||||
config.ProductionMode = true
|
||||
realPort, err := rest.Start(&config)
|
||||
if err != nil {
|
||||
return 0, C.CString(err.Error())
|
||||
if err := bind.Init(&config); err != nil {
|
||||
return C.CString(err.Error())
|
||||
}
|
||||
return realPort, nil
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
//export Stop
|
||||
func Stop() {
|
||||
rest.Stop()
|
||||
//export Invoke
|
||||
func Invoke(req *C.char) *C.char {
|
||||
var request api.Request
|
||||
if err := json.Unmarshal([]byte(C.GoString(req)), &request); err != nil {
|
||||
return C.CString(bind.BuildResult(err))
|
||||
}
|
||||
|
||||
return C.CString(bind.Invoke(&request))
|
||||
}
|
||||
+7
-12
@@ -3,20 +3,15 @@ package libgopeed
|
||||
// #cgo LDFLAGS: -static-libstdc++
|
||||
import "C"
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/GopeedLab/gopeed/pkg/rest"
|
||||
"github.com/GopeedLab/gopeed/pkg/rest/model"
|
||||
"github.com/GopeedLab/gopeed/bind"
|
||||
"github.com/GopeedLab/gopeed/pkg/api"
|
||||
"github.com/GopeedLab/gopeed/pkg/api/model"
|
||||
)
|
||||
|
||||
func Start(cfg string) (int, error) {
|
||||
var config model.StartConfig
|
||||
if err := json.Unmarshal([]byte(cfg), &config); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
config.ProductionMode = true
|
||||
return rest.Start(&config)
|
||||
func Init(cfg *model.StartConfig) error {
|
||||
return bind.Init(cfg)
|
||||
}
|
||||
|
||||
func Stop() {
|
||||
rest.Stop()
|
||||
func Invoke(request *api.Request) string {
|
||||
return bind.Invoke(request)
|
||||
}
|
||||
+10
-4
@@ -2,15 +2,21 @@ package main
|
||||
|
||||
import (
|
||||
"github.com/GopeedLab/gopeed/cmd"
|
||||
"github.com/GopeedLab/gopeed/pkg/rest/model"
|
||||
"github.com/GopeedLab/gopeed/pkg/api/model"
|
||||
"github.com/GopeedLab/gopeed/pkg/base"
|
||||
)
|
||||
|
||||
// only for local development
|
||||
func main() {
|
||||
cfg := &model.StartConfig{
|
||||
Network: "tcp",
|
||||
Address: "127.0.0.1:9999",
|
||||
Storage: model.StorageBolt,
|
||||
Storage: model.StorageBolt,
|
||||
DownloadConfig: &base.DownloaderStoreConfig{
|
||||
Http: &base.DownloaderHttpConfig{
|
||||
Enable: true,
|
||||
Host: "127.0.0.1",
|
||||
Port: 9999,
|
||||
},
|
||||
},
|
||||
WebEnable: true,
|
||||
}
|
||||
cmd.Start(cfg)
|
||||
|
||||
+18
-14
@@ -3,10 +3,9 @@ package cmd
|
||||
import (
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"github.com/GopeedLab/gopeed/pkg/api"
|
||||
"github.com/GopeedLab/gopeed/pkg/api/model"
|
||||
"github.com/GopeedLab/gopeed/pkg/base"
|
||||
"github.com/GopeedLab/gopeed/pkg/rest"
|
||||
"github.com/GopeedLab/gopeed/pkg/rest/model"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
@@ -18,20 +17,22 @@ var banner string
|
||||
|
||||
func Start(cfg *model.StartConfig) {
|
||||
fmt.Println(banner)
|
||||
srv, listener, err := rest.BuildServer(cfg)
|
||||
instance, err := api.Create(cfg)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
downloadCfg, err := rest.Downloader.GetConfig()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
||||
downloadCfgResult := instance.GetConfig()
|
||||
if downloadCfgResult.HasError() {
|
||||
panic(downloadCfgResult.Msg)
|
||||
}
|
||||
downloadCfg := downloadCfgResult.Data
|
||||
if downloadCfg.FirstLoad {
|
||||
// Set default download config
|
||||
if cfg.DownloadConfig != nil {
|
||||
cfg.DownloadConfig.Merge(downloadCfg)
|
||||
// TODO Use PatchConfig
|
||||
rest.Downloader.PutConfig(cfg.DownloadConfig)
|
||||
instance.PutConfig(cfg.DownloadConfig)
|
||||
downloadCfg = cfg.DownloadConfig
|
||||
}
|
||||
|
||||
@@ -48,25 +49,28 @@ func Start(cfg *model.StartConfig) {
|
||||
}
|
||||
if downloadDir != "" {
|
||||
downloadCfg.DownloadDir = downloadDir
|
||||
rest.Downloader.PutConfig(downloadCfg)
|
||||
instance.PutConfig(downloadCfg)
|
||||
}
|
||||
}
|
||||
}
|
||||
watchExit()
|
||||
|
||||
fmt.Printf("Server start success on http://%s\n", listener.Addr().String())
|
||||
if err := srv.Serve(listener); err != nil && err != http.ErrServerClosed {
|
||||
watchExit(instance)
|
||||
|
||||
port, start, err := api.ListenHttp(cfg.DownloadConfig.Http, instance)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Printf("Server start success on http://%s:%d\n", cfg.DownloadConfig.Http.Host, port)
|
||||
start()
|
||||
}
|
||||
|
||||
func watchExit() {
|
||||
func watchExit(instance *api.Instance) {
|
||||
sigs := make(chan os.Signal, 1)
|
||||
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
|
||||
go func() {
|
||||
sig := <-sigs
|
||||
fmt.Printf("Server is shutting down due to signal: %s\n", sig)
|
||||
rest.Downloader.Close()
|
||||
instance.Close()
|
||||
os.Exit(0)
|
||||
}()
|
||||
}
|
||||
+4
-4
@@ -10,7 +10,7 @@ import (
|
||||
)
|
||||
|
||||
type args struct {
|
||||
Address *string `json:"address"`
|
||||
Host *string `json:"host"`
|
||||
Port *int `json:"port"`
|
||||
Username *string `json:"username"`
|
||||
Password *string `json:"password"`
|
||||
@@ -24,7 +24,7 @@ type args struct {
|
||||
|
||||
func parse() *args {
|
||||
var cliArgs args
|
||||
cliArgs.Address = flag.String("A", "0.0.0.0", "Bind Address")
|
||||
cliArgs.Host = flag.String("H", "0.0.0.0", "Bind Host")
|
||||
cliArgs.Port = flag.Int("P", 9999, "Bind Port")
|
||||
cliArgs.Username = flag.String("u", "gopeed", "HTTP Basic Auth Username")
|
||||
cliArgs.Password = flag.String("p", "", "HTTP Basic Auth Pwd")
|
||||
@@ -35,8 +35,8 @@ func parse() *args {
|
||||
|
||||
// args priority: config file > cli args
|
||||
cfgArgs := loadConfig(*cliArgs.configPath)
|
||||
if cfgArgs.Address == nil {
|
||||
cfgArgs.Address = cliArgs.Address
|
||||
if cfgArgs.Host == nil {
|
||||
cfgArgs.Host = cliArgs.Host
|
||||
}
|
||||
if cfgArgs.Port == nil {
|
||||
cfgArgs.Port = cliArgs.Port
|
||||
|
||||
+12
-5
@@ -5,9 +5,9 @@ package main
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"fmt"
|
||||
"github.com/GopeedLab/gopeed/cmd"
|
||||
"github.com/GopeedLab/gopeed/pkg/rest/model"
|
||||
"github.com/GopeedLab/gopeed/pkg/api/model"
|
||||
"github.com/GopeedLab/gopeed/pkg/base"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -42,12 +42,19 @@ func main() {
|
||||
dir = filepath.Dir(exe)
|
||||
}
|
||||
|
||||
if args.DownloadConfig == nil {
|
||||
args.DownloadConfig = &base.DownloaderStoreConfig{}
|
||||
}
|
||||
args.DownloadConfig.Http = &base.DownloaderHttpConfig{
|
||||
Enable: true,
|
||||
Host: *args.Host,
|
||||
Port: *args.Port,
|
||||
ApiToken: *args.ApiToken,
|
||||
}
|
||||
|
||||
cfg := &model.StartConfig{
|
||||
Network: "tcp",
|
||||
Address: fmt.Sprintf("%s:%d", *args.Address, *args.Port),
|
||||
Storage: model.StorageBolt,
|
||||
StorageDir: filepath.Join(dir, "storage"),
|
||||
ApiToken: *args.ApiToken,
|
||||
DownloadConfig: args.DownloadConfig,
|
||||
ProductionMode: true,
|
||||
WebEnable: true,
|
||||
|
||||
+14
-11
@@ -1,12 +1,15 @@
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
gopeed:
|
||||
container_name: gopeed
|
||||
ports:
|
||||
- "9999:9999" # HTTP port (host:container)
|
||||
image: liwei2633/gopeed
|
||||
volumes:
|
||||
- ~/gopeed/Downloads:/app/Downloads # mount download path
|
||||
#- ~/gopeed/storage:/app/storage # if you need to mount storage path, uncomment this line
|
||||
restart: unless-stopped
|
||||
gopeed:
|
||||
container_name: gopeed
|
||||
ports:
|
||||
- 9999:9999 # HTTP port (host:container)
|
||||
environment:
|
||||
- PUID=0
|
||||
- PGID=0
|
||||
- UMASK=022
|
||||
volumes:
|
||||
- ~/gopeed/Downloads:/app/Downloads # mount download path
|
||||
#- ~/gopeed/storage:/app/storage # if you need to mount storage path, uncomment this line
|
||||
restart: unless-stopped
|
||||
image: liwei2633/gopeed
|
||||
# command: -u Username -p Password # optional authentication
|
||||
@@ -332,7 +332,7 @@ func (f *Fetcher) doUpload(fromUpload bool) {
|
||||
}
|
||||
}
|
||||
|
||||
// Get torrent stats maybe panic, see https://github.com/anacrolix/torrent/issues/972
|
||||
// get torrent stats maybe panic, see https://github.com/anacrolix/torrent/issues/972
|
||||
func (f *Fetcher) torrentStats() torrent.TorrentStats {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
|
||||
@@ -146,7 +146,7 @@ func (f *Fetcher) Resolve(req *base.Request) error {
|
||||
file.Name = filename
|
||||
}
|
||||
}
|
||||
// Get file filePath by URL
|
||||
// get file filePath by URL
|
||||
if file.Name == "" {
|
||||
file.Name = path.Base(httpReq.URL.Path)
|
||||
}
|
||||
@@ -162,9 +162,6 @@ func (f *Fetcher) Resolve(req *base.Request) error {
|
||||
func (f *Fetcher) Create(opts *base.Options) error {
|
||||
f.meta.Opts = opts
|
||||
|
||||
if err := base.ParseReqExtra[fhttp.ReqExtra](f.meta.Req); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := base.ParseOptsExtra[fhttp.OptsExtra](f.meta.Opts); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -198,6 +195,12 @@ func (f *Fetcher) Start() (err error) {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Avoid request extra modified by extension
|
||||
if err = base.ParseReqExtra[fhttp.ReqExtra](f.meta.Req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if f.chunks == nil {
|
||||
f.chunks = f.splitChunk()
|
||||
}
|
||||
@@ -496,7 +499,7 @@ func (fm *FetcherManager) ParseName(u string) string {
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
// Get filePath by URL
|
||||
// get filePath by URL
|
||||
name = path.Base(url.Path)
|
||||
// If file name is empty, use host name
|
||||
if name == "" || name == "/" || name == "." {
|
||||
|
||||
+400
@@ -0,0 +1,400 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/GopeedLab/gopeed/pkg/api/model"
|
||||
"github.com/GopeedLab/gopeed/pkg/base"
|
||||
"github.com/GopeedLab/gopeed/pkg/download"
|
||||
"github.com/GopeedLab/gopeed/pkg/util"
|
||||
"net"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type Instance struct {
|
||||
startCfg *model.StartConfig
|
||||
downloader *download.Downloader
|
||||
|
||||
httpLock sync.Mutex
|
||||
srv *http.Server
|
||||
listener net.Listener
|
||||
}
|
||||
|
||||
func Create(startCfg *model.StartConfig) (*Instance, error) {
|
||||
if startCfg == nil {
|
||||
startCfg = &model.StartConfig{}
|
||||
}
|
||||
startCfg.Init()
|
||||
|
||||
i := &Instance{
|
||||
startCfg: startCfg,
|
||||
}
|
||||
|
||||
downloadCfg := &download.DownloaderConfig{
|
||||
ProductionMode: startCfg.ProductionMode,
|
||||
RefreshInterval: startCfg.RefreshInterval,
|
||||
}
|
||||
if startCfg.Storage == model.StorageBolt {
|
||||
downloadCfg.Storage = download.NewBoltStorage(startCfg.StorageDir)
|
||||
} else {
|
||||
downloadCfg.Storage = download.NewMemStorage()
|
||||
}
|
||||
downloadCfg.StorageDir = startCfg.StorageDir
|
||||
downloadCfg.Init()
|
||||
downloader := download.NewDownloader(downloadCfg)
|
||||
if err := downloader.Setup(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
i.downloader = downloader
|
||||
i.httpLock = sync.Mutex{}
|
||||
|
||||
return i, nil
|
||||
}
|
||||
|
||||
func (i *Instance) Listener(listener download.Listener) *model.Result[any] {
|
||||
i.downloader.Listener(listener)
|
||||
return model.NewNilResult()
|
||||
}
|
||||
|
||||
func (i *Instance) StartHttp() *model.Result[int] {
|
||||
i.httpLock.Lock()
|
||||
defer i.httpLock.Unlock()
|
||||
|
||||
return i.startHttp()
|
||||
}
|
||||
|
||||
func (i *Instance) startHttp() *model.Result[int] {
|
||||
httpCfg := i.getHttpConfig()
|
||||
if httpCfg == nil || !httpCfg.Enable {
|
||||
return model.NewErrorResult[int]("HTTP API server not enabled")
|
||||
}
|
||||
if i.srv != nil {
|
||||
return model.NewErrorResult[int]("HTTP API server already started")
|
||||
}
|
||||
|
||||
port, start, err := ListenHttp(httpCfg, i)
|
||||
if err != nil {
|
||||
return model.NewErrorResult[int](err.Error())
|
||||
}
|
||||
httpCfg.RunningPort = port
|
||||
go start()
|
||||
return model.NewOkResult(port)
|
||||
}
|
||||
|
||||
func (i *Instance) getHttpConfig() *base.DownloaderHttpConfig {
|
||||
var httpCfg *base.DownloaderHttpConfig
|
||||
if i.startCfg.DownloadConfig != nil && i.startCfg.DownloadConfig.Http != nil {
|
||||
httpCfg = i.startCfg.DownloadConfig.Http
|
||||
} else {
|
||||
cfg, _ := i.downloader.GetConfig()
|
||||
httpCfg = cfg.Http
|
||||
}
|
||||
return httpCfg
|
||||
}
|
||||
|
||||
func (i *Instance) StopHttp() *model.Result[any] {
|
||||
i.httpLock.Lock()
|
||||
defer i.httpLock.Unlock()
|
||||
|
||||
return i.stopHttp()
|
||||
}
|
||||
|
||||
func (i *Instance) stopHttp() *model.Result[any] {
|
||||
if i.srv != nil {
|
||||
if err := i.srv.Shutdown(context.Background()); err != nil {
|
||||
i.downloader.Logger.Warn().Err(err).Msg("shutdown http server failed")
|
||||
}
|
||||
i.srv = nil
|
||||
i.listener = nil
|
||||
}
|
||||
httpCfg := i.getHttpConfig()
|
||||
if httpCfg != nil {
|
||||
httpCfg.RunningPort = 0
|
||||
}
|
||||
return model.NewNilResult()
|
||||
}
|
||||
|
||||
func (i *Instance) RestartHttp() *model.Result[int] {
|
||||
i.httpLock.Lock()
|
||||
defer i.httpLock.Unlock()
|
||||
|
||||
i.stopHttp()
|
||||
return i.startHttp()
|
||||
}
|
||||
|
||||
func (i *Instance) Info() *model.Result[map[string]any] {
|
||||
return model.NewOkResult(map[string]any{
|
||||
"version": base.Version,
|
||||
"runtime": runtime.Version(),
|
||||
"os": runtime.GOOS,
|
||||
"arch": runtime.GOARCH,
|
||||
"inDocker": base.InDocker == "true",
|
||||
})
|
||||
}
|
||||
|
||||
func (i *Instance) Resolve(req *base.Request) *model.Result[*download.ResolveResult] {
|
||||
rr, err := i.downloader.Resolve(req)
|
||||
if err != nil {
|
||||
return model.NewErrorResult[*download.ResolveResult](err.Error())
|
||||
}
|
||||
return model.NewOkResult(rr)
|
||||
}
|
||||
|
||||
func (i *Instance) CreateTask(req *model.CreateTask) *model.Result[string] {
|
||||
var (
|
||||
taskId string
|
||||
err error
|
||||
)
|
||||
if req.Rid != "" {
|
||||
taskId, err = i.downloader.Create(req.Rid, req.Opt)
|
||||
} else if req.Req != nil {
|
||||
taskId, err = i.downloader.CreateDirect(req.Req, req.Opt)
|
||||
} else {
|
||||
return model.NewErrorResult[string]("param invalid: rid or req", model.CodeInvalidParam)
|
||||
}
|
||||
if err != nil {
|
||||
return model.NewErrorResult[string](err.Error())
|
||||
}
|
||||
return model.NewOkResult(taskId)
|
||||
}
|
||||
|
||||
func (i *Instance) CreateTaskBatch(req *model.CreateTaskBatch) *model.Result[[]string] {
|
||||
if len(req.Reqs) == 0 {
|
||||
return model.NewErrorResult[[]string]("param invalid: reqs", model.CodeInvalidParam)
|
||||
}
|
||||
taskIds, err := i.downloader.CreateDirectBatch(req.Reqs, req.Opt)
|
||||
if err != nil {
|
||||
return model.NewErrorResult[[]string](err.Error())
|
||||
}
|
||||
return model.NewOkResult(taskIds)
|
||||
}
|
||||
|
||||
func (i *Instance) PauseTask(taskId string) *model.Result[any] {
|
||||
err := i.downloader.Pause(&download.TaskFilter{
|
||||
ID: []string{taskId},
|
||||
})
|
||||
if err != nil {
|
||||
return model.NewErrorResult[any](err.Error())
|
||||
}
|
||||
return model.NewNilResult()
|
||||
}
|
||||
|
||||
func (i *Instance) PauseTasks(filter *download.TaskFilter) *model.Result[any] {
|
||||
err := i.downloader.Pause(filter)
|
||||
if err != nil {
|
||||
return model.NewErrorResult[any](err.Error())
|
||||
}
|
||||
return model.NewNilResult()
|
||||
}
|
||||
|
||||
func (i *Instance) ContinueTask(taskId string) *model.Result[any] {
|
||||
err := i.downloader.Continue(&download.TaskFilter{
|
||||
ID: []string{taskId},
|
||||
})
|
||||
if err != nil {
|
||||
return model.NewErrorResult[any](err.Error())
|
||||
}
|
||||
return model.NewNilResult()
|
||||
}
|
||||
|
||||
func (i *Instance) ContinueTasks(filter *download.TaskFilter) *model.Result[any] {
|
||||
err := i.downloader.Continue(filter)
|
||||
if err != nil {
|
||||
return model.NewErrorResult[any](err.Error())
|
||||
}
|
||||
return model.NewNilResult()
|
||||
}
|
||||
|
||||
func (i *Instance) DeleteTask(taskId string, force bool) *model.Result[any] {
|
||||
err := i.downloader.Delete(&download.TaskFilter{
|
||||
ID: []string{taskId},
|
||||
}, force)
|
||||
if err != nil {
|
||||
return model.NewErrorResult[any](err.Error())
|
||||
}
|
||||
return model.NewNilResult()
|
||||
}
|
||||
|
||||
func (i *Instance) DeleteTasks(filter *download.TaskFilter, force bool) *model.Result[any] {
|
||||
err := i.downloader.Delete(filter, force)
|
||||
if err != nil {
|
||||
return model.NewErrorResult[any](err.Error())
|
||||
}
|
||||
return model.NewNilResult()
|
||||
}
|
||||
|
||||
func (i *Instance) GetTask(taskId string) *model.Result[*download.Task] {
|
||||
if taskId == "" {
|
||||
return model.NewErrorResult[*download.Task]("param invalid: id", model.CodeInvalidParam)
|
||||
}
|
||||
task := i.downloader.GetTask(taskId)
|
||||
if task == nil {
|
||||
return model.NewErrorResult[*download.Task]("task not found", model.CodeTaskNotFound)
|
||||
}
|
||||
return model.NewOkResult(task)
|
||||
}
|
||||
|
||||
func (i *Instance) GetTasks(filter *download.TaskFilter) *model.Result[[]*download.Task] {
|
||||
return model.NewOkResult(i.downloader.GetTasksByFilter(filter))
|
||||
}
|
||||
|
||||
func (i *Instance) GetTaskStats(taskId string) *model.Result[any] {
|
||||
stats, err := i.downloader.GetTaskStats(taskId)
|
||||
if err != nil {
|
||||
return model.NewErrorResult[any](err.Error())
|
||||
}
|
||||
return model.NewOkResult(stats)
|
||||
}
|
||||
|
||||
func (i *Instance) GetConfig() *model.Result[*base.DownloaderStoreConfig] {
|
||||
config, err := i.downloader.GetConfig()
|
||||
if err != nil {
|
||||
return model.NewErrorResult[*base.DownloaderStoreConfig](err.Error())
|
||||
}
|
||||
return model.NewOkResult(config)
|
||||
}
|
||||
|
||||
func (i *Instance) PutConfig(cfg *base.DownloaderStoreConfig) *model.Result[any] {
|
||||
err := i.downloader.PutConfig(cfg)
|
||||
if err != nil {
|
||||
return model.NewErrorResult[any](err.Error())
|
||||
}
|
||||
return model.NewNilResult()
|
||||
}
|
||||
|
||||
func (i *Instance) InstallExtension(req *model.InstallExtension) *model.Result[string] {
|
||||
var (
|
||||
installedExt *download.Extension
|
||||
err error
|
||||
)
|
||||
if req.DevMode {
|
||||
installedExt, err = i.downloader.InstallExtensionByFolder(req.URL, true)
|
||||
} else {
|
||||
installedExt, err = i.downloader.InstallExtensionByGit(req.URL)
|
||||
}
|
||||
if err != nil {
|
||||
return model.NewErrorResult[string](err.Error())
|
||||
}
|
||||
|
||||
return model.NewOkResult(installedExt.Identity)
|
||||
}
|
||||
|
||||
func (i *Instance) GetExtension(identity string) *model.Result[*download.Extension] {
|
||||
extension, err := i.downloader.GetExtension(identity)
|
||||
if err != nil {
|
||||
return model.NewErrorResult[*download.Extension](err.Error())
|
||||
}
|
||||
return model.NewOkResult(extension)
|
||||
}
|
||||
|
||||
func (i *Instance) GetExtensions() *model.Result[[]*download.Extension] {
|
||||
return model.NewOkResult(i.downloader.GetExtensions())
|
||||
}
|
||||
|
||||
func (i *Instance) UpdateExtensionSettings(identity string, req *model.UpdateExtensionSettings) *model.Result[any] {
|
||||
err := i.downloader.UpdateExtensionSettings(identity, req.Settings)
|
||||
if err != nil {
|
||||
return model.NewErrorResult[any](err.Error())
|
||||
}
|
||||
return model.NewNilResult()
|
||||
}
|
||||
|
||||
func (i *Instance) SwitchExtension(identity string, req *model.SwitchExtension) *model.Result[any] {
|
||||
err := i.downloader.SwitchExtension(identity, req.Status)
|
||||
if err != nil {
|
||||
return model.NewErrorResult[any](err.Error())
|
||||
}
|
||||
return model.NewNilResult()
|
||||
}
|
||||
|
||||
func (i *Instance) DeleteExtension(identity string) *model.Result[any] {
|
||||
err := i.downloader.DeleteExtension(identity)
|
||||
if err != nil {
|
||||
return model.NewErrorResult[any](err.Error())
|
||||
}
|
||||
return model.NewNilResult()
|
||||
}
|
||||
|
||||
func (i *Instance) UpgradeCheckExtension(identity string) *model.Result[*model.UpgradeCheckExtensionResp] {
|
||||
newVersion, err := i.downloader.UpgradeCheckExtension(identity)
|
||||
if err != nil {
|
||||
return model.NewErrorResult[*model.UpgradeCheckExtensionResp](err.Error())
|
||||
}
|
||||
|
||||
return model.NewOkResult(&model.UpgradeCheckExtensionResp{
|
||||
NewVersion: newVersion,
|
||||
})
|
||||
}
|
||||
|
||||
func (i *Instance) UpgradeExtension(identity string) *model.Result[any] {
|
||||
err := i.downloader.UpgradeExtension(identity)
|
||||
if err != nil {
|
||||
return model.NewErrorResult[any](err.Error())
|
||||
}
|
||||
return model.NewNilResult()
|
||||
}
|
||||
|
||||
func (i *Instance) Close() *model.Result[any] {
|
||||
i.StopHttp()
|
||||
if i.downloader != nil {
|
||||
if err := i.downloader.Close(); err != nil {
|
||||
i.downloader.Logger.Warn().Err(err).Msg("close downloader failed")
|
||||
}
|
||||
i.downloader = nil
|
||||
}
|
||||
|
||||
return model.NewNilResult()
|
||||
}
|
||||
|
||||
func (i *Instance) Clear() *model.Result[any] {
|
||||
if err := i.downloader.Clear(); err != nil {
|
||||
return model.NewErrorResult[any](err.Error())
|
||||
}
|
||||
return model.NewNilResult()
|
||||
}
|
||||
|
||||
type Request struct {
|
||||
Method string `json:"method"`
|
||||
Params []any `json:"params"`
|
||||
}
|
||||
|
||||
// Invoke support dynamic call method
|
||||
func Invoke(instance *Instance, request *Request) (ret any) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
ret = model.NewErrorResult[any](fmt.Sprintf("%v", err))
|
||||
}
|
||||
}()
|
||||
|
||||
method, args := request.Method, request.Params
|
||||
dsType := reflect.ValueOf(instance)
|
||||
fn := dsType.MethodByName(method)
|
||||
numIn := fn.Type().NumIn()
|
||||
in := make([]reflect.Value, numIn)
|
||||
for i := 0; i < numIn; i++ {
|
||||
paramType := fn.Type().In(i)
|
||||
arg := args[i]
|
||||
if arg == nil {
|
||||
in[i] = reflect.Zero(fn.Type().In(i))
|
||||
continue
|
||||
}
|
||||
var param reflect.Value
|
||||
var paramPtr any
|
||||
if paramType.Kind() == reflect.Ptr {
|
||||
param = reflect.New(paramType.Elem())
|
||||
paramPtr = param.Interface()
|
||||
} else {
|
||||
param = reflect.New(paramType).Elem()
|
||||
paramPtr = param.Addr().Interface()
|
||||
}
|
||||
if err := util.MapToStruct(arg, paramPtr); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
in[i] = param
|
||||
}
|
||||
retVals := fn.Call(in)
|
||||
return retVals[0].Interface()
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/GopeedLab/gopeed/pkg/api/model"
|
||||
"github.com/GopeedLab/gopeed/pkg/download"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestInvoke(t *testing.T) {
|
||||
doTestInvoke(t, "Info", []any{}, model.CodeOk)
|
||||
doTestInvoke(t, "Info1", []any{}, model.CodeError)
|
||||
doTestInvoke(t, "CreateTask", []any{nil}, model.CodeError)
|
||||
doTestInvoke(t, "GetTasks", nil, model.CodeError)
|
||||
doTestInvoke(t, "GetTasks", []any{}, model.CodeError)
|
||||
doTestInvoke(t, "GetTasks", []any{"{abc:123"}, model.CodeError)
|
||||
doTestInvoke(t, "GetTasks", []any{&download.TaskFilter{}}, model.CodeOk)
|
||||
doTestInvoke(t, "SwitchExtension", []any{"test", &model.SwitchExtension{Status: true}}, model.CodeError)
|
||||
}
|
||||
|
||||
func doTestInvoke(t *testing.T, method string, params []any, expectCode model.RespCode) {
|
||||
instance, _ := Create(nil)
|
||||
defer instance.Close()
|
||||
|
||||
result := Invoke(instance, &Request{
|
||||
Method: method,
|
||||
Params: params,
|
||||
})
|
||||
buf, err := json.Marshal(result)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var res model.Result[any]
|
||||
if err := json.Unmarshal(buf, &res); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if res.Code != expectCode {
|
||||
t.Fatalf("Invoke method [%s] failed, expect code [%d], got [%d], msg: %s", method, expectCode, res.Code, res.Msg)
|
||||
}
|
||||
}
|
||||
+451
@@ -0,0 +1,451 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/GopeedLab/gopeed/pkg/api/model"
|
||||
"github.com/GopeedLab/gopeed/pkg/base"
|
||||
"github.com/GopeedLab/gopeed/pkg/download"
|
||||
"github.com/gorilla/handlers"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/pkg/errors"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const instanceKey string = "apiInstance"
|
||||
|
||||
func ListenHttp(httpCfg *base.DownloaderHttpConfig, ai *Instance) (int, func(), error) {
|
||||
var r = mux.NewRouter()
|
||||
r.Methods(http.MethodGet).Path("/api/v1/info").HandlerFunc(Info)
|
||||
r.Methods(http.MethodPost).Path("/api/v1/resolve").HandlerFunc(Resolve)
|
||||
r.Methods(http.MethodPost).Path("/api/v1/tasks").HandlerFunc(CreateTask)
|
||||
r.Methods(http.MethodPost).Path("/api/v1/tasks/batch").HandlerFunc(CreateTaskBatch)
|
||||
r.Methods(http.MethodPut).Path("/api/v1/tasks/{id}/pause").HandlerFunc(PauseTask)
|
||||
r.Methods(http.MethodPut).Path("/api/v1/tasks/pause").HandlerFunc(PauseTasks)
|
||||
r.Methods(http.MethodPut).Path("/api/v1/tasks/{id}/continue").HandlerFunc(ContinueTask)
|
||||
r.Methods(http.MethodPut).Path("/api/v1/tasks/continue").HandlerFunc(ContinueTasks)
|
||||
r.Methods(http.MethodDelete).Path("/api/v1/tasks/{id}").HandlerFunc(DeleteTask)
|
||||
r.Methods(http.MethodDelete).Path("/api/v1/tasks").HandlerFunc(DeleteTasks)
|
||||
r.Methods(http.MethodGet).Path("/api/v1/tasks/{id}").HandlerFunc(GetTask)
|
||||
r.Methods(http.MethodGet).Path("/api/v1/tasks").HandlerFunc(GetTasks)
|
||||
r.Methods(http.MethodGet).Path("/api/v1/tasks/{id}/stats").HandlerFunc(GetTaskStats)
|
||||
r.Methods(http.MethodGet).Path("/api/v1/config").HandlerFunc(GetConfig)
|
||||
r.Methods(http.MethodPut).Path("/api/v1/config").HandlerFunc(PutConfig)
|
||||
r.Methods(http.MethodPost).Path("/api/v1/extensions").HandlerFunc(InstallExtension)
|
||||
r.Methods(http.MethodGet).Path("/api/v1/extensions").HandlerFunc(GetExtensions)
|
||||
r.Methods(http.MethodGet).Path("/api/v1/extensions/{identity}").HandlerFunc(GetExtension)
|
||||
r.Methods(http.MethodPut).Path("/api/v1/extensions/{identity}/settings").HandlerFunc(UpdateExtensionSettings)
|
||||
r.Methods(http.MethodPut).Path("/api/v1/extensions/{identity}/switch").HandlerFunc(SwitchExtension)
|
||||
r.Methods(http.MethodDelete).Path("/api/v1/extensions/{identity}").HandlerFunc(DeleteExtension)
|
||||
r.Methods(http.MethodGet).Path("/api/v1/extensions/{identity}/upgrade").HandlerFunc(UpgradeCheckExtension)
|
||||
r.Methods(http.MethodPost).Path("/api/v1/extensions/{identity}/upgrade").HandlerFunc(UpgradeExtension)
|
||||
if ai.startCfg.WebEnable {
|
||||
r.Path("/api/v1/proxy").HandlerFunc(DoProxy)
|
||||
r.PathPrefix("/fs/tasks").Handler(http.FileServer(&taskFileSystem{
|
||||
downloader: ai.downloader,
|
||||
}))
|
||||
r.PathPrefix("/fs/extensions").Handler(http.FileServer(&extensionFileSystem{
|
||||
downloader: ai.downloader,
|
||||
}))
|
||||
r.PathPrefix("/").Handler(http.FileServer(http.FS(ai.startCfg.WebFS)))
|
||||
}
|
||||
|
||||
r.Use(func(h http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := context.WithValue(r.Context(), instanceKey, ai)
|
||||
h.ServeHTTP(w, r.WithContext(ctx))
|
||||
})
|
||||
})
|
||||
|
||||
if httpCfg.ApiToken != "" || (ai.startCfg.WebEnable && ai.startCfg.WebBasicAuth != nil) {
|
||||
r.Use(func(h http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if httpCfg.ApiToken != "" && r.Header.Get("X-Api-Token") == httpCfg.ApiToken {
|
||||
h.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
if ai.startCfg.WebEnable && ai.startCfg.WebBasicAuth != nil {
|
||||
if r.Header.Get("Authorization") == ai.startCfg.WebBasicAuth.Authorization() {
|
||||
h.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
w.Header().Set("WWW-Authenticate", "Basic realm=\"gopeed web\"")
|
||||
}
|
||||
WriteStatusJson(w, http.StatusUnauthorized, model.NewErrorResult[any]("unauthorized", model.CodeUnauthorized))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// recover panic
|
||||
r.Use(func(h http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
defer func() {
|
||||
if v := recover(); v != nil {
|
||||
err := errors.WithStack(fmt.Errorf("%v", v))
|
||||
ai.downloader.Logger.Error().Stack().Err(err).Msgf("http server panic: %s %s", r.Method, r.RequestURI)
|
||||
WriteJson(w, model.NewErrorResult[any](err.Error(), model.CodeError))
|
||||
}
|
||||
}()
|
||||
h.ServeHTTP(w, r)
|
||||
})
|
||||
})
|
||||
|
||||
srv := &http.Server{Handler: handlers.CORS(
|
||||
handlers.AllowedHeaders([]string{"Content-Type", "X-Api-Token", "X-Target-Uri"}),
|
||||
handlers.AllowedMethods([]string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"}),
|
||||
handlers.AllowedOrigins([]string{"*"}),
|
||||
)(r)}
|
||||
|
||||
listener, err := net.Listen("tcp", net.JoinHostPort(httpCfg.Host, fmt.Sprintf("%d", httpCfg.Port)))
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
ai.srv = srv
|
||||
ai.listener = listener
|
||||
|
||||
return listener.Addr().(*net.TCPAddr).Port, func() {
|
||||
if err := srv.Serve(listener); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
ai.downloader.Logger.Error().Err(err).Msg("http server listen error")
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
|
||||
func getInstance(r *http.Request) *Instance {
|
||||
return r.Context().Value(instanceKey).(*Instance)
|
||||
}
|
||||
|
||||
func Info(w http.ResponseWriter, r *http.Request) {
|
||||
WriteJson(w, getInstance(r).Info())
|
||||
}
|
||||
|
||||
func Resolve(w http.ResponseWriter, r *http.Request) {
|
||||
var req base.Request
|
||||
if ReadJson(r, w, &req) {
|
||||
WriteJson(w, getInstance(r).Resolve(&req))
|
||||
}
|
||||
}
|
||||
|
||||
func CreateTask(w http.ResponseWriter, r *http.Request) {
|
||||
var req model.CreateTask
|
||||
if ReadJson(r, w, &req) {
|
||||
WriteJson(w, getInstance(r).CreateTask(&req))
|
||||
}
|
||||
}
|
||||
|
||||
func CreateTaskBatch(w http.ResponseWriter, r *http.Request) {
|
||||
var req model.CreateTaskBatch
|
||||
if ReadJson(r, w, &req) {
|
||||
WriteJson(w, getInstance(r).CreateTaskBatch(&req))
|
||||
}
|
||||
}
|
||||
|
||||
func PauseTask(w http.ResponseWriter, r *http.Request) {
|
||||
var taskId string
|
||||
if !parseTaskId(r, w, &taskId) {
|
||||
return
|
||||
}
|
||||
WriteJson(w, getInstance(r).PauseTask(taskId))
|
||||
}
|
||||
|
||||
func PauseTasks(w http.ResponseWriter, r *http.Request) {
|
||||
var filter download.TaskFilter
|
||||
if !parseFilter(r, w, &filter) {
|
||||
return
|
||||
}
|
||||
WriteJson(w, getInstance(r).PauseTasks(&filter))
|
||||
}
|
||||
|
||||
func ContinueTask(w http.ResponseWriter, r *http.Request) {
|
||||
var taskId string
|
||||
if !parseTaskId(r, w, &taskId) {
|
||||
return
|
||||
}
|
||||
WriteJson(w, getInstance(r).ContinueTask(taskId))
|
||||
}
|
||||
|
||||
func ContinueTasks(w http.ResponseWriter, r *http.Request) {
|
||||
var filter download.TaskFilter
|
||||
if !parseFilter(r, w, &filter) {
|
||||
return
|
||||
}
|
||||
WriteJson(w, getInstance(r).ContinueTasks(&filter))
|
||||
}
|
||||
|
||||
func DeleteTask(w http.ResponseWriter, r *http.Request) {
|
||||
var taskId string
|
||||
if !parseTaskId(r, w, &taskId) {
|
||||
return
|
||||
}
|
||||
force := r.FormValue("force")
|
||||
WriteJson(w, getInstance(r).DeleteTask(taskId, force == "true"))
|
||||
}
|
||||
|
||||
func DeleteTasks(w http.ResponseWriter, r *http.Request) {
|
||||
var filter download.TaskFilter
|
||||
if !parseFilter(r, w, &filter) {
|
||||
return
|
||||
}
|
||||
force := r.FormValue("force")
|
||||
WriteJson(w, getInstance(r).DeleteTasks(&filter, force == "true"))
|
||||
}
|
||||
|
||||
func GetTask(w http.ResponseWriter, r *http.Request) {
|
||||
var taskId string
|
||||
if !parseTaskId(r, w, &taskId) {
|
||||
return
|
||||
}
|
||||
WriteJson(w, getInstance(r).GetTask(taskId))
|
||||
}
|
||||
|
||||
func GetTasks(w http.ResponseWriter, r *http.Request) {
|
||||
var filter download.TaskFilter
|
||||
if !parseFilter(r, w, &filter) {
|
||||
return
|
||||
}
|
||||
WriteJson(w, getInstance(r).GetTasks(&filter))
|
||||
}
|
||||
|
||||
func GetTaskStats(w http.ResponseWriter, r *http.Request) {
|
||||
var taskId string
|
||||
if !parseTaskId(r, w, &taskId) {
|
||||
return
|
||||
}
|
||||
WriteJson(w, getInstance(r).GetTaskStats(taskId))
|
||||
}
|
||||
|
||||
func GetConfig(w http.ResponseWriter, r *http.Request) {
|
||||
WriteJson(w, getInstance(r).GetConfig())
|
||||
}
|
||||
|
||||
func PutConfig(w http.ResponseWriter, r *http.Request) {
|
||||
var cfg base.DownloaderStoreConfig
|
||||
if !ReadJson(r, w, &cfg) {
|
||||
return
|
||||
}
|
||||
WriteJson(w, getInstance(r).PutConfig(&cfg))
|
||||
}
|
||||
|
||||
func InstallExtension(w http.ResponseWriter, r *http.Request) {
|
||||
var req model.InstallExtension
|
||||
if !ReadJson(r, w, &req) {
|
||||
return
|
||||
}
|
||||
WriteJson(w, getInstance(r).InstallExtension(&req))
|
||||
}
|
||||
|
||||
func GetExtensions(w http.ResponseWriter, r *http.Request) {
|
||||
WriteJson(w, getInstance(r).GetExtensions())
|
||||
}
|
||||
|
||||
func GetExtension(w http.ResponseWriter, r *http.Request) {
|
||||
var identity string
|
||||
if !parseIdentity(r, w, &identity) {
|
||||
return
|
||||
}
|
||||
WriteJson(w, getInstance(r).GetExtension(identity))
|
||||
}
|
||||
|
||||
func UpdateExtensionSettings(w http.ResponseWriter, r *http.Request) {
|
||||
var identity string
|
||||
if !parseIdentity(r, w, &identity) {
|
||||
return
|
||||
}
|
||||
var req model.UpdateExtensionSettings
|
||||
if !ReadJson(r, w, &req) {
|
||||
return
|
||||
}
|
||||
WriteJson(w, getInstance(r).UpdateExtensionSettings(identity, &req))
|
||||
}
|
||||
|
||||
func SwitchExtension(w http.ResponseWriter, r *http.Request) {
|
||||
var identity string
|
||||
if !parseIdentity(r, w, &identity) {
|
||||
return
|
||||
}
|
||||
var switchExtension model.SwitchExtension
|
||||
if !ReadJson(r, w, &switchExtension) {
|
||||
return
|
||||
}
|
||||
WriteJson(w, getInstance(r).SwitchExtension(identity, &switchExtension))
|
||||
}
|
||||
|
||||
func DeleteExtension(w http.ResponseWriter, r *http.Request) {
|
||||
var identity string
|
||||
if !parseIdentity(r, w, &identity) {
|
||||
return
|
||||
}
|
||||
WriteJson(w, getInstance(r).DeleteExtension(identity))
|
||||
}
|
||||
|
||||
func UpgradeCheckExtension(w http.ResponseWriter, r *http.Request) {
|
||||
var identity string
|
||||
if !parseIdentity(r, w, &identity) {
|
||||
return
|
||||
}
|
||||
WriteJson(w, getInstance(r).UpgradeCheckExtension(identity))
|
||||
}
|
||||
|
||||
func UpgradeExtension(w http.ResponseWriter, r *http.Request) {
|
||||
var identity string
|
||||
if !parseIdentity(r, w, &identity) {
|
||||
return
|
||||
}
|
||||
WriteJson(w, getInstance(r).UpgradeExtension(identity))
|
||||
}
|
||||
|
||||
func DoProxy(w http.ResponseWriter, r *http.Request) {
|
||||
target := r.Header.Get("X-Target-Uri")
|
||||
if target == "" {
|
||||
writeError(w, "param invalid: X-Target-Uri")
|
||||
return
|
||||
}
|
||||
targetUrl, err := url.Parse(target)
|
||||
if err != nil {
|
||||
writeError(w, err.Error())
|
||||
return
|
||||
}
|
||||
r.RequestURI = ""
|
||||
r.URL = targetUrl
|
||||
r.Host = targetUrl.Host
|
||||
resp, err := http.DefaultClient.Do(r)
|
||||
if err != nil {
|
||||
writeError(w, err.Error())
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
for k, vv := range resp.Header {
|
||||
for _, v := range vv {
|
||||
w.Header().Set(k, v)
|
||||
}
|
||||
}
|
||||
w.WriteHeader(resp.StatusCode)
|
||||
buf, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
writeError(w, err.Error())
|
||||
return
|
||||
}
|
||||
w.Write(buf)
|
||||
}
|
||||
|
||||
func parsePathParam(r *http.Request, w http.ResponseWriter, name string, value *string) bool {
|
||||
vars := mux.Vars(r)
|
||||
*value = vars[name]
|
||||
if *value == "" {
|
||||
WriteJson(w, model.NewErrorResult[any]("param invalid: id", model.CodeInvalidParam))
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func parseTaskId(r *http.Request, w http.ResponseWriter, taskId *string) bool {
|
||||
return parsePathParam(r, w, "id", taskId)
|
||||
}
|
||||
|
||||
func parseIdentity(r *http.Request, w http.ResponseWriter, identity *string) bool {
|
||||
return parsePathParam(r, w, "identity", identity)
|
||||
}
|
||||
|
||||
func parseFilter(r *http.Request, w http.ResponseWriter, filter *download.TaskFilter) bool {
|
||||
if err := r.ParseForm(); err != nil {
|
||||
WriteJson(w, model.NewErrorResult[any](err.Error()))
|
||||
return false
|
||||
}
|
||||
|
||||
filter.ID = r.Form["id"]
|
||||
filter.Status = convertStatues(r.Form["status"])
|
||||
filter.NotStatus = convertStatues(r.Form["notStatus"])
|
||||
return true
|
||||
}
|
||||
|
||||
func convertStatues(statues []string) []base.Status {
|
||||
result := make([]base.Status, 0)
|
||||
for _, status := range statues {
|
||||
result = append(result, base.Status(status))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func writeError(w http.ResponseWriter, msg string) {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte(msg))
|
||||
}
|
||||
|
||||
func ReadJson(r *http.Request, w http.ResponseWriter, v any) bool {
|
||||
if err := json.NewDecoder(r.Body).Decode(v); err != nil {
|
||||
WriteJson(w, model.NewErrorResult[any](err.Error()))
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func WriteJson(w http.ResponseWriter, v any) {
|
||||
WriteStatusJson(w, http.StatusOK, v)
|
||||
}
|
||||
|
||||
func WriteStatusJson(w http.ResponseWriter, statusCode int, v any) {
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
w.WriteHeader(statusCode)
|
||||
json.NewEncoder(w).Encode(v)
|
||||
}
|
||||
|
||||
func resolvePath(urlPath string, prefix string) (identity string, path string, err error) {
|
||||
// remove prefix
|
||||
clearPath := strings.TrimPrefix(urlPath, prefix)
|
||||
// match extension identity, eg: /fs/extensions/identity/xxx
|
||||
reg := regexp.MustCompile(`^/([^/]+)/(.*)$`)
|
||||
if !reg.MatchString(clearPath) {
|
||||
err = os.ErrNotExist
|
||||
return
|
||||
}
|
||||
matched := reg.FindStringSubmatch(clearPath)
|
||||
if len(matched) != 3 {
|
||||
err = os.ErrNotExist
|
||||
return
|
||||
}
|
||||
return matched[1], matched[2], nil
|
||||
}
|
||||
|
||||
// handle task file resource
|
||||
type taskFileSystem struct {
|
||||
downloader *download.Downloader
|
||||
}
|
||||
|
||||
func (e *taskFileSystem) Open(name string) (http.File, error) {
|
||||
// get extension identity
|
||||
identity, path, err := resolvePath(name, "/fs/tasks")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
task := e.downloader.GetTask(identity)
|
||||
if task == nil {
|
||||
return nil, os.ErrNotExist
|
||||
}
|
||||
return os.Open(filepath.Join(task.Meta.RootDirPath(), path))
|
||||
}
|
||||
|
||||
// handle extension file resource
|
||||
type extensionFileSystem struct {
|
||||
downloader *download.Downloader
|
||||
}
|
||||
|
||||
func (e *extensionFileSystem) Open(name string) (http.File, error) {
|
||||
// get extension identity
|
||||
identity, path, err := resolvePath(name, "/fs/extensions")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
extension, err := e.downloader.GetExtension(identity)
|
||||
if err != nil {
|
||||
return nil, os.ErrNotExist
|
||||
}
|
||||
extensionPath := e.downloader.ExtensionPath(extension)
|
||||
return os.Open(filepath.Join(extensionPath, path))
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package rest
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -7,9 +7,9 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/GopeedLab/gopeed/internal/test"
|
||||
"github.com/GopeedLab/gopeed/pkg/api/model"
|
||||
"github.com/GopeedLab/gopeed/pkg/base"
|
||||
"github.com/GopeedLab/gopeed/pkg/download"
|
||||
"github.com/GopeedLab/gopeed/pkg/rest/model"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
@@ -21,7 +21,8 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
restPort int
|
||||
httpInstance *Instance
|
||||
restPort int
|
||||
|
||||
taskReq = &base.Request{
|
||||
Extra: map[string]any{
|
||||
@@ -90,7 +91,7 @@ func TestCreateTask(t *testing.T) {
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
Downloader.Listener(func(event *download.Event) {
|
||||
httpInstance.Listener(func(event *download.Event) {
|
||||
if event.Key == download.EventKeyFinally {
|
||||
wg.Done()
|
||||
}
|
||||
@@ -117,7 +118,7 @@ func TestCreateDirectTask(t *testing.T) {
|
||||
doTest(func() {
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
Downloader.Listener(func(event *download.Event) {
|
||||
httpInstance.Listener(func(event *download.Event) {
|
||||
if event.Key == download.EventKeyFinally {
|
||||
wg.Done()
|
||||
}
|
||||
@@ -156,7 +157,7 @@ func TestCreateDirectTaskWithResoled(t *testing.T) {
|
||||
doTest(func() {
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
Downloader.Listener(func(event *download.Event) {
|
||||
httpInstance.Listener(func(event *download.Event) {
|
||||
if event.Key == download.EventKeyFinally {
|
||||
wg.Done()
|
||||
}
|
||||
@@ -180,7 +181,7 @@ func TestPauseAndContinueTask(t *testing.T) {
|
||||
doTest(func() {
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
Downloader.Listener(func(event *download.Event) {
|
||||
httpInstance.Listener(func(event *download.Event) {
|
||||
switch event.Key {
|
||||
case download.EventKeyFinally:
|
||||
wg.Done()
|
||||
@@ -214,10 +215,7 @@ func TestPauseAndContinueTask(t *testing.T) {
|
||||
|
||||
func TestPauseAllAndContinueALLTasks(t *testing.T) {
|
||||
doTest(func() {
|
||||
cfg, err := Downloader.GetConfig()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cfg := UnwrapResult(httpInstance.GetConfig())
|
||||
|
||||
createAndPause := func() {
|
||||
taskId := httpRequestCheckOk[string](http.MethodPost, "/api/v1/tasks", createReq)
|
||||
@@ -275,7 +273,7 @@ func TestDeleteAllTasks(t *testing.T) {
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(taskCount)
|
||||
Downloader.Listener(func(event *download.Event) {
|
||||
httpInstance.Listener(func(event *download.Event) {
|
||||
if event.Key == download.EventKeyFinally {
|
||||
wg.Done()
|
||||
}
|
||||
@@ -301,7 +299,7 @@ func TestDeleteTasksByStatues(t *testing.T) {
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(taskCount)
|
||||
Downloader.Listener(func(event *download.Event) {
|
||||
httpInstance.Listener(func(event *download.Event) {
|
||||
if event.Key == download.EventKeyFinally {
|
||||
wg.Done()
|
||||
}
|
||||
@@ -325,7 +323,7 @@ func TestGetTasks(t *testing.T) {
|
||||
doTest(func() {
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
Downloader.Listener(func(event *download.Event) {
|
||||
httpInstance.Listener(func(event *download.Event) {
|
||||
if event.Key == download.EventKeyFinally {
|
||||
wg.Done()
|
||||
}
|
||||
@@ -354,7 +352,10 @@ func TestGetAndPutConfig(t *testing.T) {
|
||||
cfg := httpRequestCheckOk[*base.DownloaderStoreConfig](http.MethodGet, "/api/v1/config", nil)
|
||||
cfg.DownloadDir = "./download"
|
||||
cfg.Extra = map[string]any{
|
||||
"serverConfig": &Config{
|
||||
"serverConfig": &struct {
|
||||
Host string `json:"host"`
|
||||
Port int `json:"port"`
|
||||
}{
|
||||
Host: "127.0.0.1",
|
||||
Port: 8080,
|
||||
},
|
||||
@@ -446,16 +447,16 @@ func TestDeleteExtension(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestUpdateCheckExtension(t *testing.T) {
|
||||
func TestUpgradeCheckExtension(t *testing.T) {
|
||||
doTest(func() {
|
||||
identity := httpRequestCheckOk[string](http.MethodPost, "/api/v1/extensions", installExtensionReq)
|
||||
resp := httpRequestCheckOk[*model.UpdateCheckExtensionResp](http.MethodGet, "/api/v1/extensions/"+identity+"/update", nil)
|
||||
resp := httpRequestCheckOk[*model.UpgradeCheckExtensionResp](http.MethodGet, "/api/v1/extensions/"+identity+"/upgrade", nil)
|
||||
// no new version
|
||||
if resp.NewVersion != "" {
|
||||
t.Errorf("UpdateCheckExtension() got = %v, want %v", resp.NewVersion, "")
|
||||
t.Errorf("UpgradeCheckExtension() got = %v, want %v", resp.NewVersion, "")
|
||||
}
|
||||
// force update
|
||||
httpRequestCheckOk[any](http.MethodPost, "/api/v1/extensions/"+identity+"/update", nil)
|
||||
httpRequestCheckOk[any](http.MethodPost, "/api/v1/extensions/"+identity+"/upgrade", nil)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -506,13 +507,19 @@ func TestDoProxy(t *testing.T) {
|
||||
func TestApiToken(t *testing.T) {
|
||||
var cfg = &model.StartConfig{}
|
||||
cfg.Init()
|
||||
cfg.ApiToken = "123456"
|
||||
fileListener := doStart(cfg)
|
||||
cfg.DownloadConfig = &base.DownloaderStoreConfig{
|
||||
Http: &base.DownloaderHttpConfig{
|
||||
Enable: true,
|
||||
ApiToken: "123456",
|
||||
},
|
||||
}
|
||||
httpInstance, _ = Create(cfg)
|
||||
fileListener := doStart()
|
||||
defer func() {
|
||||
if err := fileListener.Close(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
Stop()
|
||||
httpInstance.Close()
|
||||
}()
|
||||
|
||||
status, _ := doHttpRequest0(http.MethodGet, "/api/v1/config", nil, nil)
|
||||
@@ -521,7 +528,7 @@ func TestApiToken(t *testing.T) {
|
||||
}
|
||||
|
||||
status, _ = doHttpRequest0(http.MethodGet, "/api/v1/config", map[string]string{
|
||||
"X-Api-Token": cfg.ApiToken,
|
||||
"X-Api-Token": cfg.DownloadConfig.Http.ApiToken,
|
||||
}, nil)
|
||||
if status != http.StatusOK {
|
||||
t.Errorf("TestApiToken() got = %v, want %v", status, http.StatusOK)
|
||||
@@ -532,18 +539,24 @@ func TestApiToken(t *testing.T) {
|
||||
func TestAuthorization(t *testing.T) {
|
||||
var cfg = &model.StartConfig{}
|
||||
cfg.Init()
|
||||
cfg.ApiToken = "123456"
|
||||
cfg.DownloadConfig = &base.DownloaderStoreConfig{
|
||||
Http: &base.DownloaderHttpConfig{
|
||||
Enable: true,
|
||||
ApiToken: "123456",
|
||||
},
|
||||
}
|
||||
cfg.WebEnable = true
|
||||
cfg.WebBasicAuth = &model.WebBasicAuth{
|
||||
Username: "admin",
|
||||
Password: "123456",
|
||||
}
|
||||
fileListener := doStart(cfg)
|
||||
httpInstance, _ = Create(cfg)
|
||||
fileListener := doStart()
|
||||
defer func() {
|
||||
if err := fileListener.Close(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
Stop()
|
||||
httpInstance.Close()
|
||||
}()
|
||||
|
||||
status, _ := doHttpRequest0(http.MethodGet, "/api/v1/config", nil, nil)
|
||||
@@ -559,7 +572,7 @@ func TestAuthorization(t *testing.T) {
|
||||
}
|
||||
|
||||
status, _ = doHttpRequest0(http.MethodGet, "/api/v1/config", map[string]string{
|
||||
"X-Api-Token": cfg.ApiToken,
|
||||
"X-Api-Token": cfg.DownloadConfig.Http.ApiToken,
|
||||
}, nil)
|
||||
if status != http.StatusOK {
|
||||
t.Errorf("TestAuthorization() got = %v, want %v", status, http.StatusOK)
|
||||
@@ -573,17 +586,23 @@ func doTest(handler func()) {
|
||||
cfg.Storage = storage
|
||||
cfg.StorageDir = ".test_storage"
|
||||
cfg.WebEnable = true
|
||||
fileListener := doStart(cfg)
|
||||
cfg.DownloadConfig = &base.DownloaderStoreConfig{
|
||||
Http: &base.DownloaderHttpConfig{
|
||||
Enable: true,
|
||||
},
|
||||
}
|
||||
httpInstance, _ = Create(cfg)
|
||||
fileListener := doStart()
|
||||
defer func() {
|
||||
if err := fileListener.Close(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
Stop()
|
||||
Downloader.Clear()
|
||||
httpInstance.StopHttp()
|
||||
httpInstance.Clear()
|
||||
}()
|
||||
defer func() {
|
||||
Downloader.Pause(nil)
|
||||
Downloader.Delete(nil, true)
|
||||
httpInstance.PauseTasks(nil)
|
||||
httpInstance.DeleteTasks(nil, true)
|
||||
os.RemoveAll(cfg.StorageDir)
|
||||
}()
|
||||
taskReq.URL = "http://" + fileListener.Addr().String() + "/" + test.BuildName
|
||||
@@ -593,15 +612,19 @@ func doTest(handler func()) {
|
||||
testFunc(model.StorageBolt)
|
||||
}
|
||||
|
||||
func doStart(cfg *model.StartConfig) net.Listener {
|
||||
port, err := Start(cfg)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
func doStart() net.Listener {
|
||||
port := UnwrapResult(httpInstance.StartHttp())
|
||||
restPort = port
|
||||
return test.StartTestFileServer()
|
||||
}
|
||||
|
||||
func UnwrapResult[T any](result *model.Result[T]) T {
|
||||
if result.HasError() {
|
||||
panic(result.Msg)
|
||||
}
|
||||
return result.Data
|
||||
}
|
||||
|
||||
func doHttpRequest0(method string, path string, headers map[string]string, body any) (int, []byte) {
|
||||
var reader io.Reader
|
||||
if body != nil {
|
||||
@@ -14,13 +14,10 @@ const (
|
||||
)
|
||||
|
||||
type StartConfig struct {
|
||||
Network string `json:"network"`
|
||||
Address string `json:"address"`
|
||||
RefreshInterval int `json:"refreshInterval"`
|
||||
Storage Storage `json:"storage"`
|
||||
StorageDir string `json:"storageDir"`
|
||||
WhiteDownloadDirs []string `json:"whiteDownloadDirs"`
|
||||
ApiToken string `json:"apiToken"`
|
||||
DownloadConfig *base.DownloaderStoreConfig `json:"downloadConfig"`
|
||||
|
||||
ProductionMode bool
|
||||
@@ -31,12 +28,6 @@ type StartConfig struct {
|
||||
}
|
||||
|
||||
func (cfg *StartConfig) Init() *StartConfig {
|
||||
if cfg.Network == "" {
|
||||
cfg.Network = "tcp"
|
||||
}
|
||||
if cfg.Address == "" {
|
||||
cfg.Address = "127.0.0.1:0"
|
||||
}
|
||||
if cfg.RefreshInterval == 0 {
|
||||
cfg.RefreshInterval = 350
|
||||
}
|
||||
@@ -59,3 +50,9 @@ func (cfg *WebBasicAuth) Authorization() string {
|
||||
userId := cfg.Username + ":" + cfg.Password
|
||||
return "Basic " + base64.StdEncoding.EncodeToString([]byte(userId))
|
||||
}
|
||||
|
||||
type HttpListenResult struct {
|
||||
Host string `json:"host"`
|
||||
// Port is the http server real listen port.
|
||||
Port int `json:"port"`
|
||||
}
|
||||
@@ -13,6 +13,6 @@ type SwitchExtension struct {
|
||||
Status bool `json:"status"`
|
||||
}
|
||||
|
||||
type UpdateCheckExtensionResp struct {
|
||||
type UpgradeCheckExtensionResp struct {
|
||||
NewVersion string `json:"newVersion"`
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package model
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
type RespCode int
|
||||
|
||||
const (
|
||||
@@ -20,6 +22,15 @@ type Result[T any] struct {
|
||||
Data T `json:"data"`
|
||||
}
|
||||
|
||||
func (r *Result[T]) Json() string {
|
||||
buf, _ := json.Marshal(r)
|
||||
return string(buf)
|
||||
}
|
||||
|
||||
func (r *Result[T]) HasError() bool {
|
||||
return r.Code != CodeOk
|
||||
}
|
||||
|
||||
func NewOkResult[T any](data T) *Result[T] {
|
||||
return &Result[T]{
|
||||
Code: CodeOk,
|
||||
@@ -33,14 +44,14 @@ func NewNilResult() *Result[any] {
|
||||
}
|
||||
}
|
||||
|
||||
func NewErrorResult(msg string, code ...RespCode) *Result[any] {
|
||||
func NewErrorResult[T any](msg string, code ...RespCode) *Result[T] {
|
||||
// if code is not provided, the default code is CodeError
|
||||
c := CodeError
|
||||
if len(code) > 0 {
|
||||
c = code[0]
|
||||
}
|
||||
|
||||
return &Result[any]{
|
||||
return &Result[T]{
|
||||
Code: c,
|
||||
Msg: msg,
|
||||
}
|
||||
File renamed without changes.
@@ -171,6 +171,7 @@ type DownloaderStoreConfig struct {
|
||||
MaxRunning int `json:"maxRunning"` // MaxRunning is the max running download count
|
||||
ProtocolConfig map[string]any `json:"protocolConfig"` // ProtocolConfig is special config for each protocol
|
||||
Extra map[string]any `json:"extra"`
|
||||
Http *DownloaderHttpConfig `json:"http"`
|
||||
Proxy *DownloaderProxyConfig `json:"proxy"`
|
||||
}
|
||||
|
||||
@@ -181,6 +182,12 @@ func (cfg *DownloaderStoreConfig) Init() *DownloaderStoreConfig {
|
||||
if cfg.ProtocolConfig == nil {
|
||||
cfg.ProtocolConfig = make(map[string]any)
|
||||
}
|
||||
if cfg.Extra == nil {
|
||||
cfg.Extra = make(map[string]any)
|
||||
}
|
||||
if cfg.Http == nil {
|
||||
cfg.Http = &DownloaderHttpConfig{}
|
||||
}
|
||||
if cfg.Proxy == nil {
|
||||
cfg.Proxy = &DownloaderProxyConfig{}
|
||||
}
|
||||
@@ -203,12 +210,28 @@ func (cfg *DownloaderStoreConfig) Merge(beforeCfg *DownloaderStoreConfig) *Downl
|
||||
if cfg.Extra == nil {
|
||||
cfg.Extra = beforeCfg.Extra
|
||||
}
|
||||
if cfg.Http == nil {
|
||||
cfg.Http = beforeCfg.Http
|
||||
}
|
||||
if cfg.Proxy == nil {
|
||||
cfg.Proxy = beforeCfg.Proxy
|
||||
}
|
||||
return cfg
|
||||
}
|
||||
|
||||
type DownloaderHttpConfig struct {
|
||||
// Enable is the flag that enable http server
|
||||
Enable bool `json:"Enable"`
|
||||
// Host is the http server listen address.
|
||||
Host string `json:"host"`
|
||||
// Port is the http server listen port.
|
||||
Port int `json:"port"`
|
||||
// ApiToken is the auth token for http API
|
||||
ApiToken string `json:"apiToken"`
|
||||
// RunningPort is the http server real listen port.
|
||||
RunningPort int `json:"runningPort"`
|
||||
}
|
||||
|
||||
type DownloaderProxyConfig struct {
|
||||
Enable bool `json:"enable"`
|
||||
// System is the flag that use system proxy
|
||||
|
||||
@@ -17,6 +17,8 @@ func TestDownloaderStoreConfig_Init(t *testing.T) {
|
||||
&DownloaderStoreConfig{
|
||||
MaxRunning: 5,
|
||||
ProtocolConfig: map[string]any{},
|
||||
Extra: make(map[string]any),
|
||||
Http: &DownloaderHttpConfig{},
|
||||
Proxy: &DownloaderProxyConfig{},
|
||||
},
|
||||
},
|
||||
@@ -28,6 +30,8 @@ func TestDownloaderStoreConfig_Init(t *testing.T) {
|
||||
&DownloaderStoreConfig{
|
||||
MaxRunning: 10,
|
||||
ProtocolConfig: map[string]any{},
|
||||
Extra: make(map[string]any),
|
||||
Http: &DownloaderHttpConfig{},
|
||||
Proxy: &DownloaderProxyConfig{},
|
||||
},
|
||||
},
|
||||
@@ -43,6 +47,8 @@ func TestDownloaderStoreConfig_Init(t *testing.T) {
|
||||
ProtocolConfig: map[string]any{
|
||||
"key": "value",
|
||||
},
|
||||
Extra: make(map[string]any),
|
||||
Http: &DownloaderHttpConfig{},
|
||||
Proxy: &DownloaderProxyConfig{},
|
||||
},
|
||||
},
|
||||
@@ -56,6 +62,8 @@ func TestDownloaderStoreConfig_Init(t *testing.T) {
|
||||
&DownloaderStoreConfig{
|
||||
MaxRunning: 5,
|
||||
ProtocolConfig: map[string]any{},
|
||||
Extra: make(map[string]any),
|
||||
Http: &DownloaderHttpConfig{},
|
||||
Proxy: &DownloaderProxyConfig{
|
||||
Enable: true,
|
||||
},
|
||||
|
||||
+44
-29
@@ -204,33 +204,29 @@ func (d *Downloader) Setup() error {
|
||||
|
||||
current := task.fetcher.Progress().TotalDownloaded()
|
||||
tick := float64(d.cfg.RefreshInterval) / 1000
|
||||
downloadDataChanged := false
|
||||
if task.Status == base.DownloadStatusRunning {
|
||||
downloadDataChanged = current != task.Progress.Downloaded
|
||||
task.Progress.Used = task.timer.Used()
|
||||
task.Progress.Speed = task.calcSpeed(task.speedArr, current-task.Progress.Downloaded, tick)
|
||||
task.Progress.Downloaded = current
|
||||
}
|
||||
|
||||
uploadDataChanged := false
|
||||
if task.Uploading {
|
||||
uploader := task.fetcher.(fetcher.Uploader)
|
||||
currentUploaded := uploader.UploadedBytes()
|
||||
uploadDataChanged = currentUploaded != task.Progress.Uploaded
|
||||
task.Progress.UploadSpeed = task.calcSpeed(task.uploadSpeedArr, currentUploaded-task.Progress.Uploaded, tick)
|
||||
task.Progress.Uploaded = currentUploaded
|
||||
}
|
||||
d.emit(EventKeyProgress, task)
|
||||
|
||||
// store fetcher progress
|
||||
data, err := task.fetcherManager.Store(task.fetcher)
|
||||
if err != nil {
|
||||
d.Logger.Error().Stack().Err(err).Msgf("serialize fetcher failed: %s", task.ID)
|
||||
return
|
||||
}
|
||||
if err := d.storage.Put(bucketSave, task.ID, data); err != nil {
|
||||
d.Logger.Error().Stack().Err(err).Msgf("persist fetcher failed: %s", task.ID)
|
||||
return
|
||||
}
|
||||
if err := d.storage.Put(bucketTask, task.ID, task); err != nil {
|
||||
d.Logger.Error().Stack().Err(err).Msgf("persist task failed: %s", task.ID)
|
||||
// store fetcher progress when download/upload data changed
|
||||
if !downloadDataChanged && !uploadDataChanged {
|
||||
return
|
||||
}
|
||||
d.saveTask(task)
|
||||
}()
|
||||
}
|
||||
}
|
||||
@@ -256,7 +252,7 @@ func (d *Downloader) setupFetcher(fm fetcher.FetcherManager, fetcher fetcher.Fet
|
||||
ctl.GetConfig = func(v any) {
|
||||
d.getProtocolConfig(fm.Name(), v)
|
||||
}
|
||||
// Get proxy config, task request proxy config has higher priority, then use global proxy config
|
||||
// get proxy config, task request proxy config has higher priority, then use global proxy config
|
||||
ctl.GetProxy = func(requestProxy *base.RequestProxy) func(*gohttp.Request) (*url.URL, error) {
|
||||
if requestProxy == nil {
|
||||
return d.cfg.Proxy.ToHandler()
|
||||
@@ -273,6 +269,23 @@ func (d *Downloader) setupFetcher(fm fetcher.FetcherManager, fetcher fetcher.Fet
|
||||
fetcher.Setup(ctl)
|
||||
}
|
||||
|
||||
func (d *Downloader) saveTask(task *Task) error {
|
||||
data, err := task.fetcherManager.Store(task.fetcher)
|
||||
if err != nil {
|
||||
d.Logger.Error().Stack().Err(err).Msgf("serialize fetcher failed: %s", task.ID)
|
||||
return err
|
||||
}
|
||||
if err := d.storage.Put(bucketSave, task.ID, data); err != nil {
|
||||
d.Logger.Error().Stack().Err(err).Msgf("persist fetcher failed: %s", task.ID)
|
||||
return err
|
||||
}
|
||||
if err := d.storage.Put(bucketTask, task.ID, task); err != nil {
|
||||
d.Logger.Error().Stack().Err(err).Msgf("persist task failed: %s", task.ID)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Downloader) Resolve(req *base.Request) (rr *ResolveResult, err error) {
|
||||
rrId, err := gonanoid.New()
|
||||
if err != nil {
|
||||
@@ -377,7 +390,7 @@ func (d *Downloader) Pause(filter *TaskFilter) (err error) {
|
||||
return d.pauseAll()
|
||||
}
|
||||
|
||||
filter.NotStatuses = []base.Status{base.DownloadStatusPause, base.DownloadStatusError, base.DownloadStatusDone}
|
||||
filter.NotStatus = []base.Status{base.DownloadStatusPause, base.DownloadStatusError, base.DownloadStatusDone}
|
||||
pauseTasks := d.GetTasksByFilter(filter)
|
||||
if len(pauseTasks) == 0 {
|
||||
return ErrTaskNotFound
|
||||
@@ -417,7 +430,7 @@ func (d *Downloader) Continue(filter *TaskFilter) (err error) {
|
||||
return d.continueAll()
|
||||
}
|
||||
|
||||
filter.NotStatuses = []base.Status{base.DownloadStatusRunning, base.DownloadStatusDone}
|
||||
filter.NotStatus = []base.Status{base.DownloadStatusRunning, base.DownloadStatusDone}
|
||||
continueTasks := d.GetTasksByFilter(filter)
|
||||
if len(continueTasks) == 0 {
|
||||
return ErrTaskNotFound
|
||||
@@ -514,7 +527,7 @@ func (d *Downloader) ContinueBatch(filter *TaskFilter) (err error) {
|
||||
|
||||
func (d *Downloader) Delete(filter *TaskFilter, force bool) (err error) {
|
||||
if filter == nil || filter.IsEmpty() {
|
||||
return d.deleteAll()
|
||||
return d.deleteAll(force)
|
||||
}
|
||||
|
||||
deleteTasks := d.GetTasksByFilter(filter)
|
||||
@@ -559,24 +572,27 @@ func (d *Downloader) Delete(filter *TaskFilter, force bool) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (d *Downloader) deleteAll() (err error) {
|
||||
func (d *Downloader) deleteAll(force bool) (err error) {
|
||||
deleteTasksPtr := make([]*Task, 0)
|
||||
|
||||
func() {
|
||||
d.lock.Lock()
|
||||
defer d.lock.Unlock()
|
||||
|
||||
deleteTasksPtr = append(deleteTasksPtr, d.tasks...)
|
||||
d.tasks = make([]*Task, 0)
|
||||
d.waitTasks = make([]*Task, 0)
|
||||
}()
|
||||
|
||||
for _, task := range d.tasks {
|
||||
if err = d.doDelete(task, true); err != nil {
|
||||
for _, task := range deleteTasksPtr {
|
||||
if err = d.doDelete(task, force); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (d *Downloader) Stats(id string) (sr any, err error) {
|
||||
func (d *Downloader) GetTaskStats(id string) (sr any, err error) {
|
||||
task := d.GetTask(id)
|
||||
if task == nil {
|
||||
return sr, ErrTaskNotFound
|
||||
@@ -706,10 +722,10 @@ func (d *Downloader) GetTasksByFilter(filter *TaskFilter) []*Task {
|
||||
}
|
||||
|
||||
idMatch := func(task *Task) bool {
|
||||
if len(filter.IDs) == 0 {
|
||||
if len(filter.ID) == 0 {
|
||||
return true
|
||||
}
|
||||
for _, id := range filter.IDs {
|
||||
for _, id := range filter.ID {
|
||||
if task.ID == id {
|
||||
return true
|
||||
}
|
||||
@@ -717,10 +733,10 @@ func (d *Downloader) GetTasksByFilter(filter *TaskFilter) []*Task {
|
||||
return false
|
||||
}
|
||||
statusMatch := func(task *Task) bool {
|
||||
if len(filter.Statuses) == 0 {
|
||||
if len(filter.Status) == 0 {
|
||||
return true
|
||||
}
|
||||
for _, status := range filter.Statuses {
|
||||
for _, status := range filter.Status {
|
||||
if task.Status == status {
|
||||
return true
|
||||
}
|
||||
@@ -728,10 +744,10 @@ func (d *Downloader) GetTasksByFilter(filter *TaskFilter) []*Task {
|
||||
return false
|
||||
}
|
||||
notStatusMatch := func(task *Task) bool {
|
||||
if len(filter.NotStatuses) == 0 {
|
||||
if len(filter.NotStatus) == 0 {
|
||||
return true
|
||||
}
|
||||
for _, status := range filter.NotStatuses {
|
||||
for _, status := range filter.NotStatus {
|
||||
if task.Status == status {
|
||||
return false
|
||||
}
|
||||
@@ -1005,8 +1021,6 @@ func (d *Downloader) doStart(task *Task) (err error) {
|
||||
return
|
||||
}
|
||||
isCreate = task.Status == base.DownloadStatusReady
|
||||
|
||||
d.triggerOnStart(task)
|
||||
task.updateStatus(base.DownloadStatusRunning)
|
||||
|
||||
return
|
||||
@@ -1023,6 +1037,7 @@ func (d *Downloader) doStart(task *Task) (err error) {
|
||||
task.lock.Lock()
|
||||
defer task.lock.Unlock()
|
||||
|
||||
d.triggerOnStart(task)
|
||||
if task.Meta.Res == nil {
|
||||
err := task.fetcher.Resolve(task.Meta.Req)
|
||||
if err != nil {
|
||||
@@ -1064,7 +1079,7 @@ func (d *Downloader) doStart(task *Task) (err error) {
|
||||
if err := task.fetcher.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.storage.Put(bucketTask, task.ID, task.clone()); err != nil {
|
||||
if err := d.saveTask(task); err != nil {
|
||||
return err
|
||||
}
|
||||
d.emit(EventKeyStart, task)
|
||||
|
||||
@@ -400,7 +400,7 @@ func TestDownloader_StoreAndRestore(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
time.Sleep(time.Millisecond * 1001)
|
||||
err = downloader.Pause(&TaskFilter{IDs: []string{id}})
|
||||
err = downloader.Pause(&TaskFilter{ID: []string{id}})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -424,7 +424,7 @@ func TestDownloader_StoreAndRestore(t *testing.T) {
|
||||
wg.Done()
|
||||
}
|
||||
})
|
||||
err = downloader.Continue(&TaskFilter{IDs: []string{id}})
|
||||
err = downloader.Continue(&TaskFilter{ID: []string{id}})
|
||||
wg.Wait()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -551,7 +551,7 @@ func TestDownloader_GetTasksByFilter(t *testing.T) {
|
||||
|
||||
t.Run("GetTasksByFilter ids", func(t *testing.T) {
|
||||
tasks := downloader.GetTasksByFilter(&TaskFilter{
|
||||
IDs: taskIds,
|
||||
ID: taskIds,
|
||||
})
|
||||
if len(tasks) != len(reqs) {
|
||||
t.Errorf("GetTasksByFilter ids task got = %v, want %v", len(tasks), len(reqs))
|
||||
@@ -560,7 +560,7 @@ func TestDownloader_GetTasksByFilter(t *testing.T) {
|
||||
|
||||
t.Run("GetTasksByFilter match ids", func(t *testing.T) {
|
||||
tasks := downloader.GetTasksByFilter(&TaskFilter{
|
||||
IDs: []string{taskIds[0]},
|
||||
ID: []string{taskIds[0]},
|
||||
})
|
||||
if len(tasks) != 1 {
|
||||
t.Errorf("GetTasksByFilter ids task got = %v, want %v", len(tasks), 1)
|
||||
@@ -569,7 +569,7 @@ func TestDownloader_GetTasksByFilter(t *testing.T) {
|
||||
|
||||
t.Run("GetTasksByFilter not match ids", func(t *testing.T) {
|
||||
tasks := downloader.GetTasksByFilter(&TaskFilter{
|
||||
IDs: []string{"xxx"},
|
||||
ID: []string{"xxx"},
|
||||
})
|
||||
if len(tasks) != 0 {
|
||||
t.Errorf("GetTasksByFilter ids task got = %v, want %v", len(tasks), 0)
|
||||
@@ -578,7 +578,7 @@ func TestDownloader_GetTasksByFilter(t *testing.T) {
|
||||
|
||||
t.Run("GetTasksByFilter status", func(t *testing.T) {
|
||||
tasks := downloader.GetTasksByFilter(&TaskFilter{
|
||||
Statuses: []base.Status{base.DownloadStatusDone},
|
||||
Status: []base.Status{base.DownloadStatusDone},
|
||||
})
|
||||
if len(tasks) != len(reqs) {
|
||||
t.Errorf("GetTasksByFilter status task got = %v, want %v", len(tasks), len(reqs))
|
||||
@@ -587,7 +587,7 @@ func TestDownloader_GetTasksByFilter(t *testing.T) {
|
||||
|
||||
t.Run("GetTasksByFilter not match status", func(t *testing.T) {
|
||||
tasks := downloader.GetTasksByFilter(&TaskFilter{
|
||||
Statuses: []base.Status{base.DownloadStatusError},
|
||||
Status: []base.Status{base.DownloadStatusError},
|
||||
})
|
||||
if len(tasks) != 0 {
|
||||
t.Errorf("GetTasksByFilter status task got = %v, want %v", len(tasks), 0)
|
||||
@@ -596,7 +596,7 @@ func TestDownloader_GetTasksByFilter(t *testing.T) {
|
||||
|
||||
t.Run("GetTasksByFilter match notStatus", func(t *testing.T) {
|
||||
tasks := downloader.GetTasksByFilter(&TaskFilter{
|
||||
NotStatuses: []base.Status{base.DownloadStatusRunning, base.DownloadStatusPause},
|
||||
NotStatus: []base.Status{base.DownloadStatusRunning, base.DownloadStatusPause},
|
||||
})
|
||||
if len(tasks) != len(reqs) {
|
||||
t.Errorf("GetTasksByFilter match notStatus task got = %v, want %v", len(tasks), len(reqs))
|
||||
@@ -605,7 +605,7 @@ func TestDownloader_GetTasksByFilter(t *testing.T) {
|
||||
|
||||
t.Run("GetTasksByFilter not match notStatus", func(t *testing.T) {
|
||||
tasks := downloader.GetTasksByFilter(&TaskFilter{
|
||||
NotStatuses: []base.Status{base.DownloadStatusDone},
|
||||
NotStatus: []base.Status{base.DownloadStatusDone},
|
||||
})
|
||||
if len(tasks) != 0 {
|
||||
t.Errorf("GetTasksByFilter not match notStatus task got = %v, want %v", len(tasks), 0)
|
||||
@@ -614,8 +614,8 @@ func TestDownloader_GetTasksByFilter(t *testing.T) {
|
||||
|
||||
t.Run("GetTasksByFilter match ids and status", func(t *testing.T) {
|
||||
tasks := downloader.GetTasksByFilter(&TaskFilter{
|
||||
IDs: []string{taskIds[0]},
|
||||
Statuses: []base.Status{base.DownloadStatusDone},
|
||||
ID: []string{taskIds[0]},
|
||||
Status: []base.Status{base.DownloadStatusDone},
|
||||
})
|
||||
if len(tasks) != 1 {
|
||||
t.Errorf("GetTasksByFilter match ids and status task got = %v, want %v", len(tasks), 1)
|
||||
@@ -624,8 +624,8 @@ func TestDownloader_GetTasksByFilter(t *testing.T) {
|
||||
|
||||
t.Run("GetTasksByFilter not match ids and status", func(t *testing.T) {
|
||||
tasks := downloader.GetTasksByFilter(&TaskFilter{
|
||||
IDs: []string{taskIds[0]},
|
||||
Statuses: []base.Status{base.DownloadStatusError},
|
||||
ID: []string{taskIds[0]},
|
||||
Status: []base.Status{base.DownloadStatusError},
|
||||
})
|
||||
if len(tasks) != 0 {
|
||||
t.Errorf("GetTasksByFilter not match ids and status task got = %v, want %v", len(tasks), 0)
|
||||
|
||||
@@ -703,31 +703,37 @@ type OnDoneContext struct {
|
||||
}
|
||||
|
||||
type ExtensionTask struct {
|
||||
*Task
|
||||
|
||||
download *Downloader
|
||||
task *Task
|
||||
|
||||
Meta *ExtensionTaskMeta `json:"meta"`
|
||||
}
|
||||
|
||||
// ExtensionTaskMeta restricts extension scripts to only modify request info
|
||||
type ExtensionTaskMeta struct {
|
||||
Req *base.Request `json:"req"`
|
||||
}
|
||||
|
||||
func NewExtensionTask(download *Downloader, task *Task) *ExtensionTask {
|
||||
newTask := task.clone()
|
||||
// Assign the pointer of the properties that the extension supports modification
|
||||
newTask.Meta = task.Meta
|
||||
newTask.Status = task.Status
|
||||
return &ExtensionTask{
|
||||
Task: newTask,
|
||||
task: newTask,
|
||||
download: download,
|
||||
Meta: &ExtensionTaskMeta{
|
||||
Req: task.Meta.Req,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (t *ExtensionTask) Continue() error {
|
||||
return t.download.Continue(&TaskFilter{
|
||||
IDs: []string{t.ID},
|
||||
ID: []string{t.task.ID},
|
||||
})
|
||||
}
|
||||
|
||||
func (t *ExtensionTask) Pause() error {
|
||||
return t.download.Pause(&TaskFilter{
|
||||
IDs: []string{t.ID},
|
||||
ID: []string{t.task.ID},
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ func (t *Task) Name() string {
|
||||
return t.Meta.Res.Name
|
||||
}
|
||||
|
||||
// Get the name of the first file
|
||||
// get the name of the first file
|
||||
return t.Meta.Res.Files[0].Name
|
||||
}
|
||||
|
||||
@@ -113,13 +113,13 @@ func (t *Task) calcSpeed(speedArr []int64, downloaded int64, usedTime float64) i
|
||||
}
|
||||
|
||||
type TaskFilter struct {
|
||||
IDs []string
|
||||
Statuses []base.Status
|
||||
NotStatuses []base.Status
|
||||
ID []string `json:"id"`
|
||||
Status []base.Status `json:"status"`
|
||||
NotStatus []base.Status `json:"notStatus"`
|
||||
}
|
||||
|
||||
func (f *TaskFilter) IsEmpty() bool {
|
||||
return len(f.IDs) == 0 && len(f.Statuses) == 0 && len(f.NotStatuses) == 0
|
||||
return len(f.ID) == 0 && len(f.Status) == 0 && len(f.NotStatus) == 0
|
||||
}
|
||||
|
||||
type DownloaderConfig struct {
|
||||
|
||||
-389
@@ -1,389 +0,0 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"github.com/GopeedLab/gopeed/pkg/base"
|
||||
"github.com/GopeedLab/gopeed/pkg/download"
|
||||
"github.com/GopeedLab/gopeed/pkg/rest/model"
|
||||
"github.com/gorilla/mux"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
func Info(w http.ResponseWriter, r *http.Request) {
|
||||
info := map[string]any{
|
||||
"version": base.Version,
|
||||
"runtime": runtime.Version(),
|
||||
"os": runtime.GOOS,
|
||||
"arch": runtime.GOARCH,
|
||||
"inDocker": base.InDocker == "true",
|
||||
}
|
||||
WriteJson(w, model.NewOkResult(info))
|
||||
}
|
||||
|
||||
func Resolve(w http.ResponseWriter, r *http.Request) {
|
||||
var req base.Request
|
||||
if ReadJson(r, w, &req) {
|
||||
rr, err := Downloader.Resolve(&req)
|
||||
if err != nil {
|
||||
WriteJson(w, model.NewErrorResult(err.Error()))
|
||||
return
|
||||
}
|
||||
WriteJson(w, model.NewOkResult(rr))
|
||||
}
|
||||
}
|
||||
|
||||
func CreateTask(w http.ResponseWriter, r *http.Request) {
|
||||
var req model.CreateTask
|
||||
if ReadJson(r, w, &req) {
|
||||
var (
|
||||
taskId string
|
||||
err error
|
||||
)
|
||||
if req.Rid != "" {
|
||||
taskId, err = Downloader.Create(req.Rid, req.Opt)
|
||||
} else if req.Req != nil {
|
||||
taskId, err = Downloader.CreateDirect(req.Req, req.Opt)
|
||||
} else {
|
||||
WriteJson(w, model.NewErrorResult("param invalid: rid or req", model.CodeInvalidParam))
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
WriteJson(w, model.NewErrorResult(err.Error()))
|
||||
return
|
||||
}
|
||||
WriteJson(w, model.NewOkResult(taskId))
|
||||
}
|
||||
}
|
||||
|
||||
func CreateTaskBatch(w http.ResponseWriter, r *http.Request) {
|
||||
var req model.CreateTaskBatch
|
||||
if ReadJson(r, w, &req) {
|
||||
if len(req.Reqs) == 0 {
|
||||
WriteJson(w, model.NewErrorResult("param invalid: reqs", model.CodeInvalidParam))
|
||||
return
|
||||
}
|
||||
taskIds, err := Downloader.CreateDirectBatch(req.Reqs, req.Opt)
|
||||
if err != nil {
|
||||
WriteJson(w, model.NewErrorResult(err.Error()))
|
||||
return
|
||||
}
|
||||
WriteJson(w, model.NewOkResult(taskIds))
|
||||
}
|
||||
}
|
||||
|
||||
func PauseTask(w http.ResponseWriter, r *http.Request) {
|
||||
filter, errResult := parseIdFilter(r)
|
||||
if errResult != nil {
|
||||
WriteJson(w, errResult)
|
||||
return
|
||||
}
|
||||
|
||||
if err := Downloader.Pause(filter); err != nil {
|
||||
WriteJson(w, model.NewErrorResult(err.Error()))
|
||||
return
|
||||
}
|
||||
WriteJson(w, model.NewNilResult())
|
||||
}
|
||||
|
||||
func PauseTasks(w http.ResponseWriter, r *http.Request) {
|
||||
filter, errResult := parseFilter(r)
|
||||
if errResult != nil {
|
||||
WriteJson(w, errResult)
|
||||
return
|
||||
}
|
||||
|
||||
if err := Downloader.Pause(filter); err != nil {
|
||||
WriteJson(w, model.NewErrorResult(err.Error()))
|
||||
return
|
||||
}
|
||||
WriteJson(w, model.NewNilResult())
|
||||
}
|
||||
|
||||
func ContinueTask(w http.ResponseWriter, r *http.Request) {
|
||||
filter, errResult := parseIdFilter(r)
|
||||
if errResult != nil {
|
||||
WriteJson(w, errResult)
|
||||
return
|
||||
}
|
||||
|
||||
if err := Downloader.Continue(filter); err != nil {
|
||||
WriteJson(w, model.NewErrorResult(err.Error()))
|
||||
return
|
||||
}
|
||||
WriteJson(w, model.NewNilResult())
|
||||
}
|
||||
|
||||
func ContinueTasks(w http.ResponseWriter, r *http.Request) {
|
||||
filter, errResult := parseFilter(r)
|
||||
if errResult != nil {
|
||||
WriteJson(w, errResult)
|
||||
return
|
||||
}
|
||||
|
||||
if err := Downloader.Continue(filter); err != nil {
|
||||
WriteJson(w, model.NewErrorResult(err.Error()))
|
||||
return
|
||||
}
|
||||
WriteJson(w, model.NewNilResult())
|
||||
}
|
||||
|
||||
func DeleteTask(w http.ResponseWriter, r *http.Request) {
|
||||
filter, errResult := parseIdFilter(r)
|
||||
if errResult != nil {
|
||||
WriteJson(w, errResult)
|
||||
return
|
||||
}
|
||||
force := r.FormValue("force")
|
||||
|
||||
if err := Downloader.Delete(filter, force == "true"); err != nil {
|
||||
WriteJson(w, model.NewErrorResult(err.Error()))
|
||||
return
|
||||
}
|
||||
WriteJson(w, model.NewNilResult())
|
||||
}
|
||||
|
||||
func DeleteTasks(w http.ResponseWriter, r *http.Request) {
|
||||
filter, errResult := parseFilter(r)
|
||||
if errResult != nil {
|
||||
WriteJson(w, errResult)
|
||||
return
|
||||
}
|
||||
force := r.FormValue("force")
|
||||
|
||||
if err := Downloader.Delete(filter, force == "true"); err != nil {
|
||||
WriteJson(w, model.NewErrorResult(err.Error()))
|
||||
return
|
||||
}
|
||||
WriteJson(w, model.NewNilResult())
|
||||
}
|
||||
|
||||
func GetTask(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
taskId := vars["id"]
|
||||
if taskId == "" {
|
||||
WriteJson(w, model.NewErrorResult("param invalid: id", model.CodeInvalidParam))
|
||||
return
|
||||
}
|
||||
task := Downloader.GetTask(taskId)
|
||||
if task == nil {
|
||||
WriteJson(w, model.NewErrorResult("task not found", model.CodeTaskNotFound))
|
||||
return
|
||||
}
|
||||
WriteJson(w, model.NewOkResult(task))
|
||||
}
|
||||
|
||||
func GetTasks(w http.ResponseWriter, r *http.Request) {
|
||||
filter, errResult := parseFilter(r)
|
||||
if errResult != nil {
|
||||
WriteJson(w, errResult)
|
||||
return
|
||||
}
|
||||
|
||||
tasks := Downloader.GetTasksByFilter(filter)
|
||||
WriteJson(w, model.NewOkResult(tasks))
|
||||
}
|
||||
|
||||
func GetConfig(w http.ResponseWriter, r *http.Request) {
|
||||
WriteJson(w, model.NewOkResult(getServerConfig()))
|
||||
}
|
||||
|
||||
func PutConfig(w http.ResponseWriter, r *http.Request) {
|
||||
var cfg base.DownloaderStoreConfig
|
||||
if ReadJson(r, w, &cfg) {
|
||||
if err := Downloader.PutConfig(&cfg); err != nil {
|
||||
WriteJson(w, model.NewErrorResult(err.Error()))
|
||||
return
|
||||
}
|
||||
}
|
||||
WriteJson(w, model.NewNilResult())
|
||||
}
|
||||
|
||||
func InstallExtension(w http.ResponseWriter, r *http.Request) {
|
||||
var req model.InstallExtension
|
||||
if ReadJson(r, w, &req) {
|
||||
var (
|
||||
installedExt *download.Extension
|
||||
err error
|
||||
)
|
||||
if req.DevMode {
|
||||
installedExt, err = Downloader.InstallExtensionByFolder(req.URL, true)
|
||||
} else {
|
||||
installedExt, err = Downloader.InstallExtensionByGit(req.URL)
|
||||
}
|
||||
if err != nil {
|
||||
WriteJson(w, model.NewErrorResult(err.Error()))
|
||||
return
|
||||
}
|
||||
WriteJson(w, model.NewOkResult(installedExt.Identity))
|
||||
}
|
||||
}
|
||||
|
||||
func GetExtensions(w http.ResponseWriter, r *http.Request) {
|
||||
list := Downloader.GetExtensions()
|
||||
WriteJson(w, model.NewOkResult(list))
|
||||
}
|
||||
|
||||
func GetExtension(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
identity := vars["identity"]
|
||||
ext, err := Downloader.GetExtension(identity)
|
||||
if err != nil {
|
||||
WriteJson(w, model.NewErrorResult(err.Error()))
|
||||
return
|
||||
}
|
||||
WriteJson(w, model.NewOkResult(ext))
|
||||
}
|
||||
|
||||
func UpdateExtensionSettings(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
identity := vars["identity"]
|
||||
var req model.UpdateExtensionSettings
|
||||
if ReadJson(r, w, &req) {
|
||||
if err := Downloader.UpdateExtensionSettings(identity, req.Settings); err != nil {
|
||||
WriteJson(w, model.NewErrorResult(err.Error()))
|
||||
return
|
||||
}
|
||||
}
|
||||
WriteJson(w, model.NewNilResult())
|
||||
}
|
||||
|
||||
func SwitchExtension(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
identity := vars["identity"]
|
||||
var switchExtension model.SwitchExtension
|
||||
if ReadJson(r, w, &switchExtension) {
|
||||
if err := Downloader.SwitchExtension(identity, switchExtension.Status); err != nil {
|
||||
WriteJson(w, model.NewErrorResult(err.Error()))
|
||||
return
|
||||
}
|
||||
}
|
||||
WriteJson(w, model.NewNilResult())
|
||||
}
|
||||
|
||||
func DeleteExtension(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
identity := vars["identity"]
|
||||
if err := Downloader.DeleteExtension(identity); err != nil {
|
||||
WriteJson(w, model.NewErrorResult(err.Error()))
|
||||
return
|
||||
}
|
||||
WriteJson(w, model.NewNilResult())
|
||||
}
|
||||
|
||||
func UpdateCheckExtension(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
identity := vars["identity"]
|
||||
newVersion, err := Downloader.UpgradeCheckExtension(identity)
|
||||
if err != nil {
|
||||
WriteJson(w, model.NewErrorResult(err.Error()))
|
||||
return
|
||||
}
|
||||
WriteJson(w, model.NewOkResult(&model.UpdateCheckExtensionResp{
|
||||
NewVersion: newVersion,
|
||||
}))
|
||||
}
|
||||
|
||||
func UpdateExtension(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
identity := vars["identity"]
|
||||
if err := Downloader.UpgradeExtension(identity); err != nil {
|
||||
WriteJson(w, model.NewErrorResult(err.Error()))
|
||||
return
|
||||
}
|
||||
WriteJson(w, model.NewNilResult())
|
||||
}
|
||||
|
||||
func DoProxy(w http.ResponseWriter, r *http.Request) {
|
||||
target := r.Header.Get("X-Target-Uri")
|
||||
if target == "" {
|
||||
writeError(w, "param invalid: X-Target-Uri")
|
||||
return
|
||||
}
|
||||
targetUrl, err := url.Parse(target)
|
||||
if err != nil {
|
||||
writeError(w, err.Error())
|
||||
return
|
||||
}
|
||||
r.RequestURI = ""
|
||||
r.URL = targetUrl
|
||||
r.Host = targetUrl.Host
|
||||
resp, err := http.DefaultClient.Do(r)
|
||||
if err != nil {
|
||||
writeError(w, err.Error())
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
for k, vv := range resp.Header {
|
||||
for _, v := range vv {
|
||||
w.Header().Set(k, v)
|
||||
}
|
||||
}
|
||||
w.WriteHeader(resp.StatusCode)
|
||||
buf, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
writeError(w, err.Error())
|
||||
return
|
||||
}
|
||||
w.Write(buf)
|
||||
}
|
||||
|
||||
func GetStats(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
taskId := vars["id"]
|
||||
if taskId == "" {
|
||||
WriteJson(w, model.NewErrorResult("param invalid: id", model.CodeInvalidParam))
|
||||
return
|
||||
}
|
||||
statsResult, err := Downloader.Stats(taskId)
|
||||
if err != nil {
|
||||
writeError(w, err.Error())
|
||||
return
|
||||
}
|
||||
WriteJson(w, model.NewOkResult(statsResult))
|
||||
}
|
||||
|
||||
func parseIdFilter(r *http.Request) (*download.TaskFilter, any) {
|
||||
vars := mux.Vars(r)
|
||||
taskId := vars["id"]
|
||||
if taskId == "" {
|
||||
return nil, model.NewErrorResult("param invalid: id", model.CodeInvalidParam)
|
||||
}
|
||||
|
||||
filter := &download.TaskFilter{
|
||||
IDs: []string{taskId},
|
||||
}
|
||||
return filter, nil
|
||||
}
|
||||
|
||||
func parseFilter(r *http.Request) (*download.TaskFilter, any) {
|
||||
if err := r.ParseForm(); err != nil {
|
||||
return nil, model.NewErrorResult(err.Error())
|
||||
}
|
||||
|
||||
filter := &download.TaskFilter{
|
||||
IDs: r.Form["id"],
|
||||
Statuses: convertStatues(r.Form["status"]),
|
||||
NotStatuses: convertStatues(r.Form["notStatus"]),
|
||||
}
|
||||
return filter, nil
|
||||
}
|
||||
|
||||
func convertStatues(statues []string) []base.Status {
|
||||
result := make([]base.Status, 0)
|
||||
for _, status := range statues {
|
||||
result = append(result, base.Status(status))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func writeError(w http.ResponseWriter, msg string) {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte(msg))
|
||||
}
|
||||
|
||||
func getServerConfig() *base.DownloaderStoreConfig {
|
||||
cfg, _ := Downloader.GetConfig()
|
||||
return cfg
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
package rest
|
||||
|
||||
type Config struct {
|
||||
Host string `json:"host"`
|
||||
Port int `json:"port"`
|
||||
}
|
||||
@@ -1,241 +0,0 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/GopeedLab/gopeed/pkg/download"
|
||||
"github.com/GopeedLab/gopeed/pkg/rest/model"
|
||||
"github.com/GopeedLab/gopeed/pkg/util"
|
||||
"github.com/gorilla/handlers"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/pkg/errors"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
srv *http.Server
|
||||
runningPort int
|
||||
|
||||
Downloader *download.Downloader
|
||||
)
|
||||
|
||||
func Start(startCfg *model.StartConfig) (port int, err error) {
|
||||
// avoid repeat start
|
||||
if srv != nil {
|
||||
return runningPort, nil
|
||||
}
|
||||
|
||||
var listener net.Listener
|
||||
srv, listener, err = BuildServer(startCfg)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
if err := srv.Serve(listener); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
|
||||
if addr, ok := listener.Addr().(*net.TCPAddr); ok {
|
||||
port = addr.Port
|
||||
runningPort = port
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func Stop() {
|
||||
defer func() {
|
||||
srv = nil
|
||||
}()
|
||||
|
||||
if srv != nil {
|
||||
if err := srv.Shutdown(context.TODO()); err != nil {
|
||||
Downloader.Logger.Warn().Err(err).Msg("shutdown server failed")
|
||||
}
|
||||
}
|
||||
if Downloader != nil {
|
||||
if err := Downloader.Close(); err != nil {
|
||||
Downloader.Logger.Warn().Err(err).Msg("close downloader failed")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BuildServer(startCfg *model.StartConfig) (*http.Server, net.Listener, error) {
|
||||
if startCfg == nil {
|
||||
startCfg = &model.StartConfig{}
|
||||
}
|
||||
startCfg.Init()
|
||||
|
||||
downloadCfg := &download.DownloaderConfig{
|
||||
ProductionMode: startCfg.ProductionMode,
|
||||
RefreshInterval: startCfg.RefreshInterval,
|
||||
}
|
||||
if startCfg.Storage == model.StorageBolt {
|
||||
downloadCfg.Storage = download.NewBoltStorage(startCfg.StorageDir)
|
||||
} else {
|
||||
downloadCfg.Storage = download.NewMemStorage()
|
||||
}
|
||||
downloadCfg.StorageDir = startCfg.StorageDir
|
||||
downloadCfg.Init()
|
||||
Downloader = download.NewDownloader(downloadCfg)
|
||||
if err := Downloader.Setup(); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if startCfg.Network == "unix" {
|
||||
util.SafeRemove(startCfg.Address)
|
||||
}
|
||||
|
||||
listener, err := net.Listen(startCfg.Network, startCfg.Address)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var r = mux.NewRouter()
|
||||
r.Methods(http.MethodGet).Path("/api/v1/info").HandlerFunc(Info)
|
||||
r.Methods(http.MethodPost).Path("/api/v1/resolve").HandlerFunc(Resolve)
|
||||
r.Methods(http.MethodPost).Path("/api/v1/tasks").HandlerFunc(CreateTask)
|
||||
r.Methods(http.MethodPost).Path("/api/v1/tasks/batch").HandlerFunc(CreateTaskBatch)
|
||||
r.Methods(http.MethodPut).Path("/api/v1/tasks/{id}/pause").HandlerFunc(PauseTask)
|
||||
r.Methods(http.MethodPut).Path("/api/v1/tasks/pause").HandlerFunc(PauseTasks)
|
||||
r.Methods(http.MethodPut).Path("/api/v1/tasks/{id}/continue").HandlerFunc(ContinueTask)
|
||||
r.Methods(http.MethodPut).Path("/api/v1/tasks/continue").HandlerFunc(ContinueTasks)
|
||||
r.Methods(http.MethodDelete).Path("/api/v1/tasks/{id}").HandlerFunc(DeleteTask)
|
||||
r.Methods(http.MethodDelete).Path("/api/v1/tasks").HandlerFunc(DeleteTasks)
|
||||
r.Methods(http.MethodGet).Path("/api/v1/tasks/{id}").HandlerFunc(GetTask)
|
||||
r.Methods(http.MethodGet).Path("/api/v1/tasks").HandlerFunc(GetTasks)
|
||||
r.Methods(http.MethodGet).Path("/api/v1/tasks/{id}/stats").HandlerFunc(GetStats)
|
||||
r.Methods(http.MethodGet).Path("/api/v1/config").HandlerFunc(GetConfig)
|
||||
r.Methods(http.MethodPut).Path("/api/v1/config").HandlerFunc(PutConfig)
|
||||
r.Methods(http.MethodPost).Path("/api/v1/extensions").HandlerFunc(InstallExtension)
|
||||
r.Methods(http.MethodGet).Path("/api/v1/extensions").HandlerFunc(GetExtensions)
|
||||
r.Methods(http.MethodGet).Path("/api/v1/extensions/{identity}").HandlerFunc(GetExtension)
|
||||
r.Methods(http.MethodPut).Path("/api/v1/extensions/{identity}/settings").HandlerFunc(UpdateExtensionSettings)
|
||||
r.Methods(http.MethodPut).Path("/api/v1/extensions/{identity}/switch").HandlerFunc(SwitchExtension)
|
||||
r.Methods(http.MethodDelete).Path("/api/v1/extensions/{identity}").HandlerFunc(DeleteExtension)
|
||||
r.Methods(http.MethodGet).Path("/api/v1/extensions/{identity}/update").HandlerFunc(UpdateCheckExtension)
|
||||
r.Methods(http.MethodPost).Path("/api/v1/extensions/{identity}/update").HandlerFunc(UpdateExtension)
|
||||
r.Path("/api/v1/proxy").HandlerFunc(DoProxy)
|
||||
if startCfg.WebEnable {
|
||||
r.PathPrefix("/fs/tasks").Handler(http.FileServer(new(taskFileSystem)))
|
||||
r.PathPrefix("/fs/extensions").Handler(http.FileServer(new(extensionFileSystem)))
|
||||
r.PathPrefix("/").Handler(http.FileServer(http.FS(startCfg.WebFS)))
|
||||
}
|
||||
|
||||
if startCfg.ApiToken != "" || (startCfg.WebEnable && startCfg.WebBasicAuth != nil) {
|
||||
r.Use(func(h http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if startCfg.ApiToken != "" && r.Header.Get("X-Api-Token") == startCfg.ApiToken {
|
||||
h.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
if startCfg.WebEnable && startCfg.WebBasicAuth != nil {
|
||||
if r.Header.Get("Authorization") == startCfg.WebBasicAuth.Authorization() {
|
||||
h.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
w.Header().Set("WWW-Authenticate", "Basic realm=\"gopeed web\"")
|
||||
}
|
||||
WriteStatusJson(w, http.StatusUnauthorized, model.NewErrorResult("unauthorized", model.CodeUnauthorized))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// recover panic
|
||||
r.Use(func(h http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
defer func() {
|
||||
if v := recover(); v != nil {
|
||||
err := errors.WithStack(fmt.Errorf("%v", v))
|
||||
Downloader.Logger.Error().Stack().Err(err).Msgf("http server panic: %s %s", r.Method, r.RequestURI)
|
||||
WriteJson(w, model.NewErrorResult(err.Error(), model.CodeError))
|
||||
}
|
||||
}()
|
||||
h.ServeHTTP(w, r)
|
||||
})
|
||||
})
|
||||
|
||||
srv = &http.Server{Handler: handlers.CORS(
|
||||
handlers.AllowedHeaders([]string{"Content-Type", "X-Api-Token", "X-Target-Uri"}),
|
||||
handlers.AllowedMethods([]string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"}),
|
||||
handlers.AllowedOrigins([]string{"*"}),
|
||||
)(r)}
|
||||
return srv, listener, nil
|
||||
}
|
||||
|
||||
func resolvePath(urlPath string, prefix string) (identity string, path string, err error) {
|
||||
// remove prefix
|
||||
clearPath := strings.TrimPrefix(urlPath, prefix)
|
||||
// match extension identity, eg: /fs/extensions/identity/xxx
|
||||
reg := regexp.MustCompile(`^/([^/]+)/(.*)$`)
|
||||
if !reg.MatchString(clearPath) {
|
||||
err = os.ErrNotExist
|
||||
return
|
||||
}
|
||||
matched := reg.FindStringSubmatch(clearPath)
|
||||
if len(matched) != 3 {
|
||||
err = os.ErrNotExist
|
||||
return
|
||||
}
|
||||
return matched[1], matched[2], nil
|
||||
}
|
||||
|
||||
// handle task file resource
|
||||
type taskFileSystem struct {
|
||||
}
|
||||
|
||||
func (e *taskFileSystem) Open(name string) (http.File, error) {
|
||||
// get extension identity
|
||||
identity, path, err := resolvePath(name, "/fs/tasks")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
task := Downloader.GetTask(identity)
|
||||
if task == nil {
|
||||
return nil, os.ErrNotExist
|
||||
}
|
||||
return os.Open(filepath.Join(task.Meta.RootDirPath(), path))
|
||||
}
|
||||
|
||||
// handle extension file resource
|
||||
type extensionFileSystem struct {
|
||||
}
|
||||
|
||||
func (e *extensionFileSystem) Open(name string) (http.File, error) {
|
||||
// get extension identity
|
||||
identity, path, err := resolvePath(name, "/fs/extensions")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
extension, err := Downloader.GetExtension(identity)
|
||||
if err != nil {
|
||||
return nil, os.ErrNotExist
|
||||
}
|
||||
extensionPath := Downloader.ExtensionPath(extension)
|
||||
return os.Open(filepath.Join(extensionPath, path))
|
||||
}
|
||||
|
||||
func ReadJson(r *http.Request, w http.ResponseWriter, v any) bool {
|
||||
if err := json.NewDecoder(r.Body).Decode(v); err != nil {
|
||||
WriteJson(w, model.NewErrorResult(err.Error()))
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func WriteJson(w http.ResponseWriter, v any) {
|
||||
WriteStatusJson(w, http.StatusOK, v)
|
||||
}
|
||||
|
||||
func WriteStatusJson(w http.ResponseWriter, statusCode int, v any) {
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
w.WriteHeader(statusCode)
|
||||
json.NewEncoder(w).Encode(v)
|
||||
}
|
||||
@@ -74,14 +74,8 @@ typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
/* Return type for Start */
|
||||
struct Start_return {
|
||||
GoInt r0;
|
||||
char* r1;
|
||||
};
|
||||
extern struct Start_return Start(char* cfg);
|
||||
extern void Stop();
|
||||
extern __declspec(dllexport) char* Init(char* cfg);
|
||||
extern __declspec(dllexport) char* Invoke(char* req);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
@@ -1,210 +0,0 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:dio/io.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:gopeed/api/model/extension.dart';
|
||||
import 'package:gopeed/api/model/install_extension.dart';
|
||||
import 'package:gopeed/api/model/switch_extension.dart';
|
||||
|
||||
import '../util/util.dart';
|
||||
import 'model/create_task.dart';
|
||||
import 'model/downloader_config.dart';
|
||||
import 'model/request.dart';
|
||||
import 'model/resolve_result.dart';
|
||||
import 'model/result.dart';
|
||||
import 'model/task.dart';
|
||||
import 'model/update_extension_settings.dart';
|
||||
import 'model/update_check_extension_resp.dart';
|
||||
|
||||
class _Client {
|
||||
static _Client? _instance;
|
||||
|
||||
late Dio dio;
|
||||
|
||||
_Client._internal();
|
||||
|
||||
factory _Client(String network, String address, String apiToken) {
|
||||
if (_instance == null) {
|
||||
_instance = _Client._internal();
|
||||
var dio = Dio();
|
||||
final isUnixSocket = network == 'unix';
|
||||
var baseUrl = 'http://127.0.0.1';
|
||||
if (!isUnixSocket) {
|
||||
if (Util.isWeb()) {
|
||||
baseUrl = kDebugMode ? 'http://127.0.0.1:9999' : '';
|
||||
} else {
|
||||
baseUrl = 'http://$address';
|
||||
}
|
||||
}
|
||||
dio.options.baseUrl = baseUrl;
|
||||
dio.options.contentType = Headers.jsonContentType;
|
||||
dio.options.sendTimeout = const Duration(seconds: 5);
|
||||
dio.options.connectTimeout = const Duration(seconds: 5);
|
||||
dio.options.receiveTimeout = const Duration(seconds: 60);
|
||||
dio.interceptors.add(InterceptorsWrapper(onRequest: (options, handler) {
|
||||
if (apiToken.isNotEmpty) {
|
||||
options.headers['X-Api-Token'] = apiToken;
|
||||
}
|
||||
handler.next(options);
|
||||
}));
|
||||
|
||||
_instance!.dio = dio;
|
||||
if (isUnixSocket) {
|
||||
(_instance!.dio.httpClientAdapter as IOHttpClientAdapter)
|
||||
.createHttpClient = () {
|
||||
final client = HttpClient();
|
||||
client.connectionFactory =
|
||||
(Uri uri, String? proxyHost, int? proxyPort) {
|
||||
return Socket.startConnect(
|
||||
InternetAddress(address, type: InternetAddressType.unix), 0);
|
||||
};
|
||||
return client;
|
||||
};
|
||||
}
|
||||
}
|
||||
return _instance!;
|
||||
}
|
||||
}
|
||||
|
||||
class TimeoutException implements Exception {
|
||||
final String message;
|
||||
|
||||
TimeoutException(this.message);
|
||||
}
|
||||
|
||||
late _Client _client;
|
||||
|
||||
void init(String network, String address, String apiToken) {
|
||||
_client = _Client(network, address, apiToken);
|
||||
}
|
||||
|
||||
Future<T> _parse<T>(
|
||||
Future<Response> Function() fetch,
|
||||
T Function(dynamic json)? fromJsonT,
|
||||
) async {
|
||||
try {
|
||||
var resp = await fetch();
|
||||
fromJsonT ??= (json) => null as T;
|
||||
final result = Result<T>.fromJson(resp.data, fromJsonT);
|
||||
if (result.code == 0) {
|
||||
return result.data as T;
|
||||
} else {
|
||||
throw Exception(result);
|
||||
}
|
||||
} on DioException catch (e) {
|
||||
if (e.type == DioExceptionType.sendTimeout ||
|
||||
e.type == DioExceptionType.receiveTimeout ||
|
||||
e.type == DioExceptionType.connectionTimeout) {
|
||||
throw TimeoutException("request timeout");
|
||||
}
|
||||
throw Exception(Result(code: 1000, msg: e.message));
|
||||
}
|
||||
}
|
||||
|
||||
Future<ResolveResult> resolve(Request request) async {
|
||||
return _parse<ResolveResult>(
|
||||
() => _client.dio.post("/api/v1/resolve", data: request),
|
||||
(data) => ResolveResult.fromJson(data));
|
||||
}
|
||||
|
||||
Future<String> createTask(CreateTask createTask) async {
|
||||
return _parse<String>(
|
||||
() => _client.dio.post("/api/v1/tasks", data: createTask),
|
||||
(data) => data as String);
|
||||
}
|
||||
|
||||
Future<List<Task>> getTasks(List<Status> statuses) async {
|
||||
return _parse<List<Task>>(
|
||||
() => _client.dio.get(
|
||||
"/api/v1/tasks?${statuses.map((e) => "status=${e.name}").join("&")}"),
|
||||
(data) => (data as List).map((e) => Task.fromJson(e)).toList());
|
||||
}
|
||||
|
||||
Future<void> pauseTask(String id) async {
|
||||
return _parse(() => _client.dio.put("/api/v1/tasks/$id/pause"), null);
|
||||
}
|
||||
|
||||
Future<void> continueTask(String id) async {
|
||||
return _parse(() => _client.dio.put("/api/v1/tasks/$id/continue"), null);
|
||||
}
|
||||
|
||||
Future<void> pauseAllTasks() async {
|
||||
return _parse(() => _client.dio.put("/api/v1/tasks/pause"), null);
|
||||
}
|
||||
|
||||
Future<void> continueAllTasks() async {
|
||||
return _parse(() => _client.dio.put("/api/v1/tasks/continue"), null);
|
||||
}
|
||||
|
||||
Future<void> deleteTask(String id, bool force) async {
|
||||
return _parse(
|
||||
() => _client.dio.delete("/api/v1/tasks/$id?force=$force"), null);
|
||||
}
|
||||
|
||||
Future<DownloaderConfig> getConfig() async {
|
||||
return _parse(() => _client.dio.get("/api/v1/config"),
|
||||
(data) => DownloaderConfig.fromJson(data));
|
||||
}
|
||||
|
||||
Future<void> putConfig(DownloaderConfig config) async {
|
||||
return _parse(() => _client.dio.put("/api/v1/config", data: config), null);
|
||||
}
|
||||
|
||||
Future<void> installExtension(InstallExtension installExtension) async {
|
||||
return _parse(
|
||||
() => _client.dio.post("/api/v1/extensions", data: installExtension),
|
||||
null);
|
||||
}
|
||||
|
||||
Future<List<Extension>> getExtensions() async {
|
||||
return _parse<List<Extension>>(() => _client.dio.get("/api/v1/extensions"),
|
||||
(data) => (data as List).map((e) => Extension.fromJson(e)).toList());
|
||||
}
|
||||
|
||||
Future<void> updateExtensionSettings(
|
||||
String identity, UpdateExtensionSettings updateExtensionSettings) async {
|
||||
return _parse(
|
||||
() => _client.dio.put("/api/v1/extensions/$identity/settings",
|
||||
data: updateExtensionSettings),
|
||||
null);
|
||||
}
|
||||
|
||||
Future<void> switchExtension(
|
||||
String identity, SwitchExtension switchExtension) async {
|
||||
return _parse(
|
||||
() => _client.dio
|
||||
.put("/api/v1/extensions/$identity/switch", data: switchExtension),
|
||||
null);
|
||||
}
|
||||
|
||||
Future<void> deleteExtension(String identity) async {
|
||||
return _parse(() => _client.dio.delete("/api/v1/extensions/$identity"), null);
|
||||
}
|
||||
|
||||
Future<UpdateCheckExtensionResp> upgradeCheckExtension(String identity) async {
|
||||
return _parse(() => _client.dio.get("/api/v1/extensions/$identity/update"),
|
||||
(data) => UpdateCheckExtensionResp.fromJson(data));
|
||||
}
|
||||
|
||||
Future<void> updateExtension(String identity) async {
|
||||
return _parse(
|
||||
() => _client.dio.post("/api/v1/extensions/$identity/update"), null);
|
||||
}
|
||||
|
||||
Future<Response<String>> proxyRequest<T>(String uri,
|
||||
{data, Options? options}) async {
|
||||
options ??= Options();
|
||||
options.headers ??= {};
|
||||
options.headers!["X-Target-Uri"] = uri;
|
||||
|
||||
// add timestamp to avoid cache
|
||||
return _client.dio.request(
|
||||
"/api/v1/proxy?t=${DateTime.now().millisecondsSinceEpoch}",
|
||||
data: data,
|
||||
options: options);
|
||||
}
|
||||
|
||||
String join(String path) {
|
||||
return "${_client.dio.options.baseUrl}/${Util.cleanPath(path)}";
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
class ApiException implements Exception {
|
||||
final int code;
|
||||
final String message;
|
||||
|
||||
ApiException(this.code, this.message);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import 'package:dio/dio.dart';
|
||||
|
||||
import '../api_exception.dart';
|
||||
import '../model/result.dart';
|
||||
|
||||
mixin LibgopeedBootBase {
|
||||
Dio createDio() {
|
||||
Dio dio = Dio();
|
||||
dio.options.contentType = Headers.jsonContentType;
|
||||
dio.options.sendTimeout = const Duration(seconds: 5);
|
||||
dio.options.connectTimeout = const Duration(seconds: 5);
|
||||
dio.options.receiveTimeout = const Duration(seconds: 60);
|
||||
return dio;
|
||||
}
|
||||
|
||||
T handleResult<T>(
|
||||
Result<T> result,
|
||||
) {
|
||||
if (result.code == 0) {
|
||||
return result.data as T;
|
||||
} else {
|
||||
throw ApiException(result.code, result.msg ?? '');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:ffi';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
|
||||
import '../../api/model/create_task.dart';
|
||||
import '../../api/model/create_task_batch.dart';
|
||||
import '../../api/model/downloader_config.dart';
|
||||
import '../../api/model/extension.dart';
|
||||
import '../../api/model/http_listen_result.dart';
|
||||
import '../../api/model/info.dart';
|
||||
import '../../api/model/install_extension.dart';
|
||||
import '../../api/model/request.dart';
|
||||
import '../../api/model/resolve_result.dart';
|
||||
import '../../api/model/result.dart';
|
||||
import '../../api/model/switch_extension.dart';
|
||||
import '../../api/model/task.dart';
|
||||
import '../../api/model/task_filter.dart';
|
||||
import '../../api/model/update_check_extension_resp.dart';
|
||||
import '../../api/model/update_extension_settings.dart';
|
||||
import '../../util/extensions.dart';
|
||||
import '../../util/util.dart';
|
||||
import '../libgopeed_boot.dart';
|
||||
import '../native/channel/libgopeed_channel.dart';
|
||||
import '../native/ffi/libgopeed_bind.dart';
|
||||
import '../native/ffi/libgopeed_ffi.dart';
|
||||
import '../native/libgopeed_interface.dart';
|
||||
import '../native/model/invoke_request.dart';
|
||||
import '../native/model/start_config.dart';
|
||||
import 'libgopeed_boot_base.dart';
|
||||
|
||||
LibgopeedBoot create() => LibgopeedBootNative();
|
||||
|
||||
class LibgopeedBootNative
|
||||
with LibgopeedBootBase
|
||||
implements LibgopeedBoot, LibgopeedApi {
|
||||
late LibgopeedAbi _libgopeedAbi;
|
||||
late Dio _dio;
|
||||
|
||||
LibgopeedBootNative() {
|
||||
if (Util.isDesktop()) {
|
||||
var libName = "libgopeed.";
|
||||
if (Platform.isWindows) {
|
||||
libName += "dll";
|
||||
}
|
||||
if (Platform.isMacOS) {
|
||||
libName += "dylib";
|
||||
}
|
||||
if (Platform.isLinux) {
|
||||
libName += "so";
|
||||
}
|
||||
_libgopeedAbi = LibgopeedFFi(LibgopeedBind(DynamicLibrary.open(libName)));
|
||||
} else {
|
||||
_libgopeedAbi = LibgopeedChannel();
|
||||
}
|
||||
}
|
||||
|
||||
Future<T> _invoke<T>(
|
||||
String method,
|
||||
List<dynamic> params,
|
||||
T Function(dynamic json)? fromJsonT,
|
||||
) async {
|
||||
final invokeRequest = InvokeRequest(method: method, params: params);
|
||||
final resp = await _libgopeedAbi.invoke(jsonEncode(invokeRequest));
|
||||
final result =
|
||||
Result<T>.fromJson(jsonDecode(resp), fromJsonT ??= (json) => null as T);
|
||||
return handleResult(result);
|
||||
}
|
||||
|
||||
List<dynamic> _parseTaskFilter(TaskFilter? filter) {
|
||||
if (filter == null) {
|
||||
return [null];
|
||||
}
|
||||
return [filter];
|
||||
}
|
||||
|
||||
@override
|
||||
Future<LibgopeedApi> init(StartConfig cfg) async {
|
||||
await _libgopeedAbi.init(jsonEncode(cfg.toJson()));
|
||||
_dio = createDio();
|
||||
return this;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<HttpListenResult> startHttp() async {
|
||||
return _invoke<HttpListenResult>(
|
||||
"StartHttp", [], (json) => HttpListenResult.fromJson(json));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> stopHttp() async {
|
||||
await _invoke<void>("StopHttp", [], null);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> restartHttp() async {
|
||||
await _invoke<void>("RestartHttp", [], null);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Info> info() async {
|
||||
return _invoke<Info>("Info", [], (json) => Info.fromJson(json));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<ResolveResult> resolve(Request request) async {
|
||||
return _invoke<ResolveResult>(
|
||||
"Resolve", [request], (json) => ResolveResult.fromJson(json));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String> createTask(CreateTask createTask) async {
|
||||
return _invoke<String>(
|
||||
"CreateTask", [createTask], (json) => json as String);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String> createTaskBatch(CreateTaskBatch createTask) async {
|
||||
return _invoke<String>(
|
||||
"CreateTaskBatch", [createTask], (json) => json as String);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> pauseTask(String id) async {
|
||||
await _invoke<void>("PauseTask", [id], null);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> pauseTasks(TaskFilter? filter) async {
|
||||
await _invoke<void>("PauseTasks", _parseTaskFilter(filter), null);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> continueTask(String id) async {
|
||||
await _invoke<void>("ContinueTask", [id], null);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> continueTasks(TaskFilter? filter) async {
|
||||
await _invoke<void>("ContinueTasks", _parseTaskFilter(filter), null);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> deleteTask(String id, bool force) async {
|
||||
await _invoke<void>("DeleteTask", [id, force], null);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> deleteTasks(TaskFilter? filter, bool force) async {
|
||||
await _invoke<void>(
|
||||
"DeleteTasks",
|
||||
_parseTaskFilter(filter).apply((it) {
|
||||
it.add(force);
|
||||
}),
|
||||
null);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Task> getTask(String id) async {
|
||||
return _invoke<Task>("GetTask", [id], (json) => Task.fromJson(json));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<Task>> getTasks(TaskFilter? filter) async {
|
||||
return _invoke<List<Task>>("GetTasks", _parseTaskFilter(filter),
|
||||
(json) => (json as List).map((e) => Task.fromJson(e)).toList());
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Map<String, dynamic>> getTaskStats(String id) async {
|
||||
return _invoke<Map<String, dynamic>>(
|
||||
"GetTaskStats", [id], (json) => json as Map<String, dynamic>);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<DownloaderConfig> getConfig() async {
|
||||
return _invoke<DownloaderConfig>(
|
||||
"GetConfig", [], (json) => DownloaderConfig.fromJson(json));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> putConfig(DownloaderConfig config) async {
|
||||
await _invoke<void>("PutConfig", [config], null);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> installExtension(InstallExtension installExtension) async {
|
||||
await _invoke<void>("InstallExtension", [installExtension], null);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<Extension>> getExtensions() async {
|
||||
return _invoke<List<Extension>>("GetExtensions", [],
|
||||
(json) => (json as List).map((e) => Extension.fromJson(e)).toList());
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updateExtensionSettings(
|
||||
String identity, UpdateExtensionSettings updateExtensionSettings) async {
|
||||
await _invoke<void>(
|
||||
"UpdateExtensionSettings", [identity, updateExtensionSettings], null);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> switchExtension(
|
||||
String identity, SwitchExtension switchExtension) async {
|
||||
await _invoke<void>("SwitchExtension", [identity, switchExtension], null);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> deleteExtension(String identity) async {
|
||||
await _invoke<void>("DeleteExtension", [identity], null);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<UpdateCheckExtensionResp> upgradeCheckExtension(
|
||||
String identity) async {
|
||||
return _invoke<UpdateCheckExtensionResp>("UpgradeCheckExtension",
|
||||
[identity], (json) => UpdateCheckExtensionResp.fromJson(json));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> upgradeExtension(String identity) async {
|
||||
await _invoke<void>("UpgradeExtension", [identity], null);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
await _invoke<void>("Close", [], null);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Response<String>> proxyRequest<T>(String uri,
|
||||
{data, Options? options}) {
|
||||
return _dio.request(uri, data: data, options: options);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,277 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import '../../api/model/create_task.dart';
|
||||
import '../../api/model/create_task_batch.dart';
|
||||
import '../../api/model/downloader_config.dart';
|
||||
import '../../api/model/extension.dart';
|
||||
import '../../api/model/http_listen_result.dart';
|
||||
import '../../api/model/info.dart';
|
||||
import '../../api/model/install_extension.dart';
|
||||
import '../../api/model/request.dart';
|
||||
import '../../api/model/resolve_result.dart';
|
||||
import '../../api/model/result.dart';
|
||||
import '../../api/model/switch_extension.dart';
|
||||
import '../../api/model/task.dart';
|
||||
import '../../api/model/task_filter.dart';
|
||||
import '../../api/model/update_check_extension_resp.dart';
|
||||
import '../../api/model/update_extension_settings.dart';
|
||||
import '../../util/extensions.dart';
|
||||
import '../../util/util.dart';
|
||||
import '../api_exception.dart';
|
||||
import '../libgopeed_boot.dart';
|
||||
import '../native/libgopeed_interface.dart';
|
||||
import '../native/model/start_config.dart';
|
||||
import 'libgopeed_boot_base.dart';
|
||||
|
||||
LibgopeedBoot create() => LibgopeedBootWeb();
|
||||
|
||||
class LibgopeedBootWeb
|
||||
with LibgopeedBootBase
|
||||
implements LibgopeedBoot, LibgopeedApi {
|
||||
late Dio _dio;
|
||||
static const String _apiPrefix = '/api/v1';
|
||||
|
||||
LibgopeedBootWeb();
|
||||
|
||||
Future<T> _fetch<T>(
|
||||
Future<Response> Function() fetch,
|
||||
T Function(dynamic json)? fromJsonT,
|
||||
) async {
|
||||
try {
|
||||
var resp = await fetch();
|
||||
fromJsonT ??= (json) => null as T;
|
||||
final result = Result<T>.fromJson(resp.data, fromJsonT);
|
||||
return handleResult(result);
|
||||
} on DioException catch (e) {
|
||||
if (e.type == DioExceptionType.sendTimeout ||
|
||||
e.type == DioExceptionType.receiveTimeout ||
|
||||
e.type == DioExceptionType.connectionTimeout) {
|
||||
throw ApiException(1000, 'request timeout');
|
||||
}
|
||||
throw ApiException(1000, e.message ?? "");
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, dynamic>? _parseTaskFilter(TaskFilter? filter) {
|
||||
if (filter == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Map<String, dynamic> params = {};
|
||||
if (filter.id != null) {
|
||||
params['id'] = filter.id;
|
||||
}
|
||||
if (filter.status != null) {
|
||||
params['status'] = filter.status;
|
||||
}
|
||||
if (filter.notStatus != null) {
|
||||
params['notStatus'] = filter.notStatus;
|
||||
}
|
||||
if (params.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<LibgopeedApi> init(StartConfig cfg) async {
|
||||
_dio = createDio();
|
||||
_dio.options.baseUrl = kDebugMode ? 'http://127.0.0.1:9999' : '';
|
||||
return this;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<HttpListenResult> startHttp() async {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> stopHttp() async {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> restartHttp() async {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Info> info() {
|
||||
return _fetch<Info>(
|
||||
() => _dio.get("$_apiPrefix/info"), (data) => Info.fromJson(data));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<ResolveResult> resolve(Request request) async {
|
||||
return _fetch<ResolveResult>(
|
||||
() => _dio.post("$_apiPrefix/resolve", data: request),
|
||||
(data) => ResolveResult.fromJson(data));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String> createTask(CreateTask createTask) async {
|
||||
return _fetch<String>(
|
||||
() => _dio.post("$_apiPrefix/tasks", data: createTask),
|
||||
(data) => data as String);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String> createTaskBatch(CreateTaskBatch createTask) async {
|
||||
return _fetch<String>(
|
||||
() => _dio.post("$_apiPrefix/tasks", data: createTask),
|
||||
(data) => data as String);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> pauseTask(String id) async {
|
||||
await _fetch<void>(() => _dio.put("$_apiPrefix/tasks/$id/pause"), null);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> pauseTasks(TaskFilter? filter) async {
|
||||
await _fetch<void>(
|
||||
() => _dio.put("$_apiPrefix/tasks/pause",
|
||||
queryParameters: _parseTaskFilter(filter)),
|
||||
null);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> continueTask(String id) async {
|
||||
await _fetch<void>(() => _dio.put("$_apiPrefix/tasks/$id/continue"), null);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> continueTasks(TaskFilter? filter) async {
|
||||
await _fetch<void>(
|
||||
() => _dio.put("$_apiPrefix/tasks/continue",
|
||||
queryParameters: _parseTaskFilter(filter)),
|
||||
null);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> deleteTask(String id, bool force) async {
|
||||
await _fetch<void>(
|
||||
() => _dio
|
||||
.delete("$_apiPrefix/tasks/$id", queryParameters: {'force': force}),
|
||||
null);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> deleteTasks(TaskFilter? filter, bool force) async {
|
||||
await _fetch<void>(
|
||||
() => _dio.delete("$_apiPrefix/tasks",
|
||||
queryParameters: _parseTaskFilter(filter)?.apply((it) {
|
||||
it['force'] = force;
|
||||
})),
|
||||
null);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Task> getTask(String id) async {
|
||||
return _fetch<Task>(
|
||||
() => _dio.get("$_apiPrefix/tasks/$id"), (data) => Task.fromJson(data));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<Task>> getTasks(TaskFilter? filter) async {
|
||||
return _fetch<List<Task>>(
|
||||
() => _dio.get("$_apiPrefix/tasks",
|
||||
queryParameters: _parseTaskFilter(filter)),
|
||||
(data) => (data as List).map((e) => Task.fromJson(e)).toList());
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Map<String, dynamic>> getTaskStats(String id) async {
|
||||
return _fetch<Map<String, dynamic>>(
|
||||
() => _dio.get("$_apiPrefix/tasks/$id/stats"),
|
||||
(data) => data as Map<String, dynamic>);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<DownloaderConfig> getConfig() async {
|
||||
return _fetch<DownloaderConfig>(() => _dio.get("$_apiPrefix/config"),
|
||||
(data) => DownloaderConfig.fromJson(data));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> putConfig(DownloaderConfig config) async {
|
||||
await _fetch<void>(
|
||||
() => _dio.put("$_apiPrefix/config", data: config), null);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> installExtension(InstallExtension installExtension) async {
|
||||
await _fetch<void>(
|
||||
() => _dio.post("$_apiPrefix/extensions", data: installExtension),
|
||||
null);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<Extension>> getExtensions() async {
|
||||
return _fetch<List<Extension>>(() => _dio.get("$_apiPrefix/extensions"),
|
||||
(data) => (data as List).map((e) => Extension.fromJson(e)).toList());
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updateExtensionSettings(
|
||||
String identity, UpdateExtensionSettings updateExtensionSettings) async {
|
||||
await _fetch<void>(
|
||||
() => _dio.put("$_apiPrefix/extensions/$identity/settings",
|
||||
data: updateExtensionSettings),
|
||||
null);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> switchExtension(
|
||||
String identity, SwitchExtension switchExtension) async {
|
||||
await _fetch<void>(
|
||||
() => _dio.put("$_apiPrefix/extensions/$identity/switch",
|
||||
data: switchExtension),
|
||||
null);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> deleteExtension(String identity) async {
|
||||
await _fetch<void>(
|
||||
() => _dio.delete("$_apiPrefix/extensions/$identity"), null);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<UpdateCheckExtensionResp> upgradeCheckExtension(
|
||||
String identity) async {
|
||||
return _fetch<UpdateCheckExtensionResp>(
|
||||
() => _dio.get("$_apiPrefix/extensions/$identity/upgrade"),
|
||||
(data) => UpdateCheckExtensionResp.fromJson(data));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> upgradeExtension(String identity) async {
|
||||
await _fetch<void>(
|
||||
() => _dio.post("$_apiPrefix/extensions/$identity/upgrade"), null);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
// To avoid CORS, use a backend proxy to forward the request
|
||||
@override
|
||||
Future<Response<String>> proxyRequest<T>(String uri,
|
||||
{data, Options? options}) async {
|
||||
options ??= Options();
|
||||
options.headers ??= {};
|
||||
options.headers!["X-Target-Uri"] = uri;
|
||||
|
||||
// add timestamp to avoid cache
|
||||
return _dio.request(
|
||||
"/api/v1/proxy?t=${DateTime.now().millisecondsSinceEpoch}",
|
||||
data: data,
|
||||
options: options);
|
||||
}
|
||||
|
||||
String join(String path) {
|
||||
return "${_dio.options.baseUrl}/${Util.cleanPath(path)}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import "libgopeed_boot_stub.dart"
|
||||
if (dart.library.html) 'entry/libgopeed_boot_browser.dart'
|
||||
if (dart.library.io) 'entry/libgopeed_boot_native.dart';
|
||||
import 'native/libgopeed_interface.dart';
|
||||
|
||||
abstract class LibgopeedBoot implements LibgopeedApiSingleton {
|
||||
static LibgopeedApi? _instance;
|
||||
|
||||
static void singleton(LibgopeedApi instance) {
|
||||
_instance ??= instance;
|
||||
}
|
||||
|
||||
static LibgopeedApi get instance {
|
||||
return _instance!;
|
||||
}
|
||||
|
||||
factory LibgopeedBoot() => create();
|
||||
}
|
||||
File renamed without changes.
@@ -0,0 +1,20 @@
|
||||
import 'package:gopeed/api/model/extension.dart';
|
||||
import 'package:gopeed/api/model/request.dart';
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
part 'create_task_batch.g.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
class CreateTaskBatch {
|
||||
List<Request> reqs;
|
||||
Option? opt;
|
||||
|
||||
CreateTaskBatch({
|
||||
this.reqs = const [],
|
||||
this.opt,
|
||||
});
|
||||
|
||||
factory CreateTaskBatch.fromJson(Map<String, dynamic> json) =>
|
||||
_$CreateTaskBatchFromJson(json);
|
||||
Map<String, dynamic> toJson() => _$CreateTaskBatchToJson(this);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'create_task_batch.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
CreateTaskBatch _$CreateTaskBatchFromJson(Map<String, dynamic> json) =>
|
||||
CreateTaskBatch(
|
||||
reqs: (json['reqs'] as List<dynamic>?)
|
||||
?.map((e) => Request.fromJson(e as Map<String, dynamic>))
|
||||
.toList() ??
|
||||
const [],
|
||||
opt: json['opt'] == null
|
||||
? null
|
||||
: Option.fromJson(json['opt'] as Map<String, dynamic>),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$CreateTaskBatchToJson(CreateTaskBatch instance) {
|
||||
final val = <String, dynamic>{
|
||||
'reqs': instance.reqs,
|
||||
};
|
||||
|
||||
void writeNotNull(String key, dynamic value) {
|
||||
if (value != null) {
|
||||
val[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
writeNotNull('opt', instance.opt);
|
||||
return val;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
part 'http_listen_result.g.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
class HttpListenResult {
|
||||
String host;
|
||||
int port;
|
||||
|
||||
HttpListenResult({
|
||||
this.host = '',
|
||||
this.port = 0,
|
||||
});
|
||||
|
||||
factory HttpListenResult.fromJson(Map<String, dynamic> json) =>
|
||||
_$HttpListenResultFromJson(json);
|
||||
Map<String, dynamic> toJson() => _$HttpListenResultToJson(this);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'http_listen_result.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
HttpListenResult _$HttpListenResultFromJson(Map<String, dynamic> json) =>
|
||||
HttpListenResult(
|
||||
host: json['host'] as String? ?? '',
|
||||
port: (json['port'] as num?)?.toInt() ?? 0,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$HttpListenResultToJson(HttpListenResult instance) =>
|
||||
<String, dynamic>{
|
||||
'host': instance.host,
|
||||
'port': instance.port,
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
part 'info.g.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
class Info {
|
||||
String version;
|
||||
String runtime;
|
||||
String os;
|
||||
String arch;
|
||||
bool inDocker;
|
||||
|
||||
Info({
|
||||
this.version = '',
|
||||
this.runtime = '',
|
||||
this.os = '',
|
||||
this.arch = '',
|
||||
this.inDocker = false,
|
||||
});
|
||||
|
||||
factory Info.fromJson(Map<String, dynamic> json) => _$InfoFromJson(json);
|
||||
Map<String, dynamic> toJson() => _$InfoToJson(this);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'info.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
Info _$InfoFromJson(Map<String, dynamic> json) => Info(
|
||||
version: json['version'] as String? ?? '',
|
||||
runtime: json['runtime'] as String? ?? '',
|
||||
os: json['os'] as String? ?? '',
|
||||
arch: json['arch'] as String? ?? '',
|
||||
inDocker: json['inDocker'] as bool? ?? false,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$InfoToJson(Info instance) => <String, dynamic>{
|
||||
'version': instance.version,
|
||||
'runtime': instance.runtime,
|
||||
'os': instance.os,
|
||||
'arch': instance.arch,
|
||||
'inDocker': instance.inDocker,
|
||||
};
|
||||
@@ -10,7 +10,7 @@ Options _$OptionsFromJson(Map<String, dynamic> json) => Options(
|
||||
name: json['name'] as String,
|
||||
path: json['path'] as String,
|
||||
selectFiles: (json['selectFiles'] as List<dynamic>?)
|
||||
?.map((e) => e as int)
|
||||
?.map((e) => (e as num).toInt())
|
||||
.toList() ??
|
||||
const [],
|
||||
extra: json['extra'],
|
||||
@@ -35,7 +35,7 @@ Map<String, dynamic> _$OptionsToJson(Options instance) {
|
||||
|
||||
OptsExtraHttp _$OptsExtraHttpFromJson(Map<String, dynamic> json) =>
|
||||
OptsExtraHttp()
|
||||
..connections = json['connections'] as int
|
||||
..connections = (json['connections'] as num).toInt()
|
||||
..autoTorrent = json['autoTorrent'] as bool;
|
||||
|
||||
Map<String, dynamic> _$OptsExtraHttpToJson(OptsExtraHttp instance) =>
|
||||
|
||||
@@ -11,7 +11,7 @@ Result<T> _$ResultFromJson<T>(
|
||||
T Function(Object? json) fromJsonT,
|
||||
) =>
|
||||
Result<T>(
|
||||
code: json['code'] as int,
|
||||
code: (json['code'] as num).toInt(),
|
||||
msg: json['msg'] as String?,
|
||||
data: _$nullableGenericFromJson(json['data'], fromJsonT),
|
||||
);
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import 'package:gopeed/api/model/task.dart';
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
part 'task_filter.g.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
class TaskFilter {
|
||||
List<String>? id;
|
||||
List<Status>? status;
|
||||
List<Status>? notStatus;
|
||||
|
||||
TaskFilter({this.id, this.status, this.notStatus});
|
||||
|
||||
factory TaskFilter.fromJson(Map<String, dynamic> json) =>
|
||||
_$TaskFilterFromJson(json);
|
||||
Map<String, dynamic> toJson() => _$TaskFilterToJson(this);
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'task_filter.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
TaskFilter _$TaskFilterFromJson(Map<String, dynamic> json) => TaskFilter(
|
||||
id: (json['ids'] as List<dynamic>?)?.map((e) => e as String).toList(),
|
||||
status: (json['statuses'] as List<dynamic>?)
|
||||
?.map((e) => $enumDecode(_$StatusEnumMap, e))
|
||||
.toList(),
|
||||
notStatus: (json['notStatuses'] as List<dynamic>?)
|
||||
?.map((e) => $enumDecode(_$StatusEnumMap, e))
|
||||
.toList(),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$TaskFilterToJson(TaskFilter instance) {
|
||||
final val = <String, dynamic>{};
|
||||
|
||||
void writeNotNull(String key, dynamic value) {
|
||||
if (value != null) {
|
||||
val[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
writeNotNull('ids', instance.id);
|
||||
writeNotNull(
|
||||
'statuses', instance.status?.map((e) => _$StatusEnumMap[e]!).toList());
|
||||
writeNotNull('notStatuses',
|
||||
instance.notStatus?.map((e) => _$StatusEnumMap[e]!).toList());
|
||||
return val;
|
||||
}
|
||||
|
||||
const _$StatusEnumMap = {
|
||||
Status.ready: 'ready',
|
||||
Status.running: 'running',
|
||||
Status.pause: 'pause',
|
||||
Status.wait: 'wait',
|
||||
Status.error: 'error',
|
||||
Status.done: 'done',
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import '../libgopeed_interface.dart';
|
||||
|
||||
class LibgopeedChannel implements LibgopeedAbi {
|
||||
static const _channel = MethodChannel('gopeed.com/libgopeed');
|
||||
|
||||
@override
|
||||
Future<void> init(String cfg) async {
|
||||
await _channel.invokeMethod<String>('init', {
|
||||
'cfg': cfg,
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String> invoke(String params) async {
|
||||
final result = await _channel.invokeMethod<String>('invoke', {
|
||||
'params': params,
|
||||
});
|
||||
return result!;
|
||||
}
|
||||
}
|
||||
+26
-22
@@ -857,27 +857,33 @@ class LibgopeedBind {
|
||||
late final __FCmulcr =
|
||||
__FCmulcrPtr.asFunction<_Fcomplex Function(_Fcomplex, double)>();
|
||||
|
||||
Start_return Start(
|
||||
ffi.Pointer<ffi.Char> Init(
|
||||
ffi.Pointer<ffi.Char> cfg,
|
||||
) {
|
||||
return _Start(
|
||||
return _Init(
|
||||
cfg,
|
||||
);
|
||||
}
|
||||
|
||||
late final _StartPtr =
|
||||
_lookup<ffi.NativeFunction<Start_return Function(ffi.Pointer<ffi.Char>)>>(
|
||||
'Start');
|
||||
late final _Start =
|
||||
_StartPtr.asFunction<Start_return Function(ffi.Pointer<ffi.Char>)>();
|
||||
late final _InitPtr = _lookup<
|
||||
ffi.NativeFunction<
|
||||
ffi.Pointer<ffi.Char> Function(ffi.Pointer<ffi.Char>)>>('Init');
|
||||
late final _Init = _InitPtr.asFunction<
|
||||
ffi.Pointer<ffi.Char> Function(ffi.Pointer<ffi.Char>)>();
|
||||
|
||||
void Stop() {
|
||||
return _Stop();
|
||||
ffi.Pointer<ffi.Char> Invoke(
|
||||
ffi.Pointer<ffi.Char> req,
|
||||
) {
|
||||
return _Invoke(
|
||||
req,
|
||||
);
|
||||
}
|
||||
|
||||
late final _StopPtr =
|
||||
_lookup<ffi.NativeFunction<ffi.Void Function()>>('Stop');
|
||||
late final _Stop = _StopPtr.asFunction<void Function()>();
|
||||
late final _InvokePtr = _lookup<
|
||||
ffi.NativeFunction<
|
||||
ffi.Pointer<ffi.Char> Function(ffi.Pointer<ffi.Char>)>>('Invoke');
|
||||
late final _Invoke = _InvokePtr.asFunction<
|
||||
ffi.Pointer<ffi.Char> Function(ffi.Pointer<ffi.Char>)>();
|
||||
}
|
||||
|
||||
typedef va_list = ffi.Pointer<ffi.Char>;
|
||||
@@ -914,6 +920,7 @@ final class _Mbstatet extends ffi.Struct {
|
||||
}
|
||||
|
||||
typedef errno_t = ffi.Int;
|
||||
typedef Darterrno_t = int;
|
||||
|
||||
final class _GoString_ extends ffi.Struct {
|
||||
external ffi.Pointer<ffi.Char> p;
|
||||
@@ -923,6 +930,7 @@ final class _GoString_ extends ffi.Struct {
|
||||
}
|
||||
|
||||
typedef ptrdiff_t = ffi.LongLong;
|
||||
typedef Dartptrdiff_t = int;
|
||||
|
||||
final class _C_double_complex extends ffi.Struct {
|
||||
@ffi.Array.multi([2])
|
||||
@@ -957,13 +965,7 @@ final class GoSlice extends ffi.Struct {
|
||||
|
||||
typedef GoInt = GoInt64;
|
||||
typedef GoInt64 = ffi.LongLong;
|
||||
|
||||
final class Start_return extends ffi.Struct {
|
||||
@GoInt()
|
||||
external int r0;
|
||||
|
||||
external ffi.Pointer<ffi.Char> r1;
|
||||
}
|
||||
typedef DartGoInt64 = int;
|
||||
|
||||
const int _VCRT_COMPILER_PREPROCESSOR = 1;
|
||||
|
||||
@@ -993,6 +995,10 @@ const int _HAS_CXX23 = 0;
|
||||
|
||||
const int _HAS_NODISCARD = 1;
|
||||
|
||||
const int _ARM_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE = 1;
|
||||
|
||||
const int _CRT_BUILD_DESKTOP_APP = 1;
|
||||
|
||||
const int _UCRT_DISABLED_WARNINGS = 4324;
|
||||
|
||||
const int _ARGMAX = 100;
|
||||
@@ -1009,9 +1015,7 @@ const int _CRT_FUNCTIONS_REQUIRED = 1;
|
||||
|
||||
const int _CRT_HAS_CXX17 = 0;
|
||||
|
||||
const int _ARM_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE = 1;
|
||||
|
||||
const int _CRT_BUILD_DESKTOP_APP = 1;
|
||||
const int _CRT_HAS_C11 = 0;
|
||||
|
||||
const int _CRT_INTERNAL_NONSTDC_NAMES = 1;
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import 'dart:async';
|
||||
import 'dart:ffi';
|
||||
|
||||
import 'package:ffi/ffi.dart';
|
||||
|
||||
import '../libgopeed_interface.dart';
|
||||
import 'libgopeed_bind.dart';
|
||||
|
||||
class LibgopeedFFi implements LibgopeedAbi {
|
||||
late LibgopeedBind _libgopeed;
|
||||
|
||||
LibgopeedFFi(LibgopeedBind libgopeed) {
|
||||
_libgopeed = libgopeed;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> init(String cfg) {
|
||||
final cfgPtr = cfg.toNativeUtf8();
|
||||
try {
|
||||
final result = _libgopeed.Init(cfgPtr.cast());
|
||||
if (result != nullptr) {
|
||||
throw Exception(
|
||||
'Libgopeed init failed: ${result.cast<Utf8>().toDartString()}');
|
||||
}
|
||||
return Future.value();
|
||||
} finally {
|
||||
calloc.free(cfgPtr);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String> invoke(String params) {
|
||||
final paramsPtr = params.toNativeUtf8();
|
||||
try {
|
||||
final result = _libgopeed.Invoke(paramsPtr.cast());
|
||||
return Future.value(result.cast<Utf8>().toDartString());
|
||||
} finally {
|
||||
calloc.free(paramsPtr);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
import 'package:dio/dio.dart';
|
||||
|
||||
import '../model/create_task.dart';
|
||||
import '../model/create_task_batch.dart';
|
||||
import '../model/downloader_config.dart';
|
||||
import '../model/extension.dart';
|
||||
import '../model/http_listen_result.dart';
|
||||
import '../model/info.dart';
|
||||
import '../model/install_extension.dart';
|
||||
import '../model/request.dart';
|
||||
import '../model/resolve_result.dart';
|
||||
import '../model/switch_extension.dart';
|
||||
import '../model/task.dart';
|
||||
import '../model/task_filter.dart';
|
||||
import '../model/update_check_extension_resp.dart';
|
||||
import '../model/update_extension_settings.dart';
|
||||
import 'model/start_config.dart';
|
||||
|
||||
abstract class LibgopeedAbi {
|
||||
Future<void> init(String cfg);
|
||||
Future<String> invoke(String params);
|
||||
}
|
||||
|
||||
abstract class LibgopeedApiSingleton {
|
||||
Future<LibgopeedApi> init(StartConfig config);
|
||||
}
|
||||
|
||||
abstract class LibgopeedApi {
|
||||
Future<HttpListenResult> startHttp();
|
||||
|
||||
Future<void> stopHttp();
|
||||
|
||||
Future<void> restartHttp();
|
||||
|
||||
Future<Info> info();
|
||||
|
||||
Future<ResolveResult> resolve(Request request);
|
||||
|
||||
Future<String> createTask(CreateTask createTask);
|
||||
|
||||
Future<String> createTaskBatch(CreateTaskBatch createTask);
|
||||
|
||||
Future<void> pauseTask(String id);
|
||||
|
||||
Future<void> pauseTasks(TaskFilter? filter);
|
||||
|
||||
Future<void> continueTask(String id);
|
||||
|
||||
Future<void> continueTasks(TaskFilter? filter);
|
||||
|
||||
Future<void> deleteTask(String id, bool force);
|
||||
|
||||
Future<void> deleteTasks(TaskFilter? filter, bool force);
|
||||
|
||||
Future<Task> getTask(String id);
|
||||
|
||||
Future<List<Task>> getTasks(TaskFilter? filter);
|
||||
|
||||
Future<Map<String, dynamic>> getTaskStats(String id);
|
||||
|
||||
Future<DownloaderConfig> getConfig();
|
||||
|
||||
Future<void> putConfig(DownloaderConfig config);
|
||||
|
||||
Future<void> installExtension(InstallExtension installExtension);
|
||||
|
||||
Future<List<Extension>> getExtensions();
|
||||
|
||||
Future<void> updateExtensionSettings(
|
||||
String identity, UpdateExtensionSettings updateExtensionSettings);
|
||||
|
||||
Future<void> switchExtension(
|
||||
String identity, SwitchExtension switchExtension);
|
||||
|
||||
Future<void> deleteExtension(String identity);
|
||||
|
||||
Future<UpdateCheckExtensionResp> upgradeCheckExtension(String identity);
|
||||
|
||||
Future<void> upgradeExtension(String identity);
|
||||
|
||||
Future<void> close();
|
||||
|
||||
Future<Response<String>> proxyRequest<T>(String uri,
|
||||
{data, Options? options});
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
part 'invoke_request.g.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
class InvokeRequest {
|
||||
String method;
|
||||
List<dynamic> params;
|
||||
|
||||
InvokeRequest({
|
||||
this.method = '',
|
||||
this.params = const [],
|
||||
});
|
||||
|
||||
factory InvokeRequest.fromJson(Map<String, dynamic> json) =>
|
||||
_$InvokeRequestFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$InvokeRequestToJson(this);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'invoke_request.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
InvokeRequest _$InvokeRequestFromJson(Map<String, dynamic> json) =>
|
||||
InvokeRequest(
|
||||
method: json['method'] as String? ?? '',
|
||||
params: (json['params'] as List<dynamic>?)
|
||||
?.map((e) => e as String)
|
||||
.toList() ??
|
||||
const [],
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$InvokeRequestToJson(InvokeRequest instance) =>
|
||||
<String, dynamic>{
|
||||
'method': instance.method,
|
||||
'params': instance.params,
|
||||
};
|
||||
+6
-7
@@ -4,14 +4,13 @@ part 'start_config.g.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
class StartConfig {
|
||||
late String network;
|
||||
late String address;
|
||||
late String storage;
|
||||
late String storageDir;
|
||||
late int refreshInterval;
|
||||
late String apiToken;
|
||||
String storage;
|
||||
String storageDir;
|
||||
|
||||
StartConfig();
|
||||
StartConfig({
|
||||
this.storage = '',
|
||||
this.storageDir = '',
|
||||
});
|
||||
|
||||
factory StartConfig.fromJson(Map<String, dynamic> json) =>
|
||||
_$StartConfigFromJson(json);
|
||||
+4
-11
@@ -6,20 +6,13 @@ part of 'start_config.dart';
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
StartConfig _$StartConfigFromJson(Map<String, dynamic> json) => StartConfig()
|
||||
..network = json['network'] as String
|
||||
..address = json['address'] as String
|
||||
..storage = json['storage'] as String
|
||||
..storageDir = json['storageDir'] as String
|
||||
..refreshInterval = json['refreshInterval'] as int
|
||||
..apiToken = json['apiToken'] as String;
|
||||
StartConfig _$StartConfigFromJson(Map<String, dynamic> json) => StartConfig(
|
||||
storage: json['storage'] as String? ?? '',
|
||||
storageDir: json['storageDir'] as String? ?? '',
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$StartConfigToJson(StartConfig instance) =>
|
||||
<String, dynamic>{
|
||||
'network': instance.network,
|
||||
'address': instance.address,
|
||||
'storage': instance.storage,
|
||||
'storageDir': instance.storageDir,
|
||||
'refreshInterval': instance.refreshInterval,
|
||||
'apiToken': instance.apiToken,
|
||||
};
|
||||
@@ -14,10 +14,8 @@ import 'package:uri_to_file/uri_to_file.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
|
||||
import '../../../../api/api.dart';
|
||||
import '../../../../api/libgopeed_boot.dart';
|
||||
import '../../../../api/model/downloader_config.dart';
|
||||
import '../../../../core/common/start_config.dart';
|
||||
import '../../../../core/libgopeed_boot.dart';
|
||||
import '../../../../database/database.dart';
|
||||
import '../../../../database/entity.dart';
|
||||
import '../../../../i18n/message.dart';
|
||||
@@ -74,12 +72,9 @@ final allTrackerSubscribeUrlCdns = Map.fromIterable(allTrackerSubscribeUrls,
|
||||
});
|
||||
|
||||
class AppController extends GetxController with WindowListener, TrayListener {
|
||||
static StartConfig? _defaultStartConfig;
|
||||
|
||||
final autoStartup = false.obs;
|
||||
final startConfig = StartConfig().obs;
|
||||
final runningPort = 0.obs;
|
||||
final downloaderConfig = DownloaderConfig().obs;
|
||||
final httpRunningPort = 0.obs;
|
||||
|
||||
late AppLinks _appLinks;
|
||||
StreamSubscription<Uri>? _linkSubscription;
|
||||
@@ -111,7 +106,7 @@ class AppController extends GetxController with WindowListener, TrayListener {
|
||||
void onClose() {
|
||||
_linkSubscription?.cancel();
|
||||
trayManager.removeListener(this);
|
||||
LibgopeedBoot.instance.stop();
|
||||
LibgopeedBoot.instance.close();
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -173,7 +168,7 @@ class AppController extends GetxController with WindowListener, TrayListener {
|
||||
});
|
||||
|
||||
// Check initial link if app was in cold state (terminated)
|
||||
final uri = await _appLinks.getInitialLink();
|
||||
final uri = await _appLinks.getInitialAppLink();
|
||||
if (uri != null) {
|
||||
await _toCreate(uri);
|
||||
}
|
||||
@@ -218,11 +213,12 @@ class AppController extends GetxController with WindowListener, TrayListener {
|
||||
),
|
||||
MenuItem(
|
||||
label: "startAll".tr,
|
||||
onClick: (menuItem) async => {continueAllTasks()},
|
||||
onClick: (menuItem) async =>
|
||||
{LibgopeedBoot.instance.continueTasks(null)},
|
||||
),
|
||||
MenuItem(
|
||||
label: "pauseAll".tr,
|
||||
onClick: (menuItem) async => {pauseAllTasks()},
|
||||
onClick: (menuItem) async => {LibgopeedBoot.instance.pauseTasks(null)},
|
||||
),
|
||||
MenuItem(
|
||||
label: 'setting'.tr,
|
||||
@@ -247,7 +243,7 @@ class AppController extends GetxController with WindowListener, TrayListener {
|
||||
label: 'exit'.tr,
|
||||
onClick: (menuItem) async {
|
||||
try {
|
||||
await LibgopeedBoot.instance.stop();
|
||||
await LibgopeedBoot.instance.close();
|
||||
} catch (e) {
|
||||
logger.w("libgopeed stop fail", e);
|
||||
}
|
||||
@@ -313,43 +309,9 @@ class AppController extends GetxController with WindowListener, TrayListener {
|
||||
await Get.rootDelegate.offAndToNamed(Routes.CREATE, arguments: path);
|
||||
}
|
||||
|
||||
String runningAddress() {
|
||||
if (startConfig.value.network == 'unix') {
|
||||
return startConfig.value.address;
|
||||
}
|
||||
return '${startConfig.value.address.split(':').first}:$runningPort';
|
||||
}
|
||||
|
||||
Future<StartConfig> _initDefaultStartConfig() async {
|
||||
if (_defaultStartConfig != null) {
|
||||
return _defaultStartConfig!;
|
||||
}
|
||||
_defaultStartConfig = StartConfig();
|
||||
if (!Util.supportUnixSocket()) {
|
||||
// not support unix socket, use tcp
|
||||
_defaultStartConfig!.network = "tcp";
|
||||
_defaultStartConfig!.address = "127.0.0.1:0";
|
||||
} else {
|
||||
_defaultStartConfig!.network = "unix";
|
||||
_defaultStartConfig!.address =
|
||||
"${(await getTemporaryDirectory()).path}/$unixSocketPath";
|
||||
}
|
||||
_defaultStartConfig!.apiToken = '';
|
||||
return _defaultStartConfig!;
|
||||
}
|
||||
|
||||
Future<StartConfig> loadStartConfig() async {
|
||||
final defaultCfg = await _initDefaultStartConfig();
|
||||
final saveCfg = Database.instance.getStartConfig();
|
||||
startConfig.value.network = saveCfg?.network ?? defaultCfg.network;
|
||||
startConfig.value.address = saveCfg?.address ?? defaultCfg.address;
|
||||
startConfig.value.apiToken = saveCfg?.apiToken ?? defaultCfg.apiToken;
|
||||
return startConfig.value;
|
||||
}
|
||||
|
||||
Future<DownloaderConfig> loadDownloaderConfig() async {
|
||||
try {
|
||||
downloaderConfig.value = await getConfig();
|
||||
downloaderConfig.value = await LibgopeedBoot.instance.getConfig();
|
||||
} catch (e) {
|
||||
logger.w("load downloader config fail", e, StackTrace.current);
|
||||
downloaderConfig.value = DownloaderConfig();
|
||||
@@ -382,7 +344,7 @@ class AppController extends GetxController with WindowListener, TrayListener {
|
||||
});
|
||||
refreshTrackers();
|
||||
|
||||
await saveConfig();
|
||||
await LibgopeedBoot.instance.putConfig(downloaderConfig.value);
|
||||
}
|
||||
|
||||
refreshTrackers() {
|
||||
@@ -411,7 +373,7 @@ class AppController extends GetxController with WindowListener, TrayListener {
|
||||
}
|
||||
|
||||
Future<List<String>> _fetchTrackers(String subscribeUrl) async {
|
||||
final resp = await proxyRequest(subscribeUrl);
|
||||
final resp = await LibgopeedBoot.instance.proxyRequest(subscribeUrl);
|
||||
if (resp.statusCode != 200) {
|
||||
throw Exception(
|
||||
'Failed to get trackers, status code: ${resp.statusCode}');
|
||||
@@ -468,12 +430,4 @@ class AppController extends GetxController with WindowListener, TrayListener {
|
||||
args: ['--${Args.flagHidden}']);
|
||||
autoStartup.value = await launchAtStartup.isEnabled();
|
||||
}
|
||||
|
||||
Future<void> saveConfig() async {
|
||||
Database.instance.saveStartConfig(StartConfigEntity(
|
||||
network: startConfig.value.network,
|
||||
address: startConfig.value.address,
|
||||
apiToken: startConfig.value.apiToken));
|
||||
await putConfig(downloaderConfig.value);
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import 'package:get/get.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:rounded_loading_button_plus/rounded_loading_button.dart';
|
||||
|
||||
import '../../../../api/api.dart';
|
||||
import '../../../../api/libgopeed_boot.dart';
|
||||
import '../../../../api/model/create_task.dart';
|
||||
import '../../../../api/model/options.dart';
|
||||
import '../../../../api/model/request.dart';
|
||||
@@ -594,7 +594,7 @@ class CreateView extends GetView<CreateController> {
|
||||
final isDirect = controller.directDownload.value || isMultiLine;
|
||||
if (isDirect) {
|
||||
await Future.wait(urls.map((url) {
|
||||
return createTask(CreateTask(
|
||||
return LibgopeedBoot.instance.createTask(CreateTask(
|
||||
req: Request(
|
||||
url: url,
|
||||
extra: parseReqExtra(url),
|
||||
@@ -610,7 +610,7 @@ class CreateView extends GetView<CreateController> {
|
||||
}));
|
||||
Get.rootDelegate.offNamed(Routes.TASK);
|
||||
} else {
|
||||
final rr = await resolve(Request(
|
||||
final rr = await LibgopeedBoot.instance.resolve(Request(
|
||||
url: submitUrl,
|
||||
extra: parseReqExtra(_urlController.text),
|
||||
proxy: parseProxy(),
|
||||
@@ -741,23 +741,28 @@ class CreateView extends GetView<CreateController> {
|
||||
await Future.wait(
|
||||
controller.selectedIndexes.map((index) {
|
||||
final file = rr.res.files[index];
|
||||
return createTask(CreateTask(
|
||||
req: file.req!..proxy = parseProxy(),
|
||||
opt: Options(
|
||||
name: file.name,
|
||||
path: path.join(_pathController.text,
|
||||
rr.res.name, file.path),
|
||||
selectFiles: [],
|
||||
extra: optExtra)));
|
||||
return LibgopeedBoot.instance.createTask(
|
||||
CreateTask(
|
||||
req: file.req!..proxy = parseProxy(),
|
||||
opt: Options(
|
||||
name: file.name,
|
||||
path: path.join(
|
||||
_pathController.text,
|
||||
rr.res.name,
|
||||
file.path),
|
||||
selectFiles: [],
|
||||
extra: optExtra)));
|
||||
}));
|
||||
} else {
|
||||
await createTask(CreateTask(
|
||||
rid: rr.id,
|
||||
opt: Options(
|
||||
name: _renameController.text,
|
||||
path: _pathController.text,
|
||||
selectFiles: controller.selectedIndexes,
|
||||
extra: optExtra)));
|
||||
await LibgopeedBoot.instance.createTask(
|
||||
CreateTask(
|
||||
rid: rr.id,
|
||||
opt: Options(
|
||||
name: _renameController.text,
|
||||
path: _pathController.text,
|
||||
selectFiles:
|
||||
controller.selectedIndexes,
|
||||
extra: optExtra)));
|
||||
}
|
||||
Get.back();
|
||||
Get.rootDelegate.offNamed(Routes.TASK);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:get/get.dart';
|
||||
import 'package:gopeed/api/api.dart';
|
||||
|
||||
import '../../../../api/libgopeed_boot.dart';
|
||||
import '../../../../api/model/extension.dart';
|
||||
|
||||
class ExtensionController extends GetxController {
|
||||
@@ -17,12 +17,13 @@ class ExtensionController extends GetxController {
|
||||
}
|
||||
|
||||
Future<void> load() async {
|
||||
extensions.value = await getExtensions();
|
||||
extensions.value = await LibgopeedBoot.instance.getExtensions();
|
||||
}
|
||||
|
||||
Future<void> checkUpdate() async {
|
||||
for (final ext in extensions) {
|
||||
final resp = await upgradeCheckExtension(ext.identity);
|
||||
final resp =
|
||||
await LibgopeedBoot.instance.upgradeCheckExtension(ext.identity);
|
||||
if (resp.newVersion.isNotEmpty) {
|
||||
updateFlags[ext.identity] = resp.newVersion;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,8 @@ import 'package:path/path.dart' as path;
|
||||
import 'package:rounded_loading_button_plus/rounded_loading_button.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
import '../../../../api/api.dart';
|
||||
import '../../../../api/entry/libgopeed_boot_web.dart';
|
||||
import '../../../../api/libgopeed_boot.dart';
|
||||
import '../../../../api/model/extension.dart';
|
||||
import '../../../../api/model/install_extension.dart';
|
||||
import '../../../../api/model/switch_extension.dart';
|
||||
@@ -78,8 +79,9 @@ class ExtensionView extends GetView<ExtensionController> {
|
||||
}
|
||||
_installBtnController.start();
|
||||
try {
|
||||
await installExtension(InstallExtension(
|
||||
url: _installUrlController.text));
|
||||
await LibgopeedBoot.instance.installExtension(
|
||||
InstallExtension(
|
||||
url: _installUrlController.text));
|
||||
Get.snackbar('tip'.tr, 'extensionInstallSuccess'.tr);
|
||||
await controller.load();
|
||||
} catch (e) {
|
||||
@@ -98,7 +100,7 @@ class ExtensionView extends GetView<ExtensionController> {
|
||||
if (dir != null) {
|
||||
MacSecureUtil.saveBookmark(dir);
|
||||
try {
|
||||
await installExtension(
|
||||
await LibgopeedBoot.instance.installExtension(
|
||||
InstallExtension(devMode: true, url: dir));
|
||||
Get.snackbar(
|
||||
'tip'.tr, 'extensionInstallSuccess'.tr);
|
||||
@@ -133,8 +135,10 @@ class ExtensionView extends GetView<ExtensionController> {
|
||||
)
|
||||
: Util.isWeb()
|
||||
? Image.network(
|
||||
join(
|
||||
'/fs/extensions/${extension.identity}/${extension.icon}'),
|
||||
(LibgopeedBoot.instance
|
||||
as LibgopeedBootWeb)
|
||||
.join(
|
||||
'/fs/extensions/${extension.identity}/${extension.icon}'),
|
||||
width: 48,
|
||||
height: 48,
|
||||
)
|
||||
@@ -155,9 +159,9 @@ class ExtensionView extends GetView<ExtensionController> {
|
||||
value: !extension.disabled,
|
||||
onChanged: (value) async {
|
||||
try {
|
||||
await switchExtension(
|
||||
extension.identity,
|
||||
SwitchExtension(status: value));
|
||||
await LibgopeedBoot.instance
|
||||
.switchExtension(extension.identity,
|
||||
SwitchExtension(status: value));
|
||||
await controller.load();
|
||||
} catch (e) {
|
||||
showErrorMessage(e);
|
||||
@@ -354,10 +358,11 @@ class ExtensionView extends GetView<ExtensionController> {
|
||||
try {
|
||||
confrimController.start();
|
||||
if (formKey.currentState?.saveAndValidate() == true) {
|
||||
await updateExtensionSettings(
|
||||
extension.identity,
|
||||
UpdateExtensionSettings(
|
||||
settings: formKey.currentState!.value));
|
||||
await LibgopeedBoot.instance
|
||||
.updateExtensionSettings(
|
||||
extension.identity,
|
||||
UpdateExtensionSettings(
|
||||
settings: formKey.currentState!.value));
|
||||
await controller.load();
|
||||
Get.back();
|
||||
}
|
||||
@@ -454,7 +459,8 @@ class ExtensionView extends GetView<ExtensionController> {
|
||||
),
|
||||
onPressed: () async {
|
||||
try {
|
||||
await deleteExtension(extension.identity);
|
||||
await LibgopeedBoot.instance
|
||||
.deleteExtension(extension.identity);
|
||||
await controller.load();
|
||||
Get.back();
|
||||
} catch (e) {
|
||||
@@ -487,18 +493,19 @@ class ExtensionView extends GetView<ExtensionController> {
|
||||
onPressed: () async {
|
||||
confrimController.start();
|
||||
try {
|
||||
await updateExtension(extension.identity);
|
||||
await LibgopeedBoot.instance
|
||||
.upgradeExtension(extension.identity);
|
||||
await controller.load();
|
||||
controller.updateFlags.remove(extension.identity);
|
||||
Get.back();
|
||||
showMessage('tip'.tr, 'extensionUpdateSuccess'.tr);
|
||||
showMessage('tip'.tr, 'extensionUpgradeSuccess'.tr);
|
||||
} catch (e) {
|
||||
showErrorMessage(e);
|
||||
} finally {
|
||||
confrimController.stop();
|
||||
}
|
||||
},
|
||||
child: Text('newVersionUpdate'.tr),
|
||||
child: Text('newVersionUpgrade'.tr),
|
||||
),
|
||||
],
|
||||
));
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:get/get.dart';
|
||||
import 'package:gopeed/api/api.dart';
|
||||
|
||||
import '../../../../api/libgopeed_boot.dart';
|
||||
|
||||
class SettingController extends GetxController {
|
||||
final tapStatues = <String, bool>{}.obs;
|
||||
@@ -28,12 +29,13 @@ class SettingController extends GetxController {
|
||||
void fetchLatestVersion() async {
|
||||
String? releaseDataStr;
|
||||
try {
|
||||
releaseDataStr = (await proxyRequest(
|
||||
releaseDataStr = (await LibgopeedBoot.instance.proxyRequest(
|
||||
"https://api.github.com/repos/GopeedLab/gopeed/releases/latest"))
|
||||
.data;
|
||||
} catch (e) {
|
||||
releaseDataStr =
|
||||
(await proxyRequest("https://gopeed.com/api/release")).data;
|
||||
releaseDataStr = (await LibgopeedBoot.instance
|
||||
.proxyRequest("https://gopeed.com/api/release"))
|
||||
.data;
|
||||
}
|
||||
if (releaseDataStr == null) {
|
||||
return;
|
||||
|
||||
@@ -10,6 +10,7 @@ import 'package:intl/intl.dart';
|
||||
import 'package:launch_at_startup/launch_at_startup.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
import '../../../../api/libgopeed_boot.dart';
|
||||
import '../../../../api/model/downloader_config.dart';
|
||||
import '../../../../i18n/message.dart';
|
||||
import '../../../../util/input_formatter.dart';
|
||||
@@ -35,7 +36,6 @@ class SettingView extends GetView<SettingController> {
|
||||
Widget build(BuildContext context) {
|
||||
final appController = Get.find<AppController>();
|
||||
final downloaderCfg = appController.downloaderConfig;
|
||||
final startCfg = appController.startConfig;
|
||||
|
||||
Timer? timer;
|
||||
Future<bool> debounceSave(
|
||||
@@ -51,8 +51,8 @@ class SettingView extends GetView<SettingController> {
|
||||
return;
|
||||
}
|
||||
}
|
||||
appController
|
||||
.saveConfig()
|
||||
LibgopeedBoot.instance
|
||||
.putConfig(downloaderCfg.value)
|
||||
.then((_) => completer.complete(true))
|
||||
.onError(completer.completeError);
|
||||
if (needRestart) {
|
||||
@@ -530,7 +530,7 @@ class SettingView extends GetView<SettingController> {
|
||||
launchUrl(Uri.parse('https://gopeed.com'),
|
||||
mode: LaunchMode.externalApplication);
|
||||
},
|
||||
child: Text('newVersionUpdate'.tr),
|
||||
child: Text('newVersionUpgrade'.tr),
|
||||
),
|
||||
],
|
||||
));
|
||||
@@ -637,7 +637,7 @@ class SettingView extends GetView<SettingController> {
|
||||
final portController = TextEditingController(text: port);
|
||||
updateAddress() async {
|
||||
final newAddress = '${ipController.text}:${portController.text}';
|
||||
if (newAddress != startCfg.value.address) {
|
||||
if (newAddress != proxy.host) {
|
||||
proxy.host = newAddress;
|
||||
|
||||
await debounceSave();
|
||||
@@ -718,152 +718,152 @@ class SettingView extends GetView<SettingController> {
|
||||
);
|
||||
|
||||
// advanced config API items start
|
||||
final buildApiProtocol = _buildConfigItem(
|
||||
'protocol',
|
||||
() => startCfg.value.network == 'tcp'
|
||||
? 'TCP ${startCfg.value.address}'
|
||||
: 'Unix',
|
||||
(Key key) {
|
||||
final items = <Widget>[
|
||||
SizedBox(
|
||||
width: 80,
|
||||
child: DropdownButtonFormField<String>(
|
||||
value: startCfg.value.network,
|
||||
onChanged: Util.isDesktop()
|
||||
? (value) async {
|
||||
startCfg.update((val) {
|
||||
val!.network = value!;
|
||||
});
|
||||
// final buildApiProtocol = _buildConfigItem(
|
||||
// 'protocol',
|
||||
// () => startCfg.value.host == 'tcp'
|
||||
// ? 'TCP ${startCfg.value.address}'
|
||||
// : 'Unix',
|
||||
// (Key key) {
|
||||
// final items = <Widget>[
|
||||
// SizedBox(
|
||||
// width: 80,
|
||||
// child: DropdownButtonFormField<String>(
|
||||
// value: startCfg.value.host,
|
||||
// onChanged: Util.isDesktop()
|
||||
// ? (value) async {
|
||||
// startCfg.update((val) {
|
||||
// val!.host = value!;
|
||||
// });
|
||||
|
||||
await debounceSave(needRestart: true);
|
||||
}
|
||||
: null,
|
||||
items: [
|
||||
const DropdownMenuItem<String>(
|
||||
value: 'tcp',
|
||||
child: Text('TCP'),
|
||||
),
|
||||
Util.supportUnixSocket()
|
||||
? const DropdownMenuItem<String>(
|
||||
value: 'unix',
|
||||
child: Text('Unix'),
|
||||
)
|
||||
: null,
|
||||
].where((e) => e != null).map((e) => e!).toList(),
|
||||
),
|
||||
)
|
||||
];
|
||||
if (Util.isDesktop() && startCfg.value.network == 'tcp') {
|
||||
final arr = startCfg.value.address.split(':');
|
||||
var ip = '127.0.0.1';
|
||||
var port = '0';
|
||||
if (arr.length > 1) {
|
||||
ip = arr[0];
|
||||
port = arr[1];
|
||||
}
|
||||
// await debounceSave(needRestart: true);
|
||||
// }
|
||||
// : null,
|
||||
// items: [
|
||||
// const DropdownMenuItem<String>(
|
||||
// value: 'tcp',
|
||||
// child: Text('TCP'),
|
||||
// ),
|
||||
// Util.supportUnixSocket()
|
||||
// ? const DropdownMenuItem<String>(
|
||||
// value: 'unix',
|
||||
// child: Text('Unix'),
|
||||
// )
|
||||
// : null,
|
||||
// ].where((e) => e != null).map((e) => e!).toList(),
|
||||
// ),
|
||||
// )
|
||||
// ];
|
||||
// if (Util.isDesktop() && startCfg.value.host == 'tcp') {
|
||||
// final arr = startCfg.value.address.split(':');
|
||||
// var ip = '127.0.0.1';
|
||||
// var port = '0';
|
||||
// if (arr.length > 1) {
|
||||
// ip = arr[0];
|
||||
// port = arr[1];
|
||||
// }
|
||||
|
||||
final ipController = TextEditingController(text: ip);
|
||||
final portController = TextEditingController(text: port);
|
||||
updateAddress() async {
|
||||
if (ipController.text.isEmpty || portController.text.isEmpty) {
|
||||
return;
|
||||
}
|
||||
final newAddress = '${ipController.text}:${portController.text}';
|
||||
if (newAddress != startCfg.value.address) {
|
||||
startCfg.value.address = newAddress;
|
||||
// final ipController = TextEditingController(text: ip);
|
||||
// final portController = TextEditingController(text: port);
|
||||
// updateAddress() async {
|
||||
// if (ipController.text.isEmpty || portController.text.isEmpty) {
|
||||
// return;
|
||||
// }
|
||||
// final newAddress = '${ipController.text}:${portController.text}';
|
||||
// if (newAddress != startCfg.value.address) {
|
||||
// startCfg.value.address = newAddress;
|
||||
|
||||
final saved = await debounceSave(
|
||||
check: () async {
|
||||
// Check if address already in use
|
||||
final configIp = ipController.text;
|
||||
final configPort = int.parse(portController.text);
|
||||
if (configPort == 0) {
|
||||
return '';
|
||||
}
|
||||
try {
|
||||
final socket = await Socket.connect(configIp, configPort,
|
||||
timeout: const Duration(seconds: 3));
|
||||
socket.close();
|
||||
return 'portInUse'
|
||||
.trParams({'port': configPort.toString()});
|
||||
} catch (e) {
|
||||
return '';
|
||||
}
|
||||
},
|
||||
needRestart: true);
|
||||
// final saved = await debounceSave(
|
||||
// check: () async {
|
||||
// // Check if address already in use
|
||||
// final configIp = ipController.text;
|
||||
// final configPort = int.parse(portController.text);
|
||||
// if (configPort == 0) {
|
||||
// return '';
|
||||
// }
|
||||
// try {
|
||||
// final socket = await Socket.connect(configIp, configPort,
|
||||
// timeout: const Duration(seconds: 3));
|
||||
// socket.close();
|
||||
// return 'portInUse'
|
||||
// .trParams({'port': configPort.toString()});
|
||||
// } catch (e) {
|
||||
// return '';
|
||||
// }
|
||||
// },
|
||||
// needRestart: true);
|
||||
|
||||
// If save failed, restore the old address
|
||||
if (!saved) {
|
||||
final oldAddress =
|
||||
(await appController.loadStartConfig()).address;
|
||||
startCfg.update((val) async {
|
||||
val!.address = oldAddress;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
// // If save failed, restore the old address
|
||||
// if (!saved) {
|
||||
// // final oldAddress =
|
||||
// // (await appController.loadStartConfig()).address;
|
||||
// // startCfg.update((val) async {
|
||||
// // val!.address = oldAddress;
|
||||
// // });
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
ipController.addListener(updateAddress);
|
||||
portController.addListener(updateAddress);
|
||||
items.addAll([
|
||||
const Padding(padding: EdgeInsets.only(left: 20)),
|
||||
Flexible(
|
||||
child: TextFormField(
|
||||
controller: ipController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'IP',
|
||||
contentPadding: EdgeInsets.all(0.0),
|
||||
),
|
||||
keyboardType: TextInputType.number,
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.allow(RegExp('[0-9.]')),
|
||||
],
|
||||
),
|
||||
),
|
||||
const Padding(padding: EdgeInsets.only(left: 10)),
|
||||
Flexible(
|
||||
child: TextFormField(
|
||||
controller: portController,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'port'.tr,
|
||||
contentPadding: const EdgeInsets.all(0.0),
|
||||
),
|
||||
keyboardType: TextInputType.number,
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.digitsOnly,
|
||||
NumericalRangeFormatter(min: 0, max: 65535),
|
||||
],
|
||||
),
|
||||
),
|
||||
]);
|
||||
}
|
||||
// ipController.addListener(updateAddress);
|
||||
// portController.addListener(updateAddress);
|
||||
// items.addAll([
|
||||
// const Padding(padding: EdgeInsets.only(left: 20)),
|
||||
// Flexible(
|
||||
// child: TextFormField(
|
||||
// controller: ipController,
|
||||
// decoration: const InputDecoration(
|
||||
// labelText: 'IP',
|
||||
// contentPadding: EdgeInsets.all(0.0),
|
||||
// ),
|
||||
// keyboardType: TextInputType.number,
|
||||
// inputFormatters: [
|
||||
// FilteringTextInputFormatter.allow(RegExp('[0-9.]')),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// const Padding(padding: EdgeInsets.only(left: 10)),
|
||||
// Flexible(
|
||||
// child: TextFormField(
|
||||
// controller: portController,
|
||||
// decoration: InputDecoration(
|
||||
// labelText: 'port'.tr,
|
||||
// contentPadding: const EdgeInsets.all(0.0),
|
||||
// ),
|
||||
// keyboardType: TextInputType.number,
|
||||
// inputFormatters: [
|
||||
// FilteringTextInputFormatter.digitsOnly,
|
||||
// NumericalRangeFormatter(min: 0, max: 65535),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// ]);
|
||||
// }
|
||||
|
||||
return Form(
|
||||
child: Row(
|
||||
children: items,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
final buildApiToken = _buildConfigItem('apiToken',
|
||||
() => startCfg.value.apiToken.isEmpty ? 'notSet'.tr : 'set'.tr,
|
||||
(Key key) {
|
||||
final apiTokenController =
|
||||
TextEditingController(text: startCfg.value.apiToken);
|
||||
apiTokenController.addListener(() async {
|
||||
if (apiTokenController.text != startCfg.value.apiToken) {
|
||||
startCfg.value.apiToken = apiTokenController.text;
|
||||
// return Form(
|
||||
// child: Row(
|
||||
// children: items,
|
||||
// ),
|
||||
// );
|
||||
// },
|
||||
// );
|
||||
// final buildApiToken = _buildConfigItem('apiToken',
|
||||
// () => startCfg.value.apiToken.isEmpty ? 'notSet'.tr : 'set'.tr,
|
||||
// (Key key) {
|
||||
// final apiTokenController =
|
||||
// TextEditingController(text: startCfg.value.apiToken);
|
||||
// apiTokenController.addListener(() async {
|
||||
// if (apiTokenController.text != startCfg.value.apiToken) {
|
||||
// startCfg.value.apiToken = apiTokenController.text;
|
||||
|
||||
await debounceSave(needRestart: true);
|
||||
}
|
||||
});
|
||||
return TextField(
|
||||
key: key,
|
||||
obscureText: true,
|
||||
controller: apiTokenController,
|
||||
focusNode: FocusNode(),
|
||||
);
|
||||
});
|
||||
// await debounceSave(needRestart: true);
|
||||
// }
|
||||
// });
|
||||
// return TextField(
|
||||
// key: key,
|
||||
// obscureText: true,
|
||||
// controller: apiTokenController,
|
||||
// focusNode: FocusNode(),
|
||||
// );
|
||||
// });
|
||||
|
||||
// advanced config log items start
|
||||
buildLogsDir() {
|
||||
@@ -993,16 +993,16 @@ class SettingView extends GetView<SettingController> {
|
||||
child: Column(
|
||||
children: _addDivider([buildProxy()]),
|
||||
)),
|
||||
const Text('API'),
|
||||
Card(
|
||||
child: Column(
|
||||
children: _addDivider([
|
||||
buildApiProtocol(),
|
||||
Util.isDesktop() && startCfg.value.network == 'tcp'
|
||||
? buildApiToken()
|
||||
: null,
|
||||
]),
|
||||
)),
|
||||
// const Text('API'),
|
||||
// Card(
|
||||
// child: Column(
|
||||
// children: _addDivider([
|
||||
// buildApiProtocol(),
|
||||
// Util.isDesktop() && startCfg.value.host == 'tcp'
|
||||
// ? buildApiToken()
|
||||
// : null,
|
||||
// ]),
|
||||
// )),
|
||||
Text('developer'.tr),
|
||||
Card(
|
||||
child: Column(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:get/get.dart';
|
||||
import '../../../../api/api.dart';
|
||||
import '../../../../api/libgopeed_boot.dart';
|
||||
import '../../../../api/model/resource.dart';
|
||||
import '../../../../api/model/task.dart';
|
||||
|
||||
@@ -31,7 +31,7 @@ class TaskFilesController extends GetxController {
|
||||
super.onInit();
|
||||
|
||||
final taskId = Get.rootDelegate.parameters['id'];
|
||||
final tasks = await getTasks([]);
|
||||
final tasks = await LibgopeedBoot.instance.getTasks(null);
|
||||
task.value = tasks.firstWhere((element) => element.id == taskId);
|
||||
parseDirMap(task.value!.meta.res!.files);
|
||||
toDir("/");
|
||||
|
||||
@@ -2,8 +2,9 @@ import 'dart:async';
|
||||
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../../../../api/api.dart';
|
||||
import '../../../../api/libgopeed_boot.dart';
|
||||
import '../../../../api/model/task.dart';
|
||||
import '../../../../api/model/task_filter.dart';
|
||||
|
||||
abstract class TaskListController extends GetxController {
|
||||
List<Status> statuses;
|
||||
@@ -44,7 +45,8 @@ abstract class TaskListController extends GetxController {
|
||||
}
|
||||
|
||||
getTasksState() async {
|
||||
final tasks = await getTasks(statuses);
|
||||
final tasks =
|
||||
await LibgopeedBoot.instance.getTasks(TaskFilter(status: statuses));
|
||||
// sort tasks by create time
|
||||
tasks.sort(compare);
|
||||
this.tasks.value = tasks;
|
||||
|
||||
@@ -5,7 +5,8 @@ import 'package:path/path.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
import '../../../../api/api.dart' as api;
|
||||
import '../../../../api/entry/libgopeed_boot_web.dart';
|
||||
import '../../../../api/libgopeed_boot.dart';
|
||||
import '../../../../util/browser_download/browser_download.dart';
|
||||
import '../../../../util/file_icon.dart';
|
||||
import '../../../../util/icons.dart';
|
||||
@@ -84,8 +85,10 @@ class TaskFilesView extends GetView<TaskFilesController> {
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: Util.isWeb()
|
||||
? () {
|
||||
final accessUrl = api.join(
|
||||
"/fs/tasks/${controller.task.value!.id}$fileRelativePath");
|
||||
final accessUrl = (LibgopeedBoot
|
||||
.instance as LibgopeedBootWeb)
|
||||
.join(
|
||||
"/fs/tasks/${controller.task.value!.id}$fileRelativePath");
|
||||
return [
|
||||
IconButton(
|
||||
icon:
|
||||
|
||||
@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
|
||||
import '../../api/api.dart';
|
||||
import '../../api/libgopeed_boot.dart';
|
||||
import '../../api/model/task.dart';
|
||||
import '../../util/file_icon.dart';
|
||||
import '../../util/icons.dart';
|
||||
@@ -95,8 +95,9 @@ class BuildTaskListView extends GetView {
|
||||
try {
|
||||
final force = !appController
|
||||
.downloaderConfig.value.extra.lastDeleteTaskKeep;
|
||||
await appController.saveConfig();
|
||||
await deleteTask(id, force);
|
||||
LibgopeedBoot.instance
|
||||
.putConfig(appController.downloaderConfig.value);
|
||||
await LibgopeedBoot.instance.deleteTask(id, force);
|
||||
Get.back();
|
||||
} catch (e) {
|
||||
showErrorMessage(e);
|
||||
@@ -122,7 +123,7 @@ class BuildTaskListView extends GetView {
|
||||
icon: const Icon(Icons.pause),
|
||||
onPressed: () async {
|
||||
try {
|
||||
await pauseTask(task.id);
|
||||
await LibgopeedBoot.instance.pauseTask(task.id);
|
||||
} catch (e) {
|
||||
showErrorMessage(e);
|
||||
}
|
||||
@@ -133,7 +134,7 @@ class BuildTaskListView extends GetView {
|
||||
icon: const Icon(Icons.play_arrow),
|
||||
onPressed: () async {
|
||||
try {
|
||||
await continueTask(task.id);
|
||||
await LibgopeedBoot.instance.continueTask(task.id);
|
||||
} catch (e) {
|
||||
showErrorMessage(e);
|
||||
}
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'libgopeed_interface.dart';
|
||||
import 'start_config.dart';
|
||||
|
||||
class LibgopeedChannel implements LibgopeedInterface {
|
||||
static const _channel = MethodChannel('gopeed.com/libgopeed');
|
||||
|
||||
@override
|
||||
Future<int> start(StartConfig cfg) async {
|
||||
final port = await _channel.invokeMethod('start', {
|
||||
'cfg': jsonEncode(cfg),
|
||||
});
|
||||
return port as int;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> stop() async {
|
||||
return await _channel.invokeMethod('stop');
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:ffi';
|
||||
|
||||
import 'package:ffi/ffi.dart';
|
||||
|
||||
import '../ffi/libgopeed_bind.dart';
|
||||
import 'libgopeed_interface.dart';
|
||||
import 'start_config.dart';
|
||||
|
||||
class LibgopeedFFi implements LibgopeedInterface {
|
||||
late LibgopeedBind _libgopeed;
|
||||
|
||||
LibgopeedFFi(LibgopeedBind libgopeed) {
|
||||
_libgopeed = libgopeed;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<int> start(StartConfig cfg) {
|
||||
var completer = Completer<int>();
|
||||
var result = _libgopeed.Start(jsonEncode(cfg).toNativeUtf8().cast());
|
||||
if (result.r1 != nullptr) {
|
||||
completer.completeError(Exception(result.r1.cast<Utf8>().toDartString()));
|
||||
} else {
|
||||
completer.complete(result.r0);
|
||||
}
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> stop() {
|
||||
var completer = Completer<void>();
|
||||
_libgopeed.Stop();
|
||||
completer.complete();
|
||||
return completer.future;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
import 'start_config.dart';
|
||||
|
||||
abstract class LibgopeedInterface {
|
||||
Future<int> start(StartConfig cfg);
|
||||
|
||||
Future<void> stop();
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
import 'dart:async';
|
||||
|
||||
import '../common/start_config.dart';
|
||||
|
||||
import '../libgopeed_boot.dart';
|
||||
|
||||
LibgopeedBoot create() => LibgopeedBootBrowser();
|
||||
|
||||
class LibgopeedBootBrowser implements LibgopeedBoot {
|
||||
// do nothing
|
||||
@override
|
||||
Future<int> start(StartConfig cfg) async {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> stop() async {}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
import 'dart:async';
|
||||
import 'dart:ffi';
|
||||
import 'dart:io';
|
||||
|
||||
import '../../util/util.dart';
|
||||
import '../common/libgopeed_channel.dart';
|
||||
import '../common/libgopeed_ffi.dart';
|
||||
import '../common/libgopeed_interface.dart';
|
||||
import '../common/start_config.dart';
|
||||
import '../ffi/libgopeed_bind.dart';
|
||||
import '../libgopeed_boot.dart';
|
||||
|
||||
LibgopeedBoot create() => LibgopeedBootNative();
|
||||
|
||||
class LibgopeedBootNative implements LibgopeedBoot {
|
||||
late LibgopeedInterface _libgopeed;
|
||||
|
||||
LibgopeedBootNative() {
|
||||
if (Util.isDesktop()) {
|
||||
var libName = "libgopeed.";
|
||||
if (Platform.isWindows) {
|
||||
libName += "dll";
|
||||
}
|
||||
if (Platform.isMacOS) {
|
||||
libName += "dylib";
|
||||
}
|
||||
if (Platform.isLinux) {
|
||||
libName += "so";
|
||||
}
|
||||
_libgopeed = LibgopeedFFi(LibgopeedBind(DynamicLibrary.open(libName)));
|
||||
} else {
|
||||
_libgopeed = LibgopeedChannel();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<int> start(StartConfig cfg) async {
|
||||
cfg.storage = 'bolt';
|
||||
cfg.storageDir = Util.getStorageDir();
|
||||
cfg.refreshInterval = 0;
|
||||
var port = await _libgopeed.start(cfg);
|
||||
return port;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> stop() async {
|
||||
await _libgopeed.stop();
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
import 'common/start_config.dart';
|
||||
import "libgopeed_boot_stub.dart"
|
||||
if (dart.library.html) 'entry/libgopeed_boot_browser.dart'
|
||||
if (dart.library.io) 'entry/libgopeed_boot_native.dart';
|
||||
|
||||
abstract class LibgopeedBoot {
|
||||
static LibgopeedBoot? _instance;
|
||||
|
||||
static LibgopeedBoot get instance {
|
||||
_instance ??= LibgopeedBoot();
|
||||
return _instance!;
|
||||
}
|
||||
|
||||
factory LibgopeedBoot() => create();
|
||||
|
||||
Future<int> start(StartConfig cfg);
|
||||
|
||||
Future<void> stop();
|
||||
}
|
||||
@@ -71,12 +71,12 @@ const enUS = {
|
||||
'deleteTaskTip': 'Keep downloaded files',
|
||||
'delete': 'Delete',
|
||||
'newVersionTitle': 'Discover new version @version',
|
||||
'newVersionUpdate': 'Update Now',
|
||||
'newVersionUpgrade': 'Update Now',
|
||||
'newVersionLater': 'Later',
|
||||
'extensions': 'Extensions',
|
||||
'extensionInstallUrl': 'Install URL',
|
||||
'extensionInstallSuccess': 'Installed successfully',
|
||||
'extensionUpdateSuccess': 'Updated successfully',
|
||||
'extensionUpgradeSuccess': 'Updated successfully',
|
||||
'extensionDelete': 'Delete Extension',
|
||||
'extensionAlreadyLatest': 'It\'s already the latest version',
|
||||
'extensionFind': 'Find Extensions',
|
||||
|
||||
@@ -21,9 +21,11 @@ const esES = {
|
||||
'downloadLinkValid': 'Por favor, introduce el enlace de descarga',
|
||||
'downloadLinkHit':
|
||||
'Por favor, introduce el enlace de descarga, se admite HTTP/HTTPS/MAGNET@append',
|
||||
'downloadLinkHitDesktop': ', o arrastra el archivo torrent aquí directamente',
|
||||
'downloadLinkHitDesktop':
|
||||
', o arrastra el archivo torrent aquí directamente',
|
||||
'download': 'Descargar',
|
||||
'noFileSelected': 'Por favor, selecciona al menos un archivo para continuar.',
|
||||
'noFileSelected':
|
||||
'Por favor, selecciona al menos un archivo para continuar.',
|
||||
'noStoragePermission': 'Se requiere permiso de almacenamiento',
|
||||
'selectFile': 'Seleccionar Archivo',
|
||||
'rename': 'Renombrar',
|
||||
@@ -43,7 +45,8 @@ const esES = {
|
||||
'updateDaily': 'Actualizar diariamente',
|
||||
'lastUpdate': 'Última actualización: @time',
|
||||
'addTracker': 'Añadir Tracker',
|
||||
'addTrackerHit': 'Por favor, introduce la URL del servidor tracker, una por línea',
|
||||
'addTrackerHit':
|
||||
'Por favor, introduce la URL del servidor tracker, una por línea',
|
||||
'ui': 'Interfaz',
|
||||
'theme': 'Tema',
|
||||
'themeSystem': 'Sistema',
|
||||
@@ -67,12 +70,12 @@ const esES = {
|
||||
'deleteTaskTip': 'Mantener archivos descargados',
|
||||
'delete': 'Eliminar',
|
||||
'newVersionTitle': 'Nueva versión @version disponible',
|
||||
'newVersionUpdate': 'Actualizar Ahora',
|
||||
'newVersionUpgrade': 'Actualizar Ahora',
|
||||
'newVersionLater': 'Más tarde',
|
||||
'extensions': 'Extensiones',
|
||||
'extensionInstallUrl': 'URL de instalación',
|
||||
'extensionInstallSuccess': 'Instalado con éxito',
|
||||
'extensionUpdateSuccess': 'Actualizado con éxito',
|
||||
'extensionUpgradeSuccess': 'Actualizado con éxito',
|
||||
'extensionDelete': 'Eliminar Extensión',
|
||||
'extensionAlreadyLatest': 'Ya es la versión más reciente',
|
||||
'extensionFind': 'Buscar Extensiones',
|
||||
@@ -105,4 +108,4 @@ const esES = {
|
||||
'downloadPath': 'Ruta de Descarga',
|
||||
'skipVerifyCert': 'Omitir Verificación de Certificado',
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -55,7 +55,7 @@ const faIR = {
|
||||
'deleteTaskTip': 'فایل های دانلود شده را نگه دارد',
|
||||
'delete': 'پاک کردن',
|
||||
'newVersionTitle': '@version عنوان: کشف نسخه جدید',
|
||||
'newVersionUpdate': 'بروزرسانی',
|
||||
'newVersionUpgrade': 'بروزرسانی',
|
||||
'newVersionLater': 'بعداً',
|
||||
'thanks': 'تشکر',
|
||||
'thanksDesc':
|
||||
|
||||
@@ -19,10 +19,12 @@ const frFR = {
|
||||
'advancedOptions': 'Options avancées',
|
||||
'downloadLink': 'Lien de téléchargement',
|
||||
'downloadLinkValid': 'Veuillez entrer le lien de téléchargement',
|
||||
'downloadLinkHit': 'Veuillez entrer le lien de téléchargement, HTTP/HTTPS/MAGNET pris en charge',
|
||||
'downloadLinkHit':
|
||||
'Veuillez entrer le lien de téléchargement, HTTP/HTTPS/MAGNET pris en charge',
|
||||
'downloadLinkHitDesktop': ', ou glissez directement le fichier torrent ici',
|
||||
'download': 'Télécharger',
|
||||
'noFileSelected': 'Veuillez sélectionner au moins un fichier pour continuer.',
|
||||
'noFileSelected':
|
||||
'Veuillez sélectionner au moins un fichier pour continuer.',
|
||||
'noStoragePermission': 'Permission de stockage requise',
|
||||
'selectFile': 'Sélectionner un fichier',
|
||||
'rename': 'Renommer',
|
||||
@@ -32,16 +34,19 @@ const frFR = {
|
||||
'downloadDir': 'Répertoire de téléchargement',
|
||||
'downloadDirValid': 'Veuillez sélectionner le répertoire de téléchargement',
|
||||
'connections': 'Connexions',
|
||||
'useServerCtime': 'Utiliser l\'heure du serveur pour la création de fichier',
|
||||
'useServerCtime':
|
||||
'Utiliser l\'heure du serveur pour la création de fichier',
|
||||
'maxRunning': 'Tâches en cours maximum',
|
||||
'items': '@count éléments',
|
||||
'subscribeTracker': 'S\'abonner au tracker',
|
||||
'subscribeFail': 'Abonnement échoué, veuillez vérifier le réseau ou réessayer plus tard',
|
||||
'subscribeFail':
|
||||
'Abonnement échoué, veuillez vérifier le réseau ou réessayer plus tard',
|
||||
'update': 'Mettre à jour',
|
||||
'updateDaily': 'Mettre à jour quotidiennement',
|
||||
'lastUpdate': 'Dernière mise à jour : @time',
|
||||
'addTracker': 'Ajouter un tracker',
|
||||
'addTrackerHit': 'Veuillez entrer l\'URL du serveur de tracker, une par ligne',
|
||||
'addTrackerHit':
|
||||
'Veuillez entrer l\'URL du serveur de tracker, une par ligne',
|
||||
'ui': 'Interface utilisateur',
|
||||
'theme': 'Thème',
|
||||
'themeSystem': 'Système',
|
||||
@@ -65,12 +70,12 @@ const frFR = {
|
||||
'deleteTaskTip': 'Conserver les fichiers téléchargés',
|
||||
'delete': 'Supprimer',
|
||||
'newVersionTitle': 'Nouvelle version @version disponible',
|
||||
'newVersionUpdate': 'Mettre à jour maintenant',
|
||||
'newVersionUpgrade': 'Mettre à jour maintenant',
|
||||
'newVersionLater': 'Plus tard',
|
||||
'extensions': 'Extensions',
|
||||
'extensionInstallUrl': 'URL d\'installation',
|
||||
'extensionInstallSuccess': 'Installé avec succès',
|
||||
'extensionUpdateSuccess': 'Mis à jour avec succès',
|
||||
'extensionUpgradeSuccess': 'Mis à jour avec succès',
|
||||
'extensionDelete': 'Supprimer l\'extension',
|
||||
'extensionAlreadyLatest': 'C\'est déjà la dernière version',
|
||||
'extensionFind': 'Trouver des extensions',
|
||||
@@ -89,7 +94,8 @@ const frFR = {
|
||||
'username': 'Nom d\'utilisateur',
|
||||
'password': 'Mot de passe',
|
||||
'thanks': 'Merci',
|
||||
'thanksDesc': 'Merci à tous les contributeurs qui ont aidé à construire et développer la communauté Gopeed !',
|
||||
'thanksDesc':
|
||||
'Merci à tous les contributeurs qui ont aidé à construire et développer la communauté Gopeed !',
|
||||
'browserExtension': 'Extension du navigateur',
|
||||
'launchAtStartup': 'Lancer au démarrage',
|
||||
'skipVerifyCert': 'Bypass Verifikasi Sertifikat',
|
||||
|
||||
@@ -22,7 +22,8 @@ const idID = {
|
||||
'downloadLinkValid': 'Silakan masukkan tautan unduhan',
|
||||
'downloadLinkHit':
|
||||
'Tolong masukkan tautan unduhan, HTTP/HTTPS/MAGNET didukung@append',
|
||||
'downloadLinkHitDesktop': ', atau seret file torrent ke sini secara langsung',
|
||||
'downloadLinkHitDesktop':
|
||||
', atau seret file torrent ke sini secara langsung',
|
||||
'download': 'Download',
|
||||
'noFileSelected': 'Silakan pilih setidaknya satu file untuk melanjutkan.',
|
||||
'noStoragePermission': 'Izin penyimpanan diperlukan',
|
||||
@@ -71,12 +72,12 @@ const idID = {
|
||||
'deleteTaskTip': 'Simpan file yang terunduh',
|
||||
'delete': 'Hapus',
|
||||
'newVersionTitle': 'Temukan versi batu @version',
|
||||
'newVersionUpdate': 'Perbarui Sekarang',
|
||||
'newVersionUpgrade': 'Perbarui Sekarang',
|
||||
'newVersionLater': 'Nanti',
|
||||
'extensions': 'Ekstensi',
|
||||
'extensionInstallUrl': 'URL Instal',
|
||||
'extensionInstallSuccess': 'Diinstal dengan sukses',
|
||||
'extensionUpdateSuccess': 'Diperbaruhi dengan sukses',
|
||||
'extensionUpgradeSuccess': 'Diperbaruhi dengan sukses',
|
||||
'extensionDelete': 'Hapus Ekstensi',
|
||||
'extensionAlreadyLatest': 'Ini sudah versi terbaru',
|
||||
'extensionFind': 'Temukan Ekstensi',
|
||||
|
||||
@@ -68,12 +68,12 @@ const itIT = {
|
||||
'deleteTaskTip': 'Conserva i file scaricati',
|
||||
'delete': 'Elimina',
|
||||
'newVersionTitle': 'Scopri la nuova versione @version',
|
||||
'newVersionUpdate': 'Aggiorna ora',
|
||||
'newVersionUpgrade': 'Aggiorna ora',
|
||||
'newVersionLater': 'Dopo',
|
||||
'extensions': 'Estensioni',
|
||||
'extensionInstallUrl': 'Installa URL',
|
||||
'extensionInstallSuccess': 'Installato con successo',
|
||||
'extensionUpdateSuccess': 'Aggiornato con successo',
|
||||
'extensionUpgradeSuccess': 'Aggiornato con successo',
|
||||
'extensionDelete': 'Elimina estensione',
|
||||
'extensionAlreadyLatest': "È già l'ultima versione",
|
||||
'extensionFind': 'Trova estensioni',
|
||||
|
||||
@@ -57,7 +57,7 @@ const jaJP = {
|
||||
'deleteTaskTip': 'ダウンロードしたファイルを保持',
|
||||
'delete': '削除',
|
||||
'newVersionTitle': '新しいバージョン @version を発見する',
|
||||
'newVersionUpdate': 'アップデート',
|
||||
'newVersionUpgrade': 'アップデート',
|
||||
'newVersionLater': '後で',
|
||||
'thanks': '感謝',
|
||||
'thanksDesc': 'Gopeedコミュニティの建設に協力してくださったすべての貢献者の方々に感謝します!',
|
||||
|
||||
@@ -66,12 +66,12 @@ const plPL = {
|
||||
'deleteTaskTip': 'Zachowaj pobrane pliki',
|
||||
'delete': 'Usuń',
|
||||
'newVersionTitle': 'Sprawdź aktualizację @version',
|
||||
'newVersionUpdate': 'Aktualizuj teraz',
|
||||
'newVersionUpgrade': 'Aktualizuj teraz',
|
||||
'newVersionLater': 'Później',
|
||||
'extensions': 'Wtyczki',
|
||||
'extensionInstallUrl': 'Instaluj z url',
|
||||
'extensionInstallSuccess': 'Instalacja zakończona',
|
||||
'extensionUpdateSuccess': 'Aktualizacja zakończona',
|
||||
'extensionUpgradeSuccess': 'Aktualizacja zakończona',
|
||||
'extensionDelete': 'Usuń wtyczkę',
|
||||
'extensionAlreadyLatest': 'Wtyczka w aktualnej wersji',
|
||||
'extensionFind': 'Wyszukaj wtyczkę',
|
||||
|
||||
@@ -61,11 +61,11 @@ const ruRU = {
|
||||
'deleteTaskTip': 'Сохранить загруженные файлы',
|
||||
'delete': 'Удалить',
|
||||
'newVersionTitle': 'Обнаружена новая версия @version',
|
||||
'newVersionUpdate': 'Обновить',
|
||||
'newVersionUpgrade': 'Обновить',
|
||||
'newVersionLater': 'позже',
|
||||
'extensions': 'Расширения',
|
||||
'extensionInstallSuccess': 'Установка завершена',
|
||||
'extensionUpdateSuccess': 'Обновление завершено',
|
||||
'extensionUpgradeSuccess': 'Обновление завершено',
|
||||
'extensionDelete': 'Удалить расширение',
|
||||
'extensionAlreadyLatest': 'Это последняя версия',
|
||||
'extensionFind': 'Найти расширения',
|
||||
|
||||
@@ -21,7 +21,8 @@ const taTA = {
|
||||
'downloadLinkValid': 'பதிவிறக்க இணைப்பை உள்ளிடவும்',
|
||||
'downloadLinkHit':
|
||||
'பதிவிறக்க இணைப்பை உள்ளிடவும், HTTP/HTTPS/MAGNET ஆகியவற்றை ஏற்கும்@append',
|
||||
'downloadLinkHitDesktop': ', அல்லது torrent கோப்பை நேரடியாக இங்கே இழுக்கவும்',
|
||||
'downloadLinkHitDesktop':
|
||||
', அல்லது torrent கோப்பை நேரடியாக இங்கே இழுக்கவும்',
|
||||
'download': 'பதிவிறக்கு',
|
||||
'noFileSelected': 'குறைந்தபட்சம் ஒரு கோப்பைத் தேர்ந்தெடுக்கவும்.',
|
||||
'noStoragePermission': 'சேமிப்பக அனுமதி தேவை',
|
||||
@@ -67,12 +68,12 @@ const taTA = {
|
||||
'deleteTaskTip': 'பதிவிறக்கம் செய்யப்பட்ட கோப்புகளை வைத்திரு',
|
||||
'delete': 'நீக்கு',
|
||||
'newVersionTitle': 'புதிய பதிப்பைக் கண்டறியவும் @version',
|
||||
'newVersionUpdate': 'இப்பொழுது புதுப்பி',
|
||||
'newVersionUpgrade': 'இப்பொழுது புதுப்பி',
|
||||
'newVersionLater': 'பின்னர்',
|
||||
'extensions': 'நீட்டிப்புகள்',
|
||||
'extensionInstallUrl': 'நிறுவப்படும் URL',
|
||||
'extensionInstallSuccess': 'வெற்றிகரமாக நிறுவப்பட்டது',
|
||||
'extensionUpdateSuccess': 'வெற்றிகரமாக புதுப்பிக்கப்பட்டது',
|
||||
'extensionUpgradeSuccess': 'வெற்றிகரமாக புதுப்பிக்கப்பட்டது',
|
||||
'extensionDelete': 'நீட்டிப்பை நீக்கு',
|
||||
'extensionAlreadyLatest': 'இது ஏற்கனவே சமீபத்திய பதிப்பாகும்',
|
||||
'extensionFind': 'நீட்டிப்புகளைக் கண்டறியவும்',
|
||||
|
||||
@@ -20,8 +20,10 @@ const trTR = {
|
||||
'followSettings': 'Ayarları takip et',
|
||||
'downloadLink': 'İndirme bağlantısı',
|
||||
'downloadLinkValid': 'Lütfen indirme bağlantısını girin',
|
||||
'downloadLinkHit': 'Lütfen indirme bağlantısını girin, HTTP/HTTPS/MAGNET desteklenir@append',
|
||||
'downloadLinkHitDesktop': ', veya torrent dosyasını doğrudan buraya sürükleyin',
|
||||
'downloadLinkHit':
|
||||
'Lütfen indirme bağlantısını girin, HTTP/HTTPS/MAGNET desteklenir@append',
|
||||
'downloadLinkHitDesktop':
|
||||
', veya torrent dosyasını doğrudan buraya sürükleyin',
|
||||
'download': 'İndir',
|
||||
'noFileSelected': 'Devam etmek için lütfen en az bir dosya seçin.',
|
||||
'noStoragePermission': 'Depolama izni gerekli',
|
||||
@@ -38,12 +40,14 @@ const trTR = {
|
||||
'defaultDirectDownload': 'Doğrudan indir seçili olsun',
|
||||
'items': '@count tane',
|
||||
'subscribeTracker': 'İzleyiciye abone ol',
|
||||
'subscribeFail': 'Abone olunamadı, lütfen ağı kontrol edin veya daha sonra tekrar deneyin',
|
||||
'subscribeFail':
|
||||
'Abone olunamadı, lütfen ağı kontrol edin veya daha sonra tekrar deneyin',
|
||||
'update': 'Güncelle',
|
||||
'updateDaily': 'Günlük olarak güncelle',
|
||||
'lastUpdate': 'Son güncellenme tarihi: @time',
|
||||
'addTracker': 'İzleyici ekle',
|
||||
'addTrackerHit': 'Lütfen her satıra bir tane olmak üzere izleyici sunucusu bağlantısını girin',
|
||||
'addTrackerHit':
|
||||
'Lütfen her satıra bir tane olmak üzere izleyici sunucusu bağlantısını girin',
|
||||
'ui': 'Arayüz',
|
||||
'theme': 'Tema',
|
||||
'themeSystem': 'Sistem',
|
||||
@@ -58,7 +62,8 @@ const trTR = {
|
||||
'apiToken': 'API anahtarı',
|
||||
'notSet': 'AYARLI DEĞİL',
|
||||
'set': 'AYARLI',
|
||||
'portInUse': 'Bağlantı noktası [@port] kullanımda, lütfen bağlantı noktasını değiştirin',
|
||||
'portInUse':
|
||||
'Bağlantı noktası [@port] kullanımda, lütfen bağlantı noktasını değiştirin',
|
||||
'effectAfterRestart': 'Yeniden başlatma sonrası etki edecektir',
|
||||
'developer': 'Geliştirici',
|
||||
'logDirectory': 'Kayıt dizini',
|
||||
@@ -69,12 +74,12 @@ const trTR = {
|
||||
'deleteTaskTip': 'İndirilen dosyaları sakla',
|
||||
'delete': 'Sil',
|
||||
'newVersionTitle': 'Yeni sürümü keşfet @version',
|
||||
'newVersionUpdate': 'Şimdi güncelle',
|
||||
'newVersionUpgrade': 'Şimdi güncelle',
|
||||
'newVersionLater': 'Sonra',
|
||||
'extensions': 'Uzantılar',
|
||||
'extensionInstallUrl': 'İndirme bağlantısı',
|
||||
'extensionInstallSuccess': 'Başarıyla indirildi',
|
||||
'extensionUpdateSuccess': 'Başarıyla güncellendi',
|
||||
'extensionUpgradeSuccess': 'Başarıyla güncellendi',
|
||||
'extensionDelete': 'Uzantıyı sil',
|
||||
'extensionAlreadyLatest': 'Bu zaten en son sürüm',
|
||||
'extensionFind': 'Uzantı ara',
|
||||
@@ -93,7 +98,8 @@ const trTR = {
|
||||
'username': 'Kullanıcı adı',
|
||||
'password': 'Şifre',
|
||||
'thanks': 'Teşekkürler',
|
||||
'thanksDesc': 'Gopeed topluluğunun oluşmasına ve gelişmesine yardımcı olan tüm katılımcılara teşekkürler!',
|
||||
'thanksDesc':
|
||||
'Gopeed topluluğunun oluşmasına ve gelişmesine yardımcı olan tüm katılımcılara teşekkürler!',
|
||||
'browserExtension': 'Tarayıcı uzantısı',
|
||||
'launchAtStartup': 'Başlangıçta başlat',
|
||||
'seedConfig': 'Seed ayarları',
|
||||
|
||||
@@ -65,12 +65,12 @@ const viVN = {
|
||||
'deleteTaskTip': 'Giữ các tệp đã tải về',
|
||||
'delete': 'Xóa',
|
||||
'newVersionTitle': 'Khám phá phiên bản mới @version',
|
||||
'newVersionUpdate': 'Cập nhật ngay',
|
||||
'newVersionUpgrade': 'Cập nhật ngay',
|
||||
'newVersionLater': 'Sau',
|
||||
'extensions': 'Tiện ích mở rộng',
|
||||
'extensionInstallUrl': 'Liên kết cài đặt',
|
||||
'extensionInstallSuccess': 'Cài đặt thành công',
|
||||
'extensionUpdateSuccess': 'Cập nhật thành công',
|
||||
'extensionUpgradeSuccess': 'Cập nhật thành công',
|
||||
'extensionDelete': 'Xóa tiện ích mở rộng',
|
||||
'extensionAlreadyLatest': 'Đây đã là phiên bản mới nhất',
|
||||
'extensionFind': 'Tìm tiện ích mở rộng',
|
||||
|
||||
@@ -69,12 +69,12 @@ const zhCN = {
|
||||
'deleteTaskTip': '保留已下载的文件',
|
||||
'delete': '删除',
|
||||
'newVersionTitle': '发现新版本 @version',
|
||||
'newVersionUpdate': '立即更新',
|
||||
'newVersionUpgrade': '立即更新',
|
||||
'newVersionLater': '稍后再说',
|
||||
'extensions': '扩展',
|
||||
'extensionInstallUrl': '安装链接',
|
||||
'extensionInstallSuccess': '安装成功',
|
||||
'extensionUpdateSuccess': '更新成功',
|
||||
'extensionUpgradeSuccess': '更新成功',
|
||||
'extensionDelete': '删除扩展',
|
||||
'extensionAlreadyLatest': '已经是最新版本',
|
||||
'extensionFind': '获取扩展',
|
||||
|
||||
@@ -69,12 +69,12 @@ const zhTW = {
|
||||
'deleteTaskTip': '保留已下載的檔案',
|
||||
'delete': '刪除',
|
||||
'newVersionTitle': '發現新版本 @version',
|
||||
'newVersionUpdate': '立即更新',
|
||||
'newVersionUpgrade': '立即更新',
|
||||
'newVersionLater': '稍後',
|
||||
'extensions': '擴充功能',
|
||||
'extensionInstallUrl': '安裝 URL',
|
||||
'extensionInstallSuccess': '安裝成功',
|
||||
'extensionUpdateSuccess': '更新成功',
|
||||
'extensionUpgradeSuccess': '更新成功',
|
||||
'extensionDelete': '刪除擴充功能',
|
||||
'extensionAlreadyLatest': '已是最新版本',
|
||||
'extensionFind': '尋找擴充功能',
|
||||
|
||||
@@ -2,12 +2,12 @@ import 'package:args/args.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:gopeed/api/native/model/start_config.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
|
||||
import 'api/api.dart' as api;
|
||||
import 'app/modules/app/controllers/app_controller.dart';
|
||||
import 'app/modules/app/views/app_view.dart';
|
||||
import 'core/libgopeed_boot.dart';
|
||||
import 'api/libgopeed_boot.dart';
|
||||
import 'database/database.dart';
|
||||
import 'i18n/message.dart';
|
||||
import 'util/locale_manager.dart';
|
||||
@@ -73,10 +73,11 @@ Future<void> init(Args args) async {
|
||||
|
||||
final controller = Get.put(AppController());
|
||||
try {
|
||||
await controller.loadStartConfig();
|
||||
final startCfg = controller.startConfig.value;
|
||||
controller.runningPort.value = await LibgopeedBoot.instance.start(startCfg);
|
||||
api.init(startCfg.network, controller.runningAddress(), startCfg.apiToken);
|
||||
final instance = await LibgopeedBoot().init(StartConfig(
|
||||
storage: "bolt",
|
||||
storageDir: Util.getStorageDir(),
|
||||
));
|
||||
LibgopeedBoot.singleton(instance);
|
||||
} catch (e) {
|
||||
logger.e("libgopeed init fail", e);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
extension ApplyExtension<T> on T {
|
||||
T apply(void Function(T) fn) {
|
||||
fn(this);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
extension LetExtension<T> on T {
|
||||
R let<R>(R Function(T) fn) => fn(this);
|
||||
}
|
||||
@@ -8,9 +8,16 @@ void showErrorMessage(msg) {
|
||||
Get.snackbar(title, msg.msg!);
|
||||
return;
|
||||
}
|
||||
if (msg is Exception && (msg as dynamic).message is Result) {
|
||||
Get.snackbar(title, ((msg as dynamic).message as Result).msg!);
|
||||
return;
|
||||
if (msg is Exception) {
|
||||
final message = (msg as dynamic).message;
|
||||
if (message is Result) {
|
||||
Get.snackbar(title, ((msg as dynamic).message as Result).msg!);
|
||||
return;
|
||||
}
|
||||
if (message is String) {
|
||||
Get.snackbar(title, message);
|
||||
return;
|
||||
}
|
||||
}
|
||||
Get.snackbar(title, msg.toString());
|
||||
}
|
||||
|
||||
@@ -3,8 +3,6 @@ PODS:
|
||||
- FlutterMacOS
|
||||
- desktop_drop (0.0.1):
|
||||
- FlutterMacOS
|
||||
- device_info_plus (0.0.1):
|
||||
- FlutterMacOS
|
||||
- FlutterMacOS (1.0.0)
|
||||
- macos_secure_bookmarks (0.0.3):
|
||||
- FlutterMacOS
|
||||
@@ -30,7 +28,6 @@ PODS:
|
||||
DEPENDENCIES:
|
||||
- app_links (from `Flutter/ephemeral/.symlinks/plugins/app_links/macos`)
|
||||
- desktop_drop (from `Flutter/ephemeral/.symlinks/plugins/desktop_drop/macos`)
|
||||
- device_info_plus (from `Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos`)
|
||||
- FlutterMacOS (from `Flutter/ephemeral`)
|
||||
- macos_secure_bookmarks (from `Flutter/ephemeral/.symlinks/plugins/macos_secure_bookmarks/macos`)
|
||||
- package_info_plus (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos`)
|
||||
@@ -47,8 +44,6 @@ EXTERNAL SOURCES:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/app_links/macos
|
||||
desktop_drop:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/desktop_drop/macos
|
||||
device_info_plus:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos
|
||||
FlutterMacOS:
|
||||
:path: Flutter/ephemeral
|
||||
macos_secure_bookmarks:
|
||||
@@ -73,16 +68,15 @@ EXTERNAL SOURCES:
|
||||
SPEC CHECKSUMS:
|
||||
app_links: 10e0a0ab602ffaf34d142cd4862f29d34b303b2a
|
||||
desktop_drop: 69eeff437544aa619c8db7f4481b3a65f7696898
|
||||
device_info_plus: 74e614483d05c89290d30a4c8feae15d555f7427
|
||||
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
|
||||
macos_secure_bookmarks: cff041c4b377fa00941bb901aaec4d27fac5edf2
|
||||
package_info_plus: f5790acc797bf17c3e959e9d6cf162cc68ff7523
|
||||
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
|
||||
package_info_plus: 02d7a575e80f194102bef286361c6c326e4c29ce
|
||||
path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c
|
||||
screen_retriever: 59634572a57080243dd1bf715e55b6c54f241a38
|
||||
share_plus: fd717ef89a2801d3491e737630112b80c310640e
|
||||
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
|
||||
share_plus: 76dd39142738f7a68dd57b05093b5e8193f220f7
|
||||
shared_preferences_foundation: b4c3b4cddf1c21f02770737f147a3f5da9d39695
|
||||
tray_manager: 9064e219c56d75c476e46b9a21182087930baf90
|
||||
url_launcher_macos: c82c93949963e55b228a30115bd219499a6fe404
|
||||
url_launcher_macos: 5f437abeda8c85500ceb03f5c1938a8c5a705399
|
||||
window_manager: 3a1844359a6295ab1e47659b1a777e36773cd6e8
|
||||
|
||||
PODFILE CHECKSUM: 353c8bcc5d5b0994e508d035b5431cfe18c1dea7
|
||||
|
||||
@@ -206,7 +206,7 @@
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 0920;
|
||||
LastUpgradeCheck = 1510;
|
||||
LastUpgradeCheck = 1430;
|
||||
ORGANIZATIONNAME = "";
|
||||
TargetAttributes = {
|
||||
33CC10EC2044A3C60003C045 = {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1510"
|
||||
LastUpgradeVersion = "1430"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Cocoa
|
||||
import FlutterMacOS
|
||||
|
||||
@main
|
||||
@NSApplicationMain
|
||||
class AppDelegate: FlutterAppDelegate {
|
||||
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
||||
return false
|
||||
|
||||
+162
-231
@@ -5,55 +5,26 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: _fe_analyzer_shared
|
||||
sha256: f256b0c0ba6c7577c15e2e4e114755640a875e885099367bf6e012b19314c834
|
||||
sha256: eb376e9acf6938204f90eb3b1f00b578640d3188b4c8a8ec054f9f479af8d051
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "72.0.0"
|
||||
_macros:
|
||||
dependency: transitive
|
||||
description: dart
|
||||
source: sdk
|
||||
version: "0.3.2"
|
||||
version: "64.0.0"
|
||||
analyzer:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: analyzer
|
||||
sha256: b652861553cd3990d8ed361f7979dc6d7053a9ac8843fa73820ab68ce5410139
|
||||
sha256: "69f54f967773f6c26c7dcb13e93d7ccee8b17a641689da39e878d5cf13b06893"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.7.0"
|
||||
version: "6.2.0"
|
||||
app_links:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: app_links
|
||||
sha256: ad1a6d598e7e39b46a34f746f9a8b011ee147e4c275d407fa457e7a62f84dd99
|
||||
sha256: "3ced568a5d9e309e99af71285666f1f3117bddd0bd5b3317979dccc1a40cada4"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.3.2"
|
||||
app_links_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: app_links_linux
|
||||
sha256: f5f7173a78609f3dfd4c2ff2c95bd559ab43c80a87dc6a095921d96c05688c81
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.3"
|
||||
app_links_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: app_links_platform_interface
|
||||
sha256: "05f5379577c513b534a29ddea68176a4d4802c46180ee8e2e966257158772a3f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.2"
|
||||
app_links_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: app_links_web
|
||||
sha256: af060ed76183f9e2b87510a9480e56a5352b6c249778d07bd2c95fc35632a555
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.4"
|
||||
version: "3.5.1"
|
||||
archive:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -66,10 +37,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: args
|
||||
sha256: bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6
|
||||
sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.6.0"
|
||||
version: "2.5.0"
|
||||
async:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -122,10 +93,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_daemon
|
||||
sha256: "79b2aef6ac2ed00046867ed354c88778c9c0f029df8a20fe10b5436826721ef9"
|
||||
sha256: "0343061a33da9c5810b2d6cee51945127d8f4c060b7fbdd9d54917f0a3feaaa1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.2"
|
||||
version: "4.0.1"
|
||||
build_resolvers:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -138,18 +109,18 @@ packages:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: build_runner
|
||||
sha256: "028819cfb90051c6b5440c7e574d1896f8037e3c96cf17aaeb054c9311cfbf4d"
|
||||
sha256: "3ac61a79bfb6f6cc11f693591063a7f19a7af628dc52f141743edac5c16e8c22"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.13"
|
||||
version: "2.4.9"
|
||||
build_runner_core:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_runner_core
|
||||
sha256: f8126682b87a7282a339b871298cc12009cb67109cfa1614d6436fb0289193e0
|
||||
sha256: "4ae8ffe5ac758da294ecf1802f2aff01558d8b1b00616aa7538ea9a8a5d50799"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.3.2"
|
||||
version: "7.3.0"
|
||||
built_collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -226,26 +197,26 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: convert
|
||||
sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68
|
||||
sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.2"
|
||||
version: "3.1.1"
|
||||
cross_file:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cross_file
|
||||
sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670"
|
||||
sha256: fedaadfa3a6996f75211d835aaeb8fede285dae94262485698afd832371b9a5e
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.4+2"
|
||||
version: "0.3.3+8"
|
||||
crypto:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: crypto
|
||||
sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855"
|
||||
sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.6"
|
||||
version: "3.0.3"
|
||||
cupertino_icons:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -258,10 +229,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: dart_style
|
||||
sha256: "7856d364b589d1f08986e140938578ed36ed948581fbc3bc9aef1805039ac5ab"
|
||||
sha256: "99e066ce75c89d6b29903d788a7bb9369cf754f7b24bf70bf4b6d6d6b26853b9"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.7"
|
||||
version: "2.3.6"
|
||||
desktop_drop:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -274,10 +245,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: device_info_plus
|
||||
sha256: c4af09051b4f0508f6c1dc0a5c085bf014d5c9a4a0678ce1799c2b4d716387a0
|
||||
sha256: "77f757b789ff68e4eaf9c56d1752309bd9f7ad557cb105b938a7f8eb89e59110"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "11.1.0"
|
||||
version: "9.1.2"
|
||||
device_info_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -290,18 +261,18 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: dio
|
||||
sha256: "5598aa796bbf4699afd5c67c0f5f6e2ed542afc956884b9cd58c306966efc260"
|
||||
sha256: e17f6b3097b8c51b72c74c9f071a605c47bcc8893839bd66732457a5ebe73714
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.7.0"
|
||||
version: "5.5.0+1"
|
||||
dio_web_adapter:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: dio_web_adapter
|
||||
sha256: "33259a9276d6cea88774a0000cfae0d861003497755969c92faa223108620dc8"
|
||||
sha256: "36c5b2d79eb17cdae41e974b7a8284fec631651d2a6f39a8a2ff22327e90aeac"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
version: "1.0.1"
|
||||
fake_async:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -314,50 +285,50 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: ffi
|
||||
sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6"
|
||||
sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.3"
|
||||
version: "2.1.0"
|
||||
ffigen:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: ffigen
|
||||
sha256: d3e76c2ad48a4e7f93a29a162006f00eba46ce7c08194a77bb5c5e97d1b5ff0a
|
||||
sha256: dead012f29db2be71ea152458f5eab600de98fbc244e01088ae6bf2616bceca7
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.0.2"
|
||||
version: "11.0.0"
|
||||
file:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file
|
||||
sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d"
|
||||
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.1.4"
|
||||
version: "7.0.1"
|
||||
file_picker:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: file_picker
|
||||
sha256: "167bb619cdddaa10ef2907609feb8a79c16dfa479d3afaf960f8e223f754bf12"
|
||||
sha256: "9d6e95ec73abbd31ec54d0e0df8a961017e165aba1395e462e5b31ea0c165daf"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.1.2"
|
||||
version: "5.3.1"
|
||||
fixnum:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fixnum
|
||||
sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
|
||||
sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
version: "1.1.0"
|
||||
fluent_ui:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: fluent_ui
|
||||
sha256: e7804bf3bbb3ecf9e77d5498181dc36375f5ca736ccfb3862fea17c50050eb89
|
||||
sha256: a8c76cb501303d108cb9bd33e516da7cfd078031ff427d68eab6069bf4492a2c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.9.2"
|
||||
version: "4.8.7"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
@@ -375,10 +346,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_form_builder
|
||||
sha256: c278ef69b08957d484f83413f0e77b656a39b7a7bb4eb8a295da3a820ecc6545
|
||||
sha256: "560eb5e367d81170c6ade1e7ae63ecc5167936ae2cdfaae8a345e91bce19d2f2"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "9.5.0"
|
||||
version: "9.2.1"
|
||||
flutter_launcher_icons:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
@@ -404,10 +375,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_plugin_android_lifecycle
|
||||
sha256: "9b78450b89f059e96c9ebb355fa6b3df1d6b330436e0b885fb49594c41721398"
|
||||
sha256: "8cf40eebf5dec866a6d1956ad7b4f7016e6c0cc69847ab946833b7d43743809f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.23"
|
||||
version: "2.0.19"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
@@ -422,10 +393,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: form_builder_validators
|
||||
sha256: c61ed7b1deecf0e1ebe49e2fa79e3283937c5a21c7e48e3ed9856a4a14e1191a
|
||||
sha256: "19aa5282b7cede82d0025ab031a98d0554b84aa2ba40f12013471a3b3e22bf02"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "11.0.0"
|
||||
version: "9.1.0"
|
||||
frontend_server_client:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -454,10 +425,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: graphs
|
||||
sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0"
|
||||
sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.2"
|
||||
version: "2.3.1"
|
||||
gtk:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -478,10 +449,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http
|
||||
sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010
|
||||
sha256: a2bbf9d017fcced29139daa8ed2bba4ece450ab222871df93ca9eec6f80c34ba
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.2"
|
||||
version: "1.2.0"
|
||||
http_multi_server:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -502,18 +473,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image
|
||||
sha256: f31d52537dc417fdcde36088fdf11d191026fd5e4fae742491ebd40e5a8bea7d
|
||||
sha256: "2237616a36c0d69aef7549ab439b833fb7f9fb9fc861af2cc9ac3eedddd69ca8"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.3.0"
|
||||
version: "4.2.0"
|
||||
intl:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: intl
|
||||
sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf
|
||||
sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.19.0"
|
||||
version: "0.18.1"
|
||||
io:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -550,34 +521,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: launch_at_startup
|
||||
sha256: "1f8a75520913d1038630049e6c44a2575a23ffd28cc8b14fdf37401d1d21de84"
|
||||
sha256: "93fc5638e088290004fae358bae691486673d469957d461d9dae5b12248593eb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.1"
|
||||
leak_tracker:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker
|
||||
sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.0.5"
|
||||
leak_tracker_flutter_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_flutter_testing
|
||||
sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.5"
|
||||
leak_tracker_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_testing
|
||||
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
version: "0.2.2"
|
||||
lecle_downloads_path_provider:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -614,10 +561,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: logging
|
||||
sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
|
||||
sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
version: "1.2.0"
|
||||
logging_appenders:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -626,6 +573,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.1"
|
||||
lottie:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: lottie
|
||||
sha256: "893da7a0022ec2fcaa616f34529a081f617e86cc501105b856e5a3184c58c7c2"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.3"
|
||||
macos_secure_bookmarks:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -634,30 +589,22 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.1"
|
||||
macros:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: macros
|
||||
sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.2-main.4"
|
||||
matcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
|
||||
sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.12.16+1"
|
||||
version: "0.12.16"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: material_color_utilities
|
||||
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
|
||||
sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.11.1"
|
||||
version: "0.5.0"
|
||||
math_expressions:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -678,26 +625,26 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
|
||||
sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.15.0"
|
||||
version: "1.10.0"
|
||||
mime:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: mime
|
||||
sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6"
|
||||
sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
version: "1.0.5"
|
||||
open_filex:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: open_filex
|
||||
sha256: ba425ea49affd0a98a234aa9344b9ea5d4c4f7625a1377961eae9fe194c3d523
|
||||
sha256: "74e2280754cf8161e860746c3181db2c996d6c1909c7057b738ede4a469816b8"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.5.0"
|
||||
version: "4.4.0"
|
||||
package_config:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -710,26 +657,26 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: package_info_plus
|
||||
sha256: df3eb3e0aed5c1107bb0fdb80a8e82e778114958b1c5ac5644fb1ac9cae8a998
|
||||
sha256: "7e76fad405b3e4016cd39d08f455a4eb5199723cf594cd1b8916d47140d93017"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.1.0"
|
||||
version: "4.2.0"
|
||||
package_info_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: package_info_plus_platform_interface
|
||||
sha256: ac1f4a4847f1ade8e6a87d1f39f5d7c67490738642e2542f559ec38c37489a66
|
||||
sha256: "9bc8ba46813a4cc42c66ab781470711781940780fd8beddd0c3da62506d3a6c6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
version: "2.0.1"
|
||||
path:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: path
|
||||
sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
|
||||
sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.9.0"
|
||||
version: "1.8.3"
|
||||
path_provider:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -742,18 +689,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_android
|
||||
sha256: c464428172cb986b758c6d1724c603097febb8fb855aa265aeecc9280c294d4a
|
||||
sha256: a248d8146ee5983446bf03ed5ea8f6533129a12b11f12057ad1b4a67a2b3b41d
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.12"
|
||||
version: "2.2.4"
|
||||
path_provider_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_foundation
|
||||
sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16
|
||||
sha256: "5a7999be66e000916500be4f15a3633ebceb8302719b47b9cc49ce924125350f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.0"
|
||||
version: "2.3.2"
|
||||
path_provider_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -781,20 +728,19 @@ packages:
|
||||
permission_handler:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: permission_handler
|
||||
ref: "35fc72c30262b9b49e1965b48a7524b44ba9daa7"
|
||||
resolved-ref: "35fc72c30262b9b49e1965b48a7524b44ba9daa7"
|
||||
url: "https://github.com/monkeyWie/flutter-permission-handler.git"
|
||||
source: git
|
||||
name: permission_handler
|
||||
sha256: "18bf33f7fefbd812f37e72091a15575e72d5318854877e0e4035a24ac1113ecb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "11.3.1"
|
||||
permission_handler_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_android
|
||||
sha256: "71bbecfee799e65aff7c744761a57e817e73b738fedf62ab7afd5593da21f9f1"
|
||||
sha256: "76e4ab092c1b240d31177bb64d2b0bea43f43d0e23541ec866151b9f7b2490fa"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "12.0.13"
|
||||
version: "12.0.12"
|
||||
permission_handler_apple:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -807,25 +753,26 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_html
|
||||
sha256: af26edbbb1f2674af65a8f4b56e1a6f526156bc273d0e65dd8075fab51c78851
|
||||
sha256: "54bf176b90f6eddd4ece307e2c06cf977fb3973719c35a93b85cc7093eb6070d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.3+2"
|
||||
version: "0.1.1"
|
||||
permission_handler_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_platform_interface
|
||||
sha256: e9c8eadee926c4532d0305dff94b85bf961f16759c3af791486613152af4b4f9
|
||||
sha256: fe0ffe274d665be8e34f9c59705441a7d248edebbe5d9e3ec2665f88b79358ea
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.2.3"
|
||||
version: "4.2.2"
|
||||
permission_handler_windows:
|
||||
dependency: transitive
|
||||
dependency: "direct overridden"
|
||||
description:
|
||||
name: permission_handler_windows
|
||||
sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
path: permission_handler_windows
|
||||
ref: "35fc72c30262b9b49e1965b48a7524b44ba9daa7"
|
||||
resolved-ref: "35fc72c30262b9b49e1965b48a7524b44ba9daa7"
|
||||
url: "https://github.com/monkeyWie/flutter-permission-handler.git"
|
||||
source: git
|
||||
version: "0.2.1"
|
||||
petitparser:
|
||||
dependency: transitive
|
||||
@@ -839,10 +786,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: platform
|
||||
sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
|
||||
sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.6"
|
||||
version: "3.1.5"
|
||||
plugin_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -879,10 +826,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: quiver
|
||||
sha256: ea0b925899e64ecdfbf9c7becb60d5b50e706ade44a85b2363be2a22d88117d2
|
||||
sha256: b1c1ac5ce6688d77f65f3375a9abb9319b3cb32486bdc7a1e0fdf004d7ba4e47
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.2"
|
||||
version: "3.2.1"
|
||||
recase:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -927,50 +874,50 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: share_plus
|
||||
sha256: "334fcdf0ef9c0df0e3b428faebcac9568f35c747d59831474b2fc56e156d244e"
|
||||
sha256: "3ef39599b00059db0990ca2e30fca0a29d8b37aae924d60063f8e0184cf20900"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.1.0"
|
||||
version: "7.2.2"
|
||||
share_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: share_plus_platform_interface
|
||||
sha256: c57c0bbfec7142e3a0f55633be504b796af72e60e3c791b44d5a017b985f7a48
|
||||
sha256: "251eb156a8b5fa9ce033747d73535bf53911071f8d3b6f4f0b578505ce0d4496"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.0.1"
|
||||
version: "3.4.0"
|
||||
shared_preferences:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences
|
||||
sha256: "746e5369a43170c25816cc472ee016d3a66bc13fcf430c0bc41ad7b4b2922051"
|
||||
sha256: d3bbe5553a986e83980916ded2f0b435ef2e1893dfaa29d5a7a790d0eca12180
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.2"
|
||||
version: "2.2.3"
|
||||
shared_preferences_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_android
|
||||
sha256: "3b9febd815c9ca29c9e3520d50ec32f49157711e143b7a4ca039eb87e8ade5ab"
|
||||
sha256: "1ee8bf911094a1b592de7ab29add6f826a7331fb854273d55918693d5364a1f2"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.3"
|
||||
version: "2.2.2"
|
||||
shared_preferences_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_foundation
|
||||
sha256: "07e050c7cd39bad516f8d64c455f04508d09df104be326d8c02551590a0d513d"
|
||||
sha256: "7708d83064f38060c7b39db12aefe449cb8cdc031d6062280087bc4cdb988f5c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.3"
|
||||
version: "2.3.5"
|
||||
shared_preferences_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_linux
|
||||
sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f"
|
||||
sha256: "2ba0510d3017f91655b7543e9ee46d48619de2a2af38e5c790423f7007c7ccc1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
version: "2.4.0"
|
||||
shared_preferences_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -983,18 +930,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_web
|
||||
sha256: d2ca4132d3946fec2184261726b355836a82c33d7d5b67af32692aff18a4684e
|
||||
sha256: "7b15ffb9387ea3e237bb7a66b8a23d2147663d391cafc5c8f37b2e7b4bde5d21"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.2"
|
||||
version: "2.2.2"
|
||||
shared_preferences_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_windows
|
||||
sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1"
|
||||
sha256: "398084b47b7f92110683cac45c6dc4aae853db47e470e5ddcd52cab7f7196ab2"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
version: "2.4.0"
|
||||
shelf:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1007,10 +954,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shelf_web_socket
|
||||
sha256: "073c147238594ecd0d193f3456a5fe91c4b0abbcc68bf5cd95b36c4e194ac611"
|
||||
sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
version: "1.0.4"
|
||||
shortid:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1108,10 +1055,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb"
|
||||
sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.2"
|
||||
version: "0.6.1"
|
||||
timing:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1132,66 +1079,66 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: tray_manager
|
||||
sha256: bdc3ac6c36f3d12d871459e4a9822705ce5a1165a17fa837103bc842719bf3f7
|
||||
sha256: c9a63fd88bd3546287a7eb8ccc978d707eef82c775397af17dda3a4f4c039e64
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.4"
|
||||
version: "0.2.3"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: typed_data
|
||||
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
|
||||
sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
version: "1.3.2"
|
||||
uri_to_file:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: uri_to_file
|
||||
sha256: cbbb38f975d22311efacc9a53cdcb806708606666d8092314b81c5cb7983b700
|
||||
sha256: "84afd633b1492fc465c768141e1a29edd519061bf99935b6b4d0d5de8ec7c108"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
version: "0.2.0"
|
||||
url_launcher:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: url_launcher
|
||||
sha256: "9d06212b1362abc2f0f0d78e6f09f726608c74e3b9462e8368bb03314aa8d603"
|
||||
sha256: "21b704ce5fa560ea9f3b525b43601c678728ba46725bab9b01187b4831377ed3"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.3.1"
|
||||
version: "6.3.0"
|
||||
url_launcher_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_android
|
||||
sha256: "8fc3bae0b68c02c47c5c86fa8bfa74471d42687b0eded01b78de87872db745e2"
|
||||
sha256: "17cd5e205ea615e2c6ea7a77323a11712dffa0720a8a90540db57a01347f9ad9"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.3.12"
|
||||
version: "6.3.2"
|
||||
url_launcher_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_ios
|
||||
sha256: e43b677296fadce447e987a2f519dcf5f6d1e527dc35d01ffab4fff5b8a7063e
|
||||
sha256: "75bb6fe3f60070407704282a2d295630cab232991eb52542b18347a8a941df03"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.3.1"
|
||||
version: "6.2.4"
|
||||
url_launcher_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_linux
|
||||
sha256: e2b9622b4007f97f504cd64c0128309dfb978ae66adbe944125ed9e1750f06af
|
||||
sha256: ab360eb661f8879369acac07b6bb3ff09d9471155357da8443fd5d3cf7363811
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.0"
|
||||
version: "3.1.1"
|
||||
url_launcher_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_macos
|
||||
sha256: "769549c999acdb42b8bcfa7c43d72bf79a382ca7441ab18a808e101149daf672"
|
||||
sha256: "9a1a42d5d2d95400c795b2914c36fdcb525870c752569438e4ebb09a2b5d90de"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.1"
|
||||
version: "3.2.0"
|
||||
url_launcher_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1204,26 +1151,26 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_web
|
||||
sha256: "772638d3b34c779ede05ba3d38af34657a05ac55b06279ea6edd409e323dca8e"
|
||||
sha256: fff0932192afeedf63cdd50ecbb1bc825d31aed259f02bb8dba0f3b729a5e88b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.3"
|
||||
version: "2.2.3"
|
||||
url_launcher_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_windows
|
||||
sha256: "44cf3aabcedde30f2dba119a9dea3b0f2672fbe6fa96e85536251d678216b3c4"
|
||||
sha256: "49c10f879746271804767cb45551ec5592cdab00ee105c06dddde1a98f73b185"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.3"
|
||||
version: "3.1.2"
|
||||
uuid:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: uuid
|
||||
sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff
|
||||
sha256: "83d37c7ad7aaf9aa8e275490669535c8080377cfa7a7004c24dfac53afffaa90"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.5.1"
|
||||
version: "4.4.2"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1232,14 +1179,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
vm_service:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vm_service
|
||||
sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "14.2.5"
|
||||
watcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1252,58 +1191,50 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web
|
||||
sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb
|
||||
sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
web_socket:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web_socket
|
||||
sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.6"
|
||||
version: "0.3.0"
|
||||
web_socket_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web_socket_channel
|
||||
sha256: "9f187088ed104edd8662ca07af4b124465893caf063ba29758f97af57e61da8f"
|
||||
sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
version: "2.4.0"
|
||||
win32:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: win32
|
||||
sha256: "2294c64768987ea280b43a3d8357d42d5679f3e2b5b69b602be45b2abbd165b0"
|
||||
sha256: "5a751eddf9db89b3e5f9d50c20ab8612296e4e8db69009788d6c8b060a84191c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.6.1"
|
||||
version: "4.1.4"
|
||||
win32_registry:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: win32_registry
|
||||
sha256: "21ec76dfc731550fd3e2ce7a33a9ea90b828fdf19a5c3bcf556fa992cfa99852"
|
||||
sha256: "1c52f994bdccb77103a6231ad4ea331a244dbcef5d1f37d8462f713143b0bfae"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.5"
|
||||
version: "1.1.0"
|
||||
window_manager:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: window_manager
|
||||
sha256: ab8b2a7f97543d3db2b506c9d875e637149d48ee0c6a5cb5f5fd6e0dac463792
|
||||
sha256: "8699323b30da4cdbe2aa2e7c9de567a6abd8a97d9a5c850a3c86dcd0b34bbfbf"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.4.2"
|
||||
version: "0.3.9"
|
||||
xdg_directories:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: xdg_directories
|
||||
sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
|
||||
sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
version: "1.0.4"
|
||||
xml:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1329,5 +1260,5 @@ packages:
|
||||
source: hosted
|
||||
version: "2.2.1"
|
||||
sdks:
|
||||
dart: ">=3.5.0 <4.0.0"
|
||||
flutter: ">=3.24.0"
|
||||
dart: ">=3.2.0 <4.0.0"
|
||||
flutter: ">=3.16.0"
|
||||
+27
-25
@@ -15,7 +15,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
|
||||
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
||||
# Read more about iOS versioning at
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
version: 1.6.2+1
|
||||
version: 1.6.4+1
|
||||
|
||||
environment:
|
||||
sdk: ">=3.0.0 <4.0.0"
|
||||
@@ -36,43 +36,45 @@ dependencies:
|
||||
# Use with the CupertinoIcons class for iOS style icons.
|
||||
cupertino_icons: ^1.0.2
|
||||
get: ^4.6.5
|
||||
# lottie: ^1.4.2
|
||||
lottie: ^1.4.2
|
||||
styled_widget: ^0.4.0+3
|
||||
context_menus: ^1.2.0
|
||||
context_menus: ^1.0.2
|
||||
json_annotation: ^4.8.1
|
||||
dio: ^5.2.1
|
||||
path_provider: ^2.1.4
|
||||
file_picker: ^8.1.2
|
||||
path_provider: ^2.1.1
|
||||
file_picker: 5.3.1
|
||||
macos_secure_bookmarks: ^0.2.1
|
||||
rounded_loading_button_plus: ^3.0.1
|
||||
url_launcher: ^6.3.1
|
||||
logger: ^1.4.0
|
||||
desktop_drop: ^0.4.4
|
||||
package_info_plus: ^8.1.0
|
||||
fluent_ui: ^4.9.2
|
||||
path: ^1.9.0
|
||||
url_launcher: ^6.1.6
|
||||
logger: ^1.3.0
|
||||
desktop_drop: ^0.4.3
|
||||
package_info_plus: ^4.0.0
|
||||
fluent_ui: ^4.4.0
|
||||
path: any
|
||||
badges: ^3.0.3
|
||||
app_links: ^6.3.2
|
||||
uri_to_file: ^1.0.0
|
||||
window_manager: ^0.4.2
|
||||
app_links: ^3.4.2
|
||||
uri_to_file: ^0.2.0
|
||||
window_manager: ^0.3.4
|
||||
autoscale_tabbarview: ^1.0.2
|
||||
share_plus: ^10.1.0
|
||||
flutter_form_builder: ^9.5.0
|
||||
form_builder_validators: ^11.0.0
|
||||
share_plus: ^7.1.0
|
||||
flutter_form_builder: ^9.1.1
|
||||
form_builder_validators: ^9.0.0
|
||||
flutter_foreground_task: ^8.2.0
|
||||
open_filex: ^4.5.0
|
||||
tray_manager: ^0.2.4
|
||||
open_filex: ^4.3.4
|
||||
tray_manager: ^0.2.3
|
||||
lecle_downloads_path_provider: ^0.0.2+8
|
||||
hive: ^2.2.3
|
||||
launch_at_startup: ^0.3.1
|
||||
launch_at_startup: ^0.2.2
|
||||
args: ^2.5.0
|
||||
toggle_switch: ^2.3.0
|
||||
permission_handler:
|
||||
permission_handler: ^11.3.1
|
||||
device_info_plus: ^9.1.2
|
||||
dependency_overrides:
|
||||
permission_handler_windows:
|
||||
git:
|
||||
url: https://github.com/monkeyWie/flutter-permission-handler.git
|
||||
ref: 35fc72c30262b9b49e1965b48a7524b44ba9daa7
|
||||
path: permission_handler
|
||||
device_info_plus: ^11.1.0
|
||||
path: permission_handler_windows
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
@@ -83,7 +85,7 @@ dev_dependencies:
|
||||
# package. See that file for information about deactivating specific lint
|
||||
# rules and activating additional ones.
|
||||
flutter_lints: ^2.0.0
|
||||
ffigen: ^8.0.2
|
||||
ffigen: ^11.0.0
|
||||
# build json model: dart run build_runner build --delete-conflicting-outputs
|
||||
build_runner: ^2.2.1
|
||||
json_serializable: ^6.3.2
|
||||
@@ -148,7 +150,7 @@ flutter_icons:
|
||||
ffigen:
|
||||
name: LibgopeedBind
|
||||
description: Bindings to gopeed library.
|
||||
output: "lib/core/ffi/libgopeed_bind.dart"
|
||||
output: "lib/api/native/ffi/libgopeed_bind.dart"
|
||||
headers:
|
||||
entry-points:
|
||||
- "include/libgopeed.h"
|
||||
Reference in new issue
Block a user