mirror of
https://github.com/GopeedLab/gopeed.git
synced 2026-11-11 10:54:08 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
70c16195ef | ||
|
|
eb9d966855 | ||
|
|
0e3ca912a7 | ||
|
|
b3e372326f | ||
|
|
0100f0131d | ||
|
|
e4c3b449a4 | ||
|
|
aaa8cd6406 | ||
|
|
328422b3b8 | ||
|
|
17cce89ef1 | ||
|
|
d9c5ca339f | ||
|
|
1e08ecd8db | ||
|
|
34bcbd9571 |
No files matched your search
+27
-19
@@ -3,6 +3,10 @@ name: build
|
|||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
|
env:
|
||||||
|
GO_VERSION: 1.19
|
||||||
|
FLUTTER_VERSION: 3.7.12
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
|
|
||||||
@@ -24,11 +28,12 @@ jobs:
|
|||||||
needs: [get-release]
|
needs: [get-release]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-go@v3
|
- uses: actions/setup-go@v4
|
||||||
with:
|
with:
|
||||||
go-version: "^1.19"
|
go-version: ${{ env.GO_VERSION }}
|
||||||
cache: true
|
|
||||||
- uses: subosito/flutter-action@v2
|
- uses: subosito/flutter-action@v2
|
||||||
|
with:
|
||||||
|
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||||
- name: Build
|
- name: Build
|
||||||
env:
|
env:
|
||||||
VERSION: ${{ needs.get-release.outputs.tag_name }}
|
VERSION: ${{ needs.get-release.outputs.tag_name }}
|
||||||
@@ -123,10 +128,9 @@ jobs:
|
|||||||
needs: [get-release]
|
needs: [get-release]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-go@v3
|
- uses: actions/setup-go@v4
|
||||||
with:
|
with:
|
||||||
go-version: "^1.19"
|
go-version: ${{ env.GO_VERSION }}
|
||||||
cache: true
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
docker pull techknowlogick/xgo:go-1.19.x
|
docker pull techknowlogick/xgo:go-1.19.x
|
||||||
@@ -142,14 +146,15 @@ jobs:
|
|||||||
needs: [get-release, build-macos-arm64-lib]
|
needs: [get-release, build-macos-arm64-lib]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-go@v3
|
- uses: actions/setup-go@v4
|
||||||
with:
|
with:
|
||||||
go-version: "^1.19"
|
go-version: ${{ env.GO_VERSION }}
|
||||||
cache: true
|
|
||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 16
|
node-version: 16
|
||||||
- uses: subosito/flutter-action@v2
|
- uses: subosito/flutter-action@v2
|
||||||
|
with:
|
||||||
|
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||||
- name: Install appdmg
|
- name: Install appdmg
|
||||||
run: npm install -g appdmg
|
run: npm install -g appdmg
|
||||||
- uses: actions/download-artifact@v3
|
- uses: actions/download-artifact@v3
|
||||||
@@ -197,14 +202,15 @@ jobs:
|
|||||||
needs: [get-release]
|
needs: [get-release]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-go@v3
|
- uses: actions/setup-go@v4
|
||||||
with:
|
with:
|
||||||
go-version: "^1.19"
|
go-version: ${{ env.GO_VERSION }}
|
||||||
cache: true
|
|
||||||
- uses: subosito/flutter-action@v2
|
- uses: subosito/flutter-action@v2
|
||||||
|
with:
|
||||||
|
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||||
- run: |
|
- run: |
|
||||||
sudo apt-get update -y
|
sudo apt-get update -y
|
||||||
sudo apt-get install -y ninja-build libgtk-3-dev
|
sudo apt-get install -y ninja-build libgtk-3-dev libayatana-appindicator3-dev
|
||||||
- name: Build
|
- name: Build
|
||||||
env:
|
env:
|
||||||
VERSION: ${{ needs.get-release.outputs.tag_name }}
|
VERSION: ${{ needs.get-release.outputs.tag_name }}
|
||||||
@@ -256,15 +262,16 @@ jobs:
|
|||||||
needs: [get-release]
|
needs: [get-release]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-go@v3
|
- uses: actions/setup-go@v4
|
||||||
with:
|
with:
|
||||||
go-version: "^1.19"
|
go-version: ${{ env.GO_VERSION }}
|
||||||
cache: true
|
|
||||||
- uses: actions/setup-java@v3
|
- uses: actions/setup-java@v3
|
||||||
with:
|
with:
|
||||||
distribution: "zulu"
|
distribution: "zulu"
|
||||||
java-version: "11"
|
java-version: "11"
|
||||||
- uses: subosito/flutter-action@v2
|
- uses: subosito/flutter-action@v2
|
||||||
|
with:
|
||||||
|
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||||
- name: Build
|
- name: Build
|
||||||
env:
|
env:
|
||||||
VERSION: ${{ needs.get-release.outputs.tag_name }}
|
VERSION: ${{ needs.get-release.outputs.tag_name }}
|
||||||
@@ -292,11 +299,12 @@ jobs:
|
|||||||
needs: [get-release]
|
needs: [get-release]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-go@v3
|
- uses: actions/setup-go@v4
|
||||||
with:
|
with:
|
||||||
go-version: "^1.19"
|
go-version: ${{ env.GO_VERSION }}
|
||||||
cache: true
|
|
||||||
- uses: subosito/flutter-action@v2
|
- uses: subosito/flutter-action@v2
|
||||||
|
with:
|
||||||
|
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||||
- name: Build
|
- name: Build
|
||||||
env:
|
env:
|
||||||
VERSION: ${{ needs.get-release.outputs.tag_name }}
|
VERSION: ${{ needs.get-release.outputs.tag_name }}
|
||||||
|
|||||||
@@ -16,6 +16,9 @@ on:
|
|||||||
- "go.sum"
|
- "go.sum"
|
||||||
- "Dockerfile"
|
- "Dockerfile"
|
||||||
|
|
||||||
|
env:
|
||||||
|
GO_VERSION: 1.19
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
|
|
||||||
@@ -28,9 +31,9 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-go@v3
|
- uses: actions/setup-go@v4
|
||||||
with:
|
with:
|
||||||
go-version: "^1.19"
|
go-version: ${{ env.GO_VERSION }}
|
||||||
- uses: release-drafter/release-drafter@v5
|
- uses: release-drafter/release-drafter@v5
|
||||||
id: create_release
|
id: create_release
|
||||||
env:
|
env:
|
||||||
|
|||||||
@@ -26,6 +26,10 @@ on:
|
|||||||
- "go.mod"
|
- "go.mod"
|
||||||
- "go.sum"
|
- "go.sum"
|
||||||
|
|
||||||
|
env:
|
||||||
|
GO_VERSION: 1.19
|
||||||
|
FLUTTER_VERSION: 3.7.12
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
# lint:
|
# lint:
|
||||||
# name: Lint
|
# name: Lint
|
||||||
@@ -45,10 +49,9 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-go@v3
|
- uses: actions/setup-go@v4
|
||||||
with:
|
with:
|
||||||
go-version: "^1.19"
|
go-version: ${{ env.GO_VERSION }}
|
||||||
cache: true
|
|
||||||
- name: Run Unit tests.
|
- name: Run Unit tests.
|
||||||
run: go test $(go list ./... | grep -v /cmd/ | grep -v /bind/ ) -v -covermode=count -coverprofile=coverage.txt
|
run: go test $(go list ./... | grep -v /cmd/ | grep -v /bind/ ) -v -covermode=count -coverprofile=coverage.txt
|
||||||
- uses: codecov/codecov-action@v3
|
- uses: codecov/codecov-action@v3
|
||||||
@@ -61,14 +64,15 @@ jobs:
|
|||||||
needs: [test]
|
needs: [test]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-go@v3
|
- uses: actions/setup-go@v4
|
||||||
with:
|
with:
|
||||||
go-version: "^1.19"
|
go-version: ${{ env.GO_VERSION }}
|
||||||
cache: true
|
|
||||||
- uses: subosito/flutter-action@v2
|
- uses: subosito/flutter-action@v2
|
||||||
|
with:
|
||||||
|
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||||
- run: |
|
- run: |
|
||||||
sudo apt-get update -y
|
sudo apt-get update -y
|
||||||
sudo apt-get install -y ninja-build libgtk-3-dev
|
sudo apt-get install -y ninja-build libgtk-3-dev libayatana-appindicator3-dev
|
||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
go build -tags nosqlite -ldflags="-w -s" -buildmode=c-shared -o ui/flutter/linux/bundle/lib/libgopeed.so github.com/GopeedLab/gopeed/bind/desktop
|
go build -tags nosqlite -ldflags="-w -s" -buildmode=c-shared -o ui/flutter/linux/bundle/lib/libgopeed.so github.com/GopeedLab/gopeed/bind/desktop
|
||||||
|
|||||||
@@ -153,6 +153,18 @@ go build -tags nosqlite,web -ldflags="-s -w" -o bin/ github.com/GopeedLab/gopeed
|
|||||||
go run cmd/web/main.go
|
go run cmd/web/main.go
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Thanks
|
||||||
|
|
||||||
|
### Contributors
|
||||||
|
|
||||||
|
<a href="https://github.com/GopeedLab/gopeed/graphs/contributors">
|
||||||
|
<img src="https://contrib.rocks/image?repo=GopeedLab/gopeed" />
|
||||||
|
</a>
|
||||||
|
|
||||||
|
### JetBrains
|
||||||
|
|
||||||
|
[](https://www.jetbrains.com/?from=gopeed)
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
[GPLv3](LICENSE)
|
[GPLv3](LICENSE)
|
||||||
@@ -152,6 +152,18 @@ go build -tags nosqlite,web -ldflags="-s -w" -o bin/ github.com/GopeedLab/gopeed
|
|||||||
go run cmd/web/main.go
|
go run cmd/web/main.go
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 感谢
|
||||||
|
|
||||||
|
### 贡献者
|
||||||
|
|
||||||
|
<a href="https://github.com/GopeedLab/gopeed/graphs/contributors">
|
||||||
|
<img src="https://contrib.rocks/image?repo=GopeedLab/gopeed" />
|
||||||
|
</a>
|
||||||
|
|
||||||
|
### JetBrains
|
||||||
|
|
||||||
|
[](https://www.jetbrains.com/?from=gopeed)
|
||||||
|
|
||||||
## 开源许可
|
## 开源许可
|
||||||
|
|
||||||
基于 [GPLv3](LICENSE) 协议开源。
|
基于 [GPLv3](LICENSE) 协议开源。
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<svg height="105" viewBox="0 0 105 105" width="105" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><linearGradient id="a" gradientUnits="userSpaceOnUse" x1="99.03" x2="61.54" y1="59.18" y2="90.91"><stop offset="0" stop-color="#087cfa"/><stop offset=".02" stop-color="#0d7bfa"/><stop offset=".37" stop-color="#5566f9"/><stop offset=".66" stop-color="#8a57f8"/><stop offset=".88" stop-color="#ab4ef7"/><stop offset="1" stop-color="#b74af7"/></linearGradient><linearGradient id="b" x1="37.54" x2="60.33" xlink:href="#a" y1="34.26" y2="8.33"/><linearGradient id="c" gradientUnits="userSpaceOnUse" x1="17.84" x2="87.09" y1="88.36" y2="43.86"><stop offset="0" stop-color="#087cfa"/><stop offset=".1" stop-color="#1598d3"/><stop offset=".22" stop-color="#23b6aa"/><stop offset=".35" stop-color="#2dcc8b"/><stop offset=".46" stop-color="#35dd74"/><stop offset=".57" stop-color="#39e767"/><stop offset=".67" stop-color="#3bea62"/></linearGradient><path d="m89.06 41.63 11.44 25.37-19.58 33.5-12.06-29.28z" fill="url(#a)"/><path d="m68.86 71.22 12.06 29.28-30.34-10.43z" fill="#b74af7"/><path d="m72.13 31.61-6.22-27.11h-34.49l-26.92 41.18 7.79 18.92-7.79 17.27 55.75-47.43z" fill="url(#b)"/><path d="m100.5 24.88-40.25 9.56-55.75 47.43 35.96 18.63 28.4-29.28z" fill="url(#c)"/><path d="m22.5 22.5h60v60h-60z"/><g fill="#fff"><path d="m28.13 71.25h22.5v3.75h-22.5z"/><path d="m26.44 42.81v-.07a12.7 12.7 0 0 1 12.97-12.74 13.62 13.62 0 0 1 9.73 3.39l-3.43 4.14a9.13 9.13 0 0 0 -6.47-2.53c-4 0-7.11 3.5-7.11 7.7v.07c0 4.53 3.11 7.85 7.5 7.85a8.67 8.67 0 0 0 5.12-1.49v-3.56h-5.48v-4.7h10.73v10.74a16.07 16.07 0 0 1 -10.57 3.93c-7.69 0-12.99-5.41-12.99-12.73z"/><path d="m52.3 42.81v-.07a12.79 12.79 0 0 1 13.19-12.74c7.63 0 13.11 5.69 13.11 12.72v.07a12.79 12.79 0 0 1 -13.18 12.8c-7.64-.05-13.12-5.74-13.12-12.78zm20.61 0v-.07a7.51 7.51 0 0 0 -7.49-7.74 7.4 7.4 0 0 0 -7.42 7.67v.07a7.52 7.52 0 0 0 7.5 7.78 7.4 7.4 0 0 0 7.41-7.71z"/></g></svg>
|
||||||
|
After Width: | Height: | Size: 1.9 KiB |
@@ -4,6 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"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/protocol/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -16,7 +17,9 @@ func main() {
|
|||||||
}
|
}
|
||||||
}).
|
}).
|
||||||
Create(&base.Options{
|
Create(&base.Options{
|
||||||
Connections: 8,
|
Extra: http.OptsExtra{
|
||||||
|
Connections: 8,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
|||||||
+9
-2
@@ -13,9 +13,17 @@ import (
|
|||||||
const progressWidth = 20
|
const progressWidth = 20
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
args := parse()
|
//args := parse()
|
||||||
|
var dir = "./"
|
||||||
|
var connections = 8
|
||||||
|
args := &args{
|
||||||
|
url: "https://sel-kor-ping.vultr.com/vultr.com.100MB.bin",
|
||||||
|
dir: &dir,
|
||||||
|
connections: &connections,
|
||||||
|
}
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(1)
|
||||||
_, err := download.Boot().
|
_, err := download.Boot().
|
||||||
URL(args.url).
|
URL(args.url).
|
||||||
Listener(func(event *download.Event) {
|
Listener(func(event *download.Event) {
|
||||||
@@ -46,7 +54,6 @@ func main() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
wg.Add(1)
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+4
-6
@@ -3,7 +3,6 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
_ "embed"
|
_ "embed"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/GopeedLab/gopeed/pkg/download"
|
|
||||||
"github.com/GopeedLab/gopeed/pkg/rest"
|
"github.com/GopeedLab/gopeed/pkg/rest"
|
||||||
"github.com/GopeedLab/gopeed/pkg/rest/model"
|
"github.com/GopeedLab/gopeed/pkg/rest/model"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -20,18 +19,17 @@ func Start(cfg *model.StartConfig) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
exist, _, err := rest.Downloader.GetConfig()
|
downloadCfg, err := rest.Downloader.GetConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
if !exist {
|
if downloadCfg.FirstLoad {
|
||||||
// Set default download dir to user download dir
|
// Set default download dir to user download dir
|
||||||
userDir, err := os.UserHomeDir()
|
userDir, err := os.UserHomeDir()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
downloadDir := filepath.Join(userDir, "Downloads")
|
downloadDir := filepath.Join(userDir, "Downloads")
|
||||||
rest.Downloader.PutConfig(&download.DownloaderStoreConfig{
|
downloadCfg.DownloadDir = downloadDir
|
||||||
DownloadDir: downloadDir,
|
rest.Downloader.PutConfig(downloadCfg)
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,9 +3,10 @@ package base
|
|||||||
type Status string
|
type Status string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
DownloadStatusReady Status = "ready"
|
DownloadStatusReady Status = "ready" // task create but not start
|
||||||
DownloadStatusRunning Status = "running"
|
DownloadStatusRunning Status = "running"
|
||||||
DownloadStatusPause Status = "pause"
|
DownloadStatusPause Status = "pause"
|
||||||
|
DownloadStatusWait Status = "wait" // task is wait for running
|
||||||
DownloadStatusError Status = "error"
|
DownloadStatusError Status = "error"
|
||||||
DownloadStatusDone Status = "done"
|
DownloadStatusDone Status = "done"
|
||||||
)
|
)
|
||||||
|
|||||||
+187
-65
@@ -7,8 +7,10 @@ import (
|
|||||||
"github.com/GopeedLab/gopeed/pkg/base"
|
"github.com/GopeedLab/gopeed/pkg/base"
|
||||||
"github.com/GopeedLab/gopeed/pkg/util"
|
"github.com/GopeedLab/gopeed/pkg/util"
|
||||||
gonanoid "github.com/matoous/go-nanoid/v2"
|
gonanoid "github.com/matoous/go-nanoid/v2"
|
||||||
|
"math"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
@@ -36,15 +38,15 @@ type Progress struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Downloader struct {
|
type Downloader struct {
|
||||||
|
cfg *DownloaderConfig
|
||||||
fetchBuilders map[string]fetcher.FetcherBuilder
|
fetchBuilders map[string]fetcher.FetcherBuilder
|
||||||
fetcherMap map[string]fetcher.Fetcher
|
fetcherMap map[string]fetcher.Fetcher
|
||||||
storage Storage
|
storage Storage
|
||||||
tasks []*Task
|
tasks []*Task
|
||||||
listener Listener
|
listener Listener
|
||||||
|
|
||||||
refreshInterval int
|
lock *sync.Mutex
|
||||||
lock *sync.Mutex
|
closed atomic.Bool
|
||||||
closed atomic.Bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDownloader(cfg *DownloaderConfig) *Downloader {
|
func NewDownloader(cfg *DownloaderConfig) *Downloader {
|
||||||
@@ -54,12 +56,13 @@ func NewDownloader(cfg *DownloaderConfig) *Downloader {
|
|||||||
cfg.Init()
|
cfg.Init()
|
||||||
|
|
||||||
d := &Downloader{
|
d := &Downloader{
|
||||||
|
cfg: cfg,
|
||||||
|
|
||||||
fetchBuilders: make(map[string]fetcher.FetcherBuilder),
|
fetchBuilders: make(map[string]fetcher.FetcherBuilder),
|
||||||
fetcherMap: make(map[string]fetcher.Fetcher),
|
fetcherMap: make(map[string]fetcher.Fetcher),
|
||||||
|
|
||||||
refreshInterval: cfg.RefreshInterval,
|
storage: cfg.Storage,
|
||||||
storage: cfg.Storage,
|
lock: &sync.Mutex{},
|
||||||
lock: &sync.Mutex{},
|
|
||||||
}
|
}
|
||||||
for _, f := range cfg.FetchBuilders {
|
for _, f := range cfg.FetchBuilders {
|
||||||
for _, p := range f.Schemes() {
|
for _, p := range f.Schemes() {
|
||||||
@@ -74,6 +77,21 @@ func (d *Downloader) Setup() error {
|
|||||||
if err := d.storage.Setup([]string{bucketTask, bucketSave, bucketConfig}); err != nil {
|
if err := d.storage.Setup([]string{bucketTask, bucketSave, bucketConfig}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
// load config from storage
|
||||||
|
var cfg DownloaderStoreConfig
|
||||||
|
exist, err := d.storage.Get(bucketConfig, "config", &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if exist {
|
||||||
|
d.cfg.DownloaderStoreConfig = &cfg
|
||||||
|
} else {
|
||||||
|
d.cfg.DownloaderStoreConfig = &DownloaderStoreConfig{
|
||||||
|
FirstLoad: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// init default config
|
||||||
|
d.cfg.DownloaderStoreConfig.Init()
|
||||||
// load tasks from storage
|
// load tasks from storage
|
||||||
var tasks []*Task
|
var tasks []*Task
|
||||||
if err := d.storage.List(bucketTask, &tasks); err != nil {
|
if err := d.storage.List(bucketTask, &tasks); err != nil {
|
||||||
@@ -94,6 +112,10 @@ func (d *Downloader) Setup() error {
|
|||||||
task.Status = base.DownloadStatusPause
|
task.Status = base.DownloadStatusPause
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// sort by create time
|
||||||
|
sort.Slice(tasks, func(i, j int) bool {
|
||||||
|
return tasks[i].CreatedAt.Before(tasks[j].CreatedAt)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
d.tasks = tasks
|
d.tasks = tasks
|
||||||
|
|
||||||
@@ -106,9 +128,7 @@ func (d *Downloader) Setup() error {
|
|||||||
func() {
|
func() {
|
||||||
task.lock.Lock()
|
task.lock.Lock()
|
||||||
defer task.lock.Unlock()
|
defer task.lock.Unlock()
|
||||||
if task.Status == base.DownloadStatusDone ||
|
if task.Status != base.DownloadStatusRunning {
|
||||||
task.Status == base.DownloadStatusError ||
|
|
||||||
task.Status == base.DownloadStatusPause {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// check if task is deleted
|
// check if task is deleted
|
||||||
@@ -118,7 +138,7 @@ func (d *Downloader) Setup() error {
|
|||||||
|
|
||||||
current := task.fetcher.Progress().TotalDownloaded()
|
current := task.fetcher.Progress().TotalDownloaded()
|
||||||
task.Progress.Used = task.timer.Used()
|
task.Progress.Used = task.timer.Used()
|
||||||
task.Progress.Speed = task.calcSpeed(current-task.Progress.Downloaded, float64(d.refreshInterval)/1000)
|
task.Progress.Speed = task.calcSpeed(current-task.Progress.Downloaded, float64(d.cfg.RefreshInterval)/1000)
|
||||||
task.Progress.Downloaded = current
|
task.Progress.Downloaded = current
|
||||||
d.emit(EventKeyProgress, task)
|
d.emit(EventKeyProgress, task)
|
||||||
|
|
||||||
@@ -136,7 +156,7 @@ func (d *Downloader) Setup() error {
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
time.Sleep(time.Millisecond * time.Duration(d.refreshInterval))
|
time.Sleep(time.Millisecond * time.Duration(d.cfg.RefreshInterval))
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
return nil
|
return nil
|
||||||
@@ -185,7 +205,7 @@ func (d *Downloader) Resolve(req *base.Request) (rr *ResolveResult, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Downloader) DirectCreate(req *base.Request, opts *base.Options) (taskId string, err error) {
|
func (d *Downloader) CreateDirect(req *base.Request, opts *base.Options) (taskId string, err error) {
|
||||||
rr, err := d.Resolve(req)
|
rr, err := d.Resolve(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@@ -201,6 +221,8 @@ func (d *Downloader) Create(rrId string, opts *base.Options) (taskId string, err
|
|||||||
if !ok {
|
if !ok {
|
||||||
return "", errors.New("invalid resource id")
|
return "", errors.New("invalid resource id")
|
||||||
}
|
}
|
||||||
|
defer delete(d.fetcherMap, rrId)
|
||||||
|
|
||||||
meta := fetcher.Meta()
|
meta := fetcher.Meta()
|
||||||
meta.Opts = opts
|
meta.Opts = opts
|
||||||
res := meta.Res
|
res := meta.Res
|
||||||
@@ -211,13 +233,11 @@ func (d *Downloader) Create(rrId string, opts *base.Options) (taskId string, err
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if opts.Path == "" {
|
if opts.Path == "" {
|
||||||
exist, storeConfig, err := d.GetConfig()
|
storeConfig, err := d.GetConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
if exist {
|
opts.Path = storeConfig.DownloadDir
|
||||||
opts.Path = storeConfig.DownloadDir
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fb, err := d.parseFb(fetcher.Meta().Req.URL)
|
fb, err := d.parseFb(fetcher.Meta().Req.URL)
|
||||||
@@ -253,36 +273,27 @@ func (d *Downloader) Create(rrId string, opts *base.Options) (taskId string, err
|
|||||||
task.fetcher = fetcher
|
task.fetcher = fetcher
|
||||||
task.Meta = fetcher.Meta()
|
task.Meta = fetcher.Meta()
|
||||||
task.Progress = &Progress{}
|
task.Progress = &Progress{}
|
||||||
task.Status = base.DownloadStatusRunning
|
task.Status = base.DownloadStatusReady
|
||||||
// calculate select files size
|
// calculate select files size
|
||||||
for _, selectIndex := range opts.SelectFiles {
|
for _, selectIndex := range opts.SelectFiles {
|
||||||
task.Size += res.Files[selectIndex].Size
|
task.Size += res.Files[selectIndex].Size
|
||||||
}
|
}
|
||||||
initTask(task)
|
initTask(task)
|
||||||
task.timer.Start()
|
|
||||||
err = func() error {
|
err = func() error {
|
||||||
d.lock.Lock()
|
d.lock.Lock()
|
||||||
defer d.lock.Unlock()
|
defer d.lock.Unlock()
|
||||||
if err := d.storage.Put(bucketTask, task.ID, task.clone()); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
d.tasks = append(d.tasks, task)
|
d.tasks = append(d.tasks, task)
|
||||||
return nil
|
return d.storage.Put(bucketTask, task.ID, task.clone())
|
||||||
}()
|
}()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
d.emit(EventKeyStart, task)
|
|
||||||
taskId = task.ID
|
taskId = task.ID
|
||||||
go func() {
|
d.tryRunning(func(remain int) {
|
||||||
err := fetcher.Start()
|
if remain > 0 {
|
||||||
if err != nil {
|
err = d.start(task)
|
||||||
d.emit(EventKeyError, task, err)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
d.watch(task)
|
})
|
||||||
}()
|
|
||||||
delete(d.fetcherMap, rrId)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -291,16 +302,10 @@ func (d *Downloader) Pause(id string) (err error) {
|
|||||||
if task == nil {
|
if task == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
task.lock.Lock()
|
if err = d.doPause(task, base.DownloadStatusPause); err != nil {
|
||||||
defer task.lock.Unlock()
|
return err
|
||||||
if task.Status == base.DownloadStatusRunning {
|
|
||||||
task.Status = base.DownloadStatusPause
|
|
||||||
task.timer.Pause()
|
|
||||||
task.fetcher.Pause()
|
|
||||||
d.storage.Put(bucketTask, task.ID, task.clone())
|
|
||||||
d.emit(EventKeyPause, task)
|
|
||||||
}
|
}
|
||||||
return
|
return d.notifyRunning()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Downloader) Continue(id string) (err error) {
|
func (d *Downloader) Continue(id string) (err error) {
|
||||||
@@ -308,22 +313,57 @@ func (d *Downloader) Continue(id string) (err error) {
|
|||||||
if task == nil {
|
if task == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
task.lock.Lock()
|
d.tryRunning(func(remain int) {
|
||||||
defer task.lock.Unlock()
|
if remain == 0 {
|
||||||
if task.Status == base.DownloadStatusPause || task.Status == base.DownloadStatusError {
|
// no more task can be running, need to doPause a running task
|
||||||
task.Status = base.DownloadStatusRunning
|
for _, t := range d.tasks {
|
||||||
task.Progress.Speed = 0
|
if t.Status == base.DownloadStatusRunning {
|
||||||
err = d.restoreFetcher(task)
|
err = d.doPause(t, base.DownloadStatusWait)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
task.timer.Start()
|
})
|
||||||
err = task.fetcher.Continue()
|
if err != nil {
|
||||||
if err != nil {
|
return
|
||||||
|
}
|
||||||
|
return d.doContinue(task)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Downloader) PauseAll() (err error) {
|
||||||
|
d.lock.Lock()
|
||||||
|
defer d.lock.Unlock()
|
||||||
|
for _, task := range d.tasks {
|
||||||
|
if task.Status == base.DownloadStatusRunning {
|
||||||
|
if err = d.doPause(task, base.DownloadStatusPause); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Downloader) ContinueAll() (err error) {
|
||||||
|
d.lock.Lock()
|
||||||
|
defer d.lock.Unlock()
|
||||||
|
runningCount := 0
|
||||||
|
continueTasks := make([]*Task, 0)
|
||||||
|
for _, task := range d.tasks {
|
||||||
|
if task.Status == base.DownloadStatusRunning {
|
||||||
|
runningCount++
|
||||||
|
} else if task.Status != base.DownloadStatusDone {
|
||||||
|
continueTasks = append(continueTasks, task)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// calculate how many tasks can be continued, can't exceed maxRunning
|
||||||
|
continueCount := int(math.Min(float64(d.cfg.MaxRunning-runningCount), float64(len(continueTasks))))
|
||||||
|
for i := 0; i < continueCount; i++ {
|
||||||
|
if err = d.doContinue(continueTasks[i]); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
d.storage.Put(bucketTask, task.ID, task.clone())
|
|
||||||
d.emit(EventKeyContinue, task)
|
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -371,6 +411,11 @@ func (d *Downloader) Delete(id string, force bool) (err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
d.emit(EventKeyDelete, task)
|
d.emit(EventKeyDelete, task)
|
||||||
|
if task.Status == base.DownloadStatusRunning {
|
||||||
|
if err = d.doNotifyRunning(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
task = nil
|
task = nil
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -380,7 +425,7 @@ func (d *Downloader) Close() error {
|
|||||||
defer d.lock.Unlock()
|
defer d.lock.Unlock()
|
||||||
d.closed.Store(true)
|
d.closed.Store(true)
|
||||||
for _, task := range d.tasks {
|
for _, task := range d.tasks {
|
||||||
d.Pause(task.ID)
|
d.doPause(task, base.DownloadStatusPause)
|
||||||
}
|
}
|
||||||
return d.storage.Close()
|
return d.storage.Close()
|
||||||
}
|
}
|
||||||
@@ -426,27 +471,20 @@ func (d *Downloader) GetTasks() []*Task {
|
|||||||
return d.tasks
|
return d.tasks
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Downloader) GetConfig() (bool, *DownloaderStoreConfig, error) {
|
func (d *Downloader) GetConfig() (*DownloaderStoreConfig, error) {
|
||||||
var cfg DownloaderStoreConfig
|
return d.cfg.DownloaderStoreConfig, nil
|
||||||
exist, err := d.storage.Get(bucketConfig, "config", &cfg)
|
|
||||||
if err != nil {
|
|
||||||
return false, nil, err
|
|
||||||
}
|
|
||||||
return exist, &cfg, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Downloader) PutConfig(v *DownloaderStoreConfig) error {
|
func (d *Downloader) PutConfig(v *DownloaderStoreConfig) error {
|
||||||
|
d.cfg.DownloaderStoreConfig = v
|
||||||
return d.storage.Put(bucketConfig, "config", v)
|
return d.storage.Put(bucketConfig, "config", v)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Downloader) getProtocolConfig(name string, v any) (bool, error) {
|
func (d *Downloader) getProtocolConfig(name string, v any) (bool, error) {
|
||||||
exist, cfg, err := d.GetConfig()
|
cfg, err := d.GetConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
if !exist {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
if cfg.ProtocolConfig == nil || cfg.ProtocolConfig[name] == nil {
|
if cfg.ProtocolConfig == nil || cfg.ProtocolConfig[name] == nil {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
@@ -479,6 +517,7 @@ func (d *Downloader) watch(task *Task) {
|
|||||||
d.emit(EventKeyDone, task)
|
d.emit(EventKeyDone, task)
|
||||||
}
|
}
|
||||||
d.emit(EventKeyFinally, task, err)
|
d.emit(EventKeyFinally, task, err)
|
||||||
|
d.notifyRunning()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Downloader) restoreFetcher(task *Task) error {
|
func (d *Downloader) restoreFetcher(task *Task) error {
|
||||||
@@ -507,13 +546,96 @@ func (d *Downloader) restoreFetcher(task *Task) error {
|
|||||||
}
|
}
|
||||||
d.setupFetcher(task.fetcher)
|
d.setupFetcher(task.fetcher)
|
||||||
task.fetcher.Create(task.Meta.Opts)
|
task.fetcher.Create(task.Meta.Opts)
|
||||||
go d.watch(task)
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *Downloader) tryRunning(cb func(remain int)) {
|
||||||
|
d.lock.Lock()
|
||||||
|
defer d.lock.Unlock()
|
||||||
|
|
||||||
|
runningCount := 0
|
||||||
|
for _, task := range d.tasks {
|
||||||
|
if task.Status == base.DownloadStatusRunning {
|
||||||
|
runningCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cb(d.cfg.MaxRunning - runningCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Downloader) notifyRunning() error {
|
||||||
|
d.lock.Lock()
|
||||||
|
defer d.lock.Unlock()
|
||||||
|
|
||||||
|
return d.doNotifyRunning()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Downloader) doNotifyRunning() error {
|
||||||
|
for _, task := range d.tasks {
|
||||||
|
if task.Status == base.DownloadStatusReady || task.Status == base.DownloadStatusWait {
|
||||||
|
return d.doContinue(task)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Downloader) start(task *Task) error {
|
||||||
|
var event EventKey
|
||||||
|
if task.Status == base.DownloadStatusReady {
|
||||||
|
event = EventKeyStart
|
||||||
|
} else {
|
||||||
|
event = EventKeyContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
task.Status = base.DownloadStatusRunning
|
||||||
|
task.Progress.Speed = 0
|
||||||
|
task.timer.Start()
|
||||||
|
if err := d.storage.Put(bucketTask, task.ID, task.clone()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
if event == EventKeyStart {
|
||||||
|
err = task.fetcher.Start()
|
||||||
|
}
|
||||||
|
if event == EventKeyContinue {
|
||||||
|
err = task.fetcher.Continue()
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
go d.watch(task)
|
||||||
|
d.emit(event, task)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Downloader) doPause(task *Task, status base.Status) (err error) {
|
||||||
|
task.lock.Lock()
|
||||||
|
defer task.lock.Unlock()
|
||||||
|
if task.Status == base.DownloadStatusRunning {
|
||||||
|
task.Status = status
|
||||||
|
task.timer.Pause()
|
||||||
|
task.fetcher.Pause()
|
||||||
|
d.storage.Put(bucketTask, task.ID, task.clone())
|
||||||
|
d.emit(EventKeyPause, task)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Downloader) doContinue(task *Task) (err error) {
|
||||||
|
task.lock.Lock()
|
||||||
|
defer task.lock.Unlock()
|
||||||
|
if task.Status != base.DownloadStatusRunning && task.Status != base.DownloadStatusDone {
|
||||||
|
err = d.restoreFetcher(task)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = d.start(task)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func initTask(task *Task) {
|
func initTask(task *Task) {
|
||||||
task.timer = &util.Timer{}
|
task.timer = util.NewTimer(task.Progress.Used)
|
||||||
task.lock = &sync.Mutex{}
|
task.lock = &sync.Mutex{}
|
||||||
task.speedArr = make([]int64, 0)
|
task.speedArr = make([]int64, 0)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -170,8 +170,7 @@ func TestDownloader_Protocol_Config(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
storeCfg := &DownloaderStoreConfig{
|
storeCfg := &DownloaderStoreConfig{
|
||||||
RefreshInterval: 500,
|
DownloadDir: "./downloads",
|
||||||
DownloadDir: "./downloads",
|
|
||||||
ProtocolConfig: map[string]any{
|
ProtocolConfig: map[string]any{
|
||||||
"http": map[string]any{
|
"http": map[string]any{
|
||||||
"connections": 4,
|
"connections": 4,
|
||||||
@@ -191,17 +190,14 @@ func TestDownloader_Protocol_Config(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := downloader.PutConfig(storeCfg); err != nil {
|
if err = downloader.PutConfig(storeCfg); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
exits, newStoreCfg, err := downloader.GetConfig()
|
newStoreCfg, err := downloader.GetConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if !exits {
|
|
||||||
t.Errorf("GetConfig() got = %v, want %v", exits, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !test.JsonEqual(storeCfg, newStoreCfg) {
|
if !test.JsonEqual(storeCfg, newStoreCfg) {
|
||||||
t.Errorf("GetConfig() got = %v, want %v", test.ToJson(storeCfg), test.ToJson(newStoreCfg))
|
t.Errorf("GetConfig() got = %v, want %v", test.ToJson(storeCfg), test.ToJson(newStoreCfg))
|
||||||
|
|||||||
+19
-13
@@ -70,7 +70,9 @@ func (t *Task) calcSpeed(downloaded int64, usedTime float64) int64 {
|
|||||||
type DownloaderConfig struct {
|
type DownloaderConfig struct {
|
||||||
Controller *controller.Controller
|
Controller *controller.Controller
|
||||||
FetchBuilders []fetcher.FetcherBuilder
|
FetchBuilders []fetcher.FetcherBuilder
|
||||||
Storage Storage
|
|
||||||
|
RefreshInterval int `json:"refreshInterval"` // RefreshInterval time duration to refresh task progress(ms)
|
||||||
|
Storage Storage
|
||||||
|
|
||||||
*DownloaderStoreConfig
|
*DownloaderStoreConfig
|
||||||
}
|
}
|
||||||
@@ -85,24 +87,28 @@ func (cfg *DownloaderConfig) Init() *DownloaderConfig {
|
|||||||
new(bt.FetcherBuilder),
|
new(bt.FetcherBuilder),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if cfg.Storage == nil {
|
|
||||||
cfg.Storage = NewMemStorage()
|
|
||||||
}
|
|
||||||
|
|
||||||
if cfg.DownloaderStoreConfig == nil {
|
|
||||||
cfg.DownloaderStoreConfig = &DownloaderStoreConfig{}
|
|
||||||
}
|
|
||||||
|
|
||||||
if cfg.RefreshInterval == 0 {
|
if cfg.RefreshInterval == 0 {
|
||||||
cfg.RefreshInterval = 350
|
cfg.RefreshInterval = 350
|
||||||
}
|
}
|
||||||
|
if cfg.Storage == nil {
|
||||||
|
cfg.Storage = NewMemStorage()
|
||||||
|
}
|
||||||
return cfg
|
return cfg
|
||||||
}
|
}
|
||||||
|
|
||||||
// DownloaderStoreConfig is the config that can restore the downloader.
|
// DownloaderStoreConfig is the config that can restore the downloader.
|
||||||
type DownloaderStoreConfig struct {
|
type DownloaderStoreConfig struct {
|
||||||
RefreshInterval int `json:"refreshInterval"` // RefreshInterval time duration to refresh task progress(ms)
|
FirstLoad bool `json:"-"` // fromNoStore is the flag that the config is first time init and not from store
|
||||||
DownloadDir string `json:"downloadDir"` // DownloadDir is the directory to save the downloaded files
|
|
||||||
ProtocolConfig map[string]any `json:"protocolConfig"` // ProtocolConfig is special config for each protocol
|
DownloadDir string `json:"downloadDir"` // DownloadDir is the default directory to save the downloaded files
|
||||||
Extra map[string]any `json:"extra"` // Extra is the extra config
|
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"` // Extra is the extra config
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *DownloaderStoreConfig) Init() *DownloaderStoreConfig {
|
||||||
|
if cfg.MaxRunning == 0 {
|
||||||
|
cfg.MaxRunning = 3
|
||||||
|
}
|
||||||
|
return cfg
|
||||||
}
|
}
|
||||||
+18
-2
@@ -33,7 +33,7 @@ func CreateTask(w http.ResponseWriter, r *http.Request) {
|
|||||||
if req.Rid != "" {
|
if req.Rid != "" {
|
||||||
taskId, err = Downloader.Create(req.Rid, req.Opts)
|
taskId, err = Downloader.Create(req.Rid, req.Opts)
|
||||||
} else if req.Req != nil {
|
} else if req.Req != nil {
|
||||||
taskId, err = Downloader.DirectCreate(req.Req, req.Opts)
|
taskId, err = Downloader.CreateDirect(req.Req, req.Opts)
|
||||||
} else {
|
} else {
|
||||||
WriteJson(w, model.NewErrorResult("param invalid: rid or req", model.CodeInvalidParam))
|
WriteJson(w, model.NewErrorResult("param invalid: rid or req", model.CodeInvalidParam))
|
||||||
return
|
return
|
||||||
@@ -74,6 +74,22 @@ func ContinueTask(w http.ResponseWriter, r *http.Request) {
|
|||||||
WriteJson(w, model.NewNilResult())
|
WriteJson(w, model.NewNilResult())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func PauseAllTask(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if err := Downloader.PauseAll(); err != nil {
|
||||||
|
WriteJson(w, model.NewErrorResult(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
WriteJson(w, model.NewNilResult())
|
||||||
|
}
|
||||||
|
|
||||||
|
func ContinueAllTask(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if err := Downloader.ContinueAll(); err != nil {
|
||||||
|
WriteJson(w, model.NewErrorResult(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
WriteJson(w, model.NewNilResult())
|
||||||
|
}
|
||||||
|
|
||||||
func DeleteTask(w http.ResponseWriter, r *http.Request) {
|
func DeleteTask(w http.ResponseWriter, r *http.Request) {
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
taskId := vars["id"]
|
taskId := vars["id"]
|
||||||
@@ -195,6 +211,6 @@ func writeError(w http.ResponseWriter, msg string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getServerConfig() *download.DownloaderStoreConfig {
|
func getServerConfig() *download.DownloaderStoreConfig {
|
||||||
_, cfg, _ := Downloader.GetConfig()
|
cfg, _ := Downloader.GetConfig()
|
||||||
return cfg
|
return cfg
|
||||||
}
|
}
|
||||||
@@ -12,9 +12,9 @@ const (
|
|||||||
type StartConfig struct {
|
type StartConfig struct {
|
||||||
Network string `json:"network"`
|
Network string `json:"network"`
|
||||||
Address string `json:"address"`
|
Address string `json:"address"`
|
||||||
|
RefreshInterval int `json:"refreshInterval"`
|
||||||
Storage Storage `json:"storage"`
|
Storage Storage `json:"storage"`
|
||||||
StorageDir string `json:"storageDir"`
|
StorageDir string `json:"storageDir"`
|
||||||
RefreshInterval int `json:"refreshInterval"`
|
|
||||||
ApiToken string `json:"apiToken"`
|
ApiToken string `json:"apiToken"`
|
||||||
|
|
||||||
WebEnable bool
|
WebEnable bool
|
||||||
@@ -28,6 +28,9 @@ func (cfg *StartConfig) Init() *StartConfig {
|
|||||||
if cfg.Address == "" {
|
if cfg.Address == "" {
|
||||||
cfg.Address = "127.0.0.1:0"
|
cfg.Address = "127.0.0.1:0"
|
||||||
}
|
}
|
||||||
|
if cfg.RefreshInterval == 0 {
|
||||||
|
cfg.RefreshInterval = 350
|
||||||
|
}
|
||||||
if cfg.Storage == "" {
|
if cfg.Storage == "" {
|
||||||
cfg.Storage = StorageBolt
|
cfg.Storage = StorageBolt
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-2
@@ -57,14 +57,15 @@ func BuildServer(startCfg *model.StartConfig) (*http.Server, net.Listener, error
|
|||||||
}
|
}
|
||||||
startCfg.Init()
|
startCfg.Init()
|
||||||
|
|
||||||
downloadCfg := &download.DownloaderConfig{}
|
downloadCfg := &download.DownloaderConfig{
|
||||||
|
RefreshInterval: startCfg.RefreshInterval,
|
||||||
|
}
|
||||||
if startCfg.Storage == model.StorageBolt {
|
if startCfg.Storage == model.StorageBolt {
|
||||||
downloadCfg.Storage = download.NewBoltStorage(startCfg.StorageDir)
|
downloadCfg.Storage = download.NewBoltStorage(startCfg.StorageDir)
|
||||||
} else {
|
} else {
|
||||||
downloadCfg.Storage = download.NewMemStorage()
|
downloadCfg.Storage = download.NewMemStorage()
|
||||||
}
|
}
|
||||||
downloadCfg.Init()
|
downloadCfg.Init()
|
||||||
downloadCfg.RefreshInterval = startCfg.RefreshInterval
|
|
||||||
Downloader = download.NewDownloader(downloadCfg)
|
Downloader = download.NewDownloader(downloadCfg)
|
||||||
if err := Downloader.Setup(); err != nil {
|
if err := Downloader.Setup(); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
@@ -84,6 +85,8 @@ func BuildServer(startCfg *model.StartConfig) (*http.Server, net.Listener, error
|
|||||||
r.Methods(http.MethodPost).Path("/api/v1/tasks").HandlerFunc(CreateTask)
|
r.Methods(http.MethodPost).Path("/api/v1/tasks").HandlerFunc(CreateTask)
|
||||||
r.Methods(http.MethodPut).Path("/api/v1/tasks/{id}/pause").HandlerFunc(PauseTask)
|
r.Methods(http.MethodPut).Path("/api/v1/tasks/{id}/pause").HandlerFunc(PauseTask)
|
||||||
r.Methods(http.MethodPut).Path("/api/v1/tasks/{id}/continue").HandlerFunc(ContinueTask)
|
r.Methods(http.MethodPut).Path("/api/v1/tasks/{id}/continue").HandlerFunc(ContinueTask)
|
||||||
|
r.Methods(http.MethodPut).Path("/api/v1/tasks/pause").HandlerFunc(PauseAllTask)
|
||||||
|
r.Methods(http.MethodPut).Path("/api/v1/tasks/continue").HandlerFunc(ContinueAllTask)
|
||||||
r.Methods(http.MethodDelete).Path("/api/v1/tasks/{id}").HandlerFunc(DeleteTask)
|
r.Methods(http.MethodDelete).Path("/api/v1/tasks/{id}").HandlerFunc(DeleteTask)
|
||||||
r.Methods(http.MethodGet).Path("/api/v1/tasks/{id}").HandlerFunc(GetTask)
|
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").HandlerFunc(GetTasks)
|
||||||
|
|||||||
@@ -144,6 +144,39 @@ func TestPauseAndContinueTask(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPauseAllAndContinueALLTasks(t *testing.T) {
|
||||||
|
doTest(func() {
|
||||||
|
cfg, err := Downloader.GetConfig()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
createAndPause := func() {
|
||||||
|
taskId := httpRequestCheckOk[string](http.MethodPost, "/api/v1/tasks", createReq)
|
||||||
|
httpRequestCheckOk[*download.Task](http.MethodGet, "/api/v1/tasks/"+taskId, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
total := cfg.MaxRunning + 2
|
||||||
|
for i := 0; i < total; i++ {
|
||||||
|
createAndPause()
|
||||||
|
}
|
||||||
|
|
||||||
|
// continue all
|
||||||
|
httpRequestCheckOk[any](http.MethodPut, "/api/v1/tasks/continue", nil)
|
||||||
|
tasks := httpRequestCheckOk[[]*download.Task](http.MethodGet, fmt.Sprintf("/api/v1/tasks?status=%s", base.DownloadStatusRunning), nil)
|
||||||
|
if len(tasks) != cfg.MaxRunning {
|
||||||
|
t.Errorf("ContinueAllTasks() got = %v, want %v", len(tasks), cfg.MaxRunning)
|
||||||
|
}
|
||||||
|
// pause all
|
||||||
|
httpRequestCheckOk[any](http.MethodPut, "/api/v1/tasks/pause", nil)
|
||||||
|
tasks = httpRequestCheckOk[[]*download.Task](http.MethodGet, fmt.Sprintf("/api/v1/tasks?status=%s,%s", base.DownloadStatusReady, base.DownloadStatusPause), nil)
|
||||||
|
if len(tasks) != total {
|
||||||
|
t.Errorf("PauseAllTasks() got = %v, want %v", len(tasks), total)
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestDeleteTask(t *testing.T) {
|
func TestDeleteTask(t *testing.T) {
|
||||||
doTest(func() {
|
doTest(func() {
|
||||||
taskId := httpRequestCheckOk[string](http.MethodPost, "/api/v1/tasks", createReq)
|
taskId := httpRequestCheckOk[string](http.MethodPost, "/api/v1/tasks", createReq)
|
||||||
@@ -275,6 +308,16 @@ func doTest(handler func()) {
|
|||||||
Stop()
|
Stop()
|
||||||
}()
|
}()
|
||||||
defer Downloader.Clear()
|
defer Downloader.Clear()
|
||||||
|
defer func() {
|
||||||
|
tasks := Downloader.GetTasks()
|
||||||
|
taskIds := make([]string, len(tasks))
|
||||||
|
for i, task := range tasks {
|
||||||
|
taskIds[i] = task.ID
|
||||||
|
}
|
||||||
|
for _, id := range taskIds {
|
||||||
|
Downloader.Delete(id, true)
|
||||||
|
}
|
||||||
|
}()
|
||||||
taskReq.URL = "http://" + fileListener.Addr().String() + "/" + test.BuildName
|
taskReq.URL = "http://" + fileListener.Addr().String() + "/" + test.BuildName
|
||||||
handler()
|
handler()
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-1
@@ -2,12 +2,17 @@ package util
|
|||||||
|
|
||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
// 计时器
|
|
||||||
type Timer struct {
|
type Timer struct {
|
||||||
t int64
|
t int64
|
||||||
used int64
|
used int64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewTimer(used int64) *Timer {
|
||||||
|
return &Timer{
|
||||||
|
used: used,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (t *Timer) Start() {
|
func (t *Timer) Start() {
|
||||||
t.t = time.Now().UnixNano()
|
t.t = time.Now().UnixNano()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,9 +12,54 @@
|
|||||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:hardwareAccelerated="true"
|
android:hardwareAccelerated="true"
|
||||||
android:launchMode="singleTop"
|
android:launchMode="singleInstance"
|
||||||
android:theme="@style/LaunchTheme"
|
android:theme="@style/LaunchTheme"
|
||||||
android:windowSoftInputMode="adjustResize">
|
android:windowSoftInputMode="adjustResize">
|
||||||
|
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
<data
|
||||||
|
android:scheme="file"
|
||||||
|
android:host="*"
|
||||||
|
android:pathPattern=".*torrent" />
|
||||||
|
</intent-filter>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
<data
|
||||||
|
android:scheme="file"
|
||||||
|
android:host="*"
|
||||||
|
android:mimeType="application/x-bittorrent" />
|
||||||
|
</intent-filter>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
<data
|
||||||
|
android:scheme="content"
|
||||||
|
android:host="*"
|
||||||
|
android:pathPattern=".*torrent" />
|
||||||
|
</intent-filter>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
<data
|
||||||
|
android:scheme="content"
|
||||||
|
android:host="*"
|
||||||
|
android:mimeType="application/x-bittorrent" />
|
||||||
|
</intent-filter>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
<data
|
||||||
|
android:scheme="magnet" />
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
<!-- Specifies an Android theme to apply to this Activity as soon as
|
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||||
the Android process has started. This theme is visible to the user
|
the Android process has started. This theme is visible to the user
|
||||||
while the Flutter UI initializes. After that, this theme continues
|
while the Flutter UI initializes. After that, this theme continues
|
||||||
|
|||||||
@@ -9,6 +9,8 @@
|
|||||||
"downloading": "downloading",
|
"downloading": "downloading",
|
||||||
"downloaded": "downloaded",
|
"downloaded": "downloaded",
|
||||||
"setting": "Setting",
|
"setting": "Setting",
|
||||||
|
"donate": "Donate",
|
||||||
|
"exit": "Exit",
|
||||||
"create": "Create Task",
|
"create": "Create Task",
|
||||||
"downloadLink": "Download Link",
|
"downloadLink": "Download Link",
|
||||||
"downloadLinkValid": "Please enter the download link",
|
"downloadLinkValid": "Please enter the download link",
|
||||||
@@ -24,6 +26,7 @@
|
|||||||
"downloadDir": "Download Directory",
|
"downloadDir": "Download Directory",
|
||||||
"downloadDirValid": "Please select the download directory",
|
"downloadDirValid": "Please select the download directory",
|
||||||
"connections": "Connections",
|
"connections": "Connections",
|
||||||
|
"maxRunning": "Max Running Tasks",
|
||||||
"items": "@count items",
|
"items": "@count items",
|
||||||
"subscribeTracker": "Subscribe Tracker",
|
"subscribeTracker": "Subscribe Tracker",
|
||||||
"subscribeFail": "Subscribe failed, please check network or try again later",
|
"subscribeFail": "Subscribe failed, please check network or try again later",
|
||||||
@@ -47,6 +50,8 @@
|
|||||||
"notSet": "NS",
|
"notSet": "NS",
|
||||||
"set": "SET",
|
"set": "SET",
|
||||||
"effectAfterRestart": "Effect after restart",
|
"effectAfterRestart": "Effect after restart",
|
||||||
|
"startAll": "Start All",
|
||||||
|
"pauseAll": "Pause All",
|
||||||
"deleteTask": "Delete Task",
|
"deleteTask": "Delete Task",
|
||||||
"deleteTaskTip": "Keep downloaded files",
|
"deleteTaskTip": "Keep downloaded files",
|
||||||
"delete": "Delete",
|
"delete": "Delete",
|
||||||
|
|||||||
@@ -7,6 +7,8 @@
|
|||||||
"selectAll": "انتخاب همه",
|
"selectAll": "انتخاب همه",
|
||||||
"task": "کار",
|
"task": "کار",
|
||||||
"setting": "تنظیمات",
|
"setting": "تنظیمات",
|
||||||
|
"donate": "کمک مالی",
|
||||||
|
"exit": "خروج",
|
||||||
"create": "ایجاد کار",
|
"create": "ایجاد کار",
|
||||||
"downloading": "دانلود کردن",
|
"downloading": "دانلود کردن",
|
||||||
"downloaded": "دانلود شدن",
|
"downloaded": "دانلود شدن",
|
||||||
@@ -45,6 +47,8 @@
|
|||||||
"notSet": "NS",
|
"notSet": "NS",
|
||||||
"set": "SET",
|
"set": "SET",
|
||||||
"effectAfterRestart": "Effect after restart",
|
"effectAfterRestart": "Effect after restart",
|
||||||
|
"startAll": "شروع همه",
|
||||||
|
"pauseAll": "توقف همه",
|
||||||
"deleteTask": "پاک کردن کار",
|
"deleteTask": "پاک کردن کار",
|
||||||
"deleteTaskTip": "فایل های دانلود شده را نگه دارد",
|
"deleteTaskTip": "فایل های دانلود شده را نگه دارد",
|
||||||
"delete": "پاک کردن",
|
"delete": "پاک کردن",
|
||||||
|
|||||||
@@ -9,6 +9,8 @@
|
|||||||
"downloading": "Загрузка",
|
"downloading": "Загрузка",
|
||||||
"downloaded": "Загружено",
|
"downloaded": "Загружено",
|
||||||
"setting": "Настройки",
|
"setting": "Настройки",
|
||||||
|
"donate": "Пожертвовать",
|
||||||
|
"exit": "Выход",
|
||||||
"create": "Создать задачу",
|
"create": "Создать задачу",
|
||||||
"downloadLink": "Ссылка для скачивание",
|
"downloadLink": "Ссылка для скачивание",
|
||||||
"downloadLinkValid": "Пожалуйста, введите ссылку для скачивания",
|
"downloadLinkValid": "Пожалуйста, введите ссылку для скачивания",
|
||||||
@@ -24,6 +26,7 @@
|
|||||||
"downloadDir": "Папка загрузки",
|
"downloadDir": "Папка загрузки",
|
||||||
"downloadDirValid": "Пожалуйста, выберите папку для загрузки",
|
"downloadDirValid": "Пожалуйста, выберите папку для загрузки",
|
||||||
"connections": "Соединений",
|
"connections": "Соединений",
|
||||||
|
"maxRunning": "Максимальное количество активных задач",
|
||||||
"items": "@подсчет элементов",
|
"items": "@подсчет элементов",
|
||||||
"subscribeTracker": "Подпись трекера",
|
"subscribeTracker": "Подпись трекера",
|
||||||
"subscribeFail": "Подписка не удалась, пожалуйста, проверьте сеть или повторите попытку позже",
|
"subscribeFail": "Подписка не удалась, пожалуйста, проверьте сеть или повторите попытку позже",
|
||||||
@@ -47,6 +50,8 @@
|
|||||||
"notSet": "Не установлено",
|
"notSet": "Не установлено",
|
||||||
"set": "Установлено",
|
"set": "Установлено",
|
||||||
"effectAfterRestart": "Эффект после перезагрузки",
|
"effectAfterRestart": "Эффект после перезагрузки",
|
||||||
|
"startAll": "Запустить все",
|
||||||
|
"pauseAll": "Приостановить все",
|
||||||
"deleteTask": "Удалить задачу",
|
"deleteTask": "Удалить задачу",
|
||||||
"deleteTaskTip": "Сохранить загруженные файлы",
|
"deleteTaskTip": "Сохранить загруженные файлы",
|
||||||
"delete": "Удалить",
|
"delete": "Удалить",
|
||||||
|
|||||||
@@ -9,6 +9,8 @@
|
|||||||
"downloading": "下载中",
|
"downloading": "下载中",
|
||||||
"downloaded": "已完成",
|
"downloaded": "已完成",
|
||||||
"setting": "设置",
|
"setting": "设置",
|
||||||
|
"donate": "打赏",
|
||||||
|
"exit": "退出",
|
||||||
"create": "创建任务",
|
"create": "创建任务",
|
||||||
"downloadLink": "下载链接",
|
"downloadLink": "下载链接",
|
||||||
"downloadLinkValid": "请输入下载链接",
|
"downloadLinkValid": "请输入下载链接",
|
||||||
@@ -24,6 +26,7 @@
|
|||||||
"downloadDir": "下载目录",
|
"downloadDir": "下载目录",
|
||||||
"downloadDirValid": "请选择下载目录",
|
"downloadDirValid": "请选择下载目录",
|
||||||
"connections": "连接数",
|
"connections": "连接数",
|
||||||
|
"maxRunning": "最大下载数",
|
||||||
"items": "@count 条",
|
"items": "@count 条",
|
||||||
"subscribeTracker": "订阅 Tracker",
|
"subscribeTracker": "订阅 Tracker",
|
||||||
"subscribeFail": "订阅失败,请检查网络或稍后重试",
|
"subscribeFail": "订阅失败,请检查网络或稍后重试",
|
||||||
@@ -47,6 +50,8 @@
|
|||||||
"notSet": "未设置",
|
"notSet": "未设置",
|
||||||
"set": "已设置",
|
"set": "已设置",
|
||||||
"effectAfterRestart": "此配置项将在重启应用后生效",
|
"effectAfterRestart": "此配置项将在重启应用后生效",
|
||||||
|
"startAll": "全部开始",
|
||||||
|
"pauseAll": "全部暂停",
|
||||||
"deleteTask": "删除任务",
|
"deleteTask": "删除任务",
|
||||||
"deleteTaskTip": "保留已下载的文件",
|
"deleteTaskTip": "保留已下载的文件",
|
||||||
"delete": "删除",
|
"delete": "删除",
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 9.9 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.7 KiB |
@@ -1,4 +1,3 @@
|
|||||||
import 'dart:convert';
|
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
@@ -73,15 +72,12 @@ Future<T> _parse<T>(
|
|||||||
) async {
|
) async {
|
||||||
try {
|
try {
|
||||||
var resp = await fetch();
|
var resp = await fetch();
|
||||||
if (fromJsonT != null) {
|
fromJsonT ??= (json) => null as T;
|
||||||
final result = Result<T>.fromJson(resp.data, fromJsonT);
|
final result = Result<T>.fromJson(resp.data, fromJsonT);
|
||||||
if (result.code == 0) {
|
if (result.code == 0) {
|
||||||
return result.data as T;
|
return result.data as T;
|
||||||
} else {
|
|
||||||
throw Exception(result);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
return null as T;
|
throw Exception(result);
|
||||||
}
|
}
|
||||||
} on DioError catch (e) {
|
} on DioError catch (e) {
|
||||||
throw Exception(Result(code: 1000, msg: e.message));
|
throw Exception(Result(code: 1000, msg: e.message));
|
||||||
@@ -115,6 +111,14 @@ Future<void> continueTask(String id) async {
|
|||||||
return _parse(() => _client.dio.put("/api/v1/tasks/$id/continue"), null);
|
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 {
|
Future<void> deleteTask(String id, bool force) async {
|
||||||
return _parse(
|
return _parse(
|
||||||
() => _client.dio.delete("/api/v1/tasks/$id?force=$force"), null);
|
() => _client.dio.delete("/api/v1/tasks/$id?force=$force"), null);
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ part 'downloader_config.g.dart';
|
|||||||
@JsonSerializable(explicitToJson: true)
|
@JsonSerializable(explicitToJson: true)
|
||||||
class DownloaderConfig {
|
class DownloaderConfig {
|
||||||
String downloadDir = '';
|
String downloadDir = '';
|
||||||
|
int maxRunning = 0;
|
||||||
ProtocolConfig protocolConfig = ProtocolConfig();
|
ProtocolConfig protocolConfig = ProtocolConfig();
|
||||||
ExtraConfig extra = ExtraConfig();
|
ExtraConfig extra = ExtraConfig();
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ part of 'downloader_config.dart';
|
|||||||
DownloaderConfig _$DownloaderConfigFromJson(Map<String, dynamic> json) =>
|
DownloaderConfig _$DownloaderConfigFromJson(Map<String, dynamic> json) =>
|
||||||
DownloaderConfig()
|
DownloaderConfig()
|
||||||
..downloadDir = json['downloadDir'] as String
|
..downloadDir = json['downloadDir'] as String
|
||||||
|
..maxRunning = json['maxRunning'] as int
|
||||||
..protocolConfig = ProtocolConfig.fromJson(
|
..protocolConfig = ProtocolConfig.fromJson(
|
||||||
json['protocolConfig'] as Map<String, dynamic>?)
|
json['protocolConfig'] as Map<String, dynamic>?)
|
||||||
..extra = ExtraConfig.fromJson(json['extra'] as Map<String, dynamic>?);
|
..extra = ExtraConfig.fromJson(json['extra'] as Map<String, dynamic>?);
|
||||||
@@ -16,6 +17,7 @@ DownloaderConfig _$DownloaderConfigFromJson(Map<String, dynamic> json) =>
|
|||||||
Map<String, dynamic> _$DownloaderConfigToJson(DownloaderConfig instance) =>
|
Map<String, dynamic> _$DownloaderConfigToJson(DownloaderConfig instance) =>
|
||||||
<String, dynamic>{
|
<String, dynamic>{
|
||||||
'downloadDir': instance.downloadDir,
|
'downloadDir': instance.downloadDir,
|
||||||
|
'maxRunning': instance.maxRunning,
|
||||||
'protocolConfig': instance.protocolConfig.toJson(),
|
'protocolConfig': instance.protocolConfig.toJson(),
|
||||||
'extra': instance.extra.toJson(),
|
'extra': instance.extra.toJson(),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
import 'package:json_annotation/json_annotation.dart';
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
|
|
||||||
import 'meta.dart';
|
import 'meta.dart';
|
||||||
import 'options.dart';
|
|
||||||
import 'resource.dart';
|
|
||||||
|
|
||||||
part 'task.g.dart';
|
part 'task.g.dart';
|
||||||
|
|
||||||
enum Status { ready, running, pause, error, done }
|
enum Status { ready, running, pause, wait, error, done }
|
||||||
|
|
||||||
@JsonSerializable(explicitToJson: true)
|
@JsonSerializable(explicitToJson: true)
|
||||||
class Task {
|
class Task {
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ const _$StatusEnumMap = {
|
|||||||
Status.ready: 'ready',
|
Status.ready: 'ready',
|
||||||
Status.running: 'running',
|
Status.running: 'running',
|
||||||
Status.pause: 'pause',
|
Status.pause: 'pause',
|
||||||
|
Status.wait: 'wait',
|
||||||
Status.error: 'error',
|
Status.error: 'error',
|
||||||
Status.done: 'done',
|
Status.done: 'done',
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,10 +1,16 @@
|
|||||||
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:ui' as ui;
|
import 'dart:ui' as ui;
|
||||||
|
|
||||||
|
import 'package:app_links/app_links.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
import 'package:tray_manager/tray_manager.dart';
|
||||||
|
import 'package:uri_to_file/uri_to_file.dart';
|
||||||
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
import 'package:window_manager/window_manager.dart';
|
||||||
|
|
||||||
import '../../../../api/api.dart';
|
import '../../../../api/api.dart';
|
||||||
import '../../../../api/model/downloader_config.dart';
|
import '../../../../api/model/downloader_config.dart';
|
||||||
@@ -12,7 +18,9 @@ import '../../../../core/common/start_config.dart';
|
|||||||
import '../../../../generated/locales.g.dart';
|
import '../../../../generated/locales.g.dart';
|
||||||
import '../../../../util/localeManager.dart';
|
import '../../../../util/localeManager.dart';
|
||||||
import '../../../../util/log_util.dart';
|
import '../../../../util/log_util.dart';
|
||||||
|
import '../../../../util/package_info.dart';
|
||||||
import '../../../../util/util.dart';
|
import '../../../../util/util.dart';
|
||||||
|
import '../../../routes/app_pages.dart';
|
||||||
|
|
||||||
const _startConfigNetwork = "start.network";
|
const _startConfigNetwork = "start.network";
|
||||||
const _startConfigAddress = "start.address";
|
const _startConfigAddress = "start.address";
|
||||||
@@ -55,13 +63,153 @@ final allTrackerSubscribeUrlCdns = Map.fromIterable(allTrackerSubscribeUrls,
|
|||||||
return ret;
|
return ret;
|
||||||
});
|
});
|
||||||
|
|
||||||
class AppController extends GetxController {
|
class AppController extends GetxController with WindowListener, TrayListener {
|
||||||
static StartConfig? _defaultStartConfig;
|
static StartConfig? _defaultStartConfig;
|
||||||
|
|
||||||
final startConfig = StartConfig().obs;
|
final startConfig = StartConfig().obs;
|
||||||
final runningPort = 0.obs;
|
final runningPort = 0.obs;
|
||||||
final downloaderConfig = DownloaderConfig().obs;
|
final downloaderConfig = DownloaderConfig().obs;
|
||||||
|
|
||||||
|
late AppLinks _appLinks;
|
||||||
|
StreamSubscription<Uri>? _linkSubscription;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onReady() {
|
||||||
|
super.onReady();
|
||||||
|
try {
|
||||||
|
_initDeepLinks();
|
||||||
|
} catch (e) {
|
||||||
|
logger.w("initDeepLinks error", e);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
_initWindows();
|
||||||
|
} catch (e) {
|
||||||
|
logger.w("initWindows error", e);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
_initTray();
|
||||||
|
} catch (e) {
|
||||||
|
logger.w("initTray error", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onClose() {
|
||||||
|
_linkSubscription?.cancel();
|
||||||
|
trayManager.removeListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onWindowClose() async {
|
||||||
|
final isPreventClose = await windowManager.isPreventClose();
|
||||||
|
if (isPreventClose) {
|
||||||
|
windowManager.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onTrayIconMouseDown() {
|
||||||
|
windowManager.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onTrayIconRightMouseDown() {
|
||||||
|
trayManager.popUpContextMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _initDeepLinks() async {
|
||||||
|
// currently only support android
|
||||||
|
if (!Util.isAndroid()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_appLinks = AppLinks();
|
||||||
|
|
||||||
|
// Handle link when app is in warm state (front or background)
|
||||||
|
_linkSubscription = _appLinks.uriLinkStream.listen((uri) async {
|
||||||
|
await _toCreate(uri);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check initial link if app was in cold state (terminated)
|
||||||
|
final uri = await _appLinks.getInitialAppLink();
|
||||||
|
if (uri != null) {
|
||||||
|
await _toCreate(uri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _initWindows() async {
|
||||||
|
if (!Util.isDesktop()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
windowManager.addListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _initTray() async {
|
||||||
|
if (!Util.isDesktop()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (Util.isWindows()) {
|
||||||
|
await trayManager.setIcon('assets/tray_icon/icon.ico');
|
||||||
|
} else if (Util.isMacos()) {
|
||||||
|
await trayManager.setIcon('assets/tray_icon/icon_mac.png',
|
||||||
|
isTemplate: true);
|
||||||
|
} else {
|
||||||
|
await trayManager.setIcon('assets/tray_icon/icon.png');
|
||||||
|
}
|
||||||
|
final menu = Menu(items: [
|
||||||
|
MenuItem(
|
||||||
|
label: "create".tr,
|
||||||
|
onClick: (menuItem) async => {
|
||||||
|
await windowManager.show(),
|
||||||
|
await Get.rootDelegate.offAndToNamed(Routes.CREATE),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
MenuItem.separator(),
|
||||||
|
MenuItem(
|
||||||
|
label: "startAll".tr,
|
||||||
|
onClick: (menuItem) async => {continueAllTasks()},
|
||||||
|
),
|
||||||
|
MenuItem(
|
||||||
|
label: "pauseAll".tr,
|
||||||
|
onClick: (menuItem) async => {pauseAllTasks()},
|
||||||
|
),
|
||||||
|
MenuItem(
|
||||||
|
label: 'setting'.tr,
|
||||||
|
onClick: (menuItem) async => {
|
||||||
|
await windowManager.show(),
|
||||||
|
await Get.rootDelegate.offAndToNamed(Routes.SETTING),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
MenuItem.separator(),
|
||||||
|
MenuItem(
|
||||||
|
label: 'donate'.tr,
|
||||||
|
onClick: (menuItem) => {
|
||||||
|
launchUrl(
|
||||||
|
Uri.parse(
|
||||||
|
"https://github.com/GopeedLab/gopeed/blob/main/.donate/index.md#donate"),
|
||||||
|
mode: LaunchMode.externalApplication)
|
||||||
|
},
|
||||||
|
),
|
||||||
|
MenuItem(
|
||||||
|
label: '${"version".tr}(${packageInfo.version})',
|
||||||
|
),
|
||||||
|
MenuItem.separator(),
|
||||||
|
MenuItem(
|
||||||
|
label: 'exit'.tr,
|
||||||
|
onClick: (menuItem) => {windowManager.destroy()},
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
await trayManager.setContextMenu(menu);
|
||||||
|
trayManager.addListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _toCreate(Uri uri) async {
|
||||||
|
final path = uri.scheme == "magnet"
|
||||||
|
? uri.toString()
|
||||||
|
: (await toFile(uri.toString())).path;
|
||||||
|
await Get.rootDelegate.offAndToNamed(Routes.CREATE, arguments: path);
|
||||||
|
}
|
||||||
|
|
||||||
String runningAddress() {
|
String runningAddress() {
|
||||||
if (startConfig.value.network == 'unix') {
|
if (startConfig.value.network == 'unix') {
|
||||||
return startConfig.value.address;
|
return startConfig.value.address;
|
||||||
|
|||||||
@@ -5,4 +5,5 @@ class CreateController extends GetxController {
|
|||||||
final RxList fileInfos = [].obs;
|
final RxList fileInfos = [].obs;
|
||||||
final RxList selectedIndexes = [].obs;
|
final RxList selectedIndexes = [].obs;
|
||||||
final RxList openedFolders = [].obs;
|
final RxList openedFolders = [].obs;
|
||||||
|
final isResolving = false.obs;
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import 'package:desktop_drop/desktop_drop.dart';
|
import 'package:desktop_drop/desktop_drop.dart';
|
||||||
|
import 'package:file_picker/file_picker.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import '../../../../api/model/resolve_result.dart';
|
import '../../../../api/model/resolve_result.dart';
|
||||||
import '../../../../api/model/result.dart';
|
|
||||||
import 'package:rounded_loading_button/rounded_loading_button.dart';
|
import 'package:rounded_loading_button/rounded_loading_button.dart';
|
||||||
|
|
||||||
import '../../../../api/api.dart';
|
import '../../../../api/api.dart';
|
||||||
@@ -12,8 +12,8 @@ import '../../../../api/model/request.dart';
|
|||||||
import '../../../../util/message.dart';
|
import '../../../../util/message.dart';
|
||||||
import '../../../../util/util.dart';
|
import '../../../../util/util.dart';
|
||||||
import '../../../routes/app_pages.dart';
|
import '../../../routes/app_pages.dart';
|
||||||
import '../../../views/views/directory_selector.dart';
|
import '../../../views/directory_selector.dart';
|
||||||
import '../../../views/views/file_list_view.dart';
|
import '../../../views/file_list_view.dart';
|
||||||
import '../../app/controllers/app_controller.dart';
|
import '../../app/controllers/app_controller.dart';
|
||||||
import '../controllers/create_controller.dart';
|
import '../controllers/create_controller.dart';
|
||||||
|
|
||||||
@@ -27,6 +27,14 @@ class CreateView extends GetView<CreateController> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final String? filePath = Get.rootDelegate.arguments();
|
||||||
|
if (_urlController.text.isEmpty && filePath != null) {
|
||||||
|
_urlController.text = filePath;
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||||
|
await _doResolve();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
leading: IconButton(
|
leading: IconButton(
|
||||||
@@ -46,21 +54,42 @@ class CreateView extends GetView<CreateController> {
|
|||||||
autovalidateMode: AutovalidateMode.always,
|
autovalidateMode: AutovalidateMode.always,
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
TextFormField(
|
Row(
|
||||||
autofocus: true,
|
children: [
|
||||||
controller: _urlController,
|
Expanded(
|
||||||
minLines: 1,
|
child: TextFormField(
|
||||||
maxLines: 30,
|
autofocus: true,
|
||||||
decoration: InputDecoration(
|
controller: _urlController,
|
||||||
hintText: _hitText(),
|
minLines: 1,
|
||||||
hintStyle: const TextStyle(fontSize: 12),
|
maxLines: 30,
|
||||||
labelText: 'downloadLink'.tr,
|
decoration: InputDecoration(
|
||||||
icon: const Icon(Icons.link)),
|
hintText: _hitText(),
|
||||||
validator: (v) {
|
hintStyle: const TextStyle(fontSize: 12),
|
||||||
return v!.trim().isNotEmpty
|
labelText: 'downloadLink'.tr,
|
||||||
? null
|
icon: const Icon(Icons.link),
|
||||||
: 'downloadLinkValid'.tr;
|
suffixIcon: IconButton(
|
||||||
}),
|
onPressed: _urlController.clear,
|
||||||
|
icon: const Icon(Icons.clear),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
validator: (v) {
|
||||||
|
return v!.trim().isNotEmpty
|
||||||
|
? null
|
||||||
|
: 'downloadLinkValid'.tr;
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.folder_open),
|
||||||
|
onPressed: () async {
|
||||||
|
var pr = await FilePicker.platform.pickFiles(
|
||||||
|
type: FileType.custom,
|
||||||
|
allowedExtensions: ["torrent"]);
|
||||||
|
if (pr != null) {
|
||||||
|
_urlController.text = pr.files[0].path ?? "";
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
),
|
||||||
Center(
|
Center(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(15.0),
|
padding: const EdgeInsets.all(15.0),
|
||||||
@@ -71,21 +100,7 @@ class CreateView extends GetView<CreateController> {
|
|||||||
),
|
),
|
||||||
child: RoundedLoadingButton(
|
child: RoundedLoadingButton(
|
||||||
color: Get.theme.colorScheme.secondary,
|
color: Get.theme.colorScheme.secondary,
|
||||||
onPressed: () async {
|
onPressed: _doResolve,
|
||||||
try {
|
|
||||||
_confirmController.start();
|
|
||||||
if (_resolveFormKey.currentState!.validate()) {
|
|
||||||
final rr = await resolve(Request(
|
|
||||||
url: _urlController.text,
|
|
||||||
));
|
|
||||||
await _showResolveDialog(rr);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
showErrorMessage(e);
|
|
||||||
} finally {
|
|
||||||
_confirmController.reset();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
controller: _confirmController,
|
controller: _confirmController,
|
||||||
child: Text('confirm'.tr),
|
child: Text('confirm'.tr),
|
||||||
),
|
),
|
||||||
@@ -99,6 +114,27 @@ class CreateView extends GetView<CreateController> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _doResolve() async {
|
||||||
|
if (controller.isResolving.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
controller.isResolving.value = true;
|
||||||
|
try {
|
||||||
|
_confirmController.start();
|
||||||
|
if (_resolveFormKey.currentState!.validate()) {
|
||||||
|
final rr = await resolve(Request(
|
||||||
|
url: _urlController.text,
|
||||||
|
));
|
||||||
|
await _showResolveDialog(rr);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
showErrorMessage(e);
|
||||||
|
} finally {
|
||||||
|
_confirmController.reset();
|
||||||
|
controller.isResolving.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
String _hitText() {
|
String _hitText() {
|
||||||
return 'downloadLinkHit'.trParams({
|
return 'downloadLinkHit'.trParams({
|
||||||
'append': Util.isDesktop() ? 'downloadLinkHitDesktop'.tr : '',
|
'append': Util.isDesktop() ? 'downloadLinkHitDesktop'.tr : '',
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
import '../../../routes/app_pages.dart';
|
import '../../../routes/app_pages.dart';
|
||||||
import '../../../views/views/responsive_builder.dart';
|
import '../../../views/responsive_builder.dart';
|
||||||
import '../controllers/home_controller.dart';
|
import '../controllers/home_controller.dart';
|
||||||
|
|
||||||
class HomeView extends GetView<HomeController> {
|
class HomeView extends GetView<HomeController> {
|
||||||
@@ -11,6 +11,15 @@ class HomeView extends GetView<HomeController> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return GetRouterOutlet.builder(builder: (context, delegate, currentRoute) {
|
return GetRouterOutlet.builder(builder: (context, delegate, currentRoute) {
|
||||||
|
switch (currentRoute?.location) {
|
||||||
|
case Routes.SETTING:
|
||||||
|
controller.currentIndex.value = 1;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
controller.currentIndex.value = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
// extendBody: true,
|
// extendBody: true,
|
||||||
body: Row(
|
body: Row(
|
||||||
|
|||||||
@@ -1,21 +1,21 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:badges/badges.dart' as badges;
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import '../../../../util/message.dart';
|
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
import 'package:badges/badges.dart' as badges;
|
|
||||||
|
|
||||||
import '../../../../generated/locales.g.dart';
|
import '../../../../generated/locales.g.dart';
|
||||||
import '../../../../util/localeManager.dart';
|
import '../../../../util/localeManager.dart';
|
||||||
|
import '../../../../util/message.dart';
|
||||||
import '../../../../util/package_info.dart';
|
import '../../../../util/package_info.dart';
|
||||||
import '../../../../util/util.dart';
|
import '../../../../util/util.dart';
|
||||||
import '../../../views/views/check_list_view.dart';
|
import '../../../views/check_list_view.dart';
|
||||||
import '../../../views/views/directory_selector.dart';
|
import '../../../views/directory_selector.dart';
|
||||||
import '../../../views/views/outlined_button_loading.dart';
|
import '../../../views/outlined_button_loading.dart';
|
||||||
import '../../app/controllers/app_controller.dart';
|
import '../../app/controllers/app_controller.dart';
|
||||||
import '../controllers/setting_controller.dart';
|
import '../controllers/setting_controller.dart';
|
||||||
|
|
||||||
@@ -67,11 +67,37 @@ class SettingView extends GetView<SettingController> {
|
|||||||
showLabel: false,
|
showLabel: false,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
final buildMaxRunning = _buildConfigItem(
|
||||||
|
'maxRunning', () => downloaderCfg.value.maxRunning.toString(),
|
||||||
|
(Key key) {
|
||||||
|
final maxRunningController = TextEditingController(
|
||||||
|
text: downloaderCfg.value.maxRunning.toString());
|
||||||
|
maxRunningController.addListener(() async {
|
||||||
|
if (maxRunningController.text.isNotEmpty &&
|
||||||
|
maxRunningController.text !=
|
||||||
|
downloaderCfg.value.maxRunning.toString()) {
|
||||||
|
downloaderCfg.value.maxRunning = int.parse(maxRunningController.text);
|
||||||
|
|
||||||
|
await debounceSave();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return TextField(
|
||||||
|
key: key,
|
||||||
|
focusNode: FocusNode(),
|
||||||
|
controller: maxRunningController,
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
inputFormatters: [
|
||||||
|
FilteringTextInputFormatter.digitsOnly,
|
||||||
|
_NumericalRangeFormatter(min: 1, max: 256),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
// http config items start
|
// http config items start
|
||||||
final httpConfig = downloaderCfg.value.protocolConfig.http;
|
final httpConfig = downloaderCfg.value.protocolConfig.http;
|
||||||
final buildHttpConnections = _buildConfigItem(
|
final buildHttpConnections = _buildConfigItem(
|
||||||
'connections'.tr, () => httpConfig.connections.toString(), (Key key) {
|
'connections', () => httpConfig.connections.toString(), (Key key) {
|
||||||
final connectionsController =
|
final connectionsController =
|
||||||
TextEditingController(text: httpConfig.connections.toString());
|
TextEditingController(text: httpConfig.connections.toString());
|
||||||
connectionsController.addListener(() async {
|
connectionsController.addListener(() async {
|
||||||
@@ -99,7 +125,7 @@ class SettingView extends GetView<SettingController> {
|
|||||||
final btExtConfig = downloaderCfg.value.extra.bt;
|
final btExtConfig = downloaderCfg.value.extra.bt;
|
||||||
|
|
||||||
final buildBtTrackerSubscribeUrls = _buildConfigItem(
|
final buildBtTrackerSubscribeUrls = _buildConfigItem(
|
||||||
'subscribeTracker'.tr,
|
'subscribeTracker',
|
||||||
() => 'items'.trParams(
|
() => 'items'.trParams(
|
||||||
{'count': btExtConfig.trackerSubscribeUrls.length.toString()}),
|
{'count': btExtConfig.trackerSubscribeUrls.length.toString()}),
|
||||||
(Key key) {
|
(Key key) {
|
||||||
@@ -155,7 +181,7 @@ class SettingView extends GetView<SettingController> {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
final buildBtTrackers = _buildConfigItem(
|
final buildBtTrackers = _buildConfigItem(
|
||||||
'addTracker'.tr,
|
'addTracker',
|
||||||
() => 'items'
|
() => 'items'
|
||||||
.trParams({'count': btExtConfig.customTrackers.length.toString()}),
|
.trParams({'count': btExtConfig.customTrackers.length.toString()}),
|
||||||
(Key key) {
|
(Key key) {
|
||||||
@@ -298,7 +324,7 @@ class SettingView extends GetView<SettingController> {
|
|||||||
|
|
||||||
// advanced config items start
|
// advanced config items start
|
||||||
final buildApiProtocol = _buildConfigItem(
|
final buildApiProtocol = _buildConfigItem(
|
||||||
'protocol'.tr,
|
'protocol',
|
||||||
() => startCfg.value.network == 'tcp'
|
() => startCfg.value.network == 'tcp'
|
||||||
? 'TCP ${startCfg.value.address}'
|
? 'TCP ${startCfg.value.address}'
|
||||||
: 'Unix',
|
: 'Unix',
|
||||||
@@ -396,7 +422,7 @@ class SettingView extends GetView<SettingController> {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
final buildApiToken = _buildConfigItem('apiToken'.tr,
|
final buildApiToken = _buildConfigItem('apiToken',
|
||||||
() => startCfg.value.apiToken.isEmpty ? 'notSet'.tr : 'seted'.tr,
|
() => startCfg.value.apiToken.isEmpty ? 'notSet'.tr : 'seted'.tr,
|
||||||
(Key key) {
|
(Key key) {
|
||||||
final apiTokenController =
|
final apiTokenController =
|
||||||
@@ -426,7 +452,7 @@ class SettingView extends GetView<SettingController> {
|
|||||||
length: 2,
|
length: 2,
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
appBar: PreferredSize(
|
appBar: PreferredSize(
|
||||||
preferredSize: Size.fromHeight(56),
|
preferredSize: const Size.fromHeight(56),
|
||||||
child: AppBar(
|
child: AppBar(
|
||||||
bottom: TabBar(
|
bottom: TabBar(
|
||||||
tabs: [
|
tabs: [
|
||||||
@@ -448,9 +474,8 @@ class SettingView extends GetView<SettingController> {
|
|||||||
Text('general'.tr),
|
Text('general'.tr),
|
||||||
Card(
|
Card(
|
||||||
child: Column(
|
child: Column(
|
||||||
children: _addDivider([
|
children: _addDivider(
|
||||||
buildDownloadDir(),
|
[buildDownloadDir(), buildMaxRunning()]),
|
||||||
]),
|
|
||||||
)),
|
)),
|
||||||
const Text('HTTP'),
|
const Text('HTTP'),
|
||||||
Card(
|
Card(
|
||||||
@@ -502,7 +527,7 @@ class SettingView extends GetView<SettingController> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
).paddingOnly(left: 16, right: 16, top: 16, bottom: 64)),
|
).paddingOnly(left: 16, right: 16, top: 16, bottom: 16)),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import 'dart:async';
|
|
||||||
|
|
||||||
import 'package:gopeed/app/modules/task/controllers/task_list_controller.dart';
|
import 'package:gopeed/app/modules/task/controllers/task_list_controller.dart';
|
||||||
|
|
||||||
|
|||||||
@@ -3,5 +3,11 @@ import 'task_list_controller.dart';
|
|||||||
|
|
||||||
class TaskDownloadingController extends TaskListController {
|
class TaskDownloadingController extends TaskListController {
|
||||||
TaskDownloadingController()
|
TaskDownloadingController()
|
||||||
: super([Status.ready, Status.running, Status.pause, Status.error]);
|
: super([
|
||||||
|
Status.ready,
|
||||||
|
Status.running,
|
||||||
|
Status.pause,
|
||||||
|
Status.wait,
|
||||||
|
Status.error
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
import '../../../views/views/buid_task_list_view.dart';
|
import '../../../views/buid_task_list_view.dart';
|
||||||
import '../controllers/task_downloaded_controller.dart';
|
import '../controllers/task_downloaded_controller.dart';
|
||||||
|
|
||||||
class TaskDownloadedView extends GetView<TaskDownloadedController> {
|
class TaskDownloadedView extends GetView<TaskDownloadedController> {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
import '../../../views/views/buid_task_list_view.dart';
|
import '../../../views/buid_task_list_view.dart';
|
||||||
import '../controllers/task_downloading_controller.dart';
|
import '../controllers/task_downloading_controller.dart';
|
||||||
|
|
||||||
class TaskDownloadingView extends GetView<TaskDownloadingController> {
|
class TaskDownloadingView extends GetView<TaskDownloadingController> {
|
||||||
|
|||||||
+35
-19
@@ -1,16 +1,16 @@
|
|||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:path/path.dart' as path;
|
||||||
|
|
||||||
import '../../../api/api.dart';
|
import '../../api/api.dart';
|
||||||
import '../../../api/model/task.dart';
|
import '../../api/model/task.dart';
|
||||||
import '../../../util/file_icon.dart';
|
import '../../util/file_explorer.dart';
|
||||||
import '../../../util/icons.dart';
|
import '../../util/file_icon.dart';
|
||||||
import '../../../util/message.dart';
|
import '../../util/icons.dart';
|
||||||
import '../../../util/util.dart';
|
import '../../util/message.dart';
|
||||||
import '../../routes/app_pages.dart';
|
import '../../util/util.dart';
|
||||||
|
import '../routes/app_pages.dart';
|
||||||
|
|
||||||
class BuildTaskListView extends GetView {
|
class BuildTaskListView extends GetView {
|
||||||
final List<Task> tasks;
|
final List<Task> tasks;
|
||||||
@@ -53,6 +53,18 @@ class BuildTaskListView extends GetView {
|
|||||||
return task.status == Status.running;
|
return task.status == Status.running;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String buildExplorerUrl(Task task) {
|
||||||
|
if (task.meta.res.rootDir.trim().isEmpty) {
|
||||||
|
return path.join(
|
||||||
|
Util.safeDir(task.meta.opts.path),
|
||||||
|
Util.safeDir(task.meta.res.files[0].path),
|
||||||
|
task.meta.res.files[0].name);
|
||||||
|
} else {
|
||||||
|
return path.join(Util.safeDir(task.meta.opts.path),
|
||||||
|
Util.safeDir(task.meta.res.rootDir));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> showDeleteDialog(String id) {
|
Future<void> showDeleteDialog(String id) {
|
||||||
final keep = true.obs;
|
final keep = true.obs;
|
||||||
|
|
||||||
@@ -100,9 +112,7 @@ class BuildTaskListView extends GetView {
|
|||||||
list.add(IconButton(
|
list.add(IconButton(
|
||||||
icon: const Icon(Icons.folder_open),
|
icon: const Icon(Icons.folder_open),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
final file = File(Util.buildAbsPath(task.meta.opts.path,
|
await FileExplorer.openAndSelectFile(buildExplorerUrl(task));
|
||||||
task.meta.res.files[0].path, task.meta.res.files[0].name));
|
|
||||||
await launchUrl(file.parent.uri);
|
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@@ -111,14 +121,22 @@ class BuildTaskListView extends GetView {
|
|||||||
list.add(IconButton(
|
list.add(IconButton(
|
||||||
icon: const Icon(Icons.pause),
|
icon: const Icon(Icons.pause),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await pauseTask(task.id);
|
try {
|
||||||
|
await pauseTask(task.id);
|
||||||
|
} catch (e) {
|
||||||
|
showErrorMessage(e);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
list.add(IconButton(
|
list.add(IconButton(
|
||||||
icon: const Icon(Icons.play_arrow),
|
icon: const Icon(Icons.play_arrow),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await continueTask(task.id);
|
try {
|
||||||
|
await continueTask(task.id);
|
||||||
|
} catch (e) {
|
||||||
|
showErrorMessage(e);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@@ -186,16 +204,14 @@ class BuildTaskListView extends GetView {
|
|||||||
: Icon(FaIcons.allIcons[findIcon(task.meta.res.name)]),
|
: Icon(FaIcons.allIcons[findIcon(task.meta.res.name)]),
|
||||||
|
|
||||||
trailing: SizedBox(
|
trailing: SizedBox(
|
||||||
width: task.status == Status.done ? 80 : 180,
|
width: 180,
|
||||||
child: Row(
|
child: Row(
|
||||||
// crossAxisAlignment: CrossAxisAlignment.baseline,
|
// crossAxisAlignment: CrossAxisAlignment.baseline,
|
||||||
// textBaseline: DefaultTextStyle.of(context).style.textBaseline,
|
// textBaseline: DefaultTextStyle.of(context).style.textBaseline,
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
children: [
|
children: [
|
||||||
task.status == Status.done
|
Text("${Util.fmtByte(task.progress.speed)} / s",
|
||||||
? const SizedBox.shrink()
|
style: context.textTheme.titleSmall),
|
||||||
: Text("${Util.fmtByte(task.progress.speed)} / s",
|
|
||||||
style: context.textTheme.titleSmall),
|
|
||||||
...buildActions()
|
...buildActions()
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
File renamed without changes.
+9
-11
@@ -2,8 +2,8 @@ import 'package:file_picker/file_picker.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
import '../../../util/mac_secure_util.dart';
|
import '../../util/mac_secure_util.dart';
|
||||||
import '../../../util/util.dart';
|
import '../../util/util.dart';
|
||||||
|
|
||||||
class DirectorySelector extends StatefulWidget {
|
class DirectorySelector extends StatefulWidget {
|
||||||
final TextEditingController controller;
|
final TextEditingController controller;
|
||||||
@@ -35,19 +35,17 @@ class _DirectorySelectorState extends State<DirectorySelector> {
|
|||||||
return v!.trim().isNotEmpty ? null : 'downloadDirValid'.tr;
|
return v!.trim().isNotEmpty ? null : 'downloadDirValid'.tr;
|
||||||
},
|
},
|
||||||
)),
|
)),
|
||||||
!Util.isDesktop()
|
Util.isDesktop() || Util.isAndroid()
|
||||||
? null
|
? IconButton(
|
||||||
: IconButton(
|
|
||||||
icon: const Icon(Icons.folder_open),
|
icon: const Icon(Icons.folder_open),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
if (GetPlatform.isDesktop) {
|
var dir = await FilePicker.platform.getDirectoryPath();
|
||||||
var dir = await FilePicker.platform.getDirectoryPath();
|
if (dir != null) {
|
||||||
if (dir != null) {
|
widget.controller.text = dir;
|
||||||
widget.controller.text = dir;
|
MacSecureUtil.saveBookmark(dir);
|
||||||
MacSecureUtil.saveBookmark(dir);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
: null
|
||||||
].where((e) => e != null).map((e) => e!).toList(),
|
].where((e) => e != null).map((e) => e!).toList(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
+6
-6
@@ -3,14 +3,14 @@ import 'dart:ui' as ui;
|
|||||||
import 'package:fluent_ui/fluent_ui.dart' as fluent;
|
import 'package:fluent_ui/fluent_ui.dart' as fluent;
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import '../../../api/model/resource.dart';
|
import '../../api/model/resource.dart';
|
||||||
import 'package:path/path.dart' as path;
|
import 'package:path/path.dart' as path;
|
||||||
|
|
||||||
import '../../../util/file_icon.dart';
|
import '../../util/file_icon.dart';
|
||||||
import '../../../util/icons.dart';
|
import '../../util/icons.dart';
|
||||||
import '../../../util/util.dart';
|
import '../../util/util.dart';
|
||||||
import '../../modules/app/controllers/app_controller.dart';
|
import '../modules/app/controllers/app_controller.dart';
|
||||||
import '../../modules/create/controllers/create_controller.dart';
|
import '../modules/create/controllers/create_controller.dart';
|
||||||
|
|
||||||
class FileListView extends GetView {
|
class FileListView extends GetView {
|
||||||
final List<FileInfo> files;
|
final List<FileInfo> files;
|
||||||
File renamed without changes.
File renamed without changes.
@@ -23,6 +23,8 @@ class LocaleKeys {
|
|||||||
static const downloading = 'downloading';
|
static const downloading = 'downloading';
|
||||||
static const downloaded = 'downloaded';
|
static const downloaded = 'downloaded';
|
||||||
static const setting = 'setting';
|
static const setting = 'setting';
|
||||||
|
static const donate = 'donate';
|
||||||
|
static const exit = 'exit';
|
||||||
static const create = 'create';
|
static const create = 'create';
|
||||||
static const downloadLink = 'downloadLink';
|
static const downloadLink = 'downloadLink';
|
||||||
static const downloadLinkValid = 'downloadLinkValid';
|
static const downloadLinkValid = 'downloadLinkValid';
|
||||||
@@ -38,6 +40,7 @@ class LocaleKeys {
|
|||||||
static const downloadDir = 'downloadDir';
|
static const downloadDir = 'downloadDir';
|
||||||
static const downloadDirValid = 'downloadDirValid';
|
static const downloadDirValid = 'downloadDirValid';
|
||||||
static const connections = 'connections';
|
static const connections = 'connections';
|
||||||
|
static const maxRunning = 'maxRunning';
|
||||||
static const items = 'items';
|
static const items = 'items';
|
||||||
static const subscribeTracker = 'subscribeTracker';
|
static const subscribeTracker = 'subscribeTracker';
|
||||||
static const subscribeFail = 'subscribeFail';
|
static const subscribeFail = 'subscribeFail';
|
||||||
@@ -61,6 +64,8 @@ class LocaleKeys {
|
|||||||
static const notSet = 'notSet';
|
static const notSet = 'notSet';
|
||||||
static const set = 'set';
|
static const set = 'set';
|
||||||
static const effectAfterRestart = 'effectAfterRestart';
|
static const effectAfterRestart = 'effectAfterRestart';
|
||||||
|
static const startAll = 'startAll';
|
||||||
|
static const pauseAll = 'pauseAll';
|
||||||
static const deleteTask = 'deleteTask';
|
static const deleteTask = 'deleteTask';
|
||||||
static const deleteTaskTip = 'deleteTaskTip';
|
static const deleteTaskTip = 'deleteTaskTip';
|
||||||
static const delete = 'delete';
|
static const delete = 'delete';
|
||||||
@@ -81,6 +86,8 @@ class Locales {
|
|||||||
'downloading': 'downloading',
|
'downloading': 'downloading',
|
||||||
'downloaded': 'downloaded',
|
'downloaded': 'downloaded',
|
||||||
'setting': 'Setting',
|
'setting': 'Setting',
|
||||||
|
'donate': 'Donate',
|
||||||
|
'exit': 'Exit',
|
||||||
'create': 'Create Task',
|
'create': 'Create Task',
|
||||||
'downloadLink': 'Download Link',
|
'downloadLink': 'Download Link',
|
||||||
'downloadLinkValid': 'Please enter the download link',
|
'downloadLinkValid': 'Please enter the download link',
|
||||||
@@ -97,6 +104,7 @@ class Locales {
|
|||||||
'downloadDir': 'Download Directory',
|
'downloadDir': 'Download Directory',
|
||||||
'downloadDirValid': 'Please select the download directory',
|
'downloadDirValid': 'Please select the download directory',
|
||||||
'connections': 'Connections',
|
'connections': 'Connections',
|
||||||
|
'maxRunning': 'Max Running Tasks',
|
||||||
'items': '@count items',
|
'items': '@count items',
|
||||||
'subscribeTracker': 'Subscribe Tracker',
|
'subscribeTracker': 'Subscribe Tracker',
|
||||||
'subscribeFail':
|
'subscribeFail':
|
||||||
@@ -121,6 +129,8 @@ class Locales {
|
|||||||
'notSet': 'NS',
|
'notSet': 'NS',
|
||||||
'set': 'SET',
|
'set': 'SET',
|
||||||
'effectAfterRestart': 'Effect after restart',
|
'effectAfterRestart': 'Effect after restart',
|
||||||
|
'startAll': 'Start All',
|
||||||
|
'pauseAll': 'Pause All',
|
||||||
'deleteTask': 'Delete Task',
|
'deleteTask': 'Delete Task',
|
||||||
'deleteTaskTip': 'Keep downloaded files',
|
'deleteTaskTip': 'Keep downloaded files',
|
||||||
'delete': 'Delete',
|
'delete': 'Delete',
|
||||||
@@ -137,6 +147,8 @@ class Locales {
|
|||||||
'selectAll': 'انتخاب همه',
|
'selectAll': 'انتخاب همه',
|
||||||
'task': 'کار',
|
'task': 'کار',
|
||||||
'setting': 'تنظیمات',
|
'setting': 'تنظیمات',
|
||||||
|
'donate': 'کمک مالی',
|
||||||
|
'exit': 'خروج',
|
||||||
'create': 'ایجاد کار',
|
'create': 'ایجاد کار',
|
||||||
'downloading': 'دانلود کردن',
|
'downloading': 'دانلود کردن',
|
||||||
'downloaded': 'دانلود شدن',
|
'downloaded': 'دانلود شدن',
|
||||||
@@ -176,6 +188,8 @@ class Locales {
|
|||||||
'notSet': 'NS',
|
'notSet': 'NS',
|
||||||
'set': 'SET',
|
'set': 'SET',
|
||||||
'effectAfterRestart': 'Effect after restart',
|
'effectAfterRestart': 'Effect after restart',
|
||||||
|
'startAll': 'شروع همه',
|
||||||
|
'pauseAll': 'توقف همه',
|
||||||
'deleteTask': 'پاک کردن کار',
|
'deleteTask': 'پاک کردن کار',
|
||||||
'deleteTaskTip': 'فایل های دانلود شده را نگه دارد',
|
'deleteTaskTip': 'فایل های دانلود شده را نگه دارد',
|
||||||
'delete': 'پاک کردن',
|
'delete': 'پاک کردن',
|
||||||
@@ -194,6 +208,8 @@ class Locales {
|
|||||||
'downloading': 'Загрузка',
|
'downloading': 'Загрузка',
|
||||||
'downloaded': 'Загружено',
|
'downloaded': 'Загружено',
|
||||||
'setting': 'Настройки',
|
'setting': 'Настройки',
|
||||||
|
'donate': 'Пожертвовать',
|
||||||
|
'exit': 'Выход',
|
||||||
'create': 'Создать задачу',
|
'create': 'Создать задачу',
|
||||||
'downloadLink': 'Ссылка для скачивание',
|
'downloadLink': 'Ссылка для скачивание',
|
||||||
'downloadLinkValid': 'Пожалуйста, введите ссылку для скачивания',
|
'downloadLinkValid': 'Пожалуйста, введите ссылку для скачивания',
|
||||||
@@ -210,6 +226,7 @@ class Locales {
|
|||||||
'downloadDir': 'Папка загрузки',
|
'downloadDir': 'Папка загрузки',
|
||||||
'downloadDirValid': 'Пожалуйста, выберите папку для загрузки',
|
'downloadDirValid': 'Пожалуйста, выберите папку для загрузки',
|
||||||
'connections': 'Соединений',
|
'connections': 'Соединений',
|
||||||
|
'maxRunning': 'Максимальное количество активных задач',
|
||||||
'items': '@подсчет элементов',
|
'items': '@подсчет элементов',
|
||||||
'subscribeTracker': 'Подпись трекера',
|
'subscribeTracker': 'Подпись трекера',
|
||||||
'subscribeFail':
|
'subscribeFail':
|
||||||
@@ -234,6 +251,8 @@ class Locales {
|
|||||||
'notSet': 'Не установлено',
|
'notSet': 'Не установлено',
|
||||||
'set': 'Установлено',
|
'set': 'Установлено',
|
||||||
'effectAfterRestart': 'Эффект после перезагрузки',
|
'effectAfterRestart': 'Эффект после перезагрузки',
|
||||||
|
'startAll': 'Запустить все',
|
||||||
|
'pauseAll': 'Приостановить все',
|
||||||
'deleteTask': 'Удалить задачу',
|
'deleteTask': 'Удалить задачу',
|
||||||
'deleteTaskTip': 'Сохранить загруженные файлы',
|
'deleteTaskTip': 'Сохранить загруженные файлы',
|
||||||
'delete': 'Удалить',
|
'delete': 'Удалить',
|
||||||
@@ -252,6 +271,8 @@ class Locales {
|
|||||||
'downloading': '下载中',
|
'downloading': '下载中',
|
||||||
'downloaded': '已完成',
|
'downloaded': '已完成',
|
||||||
'setting': '设置',
|
'setting': '设置',
|
||||||
|
'donate': '打赏',
|
||||||
|
'exit': '退出',
|
||||||
'create': '创建任务',
|
'create': '创建任务',
|
||||||
'downloadLink': '下载链接',
|
'downloadLink': '下载链接',
|
||||||
'downloadLinkValid': '请输入下载链接',
|
'downloadLinkValid': '请输入下载链接',
|
||||||
@@ -267,6 +288,7 @@ class Locales {
|
|||||||
'downloadDir': '下载目录',
|
'downloadDir': '下载目录',
|
||||||
'downloadDirValid': '请选择下载目录',
|
'downloadDirValid': '请选择下载目录',
|
||||||
'connections': '连接数',
|
'connections': '连接数',
|
||||||
|
'maxRunning': '最大下载数',
|
||||||
'items': '@count 条',
|
'items': '@count 条',
|
||||||
'subscribeTracker': '订阅 Tracker',
|
'subscribeTracker': '订阅 Tracker',
|
||||||
'subscribeFail': '订阅失败,请检查网络或稍后重试',
|
'subscribeFail': '订阅失败,请检查网络或稍后重试',
|
||||||
@@ -290,6 +312,8 @@ class Locales {
|
|||||||
'notSet': '未设置',
|
'notSet': '未设置',
|
||||||
'set': '已设置',
|
'set': '已设置',
|
||||||
'effectAfterRestart': '此配置项将在重启应用后生效',
|
'effectAfterRestart': '此配置项将在重启应用后生效',
|
||||||
|
'startAll': '全部开始',
|
||||||
|
'pauseAll': '全部暂停',
|
||||||
'deleteTask': '删除任务',
|
'deleteTask': '删除任务',
|
||||||
'deleteTaskTip': '保留已下载的文件',
|
'deleteTaskTip': '保留已下载的文件',
|
||||||
'delete': '删除',
|
'delete': '删除',
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
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:window_manager/window_manager.dart';
|
||||||
|
|
||||||
import 'api/api.dart' as api;
|
import 'api/api.dart' as api;
|
||||||
import 'app/modules/app/controllers/app_controller.dart';
|
import 'app/modules/app/controllers/app_controller.dart';
|
||||||
@@ -11,6 +12,7 @@ import 'util/localeManager.dart';
|
|||||||
import 'util/log_util.dart';
|
import 'util/log_util.dart';
|
||||||
import 'util/mac_secure_util.dart';
|
import 'util/mac_secure_util.dart';
|
||||||
import 'util/package_info.dart';
|
import 'util/package_info.dart';
|
||||||
|
import 'util/util.dart';
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
await init();
|
await init();
|
||||||
@@ -21,6 +23,19 @@ void main() async {
|
|||||||
|
|
||||||
Future<void> init() async {
|
Future<void> init() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
if (Util.isDesktop()) {
|
||||||
|
await windowManager.ensureInitialized();
|
||||||
|
const windowOptions = WindowOptions(
|
||||||
|
size: Size(1024, 768),
|
||||||
|
center: true,
|
||||||
|
skipTaskbar: false,
|
||||||
|
);
|
||||||
|
windowManager.waitUntilReadyToShow(windowOptions, () async {
|
||||||
|
await windowManager.show();
|
||||||
|
await windowManager.focus();
|
||||||
|
await windowManager.setPreventClose(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
final controller = Get.put(AppController());
|
final controller = Get.put(AppController());
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
class FileExplorer {
|
||||||
|
static Future<void> openAndSelectFile(String filePath) async {
|
||||||
|
if (!await FileSystemEntity.isDirectory(filePath) &&
|
||||||
|
!await FileSystemEntity.isFile(filePath)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (await FileSystemEntity.isDirectory(filePath)) {
|
||||||
|
if (Platform.isWindows) {
|
||||||
|
Process.run('explorer.exe', [filePath]);
|
||||||
|
} else if (Platform.isMacOS || Platform.isLinux) {
|
||||||
|
Process.run('xdg-open', [filePath]);
|
||||||
|
}
|
||||||
|
} else if (await FileSystemEntity.isFile(filePath)) {
|
||||||
|
if (Platform.isWindows) {
|
||||||
|
Process.run('explorer.exe', ['/select,', filePath]);
|
||||||
|
} else if (Platform.isMacOS) {
|
||||||
|
Process.run('open', ['-R', filePath]);
|
||||||
|
} else if (Platform.isLinux) {
|
||||||
|
if (await _isUbuntuOrDebian()) {
|
||||||
|
Process.run('xdg-open', [filePath]);
|
||||||
|
} else if (await _isCentOS()) {
|
||||||
|
Process.run('nautilus', ['--select', filePath]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<bool> _isUbuntuOrDebian() async {
|
||||||
|
final result = await Process.run('lsb_release', ['-i']);
|
||||||
|
if (result.exitCode != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final output = result.stdout.toString().toLowerCase();
|
||||||
|
return output.contains('ubuntu') || output.contains('debian');
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<bool> _isCentOS() async {
|
||||||
|
final result = await Process.run('cat', ['/etc/os-release']);
|
||||||
|
if (result.exitCode != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final output = result.stdout.toString().toLowerCase();
|
||||||
|
return output.contains('centos');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,12 +3,11 @@ import 'dart:io';
|
|||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
class Util {
|
class Util {
|
||||||
static String buildAbsPath(String dir, String path, String name) {
|
static String safeDir(String path) {
|
||||||
return (dir == "" ? "" : "$dir/") + buildPath(path, name);
|
if (path == "." || path == "./" || path == ".\\") {
|
||||||
}
|
return "";
|
||||||
|
}
|
||||||
static String buildPath(String path, String name) {
|
return path;
|
||||||
return (path == "" ? "" : "$path/") + name;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static String fmtByte(int byte) {
|
static String fmtByte(int byte) {
|
||||||
@@ -42,6 +41,10 @@ class Util {
|
|||||||
return Platform.isWindows || Platform.isLinux || Platform.isMacOS;
|
return Platform.isWindows || Platform.isLinux || Platform.isMacOS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static isWindows() {
|
||||||
|
return !kIsWeb && Platform.isWindows;
|
||||||
|
}
|
||||||
|
|
||||||
static isMacos() {
|
static isMacos() {
|
||||||
return !kIsWeb && Platform.isMacOS;
|
return !kIsWeb && Platform.isMacOS;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,19 @@ import FlutterMacOS
|
|||||||
@NSApplicationMain
|
@NSApplicationMain
|
||||||
class AppDelegate: FlutterAppDelegate {
|
class AppDelegate: FlutterAppDelegate {
|
||||||
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool {
|
||||||
|
if !flag {
|
||||||
|
for window in NSApp.windows {
|
||||||
|
if !window.isVisible {
|
||||||
|
window.setIsVisible(true)
|
||||||
|
}
|
||||||
|
window.makeKeyAndOrderFront(self)
|
||||||
|
NSApp.activate(ignoringOtherApps: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+79
-15
@@ -17,6 +17,14 @@ packages:
|
|||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.0.0"
|
version: "5.0.0"
|
||||||
|
app_links:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: app_links
|
||||||
|
sha256: d572dcdff49c4cfcfa6f315e2683e518ec6eb54e084d01e51d9631a4dcc1b5e8
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "3.4.2"
|
||||||
archive:
|
archive:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -269,10 +277,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: file_picker
|
name: file_picker
|
||||||
sha256: d090ae03df98b0247b82e5928f44d1b959867049d18d73635e2e0bc3f49542b9
|
sha256: c7a8e25ca60e7f331b153b0cb3d405828f18d3e72a6fa1d9440c86556fffc877
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.2.5"
|
version: "5.3.0"
|
||||||
fixnum:
|
fixnum:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -285,10 +293,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: fluent_ui
|
name: fluent_ui
|
||||||
sha256: "55294f8f8b1aab6ac7a2be529b1dcb4affcff3e3176e5e50e5f1918a40050df7"
|
sha256: "334144996f2699bb9dd678a9fe9a8e65a2e0b89bda9328ed4f0d49a8768f1416"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.4.0"
|
version: "4.5.1"
|
||||||
flutter:
|
flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description: flutter
|
description: flutter
|
||||||
@@ -320,10 +328,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: flutter_plugin_android_lifecycle
|
name: flutter_plugin_android_lifecycle
|
||||||
sha256: "60fc7b78455b94e6de2333d2f95196d32cf5c22f4b0b0520a628804cb463503b"
|
sha256: "96af49aa6b57c10a312106ad6f71deed5a754029c24789bbf620ba784f0bd0b0"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.7"
|
version: "2.0.14"
|
||||||
flutter_test:
|
flutter_test:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description: flutter
|
description: flutter
|
||||||
@@ -399,7 +407,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "3.2.2"
|
version: "3.2.2"
|
||||||
intl:
|
intl:
|
||||||
dependency: "direct main"
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: intl
|
name: intl
|
||||||
sha256: "910f85bce16fb5c6f614e117efa303e85a1731bb0081edf3604a2ae6e9a3cc91"
|
sha256: "910f85bce16fb5c6f614e117efa303e85a1731bb0081edf3604a2ae6e9a3cc91"
|
||||||
@@ -510,6 +518,22 @@ packages:
|
|||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.2.0"
|
version: "0.2.0"
|
||||||
|
math_expressions:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: math_expressions
|
||||||
|
sha256: "3576593617c3870d75728a751f6ec6e606706d44e363f088ac394b5a28a98064"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "2.4.0"
|
||||||
|
menu_base:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: menu_base
|
||||||
|
sha256: "820368014a171bd1241030278e6c2617354f492f5c703d7b7d4570a6b8b84405"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "0.1.1"
|
||||||
meta:
|
meta:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -538,10 +562,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: package_info_plus
|
name: package_info_plus
|
||||||
sha256: f619162573096d428ccde2e33f92e05b5a179cd6f0e3120c1005f181bee8ed16
|
sha256: d39e8fbff4c5aef4592737e25ad6ac500df006ce7a7a8e1f838ce1256e167542
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.2"
|
version: "4.0.0"
|
||||||
package_info_plus_platform_interface:
|
package_info_plus_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -610,10 +634,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path_provider_windows
|
name: path_provider_windows
|
||||||
sha256: bcabbe399d4042b8ee687e17548d5d3f527255253b4a639f5f8d2094a9c2b45c
|
sha256: d3f80b32e83ec208ac95253e0cd4d298e104fbc63cb29c5c69edaed43b0c69d6
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.3"
|
version: "2.1.6"
|
||||||
petitparser:
|
petitparser:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -634,10 +658,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: plugin_platform_interface
|
name: plugin_platform_interface
|
||||||
sha256: dbf0f707c78beedc9200146ad3cb0ab4d5da13c246336987be6940f026500d3a
|
sha256: "6a2128648c854906c53fa8e33986fc0247a1116122f9534dd20e3ab9e16a32bc"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.3"
|
version: "2.1.4"
|
||||||
pool:
|
pool:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -702,6 +726,14 @@ packages:
|
|||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.27.5"
|
version: "0.27.5"
|
||||||
|
screen_retriever:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: screen_retriever
|
||||||
|
sha256: "4931f226ca158123ccd765325e9fbf360bfed0af9b460a10f960f9bb13d58323"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "0.1.6"
|
||||||
scroll_pos:
|
scroll_pos:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -790,6 +822,14 @@ packages:
|
|||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.2"
|
version: "1.0.2"
|
||||||
|
shortid:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shortid
|
||||||
|
sha256: d0b40e3dbb50497dad107e19c54ca7de0d1a274eb9b4404991e443dadb9ebedb
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "0.1.2"
|
||||||
sky_engine:
|
sky_engine:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
@@ -883,6 +923,14 @@ packages:
|
|||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.0"
|
version: "1.0.0"
|
||||||
|
tray_manager:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: tray_manager
|
||||||
|
sha256: b1975a05e0c6999e983cf9a58a6a098318c896040ccebac5398a3cc9e43b9c69
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "0.2.0"
|
||||||
typed_data:
|
typed_data:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -891,6 +939,14 @@ packages:
|
|||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.1"
|
version: "1.3.1"
|
||||||
|
uri_to_file:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: uri_to_file
|
||||||
|
sha256: "84afd633b1492fc465c768141e1a29edd519061bf99935b6b4d0d5de8ec7c108"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "0.2.0"
|
||||||
url_launcher:
|
url_launcher:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -983,10 +1039,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: win32
|
name: win32
|
||||||
sha256: "1952a663c0e34fbde55916010d54bbb249bf5f2583113c497602f0ee01c6faa4"
|
sha256: "5a751eddf9db89b3e5f9d50c20ab8612296e4e8db69009788d6c8b060a84191c"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.0"
|
version: "4.1.4"
|
||||||
|
window_manager:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: window_manager
|
||||||
|
sha256: "95096fede562cbb65f30d38b62d819a458f59ba9fe4a317f6cee669710f6676b"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "0.3.4"
|
||||||
xdg_directories:
|
xdg_directories:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
|
|||||||
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
||||||
# Read more about iOS versioning at
|
# Read more about iOS versioning at
|
||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||||
version: 1.3.0+1
|
version: 1.3.2+1
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=2.17.6 <3.0.0"
|
sdk: ">=2.17.6 <3.0.0"
|
||||||
@@ -49,11 +49,14 @@ dependencies:
|
|||||||
shared_preferences: ^2.0.15
|
shared_preferences: ^2.0.15
|
||||||
logger: ^1.3.0
|
logger: ^1.3.0
|
||||||
desktop_drop: ^0.3.3
|
desktop_drop: ^0.3.3
|
||||||
intl: ^0.17.0
|
package_info_plus: ^4.0.0
|
||||||
package_info_plus: ^3.0.2
|
|
||||||
fluent_ui: ^4.4.0
|
fluent_ui: ^4.4.0
|
||||||
path: any
|
path: any
|
||||||
badges: ^3.0.3
|
badges: ^3.0.3
|
||||||
|
app_links: ^3.4.2
|
||||||
|
uri_to_file: ^0.2.0
|
||||||
|
window_manager: ^0.3.4
|
||||||
|
tray_manager: ^0.2.0
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
@@ -65,6 +68,7 @@ dev_dependencies:
|
|||||||
# rules and activating additional ones.
|
# rules and activating additional ones.
|
||||||
flutter_lints: ^2.0.0
|
flutter_lints: ^2.0.0
|
||||||
ffigen: ^6.1.2
|
ffigen: ^6.1.2
|
||||||
|
# build json model: flutter pub 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
|
||||||
flutter_launcher_icons:
|
flutter_launcher_icons:
|
||||||
@@ -83,9 +87,8 @@ flutter:
|
|||||||
uses-material-design: true
|
uses-material-design: true
|
||||||
|
|
||||||
# To add assets to your application, add an assets section, like this:
|
# To add assets to your application, add an assets section, like this:
|
||||||
# assets:
|
assets:
|
||||||
# - macos/Frameworks/libgopeed.dylib
|
- assets/tray_icon/
|
||||||
# - macos/lib/libgopeed.dylib
|
|
||||||
|
|
||||||
# An image asset can refer to one or more resolution-specific "variants", see
|
# An image asset can refer to one or more resolution-specific "variants", see
|
||||||
# https://flutter.dev/assets-and-images/#resolution-aware
|
# https://flutter.dev/assets-and-images/#resolution-aware
|
||||||
|
|||||||
@@ -5,10 +5,8 @@
|
|||||||
// gestures. You can also use WidgetTester to find child widgets in the widget
|
// gestures. You can also use WidgetTester to find child widgets in the widget
|
||||||
// tree, read text, and verify that the values of widget properties are correct.
|
// tree, read text, and verify that the values of widget properties are correct.
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
import 'package:gopeed/main.dart';
|
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
testWidgets('Counter increments smoke test', (WidgetTester tester) async {});
|
testWidgets('Counter increments smoke test', (WidgetTester tester) async {});
|
||||||
|
|||||||
@@ -7,6 +7,12 @@
|
|||||||
|
|
||||||
int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
|
int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
|
||||||
_In_ wchar_t *command_line, _In_ int show_command) {
|
_In_ wchar_t *command_line, _In_ int show_command) {
|
||||||
|
HWND hwnd = ::FindWindow(L"FLUTTER_RUNNER_WIN32_WINDOW", L"gopeed");
|
||||||
|
if (hwnd != NULL) {
|
||||||
|
::ShowWindow(hwnd, SW_NORMAL);
|
||||||
|
::SetForegroundWindow(hwnd);
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
// Attach to console when present (e.g., 'flutter run') or create a
|
// Attach to console when present (e.g., 'flutter run') or create a
|
||||||
// new console when running with a debugger.
|
// new console when running with a debugger.
|
||||||
if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) {
|
if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) {
|
||||||
|
|||||||
Reference in new issue
Block a user