mirror of
https://github.com/GopeedLab/gopeed.git
synced 2026-11-10 18:40:27 +00:00
Compare commits
7
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4736a91904 | ||
|
|
0a4d74a513 | ||
|
|
a06549a11a | ||
|
|
63296f6950 | ||
|
|
4b4c03fd90 | ||
|
|
91dfd78b77 | ||
|
|
467ada5500 |
No files matched your search
@@ -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 "C"
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"github.com/GopeedLab/gopeed/pkg/rest"
|
"github.com/GopeedLab/gopeed/bind"
|
||||||
"github.com/GopeedLab/gopeed/pkg/rest/model"
|
"github.com/GopeedLab/gopeed/pkg/api"
|
||||||
|
"github.com/GopeedLab/gopeed/pkg/api/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {}
|
func main() {}
|
||||||
|
|
||||||
//export Start
|
//export Init
|
||||||
func Start(cfg *C.char) (int, *C.char) {
|
func Init(cfg *C.char) *C.char {
|
||||||
var config model.StartConfig
|
var config model.StartConfig
|
||||||
if err := json.Unmarshal([]byte(C.GoString(cfg)), &config); err != nil {
|
if err := json.Unmarshal([]byte(C.GoString(cfg)), &config); err != nil {
|
||||||
return 0, C.CString(err.Error())
|
return C.CString(err.Error())
|
||||||
}
|
}
|
||||||
config.ProductionMode = true
|
if err := bind.Init(&config); err != nil {
|
||||||
realPort, err := rest.Start(&config)
|
return C.CString(err.Error())
|
||||||
if err != nil {
|
|
||||||
return 0, C.CString(err.Error())
|
|
||||||
}
|
}
|
||||||
return realPort, nil
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
//export Stop
|
//export Invoke
|
||||||
func Stop() {
|
func Invoke(req *C.char) *C.char {
|
||||||
rest.Stop()
|
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++
|
// #cgo LDFLAGS: -static-libstdc++
|
||||||
import "C"
|
import "C"
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"github.com/GopeedLab/gopeed/bind"
|
||||||
"github.com/GopeedLab/gopeed/pkg/rest"
|
"github.com/GopeedLab/gopeed/pkg/api"
|
||||||
"github.com/GopeedLab/gopeed/pkg/rest/model"
|
"github.com/GopeedLab/gopeed/pkg/api/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Start(cfg string) (int, error) {
|
func Init(cfg *model.StartConfig) error {
|
||||||
var config model.StartConfig
|
return bind.Init(cfg)
|
||||||
if err := json.Unmarshal([]byte(cfg), &config); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
config.ProductionMode = true
|
|
||||||
return rest.Start(&config)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Stop() {
|
func Invoke(request *api.Request) string {
|
||||||
rest.Stop()
|
return bind.Invoke(request)
|
||||||
}
|
}
|
||||||
+10
-4
@@ -2,15 +2,21 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/GopeedLab/gopeed/cmd"
|
"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
|
// only for local development
|
||||||
func main() {
|
func main() {
|
||||||
cfg := &model.StartConfig{
|
cfg := &model.StartConfig{
|
||||||
Network: "tcp",
|
Storage: model.StorageBolt,
|
||||||
Address: "127.0.0.1:9999",
|
DownloadConfig: &base.DownloaderStoreConfig{
|
||||||
Storage: model.StorageBolt,
|
Http: &base.DownloaderHttpConfig{
|
||||||
|
Enable: true,
|
||||||
|
Host: "127.0.0.1",
|
||||||
|
Port: 9999,
|
||||||
|
},
|
||||||
|
},
|
||||||
WebEnable: true,
|
WebEnable: true,
|
||||||
}
|
}
|
||||||
cmd.Start(cfg)
|
cmd.Start(cfg)
|
||||||
|
|||||||
+18
-14
@@ -3,10 +3,9 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
_ "embed"
|
_ "embed"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/GopeedLab/gopeed/pkg/api"
|
||||||
|
"github.com/GopeedLab/gopeed/pkg/api/model"
|
||||||
"github.com/GopeedLab/gopeed/pkg/base"
|
"github.com/GopeedLab/gopeed/pkg/base"
|
||||||
"github.com/GopeedLab/gopeed/pkg/rest"
|
|
||||||
"github.com/GopeedLab/gopeed/pkg/rest/model"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -18,20 +17,22 @@ var banner string
|
|||||||
|
|
||||||
func Start(cfg *model.StartConfig) {
|
func Start(cfg *model.StartConfig) {
|
||||||
fmt.Println(banner)
|
fmt.Println(banner)
|
||||||
srv, listener, err := rest.BuildServer(cfg)
|
instance, err := api.Create(cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
downloadCfg, err := rest.Downloader.GetConfig()
|
|
||||||
if err != nil {
|
downloadCfgResult := instance.GetConfig()
|
||||||
panic(err)
|
if downloadCfgResult.HasError() {
|
||||||
|
panic(downloadCfgResult.Msg)
|
||||||
}
|
}
|
||||||
|
downloadCfg := downloadCfgResult.Data
|
||||||
if downloadCfg.FirstLoad {
|
if downloadCfg.FirstLoad {
|
||||||
// Set default download config
|
// Set default download config
|
||||||
if cfg.DownloadConfig != nil {
|
if cfg.DownloadConfig != nil {
|
||||||
cfg.DownloadConfig.Merge(downloadCfg)
|
cfg.DownloadConfig.Merge(downloadCfg)
|
||||||
// TODO Use PatchConfig
|
// TODO Use PatchConfig
|
||||||
rest.Downloader.PutConfig(cfg.DownloadConfig)
|
instance.PutConfig(cfg.DownloadConfig)
|
||||||
downloadCfg = cfg.DownloadConfig
|
downloadCfg = cfg.DownloadConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,25 +49,28 @@ func Start(cfg *model.StartConfig) {
|
|||||||
}
|
}
|
||||||
if downloadDir != "" {
|
if downloadDir != "" {
|
||||||
downloadCfg.DownloadDir = downloadDir
|
downloadCfg.DownloadDir = downloadDir
|
||||||
rest.Downloader.PutConfig(downloadCfg)
|
instance.PutConfig(downloadCfg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
watchExit()
|
|
||||||
|
|
||||||
fmt.Printf("Server start success on http://%s\n", listener.Addr().String())
|
watchExit(instance)
|
||||||
if err := srv.Serve(listener); err != nil && err != http.ErrServerClosed {
|
|
||||||
|
port, start, err := api.ListenHttp(cfg.DownloadConfig.Http, instance)
|
||||||
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
fmt.Printf("Server start success on http://%s:%d\n", cfg.DownloadConfig.Http.Host, port)
|
||||||
|
start()
|
||||||
}
|
}
|
||||||
|
|
||||||
func watchExit() {
|
func watchExit(instance *api.Instance) {
|
||||||
sigs := make(chan os.Signal, 1)
|
sigs := make(chan os.Signal, 1)
|
||||||
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
|
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
|
||||||
go func() {
|
go func() {
|
||||||
sig := <-sigs
|
sig := <-sigs
|
||||||
fmt.Printf("Server is shutting down due to signal: %s\n", sig)
|
fmt.Printf("Server is shutting down due to signal: %s\n", sig)
|
||||||
rest.Downloader.Close()
|
instance.Close()
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
+4
-4
@@ -10,7 +10,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type args struct {
|
type args struct {
|
||||||
Address *string `json:"address"`
|
Host *string `json:"host"`
|
||||||
Port *int `json:"port"`
|
Port *int `json:"port"`
|
||||||
Username *string `json:"username"`
|
Username *string `json:"username"`
|
||||||
Password *string `json:"password"`
|
Password *string `json:"password"`
|
||||||
@@ -24,7 +24,7 @@ type args struct {
|
|||||||
|
|
||||||
func parse() *args {
|
func parse() *args {
|
||||||
var cliArgs args
|
var cliArgs args
|
||||||
cliArgs.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.Port = flag.Int("P", 9999, "Bind Port")
|
||||||
cliArgs.Username = flag.String("u", "gopeed", "HTTP Basic Auth Username")
|
cliArgs.Username = flag.String("u", "gopeed", "HTTP Basic Auth Username")
|
||||||
cliArgs.Password = flag.String("p", "", "HTTP Basic Auth Pwd")
|
cliArgs.Password = flag.String("p", "", "HTTP Basic Auth Pwd")
|
||||||
@@ -35,8 +35,8 @@ func parse() *args {
|
|||||||
|
|
||||||
// args priority: config file > cli args
|
// args priority: config file > cli args
|
||||||
cfgArgs := loadConfig(*cliArgs.configPath)
|
cfgArgs := loadConfig(*cliArgs.configPath)
|
||||||
if cfgArgs.Address == nil {
|
if cfgArgs.Host == nil {
|
||||||
cfgArgs.Address = cliArgs.Address
|
cfgArgs.Host = cliArgs.Host
|
||||||
}
|
}
|
||||||
if cfgArgs.Port == nil {
|
if cfgArgs.Port == nil {
|
||||||
cfgArgs.Port = cliArgs.Port
|
cfgArgs.Port = cliArgs.Port
|
||||||
|
|||||||
+12
-5
@@ -5,9 +5,9 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"embed"
|
"embed"
|
||||||
"fmt"
|
|
||||||
"github.com/GopeedLab/gopeed/cmd"
|
"github.com/GopeedLab/gopeed/cmd"
|
||||||
"github.com/GopeedLab/gopeed/pkg/rest/model"
|
"github.com/GopeedLab/gopeed/pkg/api/model"
|
||||||
|
"github.com/GopeedLab/gopeed/pkg/base"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -42,12 +42,19 @@ func main() {
|
|||||||
dir = filepath.Dir(exe)
|
dir = filepath.Dir(exe)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if args.DownloadConfig == nil {
|
||||||
|
args.DownloadConfig = &base.DownloaderStoreConfig{}
|
||||||
|
}
|
||||||
|
args.DownloadConfig.Http = &base.DownloaderHttpConfig{
|
||||||
|
Enable: true,
|
||||||
|
Host: *args.Host,
|
||||||
|
Port: *args.Port,
|
||||||
|
ApiToken: *args.ApiToken,
|
||||||
|
}
|
||||||
|
|
||||||
cfg := &model.StartConfig{
|
cfg := &model.StartConfig{
|
||||||
Network: "tcp",
|
|
||||||
Address: fmt.Sprintf("%s:%d", *args.Address, *args.Port),
|
|
||||||
Storage: model.StorageBolt,
|
Storage: model.StorageBolt,
|
||||||
StorageDir: filepath.Join(dir, "storage"),
|
StorageDir: filepath.Join(dir, "storage"),
|
||||||
ApiToken: *args.ApiToken,
|
|
||||||
DownloadConfig: args.DownloadConfig,
|
DownloadConfig: args.DownloadConfig,
|
||||||
ProductionMode: true,
|
ProductionMode: true,
|
||||||
WebEnable: true,
|
WebEnable: true,
|
||||||
|
|||||||
@@ -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 {
|
func (f *Fetcher) torrentStats() torrent.TorrentStats {
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ func (f *Fetcher) Resolve(req *base.Request) error {
|
|||||||
file.Name = filename
|
file.Name = filename
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Get file filePath by URL
|
// get file filePath by URL
|
||||||
if file.Name == "" {
|
if file.Name == "" {
|
||||||
file.Name = path.Base(httpReq.URL.Path)
|
file.Name = path.Base(httpReq.URL.Path)
|
||||||
}
|
}
|
||||||
@@ -499,7 +499,7 @@ func (fm *FetcherManager) ParseName(u string) string {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
// Get filePath by URL
|
// get filePath by URL
|
||||||
name = path.Base(url.Path)
|
name = path.Base(url.Path)
|
||||||
// If file name is empty, use host name
|
// If file name is empty, use host name
|
||||||
if name == "" || name == "/" || name == "." {
|
if name == "" || name == "/" || name == "." {
|
||||||
|
|||||||
+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 (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@@ -7,9 +7,9 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/GopeedLab/gopeed/internal/test"
|
"github.com/GopeedLab/gopeed/internal/test"
|
||||||
|
"github.com/GopeedLab/gopeed/pkg/api/model"
|
||||||
"github.com/GopeedLab/gopeed/pkg/base"
|
"github.com/GopeedLab/gopeed/pkg/base"
|
||||||
"github.com/GopeedLab/gopeed/pkg/download"
|
"github.com/GopeedLab/gopeed/pkg/download"
|
||||||
"github.com/GopeedLab/gopeed/pkg/rest/model"
|
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -21,7 +21,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
restPort int
|
httpInstance *Instance
|
||||||
|
restPort int
|
||||||
|
|
||||||
taskReq = &base.Request{
|
taskReq = &base.Request{
|
||||||
Extra: map[string]any{
|
Extra: map[string]any{
|
||||||
@@ -90,7 +91,7 @@ func TestCreateTask(t *testing.T) {
|
|||||||
|
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
Downloader.Listener(func(event *download.Event) {
|
httpInstance.Listener(func(event *download.Event) {
|
||||||
if event.Key == download.EventKeyFinally {
|
if event.Key == download.EventKeyFinally {
|
||||||
wg.Done()
|
wg.Done()
|
||||||
}
|
}
|
||||||
@@ -117,7 +118,7 @@ func TestCreateDirectTask(t *testing.T) {
|
|||||||
doTest(func() {
|
doTest(func() {
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
Downloader.Listener(func(event *download.Event) {
|
httpInstance.Listener(func(event *download.Event) {
|
||||||
if event.Key == download.EventKeyFinally {
|
if event.Key == download.EventKeyFinally {
|
||||||
wg.Done()
|
wg.Done()
|
||||||
}
|
}
|
||||||
@@ -156,7 +157,7 @@ func TestCreateDirectTaskWithResoled(t *testing.T) {
|
|||||||
doTest(func() {
|
doTest(func() {
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
Downloader.Listener(func(event *download.Event) {
|
httpInstance.Listener(func(event *download.Event) {
|
||||||
if event.Key == download.EventKeyFinally {
|
if event.Key == download.EventKeyFinally {
|
||||||
wg.Done()
|
wg.Done()
|
||||||
}
|
}
|
||||||
@@ -180,7 +181,7 @@ func TestPauseAndContinueTask(t *testing.T) {
|
|||||||
doTest(func() {
|
doTest(func() {
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
Downloader.Listener(func(event *download.Event) {
|
httpInstance.Listener(func(event *download.Event) {
|
||||||
switch event.Key {
|
switch event.Key {
|
||||||
case download.EventKeyFinally:
|
case download.EventKeyFinally:
|
||||||
wg.Done()
|
wg.Done()
|
||||||
@@ -214,10 +215,7 @@ func TestPauseAndContinueTask(t *testing.T) {
|
|||||||
|
|
||||||
func TestPauseAllAndContinueALLTasks(t *testing.T) {
|
func TestPauseAllAndContinueALLTasks(t *testing.T) {
|
||||||
doTest(func() {
|
doTest(func() {
|
||||||
cfg, err := Downloader.GetConfig()
|
cfg := UnwrapResult(httpInstance.GetConfig())
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
createAndPause := func() {
|
createAndPause := func() {
|
||||||
taskId := httpRequestCheckOk[string](http.MethodPost, "/api/v1/tasks", createReq)
|
taskId := httpRequestCheckOk[string](http.MethodPost, "/api/v1/tasks", createReq)
|
||||||
@@ -275,7 +273,7 @@ func TestDeleteAllTasks(t *testing.T) {
|
|||||||
|
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
wg.Add(taskCount)
|
wg.Add(taskCount)
|
||||||
Downloader.Listener(func(event *download.Event) {
|
httpInstance.Listener(func(event *download.Event) {
|
||||||
if event.Key == download.EventKeyFinally {
|
if event.Key == download.EventKeyFinally {
|
||||||
wg.Done()
|
wg.Done()
|
||||||
}
|
}
|
||||||
@@ -301,7 +299,7 @@ func TestDeleteTasksByStatues(t *testing.T) {
|
|||||||
|
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
wg.Add(taskCount)
|
wg.Add(taskCount)
|
||||||
Downloader.Listener(func(event *download.Event) {
|
httpInstance.Listener(func(event *download.Event) {
|
||||||
if event.Key == download.EventKeyFinally {
|
if event.Key == download.EventKeyFinally {
|
||||||
wg.Done()
|
wg.Done()
|
||||||
}
|
}
|
||||||
@@ -325,7 +323,7 @@ func TestGetTasks(t *testing.T) {
|
|||||||
doTest(func() {
|
doTest(func() {
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
Downloader.Listener(func(event *download.Event) {
|
httpInstance.Listener(func(event *download.Event) {
|
||||||
if event.Key == download.EventKeyFinally {
|
if event.Key == download.EventKeyFinally {
|
||||||
wg.Done()
|
wg.Done()
|
||||||
}
|
}
|
||||||
@@ -354,7 +352,10 @@ func TestGetAndPutConfig(t *testing.T) {
|
|||||||
cfg := httpRequestCheckOk[*base.DownloaderStoreConfig](http.MethodGet, "/api/v1/config", nil)
|
cfg := httpRequestCheckOk[*base.DownloaderStoreConfig](http.MethodGet, "/api/v1/config", nil)
|
||||||
cfg.DownloadDir = "./download"
|
cfg.DownloadDir = "./download"
|
||||||
cfg.Extra = map[string]any{
|
cfg.Extra = map[string]any{
|
||||||
"serverConfig": &Config{
|
"serverConfig": &struct {
|
||||||
|
Host string `json:"host"`
|
||||||
|
Port int `json:"port"`
|
||||||
|
}{
|
||||||
Host: "127.0.0.1",
|
Host: "127.0.0.1",
|
||||||
Port: 8080,
|
Port: 8080,
|
||||||
},
|
},
|
||||||
@@ -446,16 +447,16 @@ func TestDeleteExtension(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUpdateCheckExtension(t *testing.T) {
|
func TestUpgradeCheckExtension(t *testing.T) {
|
||||||
doTest(func() {
|
doTest(func() {
|
||||||
identity := httpRequestCheckOk[string](http.MethodPost, "/api/v1/extensions", installExtensionReq)
|
identity := httpRequestCheckOk[string](http.MethodPost, "/api/v1/extensions", installExtensionReq)
|
||||||
resp := httpRequestCheckOk[*model.UpdateCheckExtensionResp](http.MethodGet, "/api/v1/extensions/"+identity+"/update", nil)
|
resp := httpRequestCheckOk[*model.UpgradeCheckExtensionResp](http.MethodGet, "/api/v1/extensions/"+identity+"/upgrade", nil)
|
||||||
// no new version
|
// no new version
|
||||||
if resp.NewVersion != "" {
|
if resp.NewVersion != "" {
|
||||||
t.Errorf("UpdateCheckExtension() got = %v, want %v", resp.NewVersion, "")
|
t.Errorf("UpgradeCheckExtension() got = %v, want %v", resp.NewVersion, "")
|
||||||
}
|
}
|
||||||
// force update
|
// 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) {
|
func TestApiToken(t *testing.T) {
|
||||||
var cfg = &model.StartConfig{}
|
var cfg = &model.StartConfig{}
|
||||||
cfg.Init()
|
cfg.Init()
|
||||||
cfg.ApiToken = "123456"
|
cfg.DownloadConfig = &base.DownloaderStoreConfig{
|
||||||
fileListener := doStart(cfg)
|
Http: &base.DownloaderHttpConfig{
|
||||||
|
Enable: true,
|
||||||
|
ApiToken: "123456",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
httpInstance, _ = Create(cfg)
|
||||||
|
fileListener := doStart()
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := fileListener.Close(); err != nil {
|
if err := fileListener.Close(); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
Stop()
|
httpInstance.Close()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
status, _ := doHttpRequest0(http.MethodGet, "/api/v1/config", nil, nil)
|
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{
|
status, _ = doHttpRequest0(http.MethodGet, "/api/v1/config", map[string]string{
|
||||||
"X-Api-Token": cfg.ApiToken,
|
"X-Api-Token": cfg.DownloadConfig.Http.ApiToken,
|
||||||
}, nil)
|
}, nil)
|
||||||
if status != http.StatusOK {
|
if status != http.StatusOK {
|
||||||
t.Errorf("TestApiToken() got = %v, want %v", status, http.StatusOK)
|
t.Errorf("TestApiToken() got = %v, want %v", status, http.StatusOK)
|
||||||
@@ -532,18 +539,24 @@ func TestApiToken(t *testing.T) {
|
|||||||
func TestAuthorization(t *testing.T) {
|
func TestAuthorization(t *testing.T) {
|
||||||
var cfg = &model.StartConfig{}
|
var cfg = &model.StartConfig{}
|
||||||
cfg.Init()
|
cfg.Init()
|
||||||
cfg.ApiToken = "123456"
|
cfg.DownloadConfig = &base.DownloaderStoreConfig{
|
||||||
|
Http: &base.DownloaderHttpConfig{
|
||||||
|
Enable: true,
|
||||||
|
ApiToken: "123456",
|
||||||
|
},
|
||||||
|
}
|
||||||
cfg.WebEnable = true
|
cfg.WebEnable = true
|
||||||
cfg.WebBasicAuth = &model.WebBasicAuth{
|
cfg.WebBasicAuth = &model.WebBasicAuth{
|
||||||
Username: "admin",
|
Username: "admin",
|
||||||
Password: "123456",
|
Password: "123456",
|
||||||
}
|
}
|
||||||
fileListener := doStart(cfg)
|
httpInstance, _ = Create(cfg)
|
||||||
|
fileListener := doStart()
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := fileListener.Close(); err != nil {
|
if err := fileListener.Close(); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
Stop()
|
httpInstance.Close()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
status, _ := doHttpRequest0(http.MethodGet, "/api/v1/config", nil, nil)
|
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{
|
status, _ = doHttpRequest0(http.MethodGet, "/api/v1/config", map[string]string{
|
||||||
"X-Api-Token": cfg.ApiToken,
|
"X-Api-Token": cfg.DownloadConfig.Http.ApiToken,
|
||||||
}, nil)
|
}, nil)
|
||||||
if status != http.StatusOK {
|
if status != http.StatusOK {
|
||||||
t.Errorf("TestAuthorization() got = %v, want %v", status, http.StatusOK)
|
t.Errorf("TestAuthorization() got = %v, want %v", status, http.StatusOK)
|
||||||
@@ -573,17 +586,23 @@ func doTest(handler func()) {
|
|||||||
cfg.Storage = storage
|
cfg.Storage = storage
|
||||||
cfg.StorageDir = ".test_storage"
|
cfg.StorageDir = ".test_storage"
|
||||||
cfg.WebEnable = true
|
cfg.WebEnable = true
|
||||||
fileListener := doStart(cfg)
|
cfg.DownloadConfig = &base.DownloaderStoreConfig{
|
||||||
|
Http: &base.DownloaderHttpConfig{
|
||||||
|
Enable: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
httpInstance, _ = Create(cfg)
|
||||||
|
fileListener := doStart()
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := fileListener.Close(); err != nil {
|
if err := fileListener.Close(); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
Stop()
|
httpInstance.StopHttp()
|
||||||
Downloader.Clear()
|
httpInstance.Clear()
|
||||||
}()
|
}()
|
||||||
defer func() {
|
defer func() {
|
||||||
Downloader.Pause(nil)
|
httpInstance.PauseTasks(nil)
|
||||||
Downloader.Delete(nil, true)
|
httpInstance.DeleteTasks(nil, true)
|
||||||
os.RemoveAll(cfg.StorageDir)
|
os.RemoveAll(cfg.StorageDir)
|
||||||
}()
|
}()
|
||||||
taskReq.URL = "http://" + fileListener.Addr().String() + "/" + test.BuildName
|
taskReq.URL = "http://" + fileListener.Addr().String() + "/" + test.BuildName
|
||||||
@@ -593,15 +612,19 @@ func doTest(handler func()) {
|
|||||||
testFunc(model.StorageBolt)
|
testFunc(model.StorageBolt)
|
||||||
}
|
}
|
||||||
|
|
||||||
func doStart(cfg *model.StartConfig) net.Listener {
|
func doStart() net.Listener {
|
||||||
port, err := Start(cfg)
|
port := UnwrapResult(httpInstance.StartHttp())
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
restPort = port
|
restPort = port
|
||||||
return test.StartTestFileServer()
|
return test.StartTestFileServer()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func UnwrapResult[T any](result *model.Result[T]) T {
|
||||||
|
if result.HasError() {
|
||||||
|
panic(result.Msg)
|
||||||
|
}
|
||||||
|
return result.Data
|
||||||
|
}
|
||||||
|
|
||||||
func doHttpRequest0(method string, path string, headers map[string]string, body any) (int, []byte) {
|
func doHttpRequest0(method string, path string, headers map[string]string, body any) (int, []byte) {
|
||||||
var reader io.Reader
|
var reader io.Reader
|
||||||
if body != nil {
|
if body != nil {
|
||||||
@@ -14,13 +14,10 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type StartConfig struct {
|
type StartConfig struct {
|
||||||
Network string `json:"network"`
|
|
||||||
Address string `json:"address"`
|
|
||||||
RefreshInterval int `json:"refreshInterval"`
|
RefreshInterval int `json:"refreshInterval"`
|
||||||
Storage Storage `json:"storage"`
|
Storage Storage `json:"storage"`
|
||||||
StorageDir string `json:"storageDir"`
|
StorageDir string `json:"storageDir"`
|
||||||
WhiteDownloadDirs []string `json:"whiteDownloadDirs"`
|
WhiteDownloadDirs []string `json:"whiteDownloadDirs"`
|
||||||
ApiToken string `json:"apiToken"`
|
|
||||||
DownloadConfig *base.DownloaderStoreConfig `json:"downloadConfig"`
|
DownloadConfig *base.DownloaderStoreConfig `json:"downloadConfig"`
|
||||||
|
|
||||||
ProductionMode bool
|
ProductionMode bool
|
||||||
@@ -31,12 +28,6 @@ type StartConfig struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *StartConfig) Init() *StartConfig {
|
func (cfg *StartConfig) Init() *StartConfig {
|
||||||
if cfg.Network == "" {
|
|
||||||
cfg.Network = "tcp"
|
|
||||||
}
|
|
||||||
if cfg.Address == "" {
|
|
||||||
cfg.Address = "127.0.0.1:0"
|
|
||||||
}
|
|
||||||
if cfg.RefreshInterval == 0 {
|
if cfg.RefreshInterval == 0 {
|
||||||
cfg.RefreshInterval = 350
|
cfg.RefreshInterval = 350
|
||||||
}
|
}
|
||||||
@@ -59,3 +50,9 @@ func (cfg *WebBasicAuth) Authorization() string {
|
|||||||
userId := cfg.Username + ":" + cfg.Password
|
userId := cfg.Username + ":" + cfg.Password
|
||||||
return "Basic " + base64.StdEncoding.EncodeToString([]byte(userId))
|
return "Basic " + base64.StdEncoding.EncodeToString([]byte(userId))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type HttpListenResult struct {
|
||||||
|
Host string `json:"host"`
|
||||||
|
// Port is the http server real listen port.
|
||||||
|
Port int `json:"port"`
|
||||||
|
}
|
||||||
@@ -13,6 +13,6 @@ type SwitchExtension struct {
|
|||||||
Status bool `json:"status"`
|
Status bool `json:"status"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UpdateCheckExtensionResp struct {
|
type UpgradeCheckExtensionResp struct {
|
||||||
NewVersion string `json:"newVersion"`
|
NewVersion string `json:"newVersion"`
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
package model
|
package model
|
||||||
|
|
||||||
|
import "encoding/json"
|
||||||
|
|
||||||
type RespCode int
|
type RespCode int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -20,6 +22,15 @@ type Result[T any] struct {
|
|||||||
Data T `json:"data"`
|
Data T `json:"data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Result[T]) Json() string {
|
||||||
|
buf, _ := json.Marshal(r)
|
||||||
|
return string(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Result[T]) HasError() bool {
|
||||||
|
return r.Code != CodeOk
|
||||||
|
}
|
||||||
|
|
||||||
func NewOkResult[T any](data T) *Result[T] {
|
func NewOkResult[T any](data T) *Result[T] {
|
||||||
return &Result[T]{
|
return &Result[T]{
|
||||||
Code: CodeOk,
|
Code: CodeOk,
|
||||||
@@ -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
|
// if code is not provided, the default code is CodeError
|
||||||
c := CodeError
|
c := CodeError
|
||||||
if len(code) > 0 {
|
if len(code) > 0 {
|
||||||
c = code[0]
|
c = code[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Result[any]{
|
return &Result[T]{
|
||||||
Code: c,
|
Code: c,
|
||||||
Msg: msg,
|
Msg: msg,
|
||||||
}
|
}
|
||||||
File renamed without changes.
@@ -171,6 +171,7 @@ type DownloaderStoreConfig struct {
|
|||||||
MaxRunning int `json:"maxRunning"` // MaxRunning is the max running download count
|
MaxRunning int `json:"maxRunning"` // MaxRunning is the max running download count
|
||||||
ProtocolConfig map[string]any `json:"protocolConfig"` // ProtocolConfig is special config for each protocol
|
ProtocolConfig map[string]any `json:"protocolConfig"` // ProtocolConfig is special config for each protocol
|
||||||
Extra map[string]any `json:"extra"`
|
Extra map[string]any `json:"extra"`
|
||||||
|
Http *DownloaderHttpConfig `json:"http"`
|
||||||
Proxy *DownloaderProxyConfig `json:"proxy"`
|
Proxy *DownloaderProxyConfig `json:"proxy"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,6 +182,12 @@ func (cfg *DownloaderStoreConfig) Init() *DownloaderStoreConfig {
|
|||||||
if cfg.ProtocolConfig == nil {
|
if cfg.ProtocolConfig == nil {
|
||||||
cfg.ProtocolConfig = make(map[string]any)
|
cfg.ProtocolConfig = make(map[string]any)
|
||||||
}
|
}
|
||||||
|
if cfg.Extra == nil {
|
||||||
|
cfg.Extra = make(map[string]any)
|
||||||
|
}
|
||||||
|
if cfg.Http == nil {
|
||||||
|
cfg.Http = &DownloaderHttpConfig{}
|
||||||
|
}
|
||||||
if cfg.Proxy == nil {
|
if cfg.Proxy == nil {
|
||||||
cfg.Proxy = &DownloaderProxyConfig{}
|
cfg.Proxy = &DownloaderProxyConfig{}
|
||||||
}
|
}
|
||||||
@@ -203,12 +210,28 @@ func (cfg *DownloaderStoreConfig) Merge(beforeCfg *DownloaderStoreConfig) *Downl
|
|||||||
if cfg.Extra == nil {
|
if cfg.Extra == nil {
|
||||||
cfg.Extra = beforeCfg.Extra
|
cfg.Extra = beforeCfg.Extra
|
||||||
}
|
}
|
||||||
|
if cfg.Http == nil {
|
||||||
|
cfg.Http = beforeCfg.Http
|
||||||
|
}
|
||||||
if cfg.Proxy == nil {
|
if cfg.Proxy == nil {
|
||||||
cfg.Proxy = beforeCfg.Proxy
|
cfg.Proxy = beforeCfg.Proxy
|
||||||
}
|
}
|
||||||
return cfg
|
return cfg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DownloaderHttpConfig struct {
|
||||||
|
// Enable is the flag that enable http server
|
||||||
|
Enable bool `json:"Enable"`
|
||||||
|
// Host is the http server listen address.
|
||||||
|
Host string `json:"host"`
|
||||||
|
// Port is the http server listen port.
|
||||||
|
Port int `json:"port"`
|
||||||
|
// ApiToken is the auth token for http API
|
||||||
|
ApiToken string `json:"apiToken"`
|
||||||
|
// RunningPort is the http server real listen port.
|
||||||
|
RunningPort int `json:"runningPort"`
|
||||||
|
}
|
||||||
|
|
||||||
type DownloaderProxyConfig struct {
|
type DownloaderProxyConfig struct {
|
||||||
Enable bool `json:"enable"`
|
Enable bool `json:"enable"`
|
||||||
// System is the flag that use system proxy
|
// System is the flag that use system proxy
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ func TestDownloaderStoreConfig_Init(t *testing.T) {
|
|||||||
&DownloaderStoreConfig{
|
&DownloaderStoreConfig{
|
||||||
MaxRunning: 5,
|
MaxRunning: 5,
|
||||||
ProtocolConfig: map[string]any{},
|
ProtocolConfig: map[string]any{},
|
||||||
|
Extra: make(map[string]any),
|
||||||
|
Http: &DownloaderHttpConfig{},
|
||||||
Proxy: &DownloaderProxyConfig{},
|
Proxy: &DownloaderProxyConfig{},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -28,6 +30,8 @@ func TestDownloaderStoreConfig_Init(t *testing.T) {
|
|||||||
&DownloaderStoreConfig{
|
&DownloaderStoreConfig{
|
||||||
MaxRunning: 10,
|
MaxRunning: 10,
|
||||||
ProtocolConfig: map[string]any{},
|
ProtocolConfig: map[string]any{},
|
||||||
|
Extra: make(map[string]any),
|
||||||
|
Http: &DownloaderHttpConfig{},
|
||||||
Proxy: &DownloaderProxyConfig{},
|
Proxy: &DownloaderProxyConfig{},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -43,6 +47,8 @@ func TestDownloaderStoreConfig_Init(t *testing.T) {
|
|||||||
ProtocolConfig: map[string]any{
|
ProtocolConfig: map[string]any{
|
||||||
"key": "value",
|
"key": "value",
|
||||||
},
|
},
|
||||||
|
Extra: make(map[string]any),
|
||||||
|
Http: &DownloaderHttpConfig{},
|
||||||
Proxy: &DownloaderProxyConfig{},
|
Proxy: &DownloaderProxyConfig{},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -56,6 +62,8 @@ func TestDownloaderStoreConfig_Init(t *testing.T) {
|
|||||||
&DownloaderStoreConfig{
|
&DownloaderStoreConfig{
|
||||||
MaxRunning: 5,
|
MaxRunning: 5,
|
||||||
ProtocolConfig: map[string]any{},
|
ProtocolConfig: map[string]any{},
|
||||||
|
Extra: make(map[string]any),
|
||||||
|
Http: &DownloaderHttpConfig{},
|
||||||
Proxy: &DownloaderProxyConfig{
|
Proxy: &DownloaderProxyConfig{
|
||||||
Enable: true,
|
Enable: true,
|
||||||
},
|
},
|
||||||
|
|||||||
+17
-14
@@ -252,7 +252,7 @@ func (d *Downloader) setupFetcher(fm fetcher.FetcherManager, fetcher fetcher.Fet
|
|||||||
ctl.GetConfig = func(v any) {
|
ctl.GetConfig = func(v any) {
|
||||||
d.getProtocolConfig(fm.Name(), v)
|
d.getProtocolConfig(fm.Name(), v)
|
||||||
}
|
}
|
||||||
// Get proxy config, task request proxy config has higher priority, then use global proxy config
|
// get proxy config, task request proxy config has higher priority, then use global proxy config
|
||||||
ctl.GetProxy = func(requestProxy *base.RequestProxy) func(*gohttp.Request) (*url.URL, error) {
|
ctl.GetProxy = func(requestProxy *base.RequestProxy) func(*gohttp.Request) (*url.URL, error) {
|
||||||
if requestProxy == nil {
|
if requestProxy == nil {
|
||||||
return d.cfg.Proxy.ToHandler()
|
return d.cfg.Proxy.ToHandler()
|
||||||
@@ -390,7 +390,7 @@ func (d *Downloader) Pause(filter *TaskFilter) (err error) {
|
|||||||
return d.pauseAll()
|
return d.pauseAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
filter.NotStatuses = []base.Status{base.DownloadStatusPause, base.DownloadStatusError, base.DownloadStatusDone}
|
filter.NotStatus = []base.Status{base.DownloadStatusPause, base.DownloadStatusError, base.DownloadStatusDone}
|
||||||
pauseTasks := d.GetTasksByFilter(filter)
|
pauseTasks := d.GetTasksByFilter(filter)
|
||||||
if len(pauseTasks) == 0 {
|
if len(pauseTasks) == 0 {
|
||||||
return ErrTaskNotFound
|
return ErrTaskNotFound
|
||||||
@@ -430,7 +430,7 @@ func (d *Downloader) Continue(filter *TaskFilter) (err error) {
|
|||||||
return d.continueAll()
|
return d.continueAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
filter.NotStatuses = []base.Status{base.DownloadStatusRunning, base.DownloadStatusDone}
|
filter.NotStatus = []base.Status{base.DownloadStatusRunning, base.DownloadStatusDone}
|
||||||
continueTasks := d.GetTasksByFilter(filter)
|
continueTasks := d.GetTasksByFilter(filter)
|
||||||
if len(continueTasks) == 0 {
|
if len(continueTasks) == 0 {
|
||||||
return ErrTaskNotFound
|
return ErrTaskNotFound
|
||||||
@@ -527,7 +527,7 @@ func (d *Downloader) ContinueBatch(filter *TaskFilter) (err error) {
|
|||||||
|
|
||||||
func (d *Downloader) Delete(filter *TaskFilter, force bool) (err error) {
|
func (d *Downloader) Delete(filter *TaskFilter, force bool) (err error) {
|
||||||
if filter == nil || filter.IsEmpty() {
|
if filter == nil || filter.IsEmpty() {
|
||||||
return d.deleteAll()
|
return d.deleteAll(force)
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteTasks := d.GetTasksByFilter(filter)
|
deleteTasks := d.GetTasksByFilter(filter)
|
||||||
@@ -572,24 +572,27 @@ func (d *Downloader) Delete(filter *TaskFilter, force bool) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Downloader) deleteAll() (err error) {
|
func (d *Downloader) deleteAll(force bool) (err error) {
|
||||||
|
deleteTasksPtr := make([]*Task, 0)
|
||||||
|
|
||||||
func() {
|
func() {
|
||||||
d.lock.Lock()
|
d.lock.Lock()
|
||||||
defer d.lock.Unlock()
|
defer d.lock.Unlock()
|
||||||
|
|
||||||
|
deleteTasksPtr = append(deleteTasksPtr, d.tasks...)
|
||||||
d.tasks = make([]*Task, 0)
|
d.tasks = make([]*Task, 0)
|
||||||
d.waitTasks = make([]*Task, 0)
|
d.waitTasks = make([]*Task, 0)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
for _, task := range d.tasks {
|
for _, task := range deleteTasksPtr {
|
||||||
if err = d.doDelete(task, true); err != nil {
|
if err = d.doDelete(task, force); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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)
|
task := d.GetTask(id)
|
||||||
if task == nil {
|
if task == nil {
|
||||||
return sr, ErrTaskNotFound
|
return sr, ErrTaskNotFound
|
||||||
@@ -719,10 +722,10 @@ func (d *Downloader) GetTasksByFilter(filter *TaskFilter) []*Task {
|
|||||||
}
|
}
|
||||||
|
|
||||||
idMatch := func(task *Task) bool {
|
idMatch := func(task *Task) bool {
|
||||||
if len(filter.IDs) == 0 {
|
if len(filter.ID) == 0 {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
for _, id := range filter.IDs {
|
for _, id := range filter.ID {
|
||||||
if task.ID == id {
|
if task.ID == id {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -730,10 +733,10 @@ func (d *Downloader) GetTasksByFilter(filter *TaskFilter) []*Task {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
statusMatch := func(task *Task) bool {
|
statusMatch := func(task *Task) bool {
|
||||||
if len(filter.Statuses) == 0 {
|
if len(filter.Status) == 0 {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
for _, status := range filter.Statuses {
|
for _, status := range filter.Status {
|
||||||
if task.Status == status {
|
if task.Status == status {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -741,10 +744,10 @@ func (d *Downloader) GetTasksByFilter(filter *TaskFilter) []*Task {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
notStatusMatch := func(task *Task) bool {
|
notStatusMatch := func(task *Task) bool {
|
||||||
if len(filter.NotStatuses) == 0 {
|
if len(filter.NotStatus) == 0 {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
for _, status := range filter.NotStatuses {
|
for _, status := range filter.NotStatus {
|
||||||
if task.Status == status {
|
if task.Status == status {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -400,7 +400,7 @@ func TestDownloader_StoreAndRestore(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
time.Sleep(time.Millisecond * 1001)
|
time.Sleep(time.Millisecond * 1001)
|
||||||
err = downloader.Pause(&TaskFilter{IDs: []string{id}})
|
err = downloader.Pause(&TaskFilter{ID: []string{id}})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -424,7 +424,7 @@ func TestDownloader_StoreAndRestore(t *testing.T) {
|
|||||||
wg.Done()
|
wg.Done()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
err = downloader.Continue(&TaskFilter{IDs: []string{id}})
|
err = downloader.Continue(&TaskFilter{ID: []string{id}})
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -551,7 +551,7 @@ func TestDownloader_GetTasksByFilter(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("GetTasksByFilter ids", func(t *testing.T) {
|
t.Run("GetTasksByFilter ids", func(t *testing.T) {
|
||||||
tasks := downloader.GetTasksByFilter(&TaskFilter{
|
tasks := downloader.GetTasksByFilter(&TaskFilter{
|
||||||
IDs: taskIds,
|
ID: taskIds,
|
||||||
})
|
})
|
||||||
if len(tasks) != len(reqs) {
|
if len(tasks) != len(reqs) {
|
||||||
t.Errorf("GetTasksByFilter ids task got = %v, want %v", len(tasks), len(reqs))
|
t.Errorf("GetTasksByFilter ids task got = %v, want %v", len(tasks), len(reqs))
|
||||||
@@ -560,7 +560,7 @@ func TestDownloader_GetTasksByFilter(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("GetTasksByFilter match ids", func(t *testing.T) {
|
t.Run("GetTasksByFilter match ids", func(t *testing.T) {
|
||||||
tasks := downloader.GetTasksByFilter(&TaskFilter{
|
tasks := downloader.GetTasksByFilter(&TaskFilter{
|
||||||
IDs: []string{taskIds[0]},
|
ID: []string{taskIds[0]},
|
||||||
})
|
})
|
||||||
if len(tasks) != 1 {
|
if len(tasks) != 1 {
|
||||||
t.Errorf("GetTasksByFilter ids task got = %v, want %v", len(tasks), 1)
|
t.Errorf("GetTasksByFilter ids task got = %v, want %v", len(tasks), 1)
|
||||||
@@ -569,7 +569,7 @@ func TestDownloader_GetTasksByFilter(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("GetTasksByFilter not match ids", func(t *testing.T) {
|
t.Run("GetTasksByFilter not match ids", func(t *testing.T) {
|
||||||
tasks := downloader.GetTasksByFilter(&TaskFilter{
|
tasks := downloader.GetTasksByFilter(&TaskFilter{
|
||||||
IDs: []string{"xxx"},
|
ID: []string{"xxx"},
|
||||||
})
|
})
|
||||||
if len(tasks) != 0 {
|
if len(tasks) != 0 {
|
||||||
t.Errorf("GetTasksByFilter ids task got = %v, want %v", len(tasks), 0)
|
t.Errorf("GetTasksByFilter ids task got = %v, want %v", len(tasks), 0)
|
||||||
@@ -578,7 +578,7 @@ func TestDownloader_GetTasksByFilter(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("GetTasksByFilter status", func(t *testing.T) {
|
t.Run("GetTasksByFilter status", func(t *testing.T) {
|
||||||
tasks := downloader.GetTasksByFilter(&TaskFilter{
|
tasks := downloader.GetTasksByFilter(&TaskFilter{
|
||||||
Statuses: []base.Status{base.DownloadStatusDone},
|
Status: []base.Status{base.DownloadStatusDone},
|
||||||
})
|
})
|
||||||
if len(tasks) != len(reqs) {
|
if len(tasks) != len(reqs) {
|
||||||
t.Errorf("GetTasksByFilter status task got = %v, want %v", len(tasks), len(reqs))
|
t.Errorf("GetTasksByFilter status task got = %v, want %v", len(tasks), len(reqs))
|
||||||
@@ -587,7 +587,7 @@ func TestDownloader_GetTasksByFilter(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("GetTasksByFilter not match status", func(t *testing.T) {
|
t.Run("GetTasksByFilter not match status", func(t *testing.T) {
|
||||||
tasks := downloader.GetTasksByFilter(&TaskFilter{
|
tasks := downloader.GetTasksByFilter(&TaskFilter{
|
||||||
Statuses: []base.Status{base.DownloadStatusError},
|
Status: []base.Status{base.DownloadStatusError},
|
||||||
})
|
})
|
||||||
if len(tasks) != 0 {
|
if len(tasks) != 0 {
|
||||||
t.Errorf("GetTasksByFilter status task got = %v, want %v", len(tasks), 0)
|
t.Errorf("GetTasksByFilter status task got = %v, want %v", len(tasks), 0)
|
||||||
@@ -596,7 +596,7 @@ func TestDownloader_GetTasksByFilter(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("GetTasksByFilter match notStatus", func(t *testing.T) {
|
t.Run("GetTasksByFilter match notStatus", func(t *testing.T) {
|
||||||
tasks := downloader.GetTasksByFilter(&TaskFilter{
|
tasks := downloader.GetTasksByFilter(&TaskFilter{
|
||||||
NotStatuses: []base.Status{base.DownloadStatusRunning, base.DownloadStatusPause},
|
NotStatus: []base.Status{base.DownloadStatusRunning, base.DownloadStatusPause},
|
||||||
})
|
})
|
||||||
if len(tasks) != len(reqs) {
|
if len(tasks) != len(reqs) {
|
||||||
t.Errorf("GetTasksByFilter match notStatus task got = %v, want %v", len(tasks), len(reqs))
|
t.Errorf("GetTasksByFilter match notStatus task got = %v, want %v", len(tasks), len(reqs))
|
||||||
@@ -605,7 +605,7 @@ func TestDownloader_GetTasksByFilter(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("GetTasksByFilter not match notStatus", func(t *testing.T) {
|
t.Run("GetTasksByFilter not match notStatus", func(t *testing.T) {
|
||||||
tasks := downloader.GetTasksByFilter(&TaskFilter{
|
tasks := downloader.GetTasksByFilter(&TaskFilter{
|
||||||
NotStatuses: []base.Status{base.DownloadStatusDone},
|
NotStatus: []base.Status{base.DownloadStatusDone},
|
||||||
})
|
})
|
||||||
if len(tasks) != 0 {
|
if len(tasks) != 0 {
|
||||||
t.Errorf("GetTasksByFilter not match notStatus task got = %v, want %v", len(tasks), 0)
|
t.Errorf("GetTasksByFilter not match notStatus task got = %v, want %v", len(tasks), 0)
|
||||||
@@ -614,8 +614,8 @@ func TestDownloader_GetTasksByFilter(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("GetTasksByFilter match ids and status", func(t *testing.T) {
|
t.Run("GetTasksByFilter match ids and status", func(t *testing.T) {
|
||||||
tasks := downloader.GetTasksByFilter(&TaskFilter{
|
tasks := downloader.GetTasksByFilter(&TaskFilter{
|
||||||
IDs: []string{taskIds[0]},
|
ID: []string{taskIds[0]},
|
||||||
Statuses: []base.Status{base.DownloadStatusDone},
|
Status: []base.Status{base.DownloadStatusDone},
|
||||||
})
|
})
|
||||||
if len(tasks) != 1 {
|
if len(tasks) != 1 {
|
||||||
t.Errorf("GetTasksByFilter match ids and status task got = %v, want %v", len(tasks), 1)
|
t.Errorf("GetTasksByFilter match ids and status task got = %v, want %v", len(tasks), 1)
|
||||||
@@ -624,8 +624,8 @@ func TestDownloader_GetTasksByFilter(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("GetTasksByFilter not match ids and status", func(t *testing.T) {
|
t.Run("GetTasksByFilter not match ids and status", func(t *testing.T) {
|
||||||
tasks := downloader.GetTasksByFilter(&TaskFilter{
|
tasks := downloader.GetTasksByFilter(&TaskFilter{
|
||||||
IDs: []string{taskIds[0]},
|
ID: []string{taskIds[0]},
|
||||||
Statuses: []base.Status{base.DownloadStatusError},
|
Status: []base.Status{base.DownloadStatusError},
|
||||||
})
|
})
|
||||||
if len(tasks) != 0 {
|
if len(tasks) != 0 {
|
||||||
t.Errorf("GetTasksByFilter not match ids and status task got = %v, want %v", len(tasks), 0)
|
t.Errorf("GetTasksByFilter not match ids and status task got = %v, want %v", len(tasks), 0)
|
||||||
|
|||||||
@@ -727,13 +727,13 @@ func NewExtensionTask(download *Downloader, task *Task) *ExtensionTask {
|
|||||||
|
|
||||||
func (t *ExtensionTask) Continue() error {
|
func (t *ExtensionTask) Continue() error {
|
||||||
return t.download.Continue(&TaskFilter{
|
return t.download.Continue(&TaskFilter{
|
||||||
IDs: []string{t.task.ID},
|
ID: []string{t.task.ID},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *ExtensionTask) Pause() error {
|
func (t *ExtensionTask) Pause() error {
|
||||||
return t.download.Pause(&TaskFilter{
|
return t.download.Pause(&TaskFilter{
|
||||||
IDs: []string{t.task.ID},
|
ID: []string{t.task.ID},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ func (t *Task) Name() string {
|
|||||||
return t.Meta.Res.Name
|
return t.Meta.Res.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the name of the first file
|
// get the name of the first file
|
||||||
return t.Meta.Res.Files[0].Name
|
return t.Meta.Res.Files[0].Name
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,13 +113,13 @@ func (t *Task) calcSpeed(speedArr []int64, downloaded int64, usedTime float64) i
|
|||||||
}
|
}
|
||||||
|
|
||||||
type TaskFilter struct {
|
type TaskFilter struct {
|
||||||
IDs []string
|
ID []string `json:"id"`
|
||||||
Statuses []base.Status
|
Status []base.Status `json:"status"`
|
||||||
NotStatuses []base.Status
|
NotStatus []base.Status `json:"notStatus"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *TaskFilter) IsEmpty() bool {
|
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 {
|
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" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
extern __declspec(dllexport) char* Init(char* cfg);
|
||||||
/* Return type for Start */
|
extern __declspec(dllexport) char* Invoke(char* req);
|
||||||
struct Start_return {
|
|
||||||
GoInt r0;
|
|
||||||
char* r1;
|
|
||||||
};
|
|
||||||
extern struct Start_return Start(char* cfg);
|
|
||||||
extern void Stop();
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#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,
|
name: json['name'] as String,
|
||||||
path: json['path'] as String,
|
path: json['path'] as String,
|
||||||
selectFiles: (json['selectFiles'] as List<dynamic>?)
|
selectFiles: (json['selectFiles'] as List<dynamic>?)
|
||||||
?.map((e) => e as int)
|
?.map((e) => (e as num).toInt())
|
||||||
.toList() ??
|
.toList() ??
|
||||||
const [],
|
const [],
|
||||||
extra: json['extra'],
|
extra: json['extra'],
|
||||||
@@ -35,7 +35,7 @@ Map<String, dynamic> _$OptionsToJson(Options instance) {
|
|||||||
|
|
||||||
OptsExtraHttp _$OptsExtraHttpFromJson(Map<String, dynamic> json) =>
|
OptsExtraHttp _$OptsExtraHttpFromJson(Map<String, dynamic> json) =>
|
||||||
OptsExtraHttp()
|
OptsExtraHttp()
|
||||||
..connections = json['connections'] as int
|
..connections = (json['connections'] as num).toInt()
|
||||||
..autoTorrent = json['autoTorrent'] as bool;
|
..autoTorrent = json['autoTorrent'] as bool;
|
||||||
|
|
||||||
Map<String, dynamic> _$OptsExtraHttpToJson(OptsExtraHttp instance) =>
|
Map<String, dynamic> _$OptsExtraHttpToJson(OptsExtraHttp instance) =>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ Result<T> _$ResultFromJson<T>(
|
|||||||
T Function(Object? json) fromJsonT,
|
T Function(Object? json) fromJsonT,
|
||||||
) =>
|
) =>
|
||||||
Result<T>(
|
Result<T>(
|
||||||
code: json['code'] as int,
|
code: (json['code'] as num).toInt(),
|
||||||
msg: json['msg'] as String?,
|
msg: json['msg'] as String?,
|
||||||
data: _$nullableGenericFromJson(json['data'], fromJsonT),
|
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 =
|
late final __FCmulcr =
|
||||||
__FCmulcrPtr.asFunction<_Fcomplex Function(_Fcomplex, double)>();
|
__FCmulcrPtr.asFunction<_Fcomplex Function(_Fcomplex, double)>();
|
||||||
|
|
||||||
Start_return Start(
|
ffi.Pointer<ffi.Char> Init(
|
||||||
ffi.Pointer<ffi.Char> cfg,
|
ffi.Pointer<ffi.Char> cfg,
|
||||||
) {
|
) {
|
||||||
return _Start(
|
return _Init(
|
||||||
cfg,
|
cfg,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
late final _StartPtr =
|
late final _InitPtr = _lookup<
|
||||||
_lookup<ffi.NativeFunction<Start_return Function(ffi.Pointer<ffi.Char>)>>(
|
ffi.NativeFunction<
|
||||||
'Start');
|
ffi.Pointer<ffi.Char> Function(ffi.Pointer<ffi.Char>)>>('Init');
|
||||||
late final _Start =
|
late final _Init = _InitPtr.asFunction<
|
||||||
_StartPtr.asFunction<Start_return Function(ffi.Pointer<ffi.Char>)>();
|
ffi.Pointer<ffi.Char> Function(ffi.Pointer<ffi.Char>)>();
|
||||||
|
|
||||||
void Stop() {
|
ffi.Pointer<ffi.Char> Invoke(
|
||||||
return _Stop();
|
ffi.Pointer<ffi.Char> req,
|
||||||
|
) {
|
||||||
|
return _Invoke(
|
||||||
|
req,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
late final _StopPtr =
|
late final _InvokePtr = _lookup<
|
||||||
_lookup<ffi.NativeFunction<ffi.Void Function()>>('Stop');
|
ffi.NativeFunction<
|
||||||
late final _Stop = _StopPtr.asFunction<void Function()>();
|
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>;
|
typedef va_list = ffi.Pointer<ffi.Char>;
|
||||||
@@ -914,6 +920,7 @@ final class _Mbstatet extends ffi.Struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
typedef errno_t = ffi.Int;
|
typedef errno_t = ffi.Int;
|
||||||
|
typedef Darterrno_t = int;
|
||||||
|
|
||||||
final class _GoString_ extends ffi.Struct {
|
final class _GoString_ extends ffi.Struct {
|
||||||
external ffi.Pointer<ffi.Char> p;
|
external ffi.Pointer<ffi.Char> p;
|
||||||
@@ -923,6 +930,7 @@ final class _GoString_ extends ffi.Struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
typedef ptrdiff_t = ffi.LongLong;
|
typedef ptrdiff_t = ffi.LongLong;
|
||||||
|
typedef Dartptrdiff_t = int;
|
||||||
|
|
||||||
final class _C_double_complex extends ffi.Struct {
|
final class _C_double_complex extends ffi.Struct {
|
||||||
@ffi.Array.multi([2])
|
@ffi.Array.multi([2])
|
||||||
@@ -957,13 +965,7 @@ final class GoSlice extends ffi.Struct {
|
|||||||
|
|
||||||
typedef GoInt = GoInt64;
|
typedef GoInt = GoInt64;
|
||||||
typedef GoInt64 = ffi.LongLong;
|
typedef GoInt64 = ffi.LongLong;
|
||||||
|
typedef DartGoInt64 = int;
|
||||||
final class Start_return extends ffi.Struct {
|
|
||||||
@GoInt()
|
|
||||||
external int r0;
|
|
||||||
|
|
||||||
external ffi.Pointer<ffi.Char> r1;
|
|
||||||
}
|
|
||||||
|
|
||||||
const int _VCRT_COMPILER_PREPROCESSOR = 1;
|
const int _VCRT_COMPILER_PREPROCESSOR = 1;
|
||||||
|
|
||||||
@@ -993,6 +995,10 @@ const int _HAS_CXX23 = 0;
|
|||||||
|
|
||||||
const int _HAS_NODISCARD = 1;
|
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 _UCRT_DISABLED_WARNINGS = 4324;
|
||||||
|
|
||||||
const int _ARGMAX = 100;
|
const int _ARGMAX = 100;
|
||||||
@@ -1009,9 +1015,7 @@ const int _CRT_FUNCTIONS_REQUIRED = 1;
|
|||||||
|
|
||||||
const int _CRT_HAS_CXX17 = 0;
|
const int _CRT_HAS_CXX17 = 0;
|
||||||
|
|
||||||
const int _ARM_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE = 1;
|
const int _CRT_HAS_C11 = 0;
|
||||||
|
|
||||||
const int _CRT_BUILD_DESKTOP_APP = 1;
|
|
||||||
|
|
||||||
const int _CRT_INTERNAL_NONSTDC_NAMES = 1;
|
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()
|
@JsonSerializable()
|
||||||
class StartConfig {
|
class StartConfig {
|
||||||
late String network;
|
String storage;
|
||||||
late String address;
|
String storageDir;
|
||||||
late String storage;
|
|
||||||
late String storageDir;
|
|
||||||
late int refreshInterval;
|
|
||||||
late String apiToken;
|
|
||||||
|
|
||||||
StartConfig();
|
StartConfig({
|
||||||
|
this.storage = '',
|
||||||
|
this.storageDir = '',
|
||||||
|
});
|
||||||
|
|
||||||
factory StartConfig.fromJson(Map<String, dynamic> json) =>
|
factory StartConfig.fromJson(Map<String, dynamic> json) =>
|
||||||
_$StartConfigFromJson(json);
|
_$StartConfigFromJson(json);
|
||||||
+4
-11
@@ -6,20 +6,13 @@ part of 'start_config.dart';
|
|||||||
// JsonSerializableGenerator
|
// JsonSerializableGenerator
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
StartConfig _$StartConfigFromJson(Map<String, dynamic> json) => StartConfig()
|
StartConfig _$StartConfigFromJson(Map<String, dynamic> json) => StartConfig(
|
||||||
..network = json['network'] as String
|
storage: json['storage'] as String? ?? '',
|
||||||
..address = json['address'] as String
|
storageDir: json['storageDir'] as String? ?? '',
|
||||||
..storage = json['storage'] as String
|
);
|
||||||
..storageDir = json['storageDir'] as String
|
|
||||||
..refreshInterval = json['refreshInterval'] as int
|
|
||||||
..apiToken = json['apiToken'] as String;
|
|
||||||
|
|
||||||
Map<String, dynamic> _$StartConfigToJson(StartConfig instance) =>
|
Map<String, dynamic> _$StartConfigToJson(StartConfig instance) =>
|
||||||
<String, dynamic>{
|
<String, dynamic>{
|
||||||
'network': instance.network,
|
|
||||||
'address': instance.address,
|
|
||||||
'storage': instance.storage,
|
'storage': instance.storage,
|
||||||
'storageDir': instance.storageDir,
|
'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:url_launcher/url_launcher.dart';
|
||||||
import 'package:window_manager/window_manager.dart';
|
import 'package:window_manager/window_manager.dart';
|
||||||
|
|
||||||
import '../../../../api/api.dart';
|
import '../../../../api/libgopeed_boot.dart';
|
||||||
import '../../../../api/model/downloader_config.dart';
|
import '../../../../api/model/downloader_config.dart';
|
||||||
import '../../../../core/common/start_config.dart';
|
|
||||||
import '../../../../core/libgopeed_boot.dart';
|
|
||||||
import '../../../../database/database.dart';
|
import '../../../../database/database.dart';
|
||||||
import '../../../../database/entity.dart';
|
import '../../../../database/entity.dart';
|
||||||
import '../../../../i18n/message.dart';
|
import '../../../../i18n/message.dart';
|
||||||
@@ -74,12 +72,9 @@ final allTrackerSubscribeUrlCdns = Map.fromIterable(allTrackerSubscribeUrls,
|
|||||||
});
|
});
|
||||||
|
|
||||||
class AppController extends GetxController with WindowListener, TrayListener {
|
class AppController extends GetxController with WindowListener, TrayListener {
|
||||||
static StartConfig? _defaultStartConfig;
|
|
||||||
|
|
||||||
final autoStartup = false.obs;
|
final autoStartup = false.obs;
|
||||||
final startConfig = StartConfig().obs;
|
|
||||||
final runningPort = 0.obs;
|
|
||||||
final downloaderConfig = DownloaderConfig().obs;
|
final downloaderConfig = DownloaderConfig().obs;
|
||||||
|
final httpRunningPort = 0.obs;
|
||||||
|
|
||||||
late AppLinks _appLinks;
|
late AppLinks _appLinks;
|
||||||
StreamSubscription<Uri>? _linkSubscription;
|
StreamSubscription<Uri>? _linkSubscription;
|
||||||
@@ -111,7 +106,7 @@ class AppController extends GetxController with WindowListener, TrayListener {
|
|||||||
void onClose() {
|
void onClose() {
|
||||||
_linkSubscription?.cancel();
|
_linkSubscription?.cancel();
|
||||||
trayManager.removeListener(this);
|
trayManager.removeListener(this);
|
||||||
LibgopeedBoot.instance.stop();
|
LibgopeedBoot.instance.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -218,11 +213,12 @@ class AppController extends GetxController with WindowListener, TrayListener {
|
|||||||
),
|
),
|
||||||
MenuItem(
|
MenuItem(
|
||||||
label: "startAll".tr,
|
label: "startAll".tr,
|
||||||
onClick: (menuItem) async => {continueAllTasks()},
|
onClick: (menuItem) async =>
|
||||||
|
{LibgopeedBoot.instance.continueTasks(null)},
|
||||||
),
|
),
|
||||||
MenuItem(
|
MenuItem(
|
||||||
label: "pauseAll".tr,
|
label: "pauseAll".tr,
|
||||||
onClick: (menuItem) async => {pauseAllTasks()},
|
onClick: (menuItem) async => {LibgopeedBoot.instance.pauseTasks(null)},
|
||||||
),
|
),
|
||||||
MenuItem(
|
MenuItem(
|
||||||
label: 'setting'.tr,
|
label: 'setting'.tr,
|
||||||
@@ -247,7 +243,7 @@ class AppController extends GetxController with WindowListener, TrayListener {
|
|||||||
label: 'exit'.tr,
|
label: 'exit'.tr,
|
||||||
onClick: (menuItem) async {
|
onClick: (menuItem) async {
|
||||||
try {
|
try {
|
||||||
await LibgopeedBoot.instance.stop();
|
await LibgopeedBoot.instance.close();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.w("libgopeed stop fail", 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);
|
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 {
|
Future<DownloaderConfig> loadDownloaderConfig() async {
|
||||||
try {
|
try {
|
||||||
downloaderConfig.value = await getConfig();
|
downloaderConfig.value = await LibgopeedBoot.instance.getConfig();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.w("load downloader config fail", e, StackTrace.current);
|
logger.w("load downloader config fail", e, StackTrace.current);
|
||||||
downloaderConfig.value = DownloaderConfig();
|
downloaderConfig.value = DownloaderConfig();
|
||||||
@@ -382,7 +344,7 @@ class AppController extends GetxController with WindowListener, TrayListener {
|
|||||||
});
|
});
|
||||||
refreshTrackers();
|
refreshTrackers();
|
||||||
|
|
||||||
await saveConfig();
|
await LibgopeedBoot.instance.putConfig(downloaderConfig.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
refreshTrackers() {
|
refreshTrackers() {
|
||||||
@@ -411,7 +373,7 @@ class AppController extends GetxController with WindowListener, TrayListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<List<String>> _fetchTrackers(String subscribeUrl) async {
|
Future<List<String>> _fetchTrackers(String subscribeUrl) async {
|
||||||
final resp = await proxyRequest(subscribeUrl);
|
final resp = await LibgopeedBoot.instance.proxyRequest(subscribeUrl);
|
||||||
if (resp.statusCode != 200) {
|
if (resp.statusCode != 200) {
|
||||||
throw Exception(
|
throw Exception(
|
||||||
'Failed to get trackers, status code: ${resp.statusCode}');
|
'Failed to get trackers, status code: ${resp.statusCode}');
|
||||||
@@ -468,12 +430,4 @@ class AppController extends GetxController with WindowListener, TrayListener {
|
|||||||
args: ['--${Args.flagHidden}']);
|
args: ['--${Args.flagHidden}']);
|
||||||
autoStartup.value = await launchAtStartup.isEnabled();
|
autoStartup.value = await launchAtStartup.isEnabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> saveConfig() async {
|
|
||||||
Database.instance.saveStartConfig(StartConfigEntity(
|
|
||||||
network: startConfig.value.network,
|
|
||||||
address: startConfig.value.address,
|
|
||||||
apiToken: startConfig.value.apiToken));
|
|
||||||
await putConfig(downloaderConfig.value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -7,7 +7,7 @@ import 'package:get/get.dart';
|
|||||||
import 'package:path/path.dart' as path;
|
import 'package:path/path.dart' as path;
|
||||||
import 'package:rounded_loading_button_plus/rounded_loading_button.dart';
|
import 'package:rounded_loading_button_plus/rounded_loading_button.dart';
|
||||||
|
|
||||||
import '../../../../api/api.dart';
|
import '../../../../api/libgopeed_boot.dart';
|
||||||
import '../../../../api/model/create_task.dart';
|
import '../../../../api/model/create_task.dart';
|
||||||
import '../../../../api/model/options.dart';
|
import '../../../../api/model/options.dart';
|
||||||
import '../../../../api/model/request.dart';
|
import '../../../../api/model/request.dart';
|
||||||
@@ -594,7 +594,7 @@ class CreateView extends GetView<CreateController> {
|
|||||||
final isDirect = controller.directDownload.value || isMultiLine;
|
final isDirect = controller.directDownload.value || isMultiLine;
|
||||||
if (isDirect) {
|
if (isDirect) {
|
||||||
await Future.wait(urls.map((url) {
|
await Future.wait(urls.map((url) {
|
||||||
return createTask(CreateTask(
|
return LibgopeedBoot.instance.createTask(CreateTask(
|
||||||
req: Request(
|
req: Request(
|
||||||
url: url,
|
url: url,
|
||||||
extra: parseReqExtra(url),
|
extra: parseReqExtra(url),
|
||||||
@@ -610,7 +610,7 @@ class CreateView extends GetView<CreateController> {
|
|||||||
}));
|
}));
|
||||||
Get.rootDelegate.offNamed(Routes.TASK);
|
Get.rootDelegate.offNamed(Routes.TASK);
|
||||||
} else {
|
} else {
|
||||||
final rr = await resolve(Request(
|
final rr = await LibgopeedBoot.instance.resolve(Request(
|
||||||
url: submitUrl,
|
url: submitUrl,
|
||||||
extra: parseReqExtra(_urlController.text),
|
extra: parseReqExtra(_urlController.text),
|
||||||
proxy: parseProxy(),
|
proxy: parseProxy(),
|
||||||
@@ -741,23 +741,28 @@ class CreateView extends GetView<CreateController> {
|
|||||||
await Future.wait(
|
await Future.wait(
|
||||||
controller.selectedIndexes.map((index) {
|
controller.selectedIndexes.map((index) {
|
||||||
final file = rr.res.files[index];
|
final file = rr.res.files[index];
|
||||||
return createTask(CreateTask(
|
return LibgopeedBoot.instance.createTask(
|
||||||
req: file.req!..proxy = parseProxy(),
|
CreateTask(
|
||||||
opt: Options(
|
req: file.req!..proxy = parseProxy(),
|
||||||
name: file.name,
|
opt: Options(
|
||||||
path: path.join(_pathController.text,
|
name: file.name,
|
||||||
rr.res.name, file.path),
|
path: path.join(
|
||||||
selectFiles: [],
|
_pathController.text,
|
||||||
extra: optExtra)));
|
rr.res.name,
|
||||||
|
file.path),
|
||||||
|
selectFiles: [],
|
||||||
|
extra: optExtra)));
|
||||||
}));
|
}));
|
||||||
} else {
|
} else {
|
||||||
await createTask(CreateTask(
|
await LibgopeedBoot.instance.createTask(
|
||||||
rid: rr.id,
|
CreateTask(
|
||||||
opt: Options(
|
rid: rr.id,
|
||||||
name: _renameController.text,
|
opt: Options(
|
||||||
path: _pathController.text,
|
name: _renameController.text,
|
||||||
selectFiles: controller.selectedIndexes,
|
path: _pathController.text,
|
||||||
extra: optExtra)));
|
selectFiles:
|
||||||
|
controller.selectedIndexes,
|
||||||
|
extra: optExtra)));
|
||||||
}
|
}
|
||||||
Get.back();
|
Get.back();
|
||||||
Get.rootDelegate.offNamed(Routes.TASK);
|
Get.rootDelegate.offNamed(Routes.TASK);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:gopeed/api/api.dart';
|
|
||||||
|
|
||||||
|
import '../../../../api/libgopeed_boot.dart';
|
||||||
import '../../../../api/model/extension.dart';
|
import '../../../../api/model/extension.dart';
|
||||||
|
|
||||||
class ExtensionController extends GetxController {
|
class ExtensionController extends GetxController {
|
||||||
@@ -17,12 +17,13 @@ class ExtensionController extends GetxController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> load() async {
|
Future<void> load() async {
|
||||||
extensions.value = await getExtensions();
|
extensions.value = await LibgopeedBoot.instance.getExtensions();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> checkUpdate() async {
|
Future<void> checkUpdate() async {
|
||||||
for (final ext in extensions) {
|
for (final ext in extensions) {
|
||||||
final resp = await upgradeCheckExtension(ext.identity);
|
final resp =
|
||||||
|
await LibgopeedBoot.instance.upgradeCheckExtension(ext.identity);
|
||||||
if (resp.newVersion.isNotEmpty) {
|
if (resp.newVersion.isNotEmpty) {
|
||||||
updateFlags[ext.identity] = resp.newVersion;
|
updateFlags[ext.identity] = resp.newVersion;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ import 'package:path/path.dart' as path;
|
|||||||
import 'package:rounded_loading_button_plus/rounded_loading_button.dart';
|
import 'package:rounded_loading_button_plus/rounded_loading_button.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
import '../../../../api/api.dart';
|
import '../../../../api/entry/libgopeed_boot_web.dart';
|
||||||
|
import '../../../../api/libgopeed_boot.dart';
|
||||||
import '../../../../api/model/extension.dart';
|
import '../../../../api/model/extension.dart';
|
||||||
import '../../../../api/model/install_extension.dart';
|
import '../../../../api/model/install_extension.dart';
|
||||||
import '../../../../api/model/switch_extension.dart';
|
import '../../../../api/model/switch_extension.dart';
|
||||||
@@ -78,8 +79,9 @@ class ExtensionView extends GetView<ExtensionController> {
|
|||||||
}
|
}
|
||||||
_installBtnController.start();
|
_installBtnController.start();
|
||||||
try {
|
try {
|
||||||
await installExtension(InstallExtension(
|
await LibgopeedBoot.instance.installExtension(
|
||||||
url: _installUrlController.text));
|
InstallExtension(
|
||||||
|
url: _installUrlController.text));
|
||||||
Get.snackbar('tip'.tr, 'extensionInstallSuccess'.tr);
|
Get.snackbar('tip'.tr, 'extensionInstallSuccess'.tr);
|
||||||
await controller.load();
|
await controller.load();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -98,7 +100,7 @@ class ExtensionView extends GetView<ExtensionController> {
|
|||||||
if (dir != null) {
|
if (dir != null) {
|
||||||
MacSecureUtil.saveBookmark(dir);
|
MacSecureUtil.saveBookmark(dir);
|
||||||
try {
|
try {
|
||||||
await installExtension(
|
await LibgopeedBoot.instance.installExtension(
|
||||||
InstallExtension(devMode: true, url: dir));
|
InstallExtension(devMode: true, url: dir));
|
||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'tip'.tr, 'extensionInstallSuccess'.tr);
|
'tip'.tr, 'extensionInstallSuccess'.tr);
|
||||||
@@ -133,8 +135,10 @@ class ExtensionView extends GetView<ExtensionController> {
|
|||||||
)
|
)
|
||||||
: Util.isWeb()
|
: Util.isWeb()
|
||||||
? Image.network(
|
? Image.network(
|
||||||
join(
|
(LibgopeedBoot.instance
|
||||||
'/fs/extensions/${extension.identity}/${extension.icon}'),
|
as LibgopeedBootWeb)
|
||||||
|
.join(
|
||||||
|
'/fs/extensions/${extension.identity}/${extension.icon}'),
|
||||||
width: 48,
|
width: 48,
|
||||||
height: 48,
|
height: 48,
|
||||||
)
|
)
|
||||||
@@ -155,9 +159,9 @@ class ExtensionView extends GetView<ExtensionController> {
|
|||||||
value: !extension.disabled,
|
value: !extension.disabled,
|
||||||
onChanged: (value) async {
|
onChanged: (value) async {
|
||||||
try {
|
try {
|
||||||
await switchExtension(
|
await LibgopeedBoot.instance
|
||||||
extension.identity,
|
.switchExtension(extension.identity,
|
||||||
SwitchExtension(status: value));
|
SwitchExtension(status: value));
|
||||||
await controller.load();
|
await controller.load();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
showErrorMessage(e);
|
showErrorMessage(e);
|
||||||
@@ -354,10 +358,11 @@ class ExtensionView extends GetView<ExtensionController> {
|
|||||||
try {
|
try {
|
||||||
confrimController.start();
|
confrimController.start();
|
||||||
if (formKey.currentState?.saveAndValidate() == true) {
|
if (formKey.currentState?.saveAndValidate() == true) {
|
||||||
await updateExtensionSettings(
|
await LibgopeedBoot.instance
|
||||||
extension.identity,
|
.updateExtensionSettings(
|
||||||
UpdateExtensionSettings(
|
extension.identity,
|
||||||
settings: formKey.currentState!.value));
|
UpdateExtensionSettings(
|
||||||
|
settings: formKey.currentState!.value));
|
||||||
await controller.load();
|
await controller.load();
|
||||||
Get.back();
|
Get.back();
|
||||||
}
|
}
|
||||||
@@ -454,7 +459,8 @@ class ExtensionView extends GetView<ExtensionController> {
|
|||||||
),
|
),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
try {
|
try {
|
||||||
await deleteExtension(extension.identity);
|
await LibgopeedBoot.instance
|
||||||
|
.deleteExtension(extension.identity);
|
||||||
await controller.load();
|
await controller.load();
|
||||||
Get.back();
|
Get.back();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -487,18 +493,19 @@ class ExtensionView extends GetView<ExtensionController> {
|
|||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
confrimController.start();
|
confrimController.start();
|
||||||
try {
|
try {
|
||||||
await updateExtension(extension.identity);
|
await LibgopeedBoot.instance
|
||||||
|
.upgradeExtension(extension.identity);
|
||||||
await controller.load();
|
await controller.load();
|
||||||
controller.updateFlags.remove(extension.identity);
|
controller.updateFlags.remove(extension.identity);
|
||||||
Get.back();
|
Get.back();
|
||||||
showMessage('tip'.tr, 'extensionUpdateSuccess'.tr);
|
showMessage('tip'.tr, 'extensionUpgradeSuccess'.tr);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
showErrorMessage(e);
|
showErrorMessage(e);
|
||||||
} finally {
|
} finally {
|
||||||
confrimController.stop();
|
confrimController.stop();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: Text('newVersionUpdate'.tr),
|
child: Text('newVersionUpgrade'.tr),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
));
|
));
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:gopeed/api/api.dart';
|
|
||||||
|
import '../../../../api/libgopeed_boot.dart';
|
||||||
|
|
||||||
class SettingController extends GetxController {
|
class SettingController extends GetxController {
|
||||||
final tapStatues = <String, bool>{}.obs;
|
final tapStatues = <String, bool>{}.obs;
|
||||||
@@ -28,12 +29,13 @@ class SettingController extends GetxController {
|
|||||||
void fetchLatestVersion() async {
|
void fetchLatestVersion() async {
|
||||||
String? releaseDataStr;
|
String? releaseDataStr;
|
||||||
try {
|
try {
|
||||||
releaseDataStr = (await proxyRequest(
|
releaseDataStr = (await LibgopeedBoot.instance.proxyRequest(
|
||||||
"https://api.github.com/repos/GopeedLab/gopeed/releases/latest"))
|
"https://api.github.com/repos/GopeedLab/gopeed/releases/latest"))
|
||||||
.data;
|
.data;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
releaseDataStr =
|
releaseDataStr = (await LibgopeedBoot.instance
|
||||||
(await proxyRequest("https://gopeed.com/api/release")).data;
|
.proxyRequest("https://gopeed.com/api/release"))
|
||||||
|
.data;
|
||||||
}
|
}
|
||||||
if (releaseDataStr == null) {
|
if (releaseDataStr == null) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import 'package:intl/intl.dart';
|
|||||||
import 'package:launch_at_startup/launch_at_startup.dart';
|
import 'package:launch_at_startup/launch_at_startup.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
|
import '../../../../api/libgopeed_boot.dart';
|
||||||
import '../../../../api/model/downloader_config.dart';
|
import '../../../../api/model/downloader_config.dart';
|
||||||
import '../../../../i18n/message.dart';
|
import '../../../../i18n/message.dart';
|
||||||
import '../../../../util/input_formatter.dart';
|
import '../../../../util/input_formatter.dart';
|
||||||
@@ -35,7 +36,6 @@ class SettingView extends GetView<SettingController> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final appController = Get.find<AppController>();
|
final appController = Get.find<AppController>();
|
||||||
final downloaderCfg = appController.downloaderConfig;
|
final downloaderCfg = appController.downloaderConfig;
|
||||||
final startCfg = appController.startConfig;
|
|
||||||
|
|
||||||
Timer? timer;
|
Timer? timer;
|
||||||
Future<bool> debounceSave(
|
Future<bool> debounceSave(
|
||||||
@@ -51,8 +51,8 @@ class SettingView extends GetView<SettingController> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
appController
|
LibgopeedBoot.instance
|
||||||
.saveConfig()
|
.putConfig(downloaderCfg.value)
|
||||||
.then((_) => completer.complete(true))
|
.then((_) => completer.complete(true))
|
||||||
.onError(completer.completeError);
|
.onError(completer.completeError);
|
||||||
if (needRestart) {
|
if (needRestart) {
|
||||||
@@ -530,7 +530,7 @@ class SettingView extends GetView<SettingController> {
|
|||||||
launchUrl(Uri.parse('https://gopeed.com'),
|
launchUrl(Uri.parse('https://gopeed.com'),
|
||||||
mode: LaunchMode.externalApplication);
|
mode: LaunchMode.externalApplication);
|
||||||
},
|
},
|
||||||
child: Text('newVersionUpdate'.tr),
|
child: Text('newVersionUpgrade'.tr),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
));
|
));
|
||||||
@@ -637,7 +637,7 @@ class SettingView extends GetView<SettingController> {
|
|||||||
final portController = TextEditingController(text: port);
|
final portController = TextEditingController(text: port);
|
||||||
updateAddress() async {
|
updateAddress() async {
|
||||||
final newAddress = '${ipController.text}:${portController.text}';
|
final newAddress = '${ipController.text}:${portController.text}';
|
||||||
if (newAddress != startCfg.value.address) {
|
if (newAddress != proxy.host) {
|
||||||
proxy.host = newAddress;
|
proxy.host = newAddress;
|
||||||
|
|
||||||
await debounceSave();
|
await debounceSave();
|
||||||
@@ -718,152 +718,152 @@ class SettingView extends GetView<SettingController> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// advanced config API items start
|
// advanced config API items start
|
||||||
final buildApiProtocol = _buildConfigItem(
|
// final buildApiProtocol = _buildConfigItem(
|
||||||
'protocol',
|
// 'protocol',
|
||||||
() => startCfg.value.network == 'tcp'
|
// () => startCfg.value.host == 'tcp'
|
||||||
? 'TCP ${startCfg.value.address}'
|
// ? 'TCP ${startCfg.value.address}'
|
||||||
: 'Unix',
|
// : 'Unix',
|
||||||
(Key key) {
|
// (Key key) {
|
||||||
final items = <Widget>[
|
// final items = <Widget>[
|
||||||
SizedBox(
|
// SizedBox(
|
||||||
width: 80,
|
// width: 80,
|
||||||
child: DropdownButtonFormField<String>(
|
// child: DropdownButtonFormField<String>(
|
||||||
value: startCfg.value.network,
|
// value: startCfg.value.host,
|
||||||
onChanged: Util.isDesktop()
|
// onChanged: Util.isDesktop()
|
||||||
? (value) async {
|
// ? (value) async {
|
||||||
startCfg.update((val) {
|
// startCfg.update((val) {
|
||||||
val!.network = value!;
|
// val!.host = value!;
|
||||||
});
|
// });
|
||||||
|
|
||||||
await debounceSave(needRestart: true);
|
// await debounceSave(needRestart: true);
|
||||||
}
|
// }
|
||||||
: null,
|
// : null,
|
||||||
items: [
|
// items: [
|
||||||
const DropdownMenuItem<String>(
|
// const DropdownMenuItem<String>(
|
||||||
value: 'tcp',
|
// value: 'tcp',
|
||||||
child: Text('TCP'),
|
// child: Text('TCP'),
|
||||||
),
|
// ),
|
||||||
Util.supportUnixSocket()
|
// Util.supportUnixSocket()
|
||||||
? const DropdownMenuItem<String>(
|
// ? const DropdownMenuItem<String>(
|
||||||
value: 'unix',
|
// value: 'unix',
|
||||||
child: Text('Unix'),
|
// child: Text('Unix'),
|
||||||
)
|
// )
|
||||||
: null,
|
// : null,
|
||||||
].where((e) => e != null).map((e) => e!).toList(),
|
// ].where((e) => e != null).map((e) => e!).toList(),
|
||||||
),
|
// ),
|
||||||
)
|
// )
|
||||||
];
|
// ];
|
||||||
if (Util.isDesktop() && startCfg.value.network == 'tcp') {
|
// if (Util.isDesktop() && startCfg.value.host == 'tcp') {
|
||||||
final arr = startCfg.value.address.split(':');
|
// final arr = startCfg.value.address.split(':');
|
||||||
var ip = '127.0.0.1';
|
// var ip = '127.0.0.1';
|
||||||
var port = '0';
|
// var port = '0';
|
||||||
if (arr.length > 1) {
|
// if (arr.length > 1) {
|
||||||
ip = arr[0];
|
// ip = arr[0];
|
||||||
port = arr[1];
|
// port = arr[1];
|
||||||
}
|
// }
|
||||||
|
|
||||||
final ipController = TextEditingController(text: ip);
|
// final ipController = TextEditingController(text: ip);
|
||||||
final portController = TextEditingController(text: port);
|
// final portController = TextEditingController(text: port);
|
||||||
updateAddress() async {
|
// updateAddress() async {
|
||||||
if (ipController.text.isEmpty || portController.text.isEmpty) {
|
// if (ipController.text.isEmpty || portController.text.isEmpty) {
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
final newAddress = '${ipController.text}:${portController.text}';
|
// final newAddress = '${ipController.text}:${portController.text}';
|
||||||
if (newAddress != startCfg.value.address) {
|
// if (newAddress != startCfg.value.address) {
|
||||||
startCfg.value.address = newAddress;
|
// startCfg.value.address = newAddress;
|
||||||
|
|
||||||
final saved = await debounceSave(
|
// final saved = await debounceSave(
|
||||||
check: () async {
|
// check: () async {
|
||||||
// Check if address already in use
|
// // Check if address already in use
|
||||||
final configIp = ipController.text;
|
// final configIp = ipController.text;
|
||||||
final configPort = int.parse(portController.text);
|
// final configPort = int.parse(portController.text);
|
||||||
if (configPort == 0) {
|
// if (configPort == 0) {
|
||||||
return '';
|
// return '';
|
||||||
}
|
// }
|
||||||
try {
|
// try {
|
||||||
final socket = await Socket.connect(configIp, configPort,
|
// final socket = await Socket.connect(configIp, configPort,
|
||||||
timeout: const Duration(seconds: 3));
|
// timeout: const Duration(seconds: 3));
|
||||||
socket.close();
|
// socket.close();
|
||||||
return 'portInUse'
|
// return 'portInUse'
|
||||||
.trParams({'port': configPort.toString()});
|
// .trParams({'port': configPort.toString()});
|
||||||
} catch (e) {
|
// } catch (e) {
|
||||||
return '';
|
// return '';
|
||||||
}
|
// }
|
||||||
},
|
// },
|
||||||
needRestart: true);
|
// needRestart: true);
|
||||||
|
|
||||||
// If save failed, restore the old address
|
// // If save failed, restore the old address
|
||||||
if (!saved) {
|
// if (!saved) {
|
||||||
final oldAddress =
|
// // final oldAddress =
|
||||||
(await appController.loadStartConfig()).address;
|
// // (await appController.loadStartConfig()).address;
|
||||||
startCfg.update((val) async {
|
// // startCfg.update((val) async {
|
||||||
val!.address = oldAddress;
|
// // val!.address = oldAddress;
|
||||||
});
|
// // });
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
ipController.addListener(updateAddress);
|
// ipController.addListener(updateAddress);
|
||||||
portController.addListener(updateAddress);
|
// portController.addListener(updateAddress);
|
||||||
items.addAll([
|
// items.addAll([
|
||||||
const Padding(padding: EdgeInsets.only(left: 20)),
|
// const Padding(padding: EdgeInsets.only(left: 20)),
|
||||||
Flexible(
|
// Flexible(
|
||||||
child: TextFormField(
|
// child: TextFormField(
|
||||||
controller: ipController,
|
// controller: ipController,
|
||||||
decoration: const InputDecoration(
|
// decoration: const InputDecoration(
|
||||||
labelText: 'IP',
|
// labelText: 'IP',
|
||||||
contentPadding: EdgeInsets.all(0.0),
|
// contentPadding: EdgeInsets.all(0.0),
|
||||||
),
|
// ),
|
||||||
keyboardType: TextInputType.number,
|
// keyboardType: TextInputType.number,
|
||||||
inputFormatters: [
|
// inputFormatters: [
|
||||||
FilteringTextInputFormatter.allow(RegExp('[0-9.]')),
|
// FilteringTextInputFormatter.allow(RegExp('[0-9.]')),
|
||||||
],
|
// ],
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
const Padding(padding: EdgeInsets.only(left: 10)),
|
// const Padding(padding: EdgeInsets.only(left: 10)),
|
||||||
Flexible(
|
// Flexible(
|
||||||
child: TextFormField(
|
// child: TextFormField(
|
||||||
controller: portController,
|
// controller: portController,
|
||||||
decoration: InputDecoration(
|
// decoration: InputDecoration(
|
||||||
labelText: 'port'.tr,
|
// labelText: 'port'.tr,
|
||||||
contentPadding: const EdgeInsets.all(0.0),
|
// contentPadding: const EdgeInsets.all(0.0),
|
||||||
),
|
// ),
|
||||||
keyboardType: TextInputType.number,
|
// keyboardType: TextInputType.number,
|
||||||
inputFormatters: [
|
// inputFormatters: [
|
||||||
FilteringTextInputFormatter.digitsOnly,
|
// FilteringTextInputFormatter.digitsOnly,
|
||||||
NumericalRangeFormatter(min: 0, max: 65535),
|
// NumericalRangeFormatter(min: 0, max: 65535),
|
||||||
],
|
// ],
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
]);
|
// ]);
|
||||||
}
|
// }
|
||||||
|
|
||||||
return Form(
|
// return Form(
|
||||||
child: Row(
|
// child: Row(
|
||||||
children: items,
|
// children: items,
|
||||||
),
|
// ),
|
||||||
);
|
// );
|
||||||
},
|
// },
|
||||||
);
|
// );
|
||||||
final buildApiToken = _buildConfigItem('apiToken',
|
// final buildApiToken = _buildConfigItem('apiToken',
|
||||||
() => startCfg.value.apiToken.isEmpty ? 'notSet'.tr : 'set'.tr,
|
// () => startCfg.value.apiToken.isEmpty ? 'notSet'.tr : 'set'.tr,
|
||||||
(Key key) {
|
// (Key key) {
|
||||||
final apiTokenController =
|
// final apiTokenController =
|
||||||
TextEditingController(text: startCfg.value.apiToken);
|
// TextEditingController(text: startCfg.value.apiToken);
|
||||||
apiTokenController.addListener(() async {
|
// apiTokenController.addListener(() async {
|
||||||
if (apiTokenController.text != startCfg.value.apiToken) {
|
// if (apiTokenController.text != startCfg.value.apiToken) {
|
||||||
startCfg.value.apiToken = apiTokenController.text;
|
// startCfg.value.apiToken = apiTokenController.text;
|
||||||
|
|
||||||
await debounceSave(needRestart: true);
|
// await debounceSave(needRestart: true);
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
return TextField(
|
// return TextField(
|
||||||
key: key,
|
// key: key,
|
||||||
obscureText: true,
|
// obscureText: true,
|
||||||
controller: apiTokenController,
|
// controller: apiTokenController,
|
||||||
focusNode: FocusNode(),
|
// focusNode: FocusNode(),
|
||||||
);
|
// );
|
||||||
});
|
// });
|
||||||
|
|
||||||
// advanced config log items start
|
// advanced config log items start
|
||||||
buildLogsDir() {
|
buildLogsDir() {
|
||||||
@@ -993,16 +993,16 @@ class SettingView extends GetView<SettingController> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
children: _addDivider([buildProxy()]),
|
children: _addDivider([buildProxy()]),
|
||||||
)),
|
)),
|
||||||
const Text('API'),
|
// const Text('API'),
|
||||||
Card(
|
// Card(
|
||||||
child: Column(
|
// child: Column(
|
||||||
children: _addDivider([
|
// children: _addDivider([
|
||||||
buildApiProtocol(),
|
// buildApiProtocol(),
|
||||||
Util.isDesktop() && startCfg.value.network == 'tcp'
|
// Util.isDesktop() && startCfg.value.host == 'tcp'
|
||||||
? buildApiToken()
|
// ? buildApiToken()
|
||||||
: null,
|
// : null,
|
||||||
]),
|
// ]),
|
||||||
)),
|
// )),
|
||||||
Text('developer'.tr),
|
Text('developer'.tr),
|
||||||
Card(
|
Card(
|
||||||
child: Column(
|
child: Column(
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import '../../../../api/api.dart';
|
import '../../../../api/libgopeed_boot.dart';
|
||||||
import '../../../../api/model/resource.dart';
|
import '../../../../api/model/resource.dart';
|
||||||
import '../../../../api/model/task.dart';
|
import '../../../../api/model/task.dart';
|
||||||
|
|
||||||
@@ -31,7 +31,7 @@ class TaskFilesController extends GetxController {
|
|||||||
super.onInit();
|
super.onInit();
|
||||||
|
|
||||||
final taskId = Get.rootDelegate.parameters['id'];
|
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);
|
task.value = tasks.firstWhere((element) => element.id == taskId);
|
||||||
parseDirMap(task.value!.meta.res!.files);
|
parseDirMap(task.value!.meta.res!.files);
|
||||||
toDir("/");
|
toDir("/");
|
||||||
|
|||||||
@@ -2,8 +2,9 @@ import 'dart:async';
|
|||||||
|
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
import '../../../../api/api.dart';
|
import '../../../../api/libgopeed_boot.dart';
|
||||||
import '../../../../api/model/task.dart';
|
import '../../../../api/model/task.dart';
|
||||||
|
import '../../../../api/model/task_filter.dart';
|
||||||
|
|
||||||
abstract class TaskListController extends GetxController {
|
abstract class TaskListController extends GetxController {
|
||||||
List<Status> statuses;
|
List<Status> statuses;
|
||||||
@@ -44,7 +45,8 @@ abstract class TaskListController extends GetxController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getTasksState() async {
|
getTasksState() async {
|
||||||
final tasks = await getTasks(statuses);
|
final tasks =
|
||||||
|
await LibgopeedBoot.instance.getTasks(TaskFilter(status: statuses));
|
||||||
// sort tasks by create time
|
// sort tasks by create time
|
||||||
tasks.sort(compare);
|
tasks.sort(compare);
|
||||||
this.tasks.value = tasks;
|
this.tasks.value = tasks;
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ import 'package:path/path.dart';
|
|||||||
import 'package:share_plus/share_plus.dart';
|
import 'package:share_plus/share_plus.dart';
|
||||||
import 'package:url_launcher/url_launcher.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/browser_download/browser_download.dart';
|
||||||
import '../../../../util/file_icon.dart';
|
import '../../../../util/file_icon.dart';
|
||||||
import '../../../../util/icons.dart';
|
import '../../../../util/icons.dart';
|
||||||
@@ -84,8 +85,10 @@ class TaskFilesView extends GetView<TaskFilesController> {
|
|||||||
mainAxisAlignment: MainAxisAlignment.end,
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
children: Util.isWeb()
|
children: Util.isWeb()
|
||||||
? () {
|
? () {
|
||||||
final accessUrl = api.join(
|
final accessUrl = (LibgopeedBoot
|
||||||
"/fs/tasks/${controller.task.value!.id}$fileRelativePath");
|
.instance as LibgopeedBootWeb)
|
||||||
|
.join(
|
||||||
|
"/fs/tasks/${controller.task.value!.id}$fileRelativePath");
|
||||||
return [
|
return [
|
||||||
IconButton(
|
IconButton(
|
||||||
icon:
|
icon:
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
|
||||||
import '../../api/api.dart';
|
import '../../api/libgopeed_boot.dart';
|
||||||
import '../../api/model/task.dart';
|
import '../../api/model/task.dart';
|
||||||
import '../../util/file_icon.dart';
|
import '../../util/file_icon.dart';
|
||||||
import '../../util/icons.dart';
|
import '../../util/icons.dart';
|
||||||
@@ -95,8 +95,9 @@ class BuildTaskListView extends GetView {
|
|||||||
try {
|
try {
|
||||||
final force = !appController
|
final force = !appController
|
||||||
.downloaderConfig.value.extra.lastDeleteTaskKeep;
|
.downloaderConfig.value.extra.lastDeleteTaskKeep;
|
||||||
await appController.saveConfig();
|
LibgopeedBoot.instance
|
||||||
await deleteTask(id, force);
|
.putConfig(appController.downloaderConfig.value);
|
||||||
|
await LibgopeedBoot.instance.deleteTask(id, force);
|
||||||
Get.back();
|
Get.back();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
showErrorMessage(e);
|
showErrorMessage(e);
|
||||||
@@ -122,7 +123,7 @@ class BuildTaskListView extends GetView {
|
|||||||
icon: const Icon(Icons.pause),
|
icon: const Icon(Icons.pause),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
try {
|
try {
|
||||||
await pauseTask(task.id);
|
await LibgopeedBoot.instance.pauseTask(task.id);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
showErrorMessage(e);
|
showErrorMessage(e);
|
||||||
}
|
}
|
||||||
@@ -133,7 +134,7 @@ class BuildTaskListView extends GetView {
|
|||||||
icon: const Icon(Icons.play_arrow),
|
icon: const Icon(Icons.play_arrow),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
try {
|
try {
|
||||||
await continueTask(task.id);
|
await LibgopeedBoot.instance.continueTask(task.id);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
showErrorMessage(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',
|
'deleteTaskTip': 'Keep downloaded files',
|
||||||
'delete': 'Delete',
|
'delete': 'Delete',
|
||||||
'newVersionTitle': 'Discover new version @version',
|
'newVersionTitle': 'Discover new version @version',
|
||||||
'newVersionUpdate': 'Update Now',
|
'newVersionUpgrade': 'Update Now',
|
||||||
'newVersionLater': 'Later',
|
'newVersionLater': 'Later',
|
||||||
'extensions': 'Extensions',
|
'extensions': 'Extensions',
|
||||||
'extensionInstallUrl': 'Install URL',
|
'extensionInstallUrl': 'Install URL',
|
||||||
'extensionInstallSuccess': 'Installed successfully',
|
'extensionInstallSuccess': 'Installed successfully',
|
||||||
'extensionUpdateSuccess': 'Updated successfully',
|
'extensionUpgradeSuccess': 'Updated successfully',
|
||||||
'extensionDelete': 'Delete Extension',
|
'extensionDelete': 'Delete Extension',
|
||||||
'extensionAlreadyLatest': 'It\'s already the latest version',
|
'extensionAlreadyLatest': 'It\'s already the latest version',
|
||||||
'extensionFind': 'Find Extensions',
|
'extensionFind': 'Find Extensions',
|
||||||
|
|||||||
@@ -21,9 +21,11 @@ const esES = {
|
|||||||
'downloadLinkValid': 'Por favor, introduce el enlace de descarga',
|
'downloadLinkValid': 'Por favor, introduce el enlace de descarga',
|
||||||
'downloadLinkHit':
|
'downloadLinkHit':
|
||||||
'Por favor, introduce el enlace de descarga, se admite HTTP/HTTPS/MAGNET@append',
|
'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',
|
'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',
|
'noStoragePermission': 'Se requiere permiso de almacenamiento',
|
||||||
'selectFile': 'Seleccionar Archivo',
|
'selectFile': 'Seleccionar Archivo',
|
||||||
'rename': 'Renombrar',
|
'rename': 'Renombrar',
|
||||||
@@ -43,7 +45,8 @@ const esES = {
|
|||||||
'updateDaily': 'Actualizar diariamente',
|
'updateDaily': 'Actualizar diariamente',
|
||||||
'lastUpdate': 'Última actualización: @time',
|
'lastUpdate': 'Última actualización: @time',
|
||||||
'addTracker': 'Añadir Tracker',
|
'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',
|
'ui': 'Interfaz',
|
||||||
'theme': 'Tema',
|
'theme': 'Tema',
|
||||||
'themeSystem': 'Sistema',
|
'themeSystem': 'Sistema',
|
||||||
@@ -67,12 +70,12 @@ const esES = {
|
|||||||
'deleteTaskTip': 'Mantener archivos descargados',
|
'deleteTaskTip': 'Mantener archivos descargados',
|
||||||
'delete': 'Eliminar',
|
'delete': 'Eliminar',
|
||||||
'newVersionTitle': 'Nueva versión @version disponible',
|
'newVersionTitle': 'Nueva versión @version disponible',
|
||||||
'newVersionUpdate': 'Actualizar Ahora',
|
'newVersionUpgrade': 'Actualizar Ahora',
|
||||||
'newVersionLater': 'Más tarde',
|
'newVersionLater': 'Más tarde',
|
||||||
'extensions': 'Extensiones',
|
'extensions': 'Extensiones',
|
||||||
'extensionInstallUrl': 'URL de instalación',
|
'extensionInstallUrl': 'URL de instalación',
|
||||||
'extensionInstallSuccess': 'Instalado con éxito',
|
'extensionInstallSuccess': 'Instalado con éxito',
|
||||||
'extensionUpdateSuccess': 'Actualizado con éxito',
|
'extensionUpgradeSuccess': 'Actualizado con éxito',
|
||||||
'extensionDelete': 'Eliminar Extensión',
|
'extensionDelete': 'Eliminar Extensión',
|
||||||
'extensionAlreadyLatest': 'Ya es la versión más reciente',
|
'extensionAlreadyLatest': 'Ya es la versión más reciente',
|
||||||
'extensionFind': 'Buscar Extensiones',
|
'extensionFind': 'Buscar Extensiones',
|
||||||
@@ -105,4 +108,4 @@ const esES = {
|
|||||||
'downloadPath': 'Ruta de Descarga',
|
'downloadPath': 'Ruta de Descarga',
|
||||||
'skipVerifyCert': 'Omitir Verificación de Certificado',
|
'skipVerifyCert': 'Omitir Verificación de Certificado',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -55,7 +55,7 @@ const faIR = {
|
|||||||
'deleteTaskTip': 'فایل های دانلود شده را نگه دارد',
|
'deleteTaskTip': 'فایل های دانلود شده را نگه دارد',
|
||||||
'delete': 'پاک کردن',
|
'delete': 'پاک کردن',
|
||||||
'newVersionTitle': '@version عنوان: کشف نسخه جدید',
|
'newVersionTitle': '@version عنوان: کشف نسخه جدید',
|
||||||
'newVersionUpdate': 'بروزرسانی',
|
'newVersionUpgrade': 'بروزرسانی',
|
||||||
'newVersionLater': 'بعداً',
|
'newVersionLater': 'بعداً',
|
||||||
'thanks': 'تشکر',
|
'thanks': 'تشکر',
|
||||||
'thanksDesc':
|
'thanksDesc':
|
||||||
|
|||||||
@@ -19,10 +19,12 @@ const frFR = {
|
|||||||
'advancedOptions': 'Options avancées',
|
'advancedOptions': 'Options avancées',
|
||||||
'downloadLink': 'Lien de téléchargement',
|
'downloadLink': 'Lien de téléchargement',
|
||||||
'downloadLinkValid': 'Veuillez entrer le 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',
|
'downloadLinkHitDesktop': ', ou glissez directement le fichier torrent ici',
|
||||||
'download': 'Télécharger',
|
'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',
|
'noStoragePermission': 'Permission de stockage requise',
|
||||||
'selectFile': 'Sélectionner un fichier',
|
'selectFile': 'Sélectionner un fichier',
|
||||||
'rename': 'Renommer',
|
'rename': 'Renommer',
|
||||||
@@ -32,16 +34,19 @@ const frFR = {
|
|||||||
'downloadDir': 'Répertoire de téléchargement',
|
'downloadDir': 'Répertoire de téléchargement',
|
||||||
'downloadDirValid': 'Veuillez sélectionner le répertoire de téléchargement',
|
'downloadDirValid': 'Veuillez sélectionner le répertoire de téléchargement',
|
||||||
'connections': 'Connexions',
|
'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',
|
'maxRunning': 'Tâches en cours maximum',
|
||||||
'items': '@count éléments',
|
'items': '@count éléments',
|
||||||
'subscribeTracker': 'S\'abonner au tracker',
|
'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',
|
'update': 'Mettre à jour',
|
||||||
'updateDaily': 'Mettre à jour quotidiennement',
|
'updateDaily': 'Mettre à jour quotidiennement',
|
||||||
'lastUpdate': 'Dernière mise à jour : @time',
|
'lastUpdate': 'Dernière mise à jour : @time',
|
||||||
'addTracker': 'Ajouter un tracker',
|
'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',
|
'ui': 'Interface utilisateur',
|
||||||
'theme': 'Thème',
|
'theme': 'Thème',
|
||||||
'themeSystem': 'Système',
|
'themeSystem': 'Système',
|
||||||
@@ -65,12 +70,12 @@ const frFR = {
|
|||||||
'deleteTaskTip': 'Conserver les fichiers téléchargés',
|
'deleteTaskTip': 'Conserver les fichiers téléchargés',
|
||||||
'delete': 'Supprimer',
|
'delete': 'Supprimer',
|
||||||
'newVersionTitle': 'Nouvelle version @version disponible',
|
'newVersionTitle': 'Nouvelle version @version disponible',
|
||||||
'newVersionUpdate': 'Mettre à jour maintenant',
|
'newVersionUpgrade': 'Mettre à jour maintenant',
|
||||||
'newVersionLater': 'Plus tard',
|
'newVersionLater': 'Plus tard',
|
||||||
'extensions': 'Extensions',
|
'extensions': 'Extensions',
|
||||||
'extensionInstallUrl': 'URL d\'installation',
|
'extensionInstallUrl': 'URL d\'installation',
|
||||||
'extensionInstallSuccess': 'Installé avec succès',
|
'extensionInstallSuccess': 'Installé avec succès',
|
||||||
'extensionUpdateSuccess': 'Mis à jour avec succès',
|
'extensionUpgradeSuccess': 'Mis à jour avec succès',
|
||||||
'extensionDelete': 'Supprimer l\'extension',
|
'extensionDelete': 'Supprimer l\'extension',
|
||||||
'extensionAlreadyLatest': 'C\'est déjà la dernière version',
|
'extensionAlreadyLatest': 'C\'est déjà la dernière version',
|
||||||
'extensionFind': 'Trouver des extensions',
|
'extensionFind': 'Trouver des extensions',
|
||||||
@@ -89,7 +94,8 @@ const frFR = {
|
|||||||
'username': 'Nom d\'utilisateur',
|
'username': 'Nom d\'utilisateur',
|
||||||
'password': 'Mot de passe',
|
'password': 'Mot de passe',
|
||||||
'thanks': 'Merci',
|
'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',
|
'browserExtension': 'Extension du navigateur',
|
||||||
'launchAtStartup': 'Lancer au démarrage',
|
'launchAtStartup': 'Lancer au démarrage',
|
||||||
'skipVerifyCert': 'Bypass Verifikasi Sertifikat',
|
'skipVerifyCert': 'Bypass Verifikasi Sertifikat',
|
||||||
|
|||||||
@@ -22,7 +22,8 @@ const idID = {
|
|||||||
'downloadLinkValid': 'Silakan masukkan tautan unduhan',
|
'downloadLinkValid': 'Silakan masukkan tautan unduhan',
|
||||||
'downloadLinkHit':
|
'downloadLinkHit':
|
||||||
'Tolong masukkan tautan unduhan, HTTP/HTTPS/MAGNET didukung@append',
|
'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',
|
'download': 'Download',
|
||||||
'noFileSelected': 'Silakan pilih setidaknya satu file untuk melanjutkan.',
|
'noFileSelected': 'Silakan pilih setidaknya satu file untuk melanjutkan.',
|
||||||
'noStoragePermission': 'Izin penyimpanan diperlukan',
|
'noStoragePermission': 'Izin penyimpanan diperlukan',
|
||||||
@@ -71,12 +72,12 @@ const idID = {
|
|||||||
'deleteTaskTip': 'Simpan file yang terunduh',
|
'deleteTaskTip': 'Simpan file yang terunduh',
|
||||||
'delete': 'Hapus',
|
'delete': 'Hapus',
|
||||||
'newVersionTitle': 'Temukan versi batu @version',
|
'newVersionTitle': 'Temukan versi batu @version',
|
||||||
'newVersionUpdate': 'Perbarui Sekarang',
|
'newVersionUpgrade': 'Perbarui Sekarang',
|
||||||
'newVersionLater': 'Nanti',
|
'newVersionLater': 'Nanti',
|
||||||
'extensions': 'Ekstensi',
|
'extensions': 'Ekstensi',
|
||||||
'extensionInstallUrl': 'URL Instal',
|
'extensionInstallUrl': 'URL Instal',
|
||||||
'extensionInstallSuccess': 'Diinstal dengan sukses',
|
'extensionInstallSuccess': 'Diinstal dengan sukses',
|
||||||
'extensionUpdateSuccess': 'Diperbaruhi dengan sukses',
|
'extensionUpgradeSuccess': 'Diperbaruhi dengan sukses',
|
||||||
'extensionDelete': 'Hapus Ekstensi',
|
'extensionDelete': 'Hapus Ekstensi',
|
||||||
'extensionAlreadyLatest': 'Ini sudah versi terbaru',
|
'extensionAlreadyLatest': 'Ini sudah versi terbaru',
|
||||||
'extensionFind': 'Temukan Ekstensi',
|
'extensionFind': 'Temukan Ekstensi',
|
||||||
|
|||||||
@@ -68,12 +68,12 @@ const itIT = {
|
|||||||
'deleteTaskTip': 'Conserva i file scaricati',
|
'deleteTaskTip': 'Conserva i file scaricati',
|
||||||
'delete': 'Elimina',
|
'delete': 'Elimina',
|
||||||
'newVersionTitle': 'Scopri la nuova versione @version',
|
'newVersionTitle': 'Scopri la nuova versione @version',
|
||||||
'newVersionUpdate': 'Aggiorna ora',
|
'newVersionUpgrade': 'Aggiorna ora',
|
||||||
'newVersionLater': 'Dopo',
|
'newVersionLater': 'Dopo',
|
||||||
'extensions': 'Estensioni',
|
'extensions': 'Estensioni',
|
||||||
'extensionInstallUrl': 'Installa URL',
|
'extensionInstallUrl': 'Installa URL',
|
||||||
'extensionInstallSuccess': 'Installato con successo',
|
'extensionInstallSuccess': 'Installato con successo',
|
||||||
'extensionUpdateSuccess': 'Aggiornato con successo',
|
'extensionUpgradeSuccess': 'Aggiornato con successo',
|
||||||
'extensionDelete': 'Elimina estensione',
|
'extensionDelete': 'Elimina estensione',
|
||||||
'extensionAlreadyLatest': "È già l'ultima versione",
|
'extensionAlreadyLatest': "È già l'ultima versione",
|
||||||
'extensionFind': 'Trova estensioni',
|
'extensionFind': 'Trova estensioni',
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ const jaJP = {
|
|||||||
'deleteTaskTip': 'ダウンロードしたファイルを保持',
|
'deleteTaskTip': 'ダウンロードしたファイルを保持',
|
||||||
'delete': '削除',
|
'delete': '削除',
|
||||||
'newVersionTitle': '新しいバージョン @version を発見する',
|
'newVersionTitle': '新しいバージョン @version を発見する',
|
||||||
'newVersionUpdate': 'アップデート',
|
'newVersionUpgrade': 'アップデート',
|
||||||
'newVersionLater': '後で',
|
'newVersionLater': '後で',
|
||||||
'thanks': '感謝',
|
'thanks': '感謝',
|
||||||
'thanksDesc': 'Gopeedコミュニティの建設に協力してくださったすべての貢献者の方々に感謝します!',
|
'thanksDesc': 'Gopeedコミュニティの建設に協力してくださったすべての貢献者の方々に感謝します!',
|
||||||
|
|||||||
@@ -66,12 +66,12 @@ const plPL = {
|
|||||||
'deleteTaskTip': 'Zachowaj pobrane pliki',
|
'deleteTaskTip': 'Zachowaj pobrane pliki',
|
||||||
'delete': 'Usuń',
|
'delete': 'Usuń',
|
||||||
'newVersionTitle': 'Sprawdź aktualizację @version',
|
'newVersionTitle': 'Sprawdź aktualizację @version',
|
||||||
'newVersionUpdate': 'Aktualizuj teraz',
|
'newVersionUpgrade': 'Aktualizuj teraz',
|
||||||
'newVersionLater': 'Później',
|
'newVersionLater': 'Później',
|
||||||
'extensions': 'Wtyczki',
|
'extensions': 'Wtyczki',
|
||||||
'extensionInstallUrl': 'Instaluj z url',
|
'extensionInstallUrl': 'Instaluj z url',
|
||||||
'extensionInstallSuccess': 'Instalacja zakończona',
|
'extensionInstallSuccess': 'Instalacja zakończona',
|
||||||
'extensionUpdateSuccess': 'Aktualizacja zakończona',
|
'extensionUpgradeSuccess': 'Aktualizacja zakończona',
|
||||||
'extensionDelete': 'Usuń wtyczkę',
|
'extensionDelete': 'Usuń wtyczkę',
|
||||||
'extensionAlreadyLatest': 'Wtyczka w aktualnej wersji',
|
'extensionAlreadyLatest': 'Wtyczka w aktualnej wersji',
|
||||||
'extensionFind': 'Wyszukaj wtyczkę',
|
'extensionFind': 'Wyszukaj wtyczkę',
|
||||||
|
|||||||
@@ -61,11 +61,11 @@ const ruRU = {
|
|||||||
'deleteTaskTip': 'Сохранить загруженные файлы',
|
'deleteTaskTip': 'Сохранить загруженные файлы',
|
||||||
'delete': 'Удалить',
|
'delete': 'Удалить',
|
||||||
'newVersionTitle': 'Обнаружена новая версия @version',
|
'newVersionTitle': 'Обнаружена новая версия @version',
|
||||||
'newVersionUpdate': 'Обновить',
|
'newVersionUpgrade': 'Обновить',
|
||||||
'newVersionLater': 'позже',
|
'newVersionLater': 'позже',
|
||||||
'extensions': 'Расширения',
|
'extensions': 'Расширения',
|
||||||
'extensionInstallSuccess': 'Установка завершена',
|
'extensionInstallSuccess': 'Установка завершена',
|
||||||
'extensionUpdateSuccess': 'Обновление завершено',
|
'extensionUpgradeSuccess': 'Обновление завершено',
|
||||||
'extensionDelete': 'Удалить расширение',
|
'extensionDelete': 'Удалить расширение',
|
||||||
'extensionAlreadyLatest': 'Это последняя версия',
|
'extensionAlreadyLatest': 'Это последняя версия',
|
||||||
'extensionFind': 'Найти расширения',
|
'extensionFind': 'Найти расширения',
|
||||||
|
|||||||
@@ -21,7 +21,8 @@ const taTA = {
|
|||||||
'downloadLinkValid': 'பதிவிறக்க இணைப்பை உள்ளிடவும்',
|
'downloadLinkValid': 'பதிவிறக்க இணைப்பை உள்ளிடவும்',
|
||||||
'downloadLinkHit':
|
'downloadLinkHit':
|
||||||
'பதிவிறக்க இணைப்பை உள்ளிடவும், HTTP/HTTPS/MAGNET ஆகியவற்றை ஏற்கும்@append',
|
'பதிவிறக்க இணைப்பை உள்ளிடவும், HTTP/HTTPS/MAGNET ஆகியவற்றை ஏற்கும்@append',
|
||||||
'downloadLinkHitDesktop': ', அல்லது torrent கோப்பை நேரடியாக இங்கே இழுக்கவும்',
|
'downloadLinkHitDesktop':
|
||||||
|
', அல்லது torrent கோப்பை நேரடியாக இங்கே இழுக்கவும்',
|
||||||
'download': 'பதிவிறக்கு',
|
'download': 'பதிவிறக்கு',
|
||||||
'noFileSelected': 'குறைந்தபட்சம் ஒரு கோப்பைத் தேர்ந்தெடுக்கவும்.',
|
'noFileSelected': 'குறைந்தபட்சம் ஒரு கோப்பைத் தேர்ந்தெடுக்கவும்.',
|
||||||
'noStoragePermission': 'சேமிப்பக அனுமதி தேவை',
|
'noStoragePermission': 'சேமிப்பக அனுமதி தேவை',
|
||||||
@@ -67,12 +68,12 @@ const taTA = {
|
|||||||
'deleteTaskTip': 'பதிவிறக்கம் செய்யப்பட்ட கோப்புகளை வைத்திரு',
|
'deleteTaskTip': 'பதிவிறக்கம் செய்யப்பட்ட கோப்புகளை வைத்திரு',
|
||||||
'delete': 'நீக்கு',
|
'delete': 'நீக்கு',
|
||||||
'newVersionTitle': 'புதிய பதிப்பைக் கண்டறியவும் @version',
|
'newVersionTitle': 'புதிய பதிப்பைக் கண்டறியவும் @version',
|
||||||
'newVersionUpdate': 'இப்பொழுது புதுப்பி',
|
'newVersionUpgrade': 'இப்பொழுது புதுப்பி',
|
||||||
'newVersionLater': 'பின்னர்',
|
'newVersionLater': 'பின்னர்',
|
||||||
'extensions': 'நீட்டிப்புகள்',
|
'extensions': 'நீட்டிப்புகள்',
|
||||||
'extensionInstallUrl': 'நிறுவப்படும் URL',
|
'extensionInstallUrl': 'நிறுவப்படும் URL',
|
||||||
'extensionInstallSuccess': 'வெற்றிகரமாக நிறுவப்பட்டது',
|
'extensionInstallSuccess': 'வெற்றிகரமாக நிறுவப்பட்டது',
|
||||||
'extensionUpdateSuccess': 'வெற்றிகரமாக புதுப்பிக்கப்பட்டது',
|
'extensionUpgradeSuccess': 'வெற்றிகரமாக புதுப்பிக்கப்பட்டது',
|
||||||
'extensionDelete': 'நீட்டிப்பை நீக்கு',
|
'extensionDelete': 'நீட்டிப்பை நீக்கு',
|
||||||
'extensionAlreadyLatest': 'இது ஏற்கனவே சமீபத்திய பதிப்பாகும்',
|
'extensionAlreadyLatest': 'இது ஏற்கனவே சமீபத்திய பதிப்பாகும்',
|
||||||
'extensionFind': 'நீட்டிப்புகளைக் கண்டறியவும்',
|
'extensionFind': 'நீட்டிப்புகளைக் கண்டறியவும்',
|
||||||
|
|||||||
@@ -20,8 +20,10 @@ const trTR = {
|
|||||||
'followSettings': 'Ayarları takip et',
|
'followSettings': 'Ayarları takip et',
|
||||||
'downloadLink': 'İndirme bağlantısı',
|
'downloadLink': 'İndirme bağlantısı',
|
||||||
'downloadLinkValid': 'Lütfen indirme bağlantısını girin',
|
'downloadLinkValid': 'Lütfen indirme bağlantısını girin',
|
||||||
'downloadLinkHit': 'Lütfen indirme bağlantısını girin, HTTP/HTTPS/MAGNET desteklenir@append',
|
'downloadLinkHit':
|
||||||
'downloadLinkHitDesktop': ', veya torrent dosyasını doğrudan buraya sürükleyin',
|
'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',
|
'download': 'İndir',
|
||||||
'noFileSelected': 'Devam etmek için lütfen en az bir dosya seçin.',
|
'noFileSelected': 'Devam etmek için lütfen en az bir dosya seçin.',
|
||||||
'noStoragePermission': 'Depolama izni gerekli',
|
'noStoragePermission': 'Depolama izni gerekli',
|
||||||
@@ -38,12 +40,14 @@ const trTR = {
|
|||||||
'defaultDirectDownload': 'Doğrudan indir seçili olsun',
|
'defaultDirectDownload': 'Doğrudan indir seçili olsun',
|
||||||
'items': '@count tane',
|
'items': '@count tane',
|
||||||
'subscribeTracker': 'İzleyiciye abone ol',
|
'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',
|
'update': 'Güncelle',
|
||||||
'updateDaily': 'Günlük olarak güncelle',
|
'updateDaily': 'Günlük olarak güncelle',
|
||||||
'lastUpdate': 'Son güncellenme tarihi: @time',
|
'lastUpdate': 'Son güncellenme tarihi: @time',
|
||||||
'addTracker': 'İzleyici ekle',
|
'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',
|
'ui': 'Arayüz',
|
||||||
'theme': 'Tema',
|
'theme': 'Tema',
|
||||||
'themeSystem': 'Sistem',
|
'themeSystem': 'Sistem',
|
||||||
@@ -58,7 +62,8 @@ const trTR = {
|
|||||||
'apiToken': 'API anahtarı',
|
'apiToken': 'API anahtarı',
|
||||||
'notSet': 'AYARLI DEĞİL',
|
'notSet': 'AYARLI DEĞİL',
|
||||||
'set': 'AYARLI',
|
'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',
|
'effectAfterRestart': 'Yeniden başlatma sonrası etki edecektir',
|
||||||
'developer': 'Geliştirici',
|
'developer': 'Geliştirici',
|
||||||
'logDirectory': 'Kayıt dizini',
|
'logDirectory': 'Kayıt dizini',
|
||||||
@@ -69,12 +74,12 @@ const trTR = {
|
|||||||
'deleteTaskTip': 'İndirilen dosyaları sakla',
|
'deleteTaskTip': 'İndirilen dosyaları sakla',
|
||||||
'delete': 'Sil',
|
'delete': 'Sil',
|
||||||
'newVersionTitle': 'Yeni sürümü keşfet @version',
|
'newVersionTitle': 'Yeni sürümü keşfet @version',
|
||||||
'newVersionUpdate': 'Şimdi güncelle',
|
'newVersionUpgrade': 'Şimdi güncelle',
|
||||||
'newVersionLater': 'Sonra',
|
'newVersionLater': 'Sonra',
|
||||||
'extensions': 'Uzantılar',
|
'extensions': 'Uzantılar',
|
||||||
'extensionInstallUrl': 'İndirme bağlantısı',
|
'extensionInstallUrl': 'İndirme bağlantısı',
|
||||||
'extensionInstallSuccess': 'Başarıyla indirildi',
|
'extensionInstallSuccess': 'Başarıyla indirildi',
|
||||||
'extensionUpdateSuccess': 'Başarıyla güncellendi',
|
'extensionUpgradeSuccess': 'Başarıyla güncellendi',
|
||||||
'extensionDelete': 'Uzantıyı sil',
|
'extensionDelete': 'Uzantıyı sil',
|
||||||
'extensionAlreadyLatest': 'Bu zaten en son sürüm',
|
'extensionAlreadyLatest': 'Bu zaten en son sürüm',
|
||||||
'extensionFind': 'Uzantı ara',
|
'extensionFind': 'Uzantı ara',
|
||||||
@@ -93,7 +98,8 @@ const trTR = {
|
|||||||
'username': 'Kullanıcı adı',
|
'username': 'Kullanıcı adı',
|
||||||
'password': 'Şifre',
|
'password': 'Şifre',
|
||||||
'thanks': 'Teşekkürler',
|
'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ı',
|
'browserExtension': 'Tarayıcı uzantısı',
|
||||||
'launchAtStartup': 'Başlangıçta başlat',
|
'launchAtStartup': 'Başlangıçta başlat',
|
||||||
'seedConfig': 'Seed ayarları',
|
'seedConfig': 'Seed ayarları',
|
||||||
|
|||||||
@@ -65,12 +65,12 @@ const viVN = {
|
|||||||
'deleteTaskTip': 'Giữ các tệp đã tải về',
|
'deleteTaskTip': 'Giữ các tệp đã tải về',
|
||||||
'delete': 'Xóa',
|
'delete': 'Xóa',
|
||||||
'newVersionTitle': 'Khám phá phiên bản mới @version',
|
'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',
|
'newVersionLater': 'Sau',
|
||||||
'extensions': 'Tiện ích mở rộng',
|
'extensions': 'Tiện ích mở rộng',
|
||||||
'extensionInstallUrl': 'Liên kết cài đặt',
|
'extensionInstallUrl': 'Liên kết cài đặt',
|
||||||
'extensionInstallSuccess': 'Cài đặt thành công',
|
'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',
|
'extensionDelete': 'Xóa tiện ích mở rộng',
|
||||||
'extensionAlreadyLatest': 'Đây đã là phiên bản mới nhất',
|
'extensionAlreadyLatest': 'Đây đã là phiên bản mới nhất',
|
||||||
'extensionFind': 'Tìm tiện ích mở rộng',
|
'extensionFind': 'Tìm tiện ích mở rộng',
|
||||||
|
|||||||
@@ -69,12 +69,12 @@ const zhCN = {
|
|||||||
'deleteTaskTip': '保留已下载的文件',
|
'deleteTaskTip': '保留已下载的文件',
|
||||||
'delete': '删除',
|
'delete': '删除',
|
||||||
'newVersionTitle': '发现新版本 @version',
|
'newVersionTitle': '发现新版本 @version',
|
||||||
'newVersionUpdate': '立即更新',
|
'newVersionUpgrade': '立即更新',
|
||||||
'newVersionLater': '稍后再说',
|
'newVersionLater': '稍后再说',
|
||||||
'extensions': '扩展',
|
'extensions': '扩展',
|
||||||
'extensionInstallUrl': '安装链接',
|
'extensionInstallUrl': '安装链接',
|
||||||
'extensionInstallSuccess': '安装成功',
|
'extensionInstallSuccess': '安装成功',
|
||||||
'extensionUpdateSuccess': '更新成功',
|
'extensionUpgradeSuccess': '更新成功',
|
||||||
'extensionDelete': '删除扩展',
|
'extensionDelete': '删除扩展',
|
||||||
'extensionAlreadyLatest': '已经是最新版本',
|
'extensionAlreadyLatest': '已经是最新版本',
|
||||||
'extensionFind': '获取扩展',
|
'extensionFind': '获取扩展',
|
||||||
|
|||||||
@@ -69,12 +69,12 @@ const zhTW = {
|
|||||||
'deleteTaskTip': '保留已下載的檔案',
|
'deleteTaskTip': '保留已下載的檔案',
|
||||||
'delete': '刪除',
|
'delete': '刪除',
|
||||||
'newVersionTitle': '發現新版本 @version',
|
'newVersionTitle': '發現新版本 @version',
|
||||||
'newVersionUpdate': '立即更新',
|
'newVersionUpgrade': '立即更新',
|
||||||
'newVersionLater': '稍後',
|
'newVersionLater': '稍後',
|
||||||
'extensions': '擴充功能',
|
'extensions': '擴充功能',
|
||||||
'extensionInstallUrl': '安裝 URL',
|
'extensionInstallUrl': '安裝 URL',
|
||||||
'extensionInstallSuccess': '安裝成功',
|
'extensionInstallSuccess': '安裝成功',
|
||||||
'extensionUpdateSuccess': '更新成功',
|
'extensionUpgradeSuccess': '更新成功',
|
||||||
'extensionDelete': '刪除擴充功能',
|
'extensionDelete': '刪除擴充功能',
|
||||||
'extensionAlreadyLatest': '已是最新版本',
|
'extensionAlreadyLatest': '已是最新版本',
|
||||||
'extensionFind': '尋找擴充功能',
|
'extensionFind': '尋找擴充功能',
|
||||||
|
|||||||
@@ -2,12 +2,12 @@ import 'package:args/args.dart';
|
|||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
import 'package:gopeed/api/native/model/start_config.dart';
|
||||||
import 'package:window_manager/window_manager.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/controllers/app_controller.dart';
|
||||||
import 'app/modules/app/views/app_view.dart';
|
import 'app/modules/app/views/app_view.dart';
|
||||||
import 'core/libgopeed_boot.dart';
|
import 'api/libgopeed_boot.dart';
|
||||||
import 'database/database.dart';
|
import 'database/database.dart';
|
||||||
import 'i18n/message.dart';
|
import 'i18n/message.dart';
|
||||||
import 'util/locale_manager.dart';
|
import 'util/locale_manager.dart';
|
||||||
@@ -73,10 +73,11 @@ Future<void> init(Args args) async {
|
|||||||
|
|
||||||
final controller = Get.put(AppController());
|
final controller = Get.put(AppController());
|
||||||
try {
|
try {
|
||||||
await controller.loadStartConfig();
|
final instance = await LibgopeedBoot().init(StartConfig(
|
||||||
final startCfg = controller.startConfig.value;
|
storage: "bolt",
|
||||||
controller.runningPort.value = await LibgopeedBoot.instance.start(startCfg);
|
storageDir: Util.getStorageDir(),
|
||||||
api.init(startCfg.network, controller.runningAddress(), startCfg.apiToken);
|
));
|
||||||
|
LibgopeedBoot.singleton(instance);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.e("libgopeed init fail", 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);
|
||||||
|
}
|
||||||
+12
-44
@@ -293,18 +293,18 @@ packages:
|
|||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: ffigen
|
name: ffigen
|
||||||
sha256: d3e76c2ad48a4e7f93a29a162006f00eba46ce7c08194a77bb5c5e97d1b5ff0a
|
sha256: dead012f29db2be71ea152458f5eab600de98fbc244e01088ae6bf2616bceca7
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "8.0.2"
|
version: "11.0.0"
|
||||||
file:
|
file:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: file
|
name: file
|
||||||
sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d"
|
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.1.4"
|
version: "7.0.1"
|
||||||
file_picker:
|
file_picker:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -525,30 +525,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.2.2"
|
version: "0.2.2"
|
||||||
leak_tracker:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: leak_tracker
|
|
||||||
sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "10.0.0"
|
|
||||||
leak_tracker_flutter_testing:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: leak_tracker_flutter_testing
|
|
||||||
sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.0.1"
|
|
||||||
leak_tracker_testing:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: leak_tracker_testing
|
|
||||||
sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.0.1"
|
|
||||||
lecle_downloads_path_provider:
|
lecle_downloads_path_provider:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -617,18 +593,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: matcher
|
name: matcher
|
||||||
sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
|
sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.12.16+1"
|
version: "0.12.16"
|
||||||
material_color_utilities:
|
material_color_utilities:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: material_color_utilities
|
name: material_color_utilities
|
||||||
sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
|
sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.8.0"
|
version: "0.5.0"
|
||||||
math_expressions:
|
math_expressions:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -649,10 +625,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: meta
|
name: meta
|
||||||
sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04
|
sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.11.0"
|
version: "1.10.0"
|
||||||
mime:
|
mime:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -697,10 +673,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: path
|
name: path
|
||||||
sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
|
sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.9.0"
|
version: "1.8.3"
|
||||||
path_provider:
|
path_provider:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -1203,14 +1179,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.4"
|
version: "2.1.4"
|
||||||
vm_service:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: vm_service
|
|
||||||
sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "13.0.0"
|
|
||||||
watcher:
|
watcher:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
|
|||||||
version: 1.6.4+1
|
version: 1.6.4+1
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=3.0.0 <4.0.0'
|
sdk: ">=3.0.0 <4.0.0"
|
||||||
|
|
||||||
# Dependencies specify other packages that your package needs in order to work.
|
# Dependencies specify other packages that your package needs in order to work.
|
||||||
# To automatically upgrade your package dependencies to the latest versions
|
# To automatically upgrade your package dependencies to the latest versions
|
||||||
@@ -85,7 +85,7 @@ dev_dependencies:
|
|||||||
# package. See that file for information about deactivating specific lint
|
# package. See that file for information about deactivating specific lint
|
||||||
# rules and activating additional ones.
|
# rules and activating additional ones.
|
||||||
flutter_lints: ^2.0.0
|
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 json model: dart run build_runner build --delete-conflicting-outputs
|
||||||
build_runner: ^2.2.1
|
build_runner: ^2.2.1
|
||||||
json_serializable: ^6.3.2
|
json_serializable: ^6.3.2
|
||||||
@@ -150,7 +150,7 @@ flutter_icons:
|
|||||||
ffigen:
|
ffigen:
|
||||||
name: LibgopeedBind
|
name: LibgopeedBind
|
||||||
description: Bindings to gopeed library.
|
description: Bindings to gopeed library.
|
||||||
output: 'lib/core/ffi/libgopeed_bind.dart'
|
output: "lib/api/native/ffi/libgopeed_bind.dart"
|
||||||
headers:
|
headers:
|
||||||
entry-points:
|
entry-points:
|
||||||
- 'include/libgopeed.h'
|
- "include/libgopeed.h"
|
||||||
Reference in new issue
Block a user