diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6342f5659..0546910ce 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -215,7 +215,7 @@ jobs: overwrite: true build-linux: if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'linux' }} - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 needs: [ get-release ] steps: - uses: actions/checkout@v3 @@ -228,7 +228,9 @@ jobs: - run: | sudo apt update -y # flutter build dependencies - sudo apt install -y ninja-build libgtk-3-dev libayatana-appindicator3-1 libayatana-appindicator3-dev + sudo apt install -y ninja-build libgtk-3-dev libappindicator3-dev + # rpm build dependencies + sudo apt install -y rpm patchelf # appimage build dependencies sudo apt install -y libfuse2 locate wget -O appimagetool "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage" diff --git a/README.md b/README.md index a96d73b0d..8c46aabd9 100644 --- a/README.md +++ b/README.md @@ -53,11 +53,11 @@ Visit ✈ [Official Website](https://gopeed.com) | 📖 [Official Docs](https:// DEB - Link + Link AppImage - Link + Link Android diff --git a/README_ja-JP.md b/README_ja-JP.md index f9873e57c..bc0cc5377 100644 --- a/README_ja-JP.md +++ b/README_ja-JP.md @@ -53,11 +53,11 @@ Gopeed (正式名 Go Speed) は `Golang` + `Flutter` によって開発された DEB - Link + Link AppImage - Link + Link Android diff --git a/README_vi-VN.md b/README_vi-VN.md index 7c8ce3690..fa8ecce5c 100644 --- a/README_vi-VN.md +++ b/README_vi-VN.md @@ -53,11 +53,11 @@ Truy cập ✈ [Trang web chính thức](https://gopeed.com) | 📖 [Tài liệu DEB - Liên kết + Liên kết AppImage - Liên kết + Liên kết Android diff --git a/README_zh-CN.md b/README_zh-CN.md index 4ec1327b5..2d94ed19c 100644 --- a/README_zh-CN.md +++ b/README_zh-CN.md @@ -53,11 +53,11 @@ Gopeed(全称 Go Speed),直译过来中文名叫做`够快下载器`(不 DEB - 前往 + 前往 AppImage - 前往 + 前往 Android diff --git a/README_zh-TW.md b/README_zh-TW.md index 147d6004c..7920663db 100644 --- a/README_zh-TW.md +++ b/README_zh-TW.md @@ -53,11 +53,11 @@ Gopeed(全稱 Go Speed),是一款使用`Golang`+`Flutter`編寫的高速 DEB - 前往 + 前往 AppImage - 前往 + 前往 Android diff --git a/_docs/img/ui-demo.png b/_docs/img/ui-demo.png index c4951fca5..6620a4593 100644 Binary files a/_docs/img/ui-demo.png and b/_docs/img/ui-demo.png differ diff --git a/go.mod b/go.mod index 0c25a2b67..c4965679d 100644 --- a/go.mod +++ b/go.mod @@ -69,6 +69,7 @@ require ( github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-ieproxy v0.0.11 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mschoch/smat v0.2.0 // indirect github.com/pion/datachannel v1.5.5 // indirect diff --git a/go.sum b/go.sum index ae4980b37..66aceab3f 100644 --- a/go.sum +++ b/go.sum @@ -270,6 +270,8 @@ github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A= github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-ieproxy v0.0.11 h1:MQ/5BuGSgDAHZOJe6YY80IF2UVCfGkwfo6AeD7HtHYo= +github.com/mattn/go-ieproxy v0.0.11/go.mod h1:/NsJd+kxZBmjMc5hrJCKMbP57B84rvq9BiDRbtO9AS0= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= diff --git a/internal/controller/controller.go b/internal/controller/controller.go index ded1ea2de..15eb299d1 100644 --- a/internal/controller/controller.go +++ b/internal/controller/controller.go @@ -1,14 +1,14 @@ package controller import ( - "net/url" + "github.com/GopeedLab/gopeed/pkg/base" "os" "path/filepath" ) type Controller struct { - GetConfig func(v any) bool - ProxyUrl *url.URL + GetConfig func(v any) bool + ProxyConfig *base.DownloaderProxyConfig FileController //ContextDialer() (proxy.Dialer, error) } diff --git a/internal/protocol/bt/fetcher.go b/internal/protocol/bt/fetcher.go index db1d8df42..75f8e3a08 100644 --- a/internal/protocol/bt/fetcher.go +++ b/internal/protocol/bt/fetcher.go @@ -10,7 +10,6 @@ import ( "github.com/GopeedLab/gopeed/pkg/util" "github.com/anacrolix/torrent" "github.com/anacrolix/torrent/metainfo" - "net/http" "path/filepath" "strings" "sync" @@ -69,9 +68,7 @@ func (f *Fetcher) initClient() (err error) { cfg.Bep20 = fmt.Sprintf("-GP%s-", parseBep20()) cfg.ExtendedHandshakeClientVersion = fmt.Sprintf("Gopeed %s", base.Version) cfg.ListenPort = f.config.ListenPort - if f.ctl.ProxyUrl != nil { - cfg.HTTPProxy = http.ProxyURL(f.ctl.ProxyUrl) - } + cfg.HTTPProxy = f.ctl.ProxyConfig.ToHandler() cfg.DefaultStorage = newFileOpts(newFileClientOpts{ ClientBaseDir: cfg.DataDir, HandleFileTorrent: func(infoHash metainfo.Hash, ft *fileTorrentImpl) { diff --git a/internal/protocol/bt/fetcher_test.go b/internal/protocol/bt/fetcher_test.go index b0bff5485..751f9f85b 100644 --- a/internal/protocol/bt/fetcher_test.go +++ b/internal/protocol/bt/fetcher_test.go @@ -8,8 +8,6 @@ import ( "github.com/GopeedLab/gopeed/internal/test" "github.com/GopeedLab/gopeed/pkg/base" "github.com/GopeedLab/gopeed/pkg/protocol/bt" - "github.com/GopeedLab/gopeed/pkg/util" - "net/url" "os" "reflect" "testing" @@ -60,7 +58,14 @@ func TestFetcher_ResolveWithProxy(t *testing.T) { proxyListener := test.StartSocks5Server(usr, pwd) defer proxyListener.Close() - doResolve(t, buildConfigFetcher(util.BuildProxyUrl("socks5", proxyListener.Addr().String(), usr, pwd))) + doResolve(t, buildConfigFetcher(&base.DownloaderProxyConfig{ + Enable: true, + System: false, + Scheme: "socks5", + Host: proxyListener.Addr().String(), + Usr: usr, + Pwd: pwd, + })) } func doResolve(t *testing.T, fetcher fetcher.Fetcher) { @@ -100,7 +105,7 @@ func buildFetcher() fetcher.Fetcher { return fetcher } -func buildConfigFetcher(proxyUrl *url.URL) fetcher.Fetcher { +func buildConfigFetcher(proxyConfig *base.DownloaderProxyConfig) fetcher.Fetcher { fetcher := new(FetcherBuilder).Build() newController := controller.NewController() mockCfg := config{ @@ -114,7 +119,7 @@ func buildConfigFetcher(proxyUrl *url.URL) fetcher.Fetcher { } return true } - newController.ProxyUrl = proxyUrl + newController.ProxyConfig = proxyConfig fetcher.Setup(newController) return fetcher } diff --git a/internal/protocol/http/fetcher.go b/internal/protocol/http/fetcher.go index e850c0df9..bda1a4574 100644 --- a/internal/protocol/http/fetcher.go +++ b/internal/protocol/http/fetcher.go @@ -451,12 +451,11 @@ func (f *Fetcher) splitChunk() (chunks []*chunk) { } func (f *Fetcher) buildClient() *http.Client { - transport := &http.Transport{} + transport := &http.Transport{ + Proxy: f.ctl.ProxyConfig.ToHandler(), + } // Cookie handle jar, _ := cookiejar.New(nil) - if f.ctl.ProxyUrl != nil { - transport.Proxy = http.ProxyURL(f.ctl.ProxyUrl) - } return &http.Client{ Transport: transport, Jar: jar, diff --git a/internal/protocol/http/fetcher_test.go b/internal/protocol/http/fetcher_test.go index b65e3a3bd..6c76a0de0 100644 --- a/internal/protocol/http/fetcher_test.go +++ b/internal/protocol/http/fetcher_test.go @@ -8,7 +8,6 @@ import ( "github.com/GopeedLab/gopeed/internal/test" "github.com/GopeedLab/gopeed/pkg/base" "github.com/GopeedLab/gopeed/pkg/protocol/http" - "github.com/GopeedLab/gopeed/pkg/util" "net" "testing" "time" @@ -338,7 +337,11 @@ func downloadResume(listener net.Listener, connections int, t *testing.T) { func downloadWithProxy(httpListener net.Listener, proxyListener net.Listener, t *testing.T) { fetcher := downloadReady(httpListener, 4, t) ctl := controller.NewController() - ctl.ProxyUrl = util.BuildProxyUrl("socks5", proxyListener.Addr().String(), "", "") + ctl.ProxyConfig = &base.DownloaderProxyConfig{ + Enable: true, + Scheme: "socks5", + Host: proxyListener.Addr().String(), + } fetcher.Setup(ctl) err := fetcher.Start() if err != nil { diff --git a/internal/test/httptest.go b/internal/test/httptest.go index 18cc3069b..fb3c38e5d 100644 --- a/internal/test/httptest.go +++ b/internal/test/httptest.go @@ -24,6 +24,11 @@ const ( Dir = "./" BuildFile = Dir + BuildName + ExternalDownloadUrl = "https://raw.githubusercontent.com/GopeedLab/gopeed/v1.5.6/_docs/img/banner.png" + ExternalDownloadName = "banner.png" + ExternalDownloadSize = 26416 + //ExternalDownloadMd5 = "c67c6e3cae79a95342485676571e8a5c" + DownloadName = "download.data" DownloadRename = "download (1).data" DownloadFile = Dir + DownloadName diff --git a/pkg/base/model.go b/pkg/base/model.go index f0b7ff040..991d6c0ec 100644 --- a/pkg/base/model.go +++ b/pkg/base/model.go @@ -3,7 +3,10 @@ package base import ( "fmt" "github.com/GopeedLab/gopeed/pkg/util" + "github.com/mattn/go-ieproxy" "golang.org/x/exp/slices" + "net/http" + "net/url" "time" ) @@ -128,3 +131,88 @@ func ParseOptsExtra[E any](opts *Options) error { opts.Extra = &t return nil } + +// DownloaderStoreConfig is the config that can restore the downloader. +type DownloaderStoreConfig struct { + FirstLoad bool `json:"-"` // FirstLoad is the flag that the config is first time init and not from store + + DownloadDir string `json:"downloadDir"` // DownloadDir is the default directory to save the downloaded files + MaxRunning int `json:"maxRunning"` // MaxRunning is the max running download count + ProtocolConfig map[string]any `json:"protocolConfig"` // ProtocolConfig is special config for each protocol + Extra map[string]any `json:"extra"` + Proxy *DownloaderProxyConfig `json:"proxy"` +} + +func (cfg *DownloaderStoreConfig) Init() *DownloaderStoreConfig { + if cfg.MaxRunning == 0 { + cfg.MaxRunning = 5 + } + if cfg.Proxy == nil { + cfg.Proxy = &DownloaderProxyConfig{} + } + return cfg +} + +type DownloaderProxyConfig struct { + Enable bool `json:"enable"` + // System is the flag that use system proxy + System bool `json:"system"` + Scheme string `json:"scheme"` + Host string `json:"host"` + Usr string `json:"usr"` + Pwd string `json:"pwd"` +} + +func (cfg *DownloaderProxyConfig) ToHandler() func(r *http.Request) (*url.URL, error) { + if cfg == nil || cfg.Enable == false { + return nil + } + if cfg.System { + ieproxy.ReloadConf() + return ieproxy.GetProxyFunc() + } + if cfg.Scheme == "" || cfg.Host == "" { + return nil + } + return http.ProxyURL(util.BuildProxyUrl(cfg.Scheme, cfg.Host, cfg.Usr, cfg.Pwd)) +} + +// ToUrl returns the proxy url, just for git clone +func (cfg *DownloaderProxyConfig) ToUrl() *url.URL { + if cfg == nil || cfg.Enable == false { + return nil + } + if cfg.System { + ieproxy.ReloadConf() + static := ieproxy.GetConf().Static + if static.Active && len(static.Protocols) > 0 { + // If only one protocol, use it + if len(static.Protocols) == 1 { + for _, v := range static.Protocols { + return parseUrlSafe(v) + } + } + // Check https + if v, ok := static.Protocols["https"]; ok { + return parseUrlSafe(v) + } + // Check http + if v, ok := static.Protocols["http"]; ok { + return parseUrlSafe(v) + } + } + return nil + } + if cfg.Scheme == "" || cfg.Host == "" { + return nil + } + return util.BuildProxyUrl(cfg.Scheme, cfg.Host, cfg.Usr, cfg.Pwd) +} + +func parseUrlSafe(rawUrl string) *url.URL { + u, err := url.Parse(rawUrl) + if err != nil { + return nil + } + return u +} diff --git a/pkg/download/downloader.go b/pkg/download/downloader.go index 5d7faeb97..59dfecdb9 100644 --- a/pkg/download/downloader.go +++ b/pkg/download/downloader.go @@ -110,7 +110,7 @@ func (d *Downloader) Setup() error { return err } // load config from storage - var cfg DownloaderStoreConfig + var cfg base.DownloaderStoreConfig exist, err := d.storage.Get(bucketConfig, "config", &cfg) if err != nil { return err @@ -118,7 +118,7 @@ func (d *Downloader) Setup() error { if exist { d.cfg.DownloaderStoreConfig = &cfg } else { - d.cfg.DownloaderStoreConfig = &DownloaderStoreConfig{ + d.cfg.DownloaderStoreConfig = &base.DownloaderStoreConfig{ FirstLoad: true, } } @@ -225,7 +225,7 @@ func (d *Downloader) setupFetcher(fetcher fetcher.Fetcher) { ctl.GetConfig = func(v any) bool { return d.getProtocolConfig(fetcher.Name(), v) } - ctl.ProxyUrl = d.cfg.ProxyUrl() + ctl.ProxyConfig = d.cfg.Proxy fetcher.Setup(ctl) } @@ -603,11 +603,11 @@ func (d *Downloader) GetTasksByStatues(statues []base.Status) []*Task { return tasks } -func (d *Downloader) GetConfig() (*DownloaderStoreConfig, error) { +func (d *Downloader) GetConfig() (*base.DownloaderStoreConfig, error) { return d.cfg.DownloaderStoreConfig, nil } -func (d *Downloader) PutConfig(v *DownloaderStoreConfig) error { +func (d *Downloader) PutConfig(v *base.DownloaderStoreConfig) error { d.cfg.DownloaderStoreConfig = v return d.storage.Put(bucketConfig, "config", v) } diff --git a/pkg/download/downloader_test.go b/pkg/download/downloader_test.go index 7836baf3b..3d9e3a292 100644 --- a/pkg/download/downloader_test.go +++ b/pkg/download/downloader_test.go @@ -4,6 +4,7 @@ import ( "github.com/GopeedLab/gopeed/internal/test" "github.com/GopeedLab/gopeed/pkg/base" "github.com/GopeedLab/gopeed/pkg/protocol/http" + "os" "sync" "testing" "time" @@ -85,37 +86,58 @@ func TestDownloader_Create(t *testing.T) { func TestDownloader_CreateWithProxy(t *testing.T) { // No proxy - doTestDownloaderCreateWithProxy(t, false, func(proxyCfg *DownloaderProxyConfig) *DownloaderProxyConfig { + doTestDownloaderCreateWithProxy(t, false, func(proxyCfg *base.DownloaderProxyConfig) *base.DownloaderProxyConfig { return nil - }) + }, nil) // Disable proxy - doTestDownloaderCreateWithProxy(t, false, func(proxyCfg *DownloaderProxyConfig) *DownloaderProxyConfig { + doTestDownloaderCreateWithProxy(t, false, func(proxyCfg *base.DownloaderProxyConfig) *base.DownloaderProxyConfig { proxyCfg.Enable = false return proxyCfg + }, nil) + // Enable system proxy but not set proxy environment variable + doTestDownloaderCreateWithProxy(t, false, func(proxyCfg *base.DownloaderProxyConfig) *base.DownloaderProxyConfig { + proxyCfg.System = true + return proxyCfg + }, nil) + // Enable proxy but error proxy environment variable + doTestDownloaderCreateWithProxy(t, false, func(proxyCfg *base.DownloaderProxyConfig) *base.DownloaderProxyConfig { + os.Setenv("HTTP_PROXY", "http://127.0.0.1:1234") + os.Setenv("HTTPS_PROXY", "http://127.0.0.1:1234") + proxyCfg.System = true + return proxyCfg + }, func(err error) { + if err == nil { + t.Fatal("doTestDownloaderCreateWithProxy() got = nil, want error") + } }) + // Enable system proxy and set proxy environment variable + doTestDownloaderCreateWithProxy(t, false, func(proxyCfg *base.DownloaderProxyConfig) *base.DownloaderProxyConfig { + os.Setenv("HTTP_PROXY", proxyCfg.ToUrl().String()) + os.Setenv("HTTPS_PROXY", proxyCfg.ToUrl().String()) + proxyCfg.System = true + return proxyCfg + }, nil) // Invalid proxy scheme - doTestDownloaderCreateWithProxy(t, false, func(proxyCfg *DownloaderProxyConfig) *DownloaderProxyConfig { + doTestDownloaderCreateWithProxy(t, false, func(proxyCfg *base.DownloaderProxyConfig) *base.DownloaderProxyConfig { proxyCfg.Scheme = "" return proxyCfg - }) + }, nil) // Invalid proxy host - doTestDownloaderCreateWithProxy(t, false, func(proxyCfg *DownloaderProxyConfig) *DownloaderProxyConfig { + doTestDownloaderCreateWithProxy(t, false, func(proxyCfg *base.DownloaderProxyConfig) *base.DownloaderProxyConfig { proxyCfg.Host = "" return proxyCfg - }) + }, nil) // Use proxy without auth - doTestDownloaderCreateWithProxy(t, false, func(proxyCfg *DownloaderProxyConfig) *DownloaderProxyConfig { + doTestDownloaderCreateWithProxy(t, false, func(proxyCfg *base.DownloaderProxyConfig) *base.DownloaderProxyConfig { return proxyCfg - }) + }, nil) // Use proxy with auth - doTestDownloaderCreateWithProxy(t, true, func(proxyCfg *DownloaderProxyConfig) *DownloaderProxyConfig { + doTestDownloaderCreateWithProxy(t, true, func(proxyCfg *base.DownloaderProxyConfig) *base.DownloaderProxyConfig { return proxyCfg - }) + }, nil) } -func doTestDownloaderCreateWithProxy(t *testing.T, auth bool, buildProxyConfig func(proxyCfg *DownloaderProxyConfig) *DownloaderProxyConfig) { - httpListener := test.StartTestFileServer() - defer httpListener.Close() +func doTestDownloaderCreateWithProxy(t *testing.T, auth bool, buildProxyConfig func(proxyCfg *base.DownloaderProxyConfig) *base.DownloaderProxyConfig, errHandler func(err error)) { usr, pwd := "", "" if auth { usr, pwd = "admin", "123" @@ -128,7 +150,7 @@ func doTestDownloaderCreateWithProxy(t *testing.T, auth bool, buildProxyConfig f t.Fatal(err) } defer downloader.Clear() - downloader.cfg.DownloaderStoreConfig.Proxy = buildProxyConfig(&DownloaderProxyConfig{ + downloader.cfg.DownloaderStoreConfig.Proxy = buildProxyConfig(&base.DownloaderProxyConfig{ Enable: true, Scheme: "socks5", Host: proxyListener.Addr().String(), @@ -137,20 +159,24 @@ func doTestDownloaderCreateWithProxy(t *testing.T, auth bool, buildProxyConfig f }) req := &base.Request{ - URL: "http://" + httpListener.Addr().String() + "/" + test.BuildName, + URL: test.ExternalDownloadUrl, } rr, err := downloader.Resolve(req) if err != nil { - t.Fatal(err) + if errHandler == nil { + t.Fatal(err) + } + errHandler(err) + return } want := &base.Resource{ - Size: test.BuildSize, + Size: test.ExternalDownloadSize, Range: true, Files: []*base.FileInfo{ { - Name: test.BuildName, + Name: test.ExternalDownloadName, Path: "", - Size: test.BuildSize, + Size: test.ExternalDownloadSize, }, }, } @@ -285,7 +311,7 @@ func TestDownloader_Protocol_Config(t *testing.T) { t.Errorf("getProtocolConfig() got = %v, want %v", exits, false) } - storeCfg := &DownloaderStoreConfig{ + storeCfg := &base.DownloaderStoreConfig{ DownloadDir: "./downloads", ProtocolConfig: map[string]any{ "http": map[string]any{ diff --git a/pkg/download/engine/engine.go b/pkg/download/engine/engine.go index 969a5cefe..ac0db4885 100644 --- a/pkg/download/engine/engine.go +++ b/pkg/download/engine/engine.go @@ -3,6 +3,7 @@ package engine import ( _ "embed" "errors" + "github.com/GopeedLab/gopeed/pkg/base" gojaerror "github.com/GopeedLab/gopeed/pkg/download/engine/inject/error" "github.com/GopeedLab/gopeed/pkg/download/engine/inject/file" "github.com/GopeedLab/gopeed/pkg/download/engine/inject/formdata" @@ -11,7 +12,6 @@ import ( "github.com/dop251/goja" "github.com/dop251/goja_nodejs/eventloop" gojaurl "github.com/dop251/goja_nodejs/url" - "net/url" "time" ) @@ -110,7 +110,7 @@ func (e *Engine) Close() { } type Config struct { - ProxyURL *url.URL + ProxyConfig *base.DownloaderProxyConfig } func NewEngine(cfg *Config) *Engine { @@ -135,7 +135,7 @@ func NewEngine(cfg *Config) *Engine { if err := formdata.Enable(runtime); err != nil { return } - if err := xhr.Enable(runtime, cfg.ProxyURL); err != nil { + if err := xhr.Enable(runtime, cfg.ProxyConfig.ToHandler()); err != nil { return } if _, err := runtime.RunString(polyfillScript); err != nil { diff --git a/pkg/download/engine/engine_test.go b/pkg/download/engine/engine_test.go index 63e0281eb..61443bd34 100644 --- a/pkg/download/engine/engine_test.go +++ b/pkg/download/engine/engine_test.go @@ -7,10 +7,10 @@ import ( "errors" "fmt" "github.com/GopeedLab/gopeed/internal/test" + "github.com/GopeedLab/gopeed/pkg/base" gojaerror "github.com/GopeedLab/gopeed/pkg/download/engine/inject/error" "github.com/GopeedLab/gopeed/pkg/download/engine/inject/file" gojautil "github.com/GopeedLab/gopeed/pkg/download/engine/util" - "github.com/GopeedLab/gopeed/pkg/util" "github.com/dop251/goja" "io" "net" @@ -217,8 +217,16 @@ func doTestFetchWithProxy(t *testing.T, usr, pwd string) { proxyListener := test.StartSocks5Server(usr, pwd) defer proxyListener.Close() - - engine := NewEngine(&Config{ProxyURL: util.BuildProxyUrl("socks5", proxyListener.Addr().String(), usr, pwd)}) + engine := NewEngine(&Config{ + ProxyConfig: &base.DownloaderProxyConfig{ + Enable: true, + System: false, + Scheme: "socks5", + Host: proxyListener.Addr().String(), + Usr: usr, + Pwd: pwd, + }, + }) if _, err := engine.RunString(fmt.Sprintf("var host = 'http://%s';", httpListener.Addr().String())); err != nil { t.Fatal(err) diff --git a/pkg/download/engine/inject/xhr/module.go b/pkg/download/engine/inject/xhr/module.go index d91c56ac0..dd6e0a775 100644 --- a/pkg/download/engine/inject/xhr/module.go +++ b/pkg/download/engine/inject/xhr/module.go @@ -2,6 +2,7 @@ package xhr import ( "bytes" + "errors" "github.com/GopeedLab/gopeed/pkg/download/engine/inject/file" "github.com/GopeedLab/gopeed/pkg/download/engine/inject/formdata" "github.com/GopeedLab/gopeed/pkg/download/engine/util" @@ -120,7 +121,7 @@ type XMLHttpRequest struct { requestHeaders map[string]string responseHeaders map[string]string aborted bool - proxyUrl *url.URL + proxyHandler func(r *http.Request) (*url.URL, error) Upload *XMLHttpRequestUpload `json:"upload"` Timeout int `json:"timeout"` @@ -202,9 +203,8 @@ func (xhr *XMLHttpRequest) Send(data goja.Value) { for k, v := range xhr.requestHeaders { req.Header.Set(k, v) } - transport := &http.Transport{} - if xhr.proxyUrl != nil { - transport.Proxy = http.ProxyURL(xhr.proxyUrl) + transport := &http.Transport{ + Proxy: xhr.proxyHandler, } client := &http.Client{ Transport: transport, @@ -213,7 +213,8 @@ func (xhr *XMLHttpRequest) Send(data goja.Value) { resp, err := client.Do(req) if err != nil { // handle timeout error - if err, ok := err.(net.Error); ok && err.Timeout() { + var ne net.Error + if errors.As(err, &ne) && ne.Timeout() { if xhr.Timeout > 0 { xhr.Upload.callOntimeout() xhr.callOntimeout() @@ -313,7 +314,7 @@ func (xhr *XMLHttpRequest) parseData(data goja.Value) any { return data.String() } -func Enable(runtime *goja.Runtime, proxyUrl *url.URL) error { +func Enable(runtime *goja.Runtime, proxyHandler func(r *http.Request) (*url.URL, error)) error { progressEvent := runtime.ToValue(func(call goja.ConstructorCall) *goja.Object { if len(call.Arguments) < 1 { util.ThrowTypeError(runtime, "Failed to construct 'ProgressEvent': 1 argument required, but only 0 present.") @@ -327,7 +328,7 @@ func Enable(runtime *goja.Runtime, proxyUrl *url.URL) error { }) xhr := runtime.ToValue(func(call goja.ConstructorCall) *goja.Object { instance := &XMLHttpRequest{ - proxyUrl: proxyUrl, + proxyHandler: proxyHandler, Upload: &XMLHttpRequestUpload{ EventProp: &EventProp{ eventListeners: make(map[string]func(event *ProgressEvent)), diff --git a/pkg/download/extension.go b/pkg/download/extension.go index 262bc5592..83955aaa6 100644 --- a/pkg/download/extension.go +++ b/pkg/download/extension.go @@ -199,7 +199,7 @@ func (d *Downloader) fetchExtensionByGit(url string, handler func(tempExtPath st return nil, err } proxyOptions := transport.ProxyOptions{} - proxyUrl := d.cfg.ProxyUrl() + proxyUrl := d.cfg.DownloaderStoreConfig.Proxy.ToUrl() if proxyUrl != nil { proxyOptions.URL = proxyUrl.Scheme + "://" + proxyUrl.Host proxyOptions.Username = proxyUrl.User.Username() @@ -341,7 +341,7 @@ func doTrigger[T any](d *Downloader, event ActivationEvent, req *base.Request, c req.Labels = make(map[string]string) } engine := engine.NewEngine(&engine.Config{ - ProxyURL: d.cfg.ProxyUrl(), + ProxyConfig: d.cfg.Proxy, }) defer engine.Close() err = engine.Runtime.Set("gopeed", gopeed) diff --git a/pkg/download/model.go b/pkg/download/model.go index c78521ce3..4fbc5b9d0 100644 --- a/pkg/download/model.go +++ b/pkg/download/model.go @@ -7,9 +7,7 @@ import ( "github.com/GopeedLab/gopeed/internal/protocol/http" "github.com/GopeedLab/gopeed/pkg/base" "github.com/GopeedLab/gopeed/pkg/util" - gonanoid "github.com/matoous/go-nanoid/v2" - "net/url" "sync" "time" ) @@ -87,7 +85,7 @@ type DownloaderConfig struct { ProductionMode bool - *DownloaderStoreConfig + *base.DownloaderStoreConfig } func (cfg *DownloaderConfig) Init() *DownloaderConfig { @@ -108,42 +106,3 @@ func (cfg *DownloaderConfig) Init() *DownloaderConfig { } return cfg } - -// DownloaderStoreConfig is the config that can restore the downloader. -type DownloaderStoreConfig struct { - FirstLoad bool `json:"-"` // FirstLoad is the flag that the config is first time init and not from store - - DownloadDir string `json:"downloadDir"` // DownloadDir is the default directory to save the downloaded files - MaxRunning int `json:"maxRunning"` // MaxRunning is the max running download count - ProtocolConfig map[string]any `json:"protocolConfig"` // ProtocolConfig is special config for each protocol - Extra map[string]any `json:"extra"` - Proxy *DownloaderProxyConfig `json:"proxy"` -} - -func (cfg *DownloaderStoreConfig) Init() *DownloaderStoreConfig { - if cfg.MaxRunning == 0 { - cfg.MaxRunning = 5 - } - if cfg.Proxy == nil { - cfg.Proxy = &DownloaderProxyConfig{} - } - return cfg -} - -func (cfg *DownloaderStoreConfig) ProxyUrl() *url.URL { - if cfg.Proxy == nil { - return nil - } - if cfg.Proxy.Enable == false || cfg.Proxy.Scheme == "" || cfg.Proxy.Host == "" { - return nil - } - return util.BuildProxyUrl(cfg.Proxy.Scheme, cfg.Proxy.Host, cfg.Proxy.Usr, cfg.Proxy.Pwd) -} - -type DownloaderProxyConfig struct { - Enable bool `json:"enable"` - Scheme string `json:"scheme"` - Host string `json:"host"` - Usr string `json:"usr"` - Pwd string `json:"pwd"` -} diff --git a/pkg/rest/api.go b/pkg/rest/api.go index d95964dd3..8a0ae75b9 100644 --- a/pkg/rest/api.go +++ b/pkg/rest/api.go @@ -142,7 +142,7 @@ func GetConfig(w http.ResponseWriter, r *http.Request) { } func PutConfig(w http.ResponseWriter, r *http.Request) { - var cfg download.DownloaderStoreConfig + var cfg base.DownloaderStoreConfig if ReadJson(r, w, &cfg) { if err := Downloader.PutConfig(&cfg); err != nil { WriteJson(w, model.NewErrorResult(err.Error())) @@ -309,7 +309,7 @@ func writeError(w http.ResponseWriter, msg string) { w.Write([]byte(msg)) } -func getServerConfig() *download.DownloaderStoreConfig { +func getServerConfig() *base.DownloaderStoreConfig { cfg, _ := Downloader.GetConfig() return cfg } diff --git a/pkg/rest/server_test.go b/pkg/rest/server_test.go index 7b001defd..ef6b86053 100644 --- a/pkg/rest/server_test.go +++ b/pkg/rest/server_test.go @@ -324,7 +324,7 @@ func TestGetTasks(t *testing.T) { func TestGetAndPutConfig(t *testing.T) { doTest(func() { - cfg := httpRequestCheckOk[*download.DownloaderStoreConfig](http.MethodGet, "/api/v1/config", nil) + cfg := httpRequestCheckOk[*base.DownloaderStoreConfig](http.MethodGet, "/api/v1/config", nil) cfg.DownloadDir = "./download" cfg.Extra = map[string]any{ "serverConfig": &Config{ @@ -335,7 +335,7 @@ func TestGetAndPutConfig(t *testing.T) { } httpRequestCheckOk[any](http.MethodPut, "/api/v1/config", cfg) - newCfg := httpRequestCheckOk[*download.DownloaderStoreConfig](http.MethodGet, "/api/v1/config", nil) + newCfg := httpRequestCheckOk[*base.DownloaderStoreConfig](http.MethodGet, "/api/v1/config", nil) if !test.JsonEqual(cfg, newCfg) { t.Errorf("GetAndPutConfig() got = %v, want %v", test.ToJson(newCfg), test.ToJson(cfg)) } diff --git a/pkg/util/url.go b/pkg/util/url.go index 0a6ac190b..a54c6780f 100644 --- a/pkg/util/url.go +++ b/pkg/util/url.go @@ -2,6 +2,7 @@ package util import ( "encoding/base64" + "net/http" "net/url" "regexp" "strings" @@ -51,3 +52,14 @@ func BuildProxyUrl(scheme, host, usr, pwd string) *url.URL { Host: host, } } + +// ProxyUrlToHandler gets the proxy handler from the proxy url. +func ProxyUrlToHandler(proxyUrl *url.URL) func(*http.Request) (*url.URL, error) { + if proxyUrl == nil { + return nil + } + if proxyUrl.Scheme == "system" { + return http.ProxyFromEnvironment + } + return http.ProxyURL(proxyUrl) +} diff --git a/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png index ebb599e91..9f4e06ba9 100644 Binary files a/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png and b/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png index bd992b241..c8caba582 100644 Binary files a/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png and b/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png index 8000e8509..83ee21862 100644 Binary files a/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png and b/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png index 4388230ac..44af22b31 100644 Binary files a/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png and b/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png index 86b1cfd27..707836984 100644 Binary files a/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png and b/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png index 9d5dacad0..1952bfc03 100644 Binary files a/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png and b/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png index d7ad89515..a1e74ca1d 100644 Binary files a/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png and b/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png index 8000e8509..83ee21862 100644 Binary files a/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png and b/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png index 327d490d4..43b67cf69 100644 Binary files a/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png and b/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png index 2e3603dd7..bd7c8ab9a 100644 Binary files a/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png and b/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png b/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png index 3a680a905..b6e266bfd 100644 Binary files a/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png and b/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png differ diff --git a/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png b/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png index d4be832e8..20c49aa99 100644 Binary files a/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png and b/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png differ diff --git a/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png b/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png index f3fb629b5..5f0207e74 100644 Binary files a/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png and b/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png differ diff --git a/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png b/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png index e63dad432..28178b4b7 100644 Binary files a/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png and b/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png differ diff --git a/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png index 2e3603dd7..bd7c8ab9a 100644 Binary files a/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png and b/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png index a7bbecdeb..d65ee6bad 100644 Binary files a/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png and b/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png b/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png index a5666c9fb..c176e16fa 100644 Binary files a/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png and b/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png differ diff --git a/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png b/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png index 5da20a9d0..65b6968ef 100644 Binary files a/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png and b/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png differ diff --git a/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png index 4a2479aaa..1f07d8b28 100644 Binary files a/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png and b/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png index adb453f2d..62e9ec89b 100644 Binary files a/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png and b/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png index 931b4c25b..c19c31bec 100644 Binary files a/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png and b/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/ui/flutter/lib/api/model/downloader_config.dart b/ui/flutter/lib/api/model/downloader_config.dart index 01c596cb4..5cab59455 100644 --- a/ui/flutter/lib/api/model/downloader_config.dart +++ b/ui/flutter/lib/api/model/downloader_config.dart @@ -78,6 +78,7 @@ class ExtraConfig { @JsonSerializable() class ProxyConfig { bool enable; + bool system; String scheme; String host; String usr; @@ -85,6 +86,7 @@ class ProxyConfig { ProxyConfig({ this.enable = false, + this.system = false, this.scheme = '', this.host = '', this.usr = '', diff --git a/ui/flutter/lib/api/model/downloader_config.g.dart b/ui/flutter/lib/api/model/downloader_config.g.dart index 6cd41ae61..662ad4836 100644 --- a/ui/flutter/lib/api/model/downloader_config.g.dart +++ b/ui/flutter/lib/api/model/downloader_config.g.dart @@ -9,7 +9,7 @@ part of 'downloader_config.dart'; DownloaderConfig _$DownloaderConfigFromJson(Map json) => DownloaderConfig( downloadDir: json['downloadDir'] as String? ?? '', - maxRunning: json['maxRunning'] as int? ?? 0, + maxRunning: (json['maxRunning'] as num?)?.toInt() ?? 0, ) ..protocolConfig = ProtocolConfig.fromJson( json['protocolConfig'] as Map?) @@ -39,7 +39,7 @@ Map _$ProtocolConfigToJson(ProtocolConfig instance) => HttpConfig _$HttpConfigFromJson(Map json) => HttpConfig( userAgent: json['userAgent'] as String? ?? 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36', - connections: json['connections'] as int? ?? 16, + connections: (json['connections'] as num?)?.toInt() ?? 16, useServerCtime: json['useServerCtime'] as bool? ?? false, ); @@ -51,7 +51,7 @@ Map _$HttpConfigToJson(HttpConfig instance) => }; BtConfig _$BtConfigFromJson(Map json) => BtConfig() - ..listenPort = json['listenPort'] as int + ..listenPort = (json['listenPort'] as num).toInt() ..trackers = (json['trackers'] as List).map((e) => e as String).toList(); @@ -74,6 +74,7 @@ Map _$ExtraConfigToJson(ExtraConfig instance) => ProxyConfig _$ProxyConfigFromJson(Map json) => ProxyConfig( enable: json['enable'] as bool? ?? false, + system: json['system'] as bool? ?? false, scheme: json['scheme'] as String? ?? '', host: json['host'] as String? ?? '', usr: json['usr'] as String? ?? '', @@ -83,6 +84,7 @@ ProxyConfig _$ProxyConfigFromJson(Map json) => ProxyConfig( Map _$ProxyConfigToJson(ProxyConfig instance) => { 'enable': instance.enable, + 'system': instance.system, 'scheme': instance.scheme, 'host': instance.host, 'usr': instance.usr, diff --git a/ui/flutter/lib/api/model/options.g.dart b/ui/flutter/lib/api/model/options.g.dart index b496e9fc6..40a1a8a4e 100644 --- a/ui/flutter/lib/api/model/options.g.dart +++ b/ui/flutter/lib/api/model/options.g.dart @@ -10,7 +10,7 @@ Options _$OptionsFromJson(Map json) => Options( name: json['name'] as String, path: json['path'] as String, selectFiles: (json['selectFiles'] as List?) - ?.map((e) => e as int) + ?.map((e) => (e as num).toInt()) .toList() ?? const [], extra: json['extra'], @@ -35,7 +35,7 @@ Map _$OptionsToJson(Options instance) { OptsExtraHttp _$OptsExtraHttpFromJson(Map json) => OptsExtraHttp() - ..connections = json['connections'] as int + ..connections = (json['connections'] as num).toInt() ..autoTorrent = json['autoTorrent'] as bool; Map _$OptsExtraHttpToJson(OptsExtraHttp instance) => diff --git a/ui/flutter/lib/api/model/resource.g.dart b/ui/flutter/lib/api/model/resource.g.dart index 2feddd86d..0a5e8fc8a 100644 --- a/ui/flutter/lib/api/model/resource.g.dart +++ b/ui/flutter/lib/api/model/resource.g.dart @@ -8,7 +8,7 @@ part of 'resource.dart'; Resource _$ResourceFromJson(Map json) => Resource( name: json['name'] as String? ?? "", - size: json['size'] as int? ?? 0, + size: (json['size'] as num?)?.toInt() ?? 0, range: json['range'] as bool? ?? false, files: (json['files'] as List) .map((e) => FileInfo.fromJson(e as Map)) @@ -27,7 +27,7 @@ Map _$ResourceToJson(Resource instance) => { FileInfo _$FileInfoFromJson(Map json) => FileInfo( path: json['path'] as String? ?? "", name: json['name'] as String, - size: json['size'] as int? ?? 0, + size: (json['size'] as num?)?.toInt() ?? 0, req: json['req'] == null ? null : Request.fromJson(json['req'] as Map), diff --git a/ui/flutter/lib/api/model/result.g.dart b/ui/flutter/lib/api/model/result.g.dart index 452a7c7ca..03d4f734e 100644 --- a/ui/flutter/lib/api/model/result.g.dart +++ b/ui/flutter/lib/api/model/result.g.dart @@ -11,7 +11,7 @@ Result _$ResultFromJson( T Function(Object? json) fromJsonT, ) => Result( - code: json['code'] as int, + code: (json['code'] as num).toInt(), msg: json['msg'] as String?, data: _$nullableGenericFromJson(json['data'], fromJsonT), ); diff --git a/ui/flutter/lib/api/model/task.g.dart b/ui/flutter/lib/api/model/task.g.dart index b5974a168..c0a77b956 100644 --- a/ui/flutter/lib/api/model/task.g.dart +++ b/ui/flutter/lib/api/model/task.g.dart @@ -34,9 +34,9 @@ const _$StatusEnumMap = { }; Progress _$ProgressFromJson(Map json) => Progress( - used: json['used'] as int, - speed: json['speed'] as int, - downloaded: json['downloaded'] as int, + used: (json['used'] as num).toInt(), + speed: (json['speed'] as num).toInt(), + downloaded: (json['downloaded'] as num).toInt(), ); Map _$ProgressToJson(Progress instance) => { diff --git a/ui/flutter/lib/app/modules/setting/views/setting_view.dart b/ui/flutter/lib/app/modules/setting/views/setting_view.dart index 0340da0ed..02bfbbbd3 100644 --- a/ui/flutter/lib/app/modules/setting/views/setting_view.dart +++ b/ui/flutter/lib/app/modules/setting/views/setting_view.dart @@ -7,6 +7,7 @@ import 'package:get/get.dart'; import 'package:intl/intl.dart'; import 'package:url_launcher/url_launcher.dart'; +import '../../../../api/model/downloader_config.dart'; import '../../../../i18n/message.dart'; import '../../../../util/input_formatter.dart'; import '../../../../util/locale_manager.dart'; @@ -408,29 +409,53 @@ class SettingView extends GetView { } // advanced config proxy items start + final proxy = downloaderCfg.value.proxy; final buildProxy = _buildConfigItem( 'proxy', - () => downloaderCfg.value.proxy.enable - ? '${downloaderCfg.value.proxy.scheme}://${downloaderCfg.value.proxy.host}' - : 'notSet'.tr, + () { + switch (proxy.proxyMode) { + case ProxyModeEnum.noProxy: + return 'noProxy'.tr; + case ProxyModeEnum.systemProxy: + return 'systemProxy'.tr; + case ProxyModeEnum.customProxy: + return '${downloaderCfg.value.proxy.scheme}://${downloaderCfg.value.proxy.host}'; + } + }, (Key key) { - final proxy = downloaderCfg.value.proxy; - - final switcher = Switch( - value: proxy.enable, - onChanged: (bool value) async { - if (value != proxy.enable) { - downloaderCfg.update((val) { - val!.proxy.enable = value; - }); + final mode = SizedBox( + width: 150, + child: DropdownButtonFormField( + value: proxy.proxyMode, + onChanged: (value) async { + if (value != null && value != proxy.proxyMode) { + proxy.proxyMode = value; + downloaderCfg.update((val) { + val!.proxy = proxy; + }); - await debounceSave(); - } - }, + await debounceSave(); + } + }, + items: [ + DropdownMenuItem( + value: ProxyModeEnum.noProxy, + child: Text('noProxy'.tr), + ), + DropdownMenuItem( + value: ProxyModeEnum.systemProxy, + child: Text('systemProxy'.tr), + ), + DropdownMenuItem( + value: ProxyModeEnum.customProxy, + child: Text('customProxy'.tr), + ), + ], + ), ); final scheme = SizedBox( - width: 100, + width: 150, child: DropdownButtonFormField( value: proxy.scheme, onChanged: (value) async { @@ -478,7 +503,7 @@ class SettingView extends GetView { ipController.addListener(updateAddress); portController.addListener(updateAddress); - final server = [ + final server = Row(children: [ Flexible( child: TextFormField( controller: ipController, @@ -503,12 +528,12 @@ class SettingView extends GetView { ], ), ), - ]; + ]); final usrController = TextEditingController(text: proxy.usr); final pwdController = TextEditingController(text: proxy.pwd); - final auth = [ + final auth = Row(children: [ Flexible( child: TextFormField( controller: usrController, @@ -528,20 +553,21 @@ class SettingView extends GetView { ), ), ), - ]; + ]); + + List customView() { + if (proxy.proxyMode != ProxyModeEnum.customProxy) { + return []; + } + return [scheme, server, auth]; + } return Form( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: _addPadding([ - switcher, - scheme, - Row( - children: server, - ), - Row( - children: auth, - ), + mode, + ...customView(), ]), ), ); @@ -857,3 +883,37 @@ class SettingView extends GetView { } } } + +enum ProxyModeEnum { + noProxy, + systemProxy, + customProxy, +} + +extension ProxyMode on ProxyConfig { + ProxyModeEnum get proxyMode { + if (!enable) { + return ProxyModeEnum.noProxy; + } + if (system) { + return ProxyModeEnum.systemProxy; + } + return ProxyModeEnum.customProxy; + } + + set proxyMode(ProxyModeEnum value) { + switch (value) { + case ProxyModeEnum.noProxy: + enable = false; + break; + case ProxyModeEnum.systemProxy: + enable = true; + system = true; + break; + case ProxyModeEnum.customProxy: + enable = true; + system = false; + break; + } + } +} diff --git a/ui/flutter/lib/core/common/start_config.g.dart b/ui/flutter/lib/core/common/start_config.g.dart index 8732accb0..85e67b856 100644 --- a/ui/flutter/lib/core/common/start_config.g.dart +++ b/ui/flutter/lib/core/common/start_config.g.dart @@ -11,7 +11,7 @@ StartConfig _$StartConfigFromJson(Map json) => StartConfig() ..address = json['address'] as String ..storage = json['storage'] as String ..storageDir = json['storageDir'] as String - ..refreshInterval = json['refreshInterval'] as int + ..refreshInterval = (json['refreshInterval'] as num).toInt() ..apiToken = json['apiToken'] as String; Map _$StartConfigToJson(StartConfig instance) => diff --git a/ui/flutter/lib/i18n/langs/en_us.dart b/ui/flutter/lib/i18n/langs/en_us.dart index 0c64beb57..22aa8a75e 100644 --- a/ui/flutter/lib/i18n/langs/en_us.dart +++ b/ui/flutter/lib/i18n/langs/en_us.dart @@ -82,6 +82,9 @@ const enUS = { 'serviceText': 'Running', 'network': 'Network', 'proxy': 'Proxy', + 'noProxy': 'No Proxy', + 'systemProxy': 'System Proxy', + 'customProxy': 'Custom Proxy', 'server': 'Server', 'username': 'Username', 'password': 'Password', diff --git a/ui/flutter/lib/i18n/langs/zh_cn.dart b/ui/flutter/lib/i18n/langs/zh_cn.dart index a18de6a07..188d3eab6 100644 --- a/ui/flutter/lib/i18n/langs/zh_cn.dart +++ b/ui/flutter/lib/i18n/langs/zh_cn.dart @@ -80,6 +80,9 @@ const zhCN = { 'serviceText': '运行中', 'network': '网络', 'proxy': '代理', + 'noProxy': '不使用代理', + 'systemProxy': '系统代理', + 'customProxy': '自定义代理', 'server': '服务器', 'username': '用户名', 'password': '密码', diff --git a/ui/flutter/lib/i18n/langs/zh_tw.dart b/ui/flutter/lib/i18n/langs/zh_tw.dart index 30c6ff66c..044227f26 100644 --- a/ui/flutter/lib/i18n/langs/zh_tw.dart +++ b/ui/flutter/lib/i18n/langs/zh_tw.dart @@ -80,6 +80,9 @@ const zhTW = { 'serviceText': '執行中', 'network': '網路', 'proxy': '代理', + 'noProxy': '不使用代理', + 'systemProxy': '系統代理', + 'customProxy': '自定義代理', 'server': '伺服器', 'username': '名稱', 'password': '密碼', diff --git a/ui/flutter/linux/packaging/deb/make_config.yaml b/ui/flutter/linux/packaging/deb/make_config.yaml index da58a5da9..a753242c0 100644 --- a/ui/flutter/linux/packaging/deb/make_config.yaml +++ b/ui/flutter/linux/packaging/deb/make_config.yaml @@ -13,8 +13,8 @@ essential: false icon: assets/icon/icon.svg dependencies: - - libayatana-appindicator3-1 - - gir1.2-ayatanaappindicator3-0.1 + - libappindicator3-1 | libayatana-appindicator3-1 + - gir1.2-appindicator3-0.1 | gir1.2-ayatanaappindicator3-0.1 keywords: - Application diff --git a/ui/flutter/linux/packaging/rpm/make_config.yaml b/ui/flutter/linux/packaging/rpm/make_config.yaml new file mode 100644 index 000000000..774e97284 --- /dev/null +++ b/ui/flutter/linux/packaging/rpm/make_config.yaml @@ -0,0 +1,24 @@ +display_name: Gopeed +icon: assets/icon/icon.svg +summary: A modern download manager that supports all platforms. Built with Golang and Flutter. +group: Applications/Internet +vendor: monkeyWie +packager: madoka773 +packagerEmail: valigarmanda55@gmail.com +license: GPL-3.0-or-later +url: https://github.com/GopeedLab/gopeed + +description: A modern download manager that supports all platforms. Built with Golang and Flutter. +keywords: + - Application + - DownloadManager + - Network + - Utility + +generic_name: Download Manager + +categories: + - Network + - Utility + +startup_notify: true