mirror of
https://github.com/GopeedLab/gopeed.git
synced 2026-11-10 10:30:28 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
45cf62f88d | ||
|
|
6e9d3499d9 | ||
|
|
b6d60f6bdf | ||
|
|
e1684e755a | ||
|
|
e63cfca82a | ||
|
|
85ef8d3be4 | ||
|
|
bf2ccdb5a7 | ||
|
|
a75bfd6118 | ||
|
|
a5d8713ff7 | ||
|
|
81518d794f | ||
|
|
7c2b81caf7 | ||
|
|
2221332243 | ||
|
|
dc407434d0 | ||
|
|
1c2e86a991 | ||
|
|
c78dc5f18f | ||
|
|
8ea5abb5c3 | ||
|
|
d1a4067dfa | ||
|
|
a1928d202f | ||
|
|
7376e85895 | ||
|
|
eb79dd5ac5 | ||
|
|
5b945b16c1 | ||
|
|
80e60f4190 | ||
|
|
3087796ef5 | ||
|
|
99de0aaf9b | ||
|
|
6bea61cf0a | ||
|
|
6c3553805e | ||
|
|
adc7c7766d | ||
|
|
15d0296307 | ||
|
|
32d2e46118 | ||
|
|
abe3cec13d | ||
|
|
9249613601 | ||
|
|
5bafb40599 | ||
|
|
5968d3c14a | ||
|
|
fcb54d3acc | ||
|
|
e2c4567ed0 | ||
|
|
c3b28c7034 | ||
|
|
32061200a5 |
No files matched your search
@@ -5,7 +5,7 @@ on:
|
||||
|
||||
env:
|
||||
GO_VERSION: 1.19
|
||||
FLUTTER_VERSION: 3.7.12
|
||||
FLUTTER_VERSION: 3.10.6
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
@@ -28,7 +28,7 @@ on:
|
||||
|
||||
env:
|
||||
GO_VERSION: 1.19
|
||||
FLUTTER_VERSION: 3.7.12
|
||||
FLUTTER_VERSION: 3.10.6
|
||||
|
||||
jobs:
|
||||
# lint:
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
name: 'translator'
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened, edited]
|
||||
issue_comment:
|
||||
types: [created, edited]
|
||||
discussion:
|
||||
types: [created, edited]
|
||||
discussion_comment:
|
||||
types: [created, edited]
|
||||
pull_request_target:
|
||||
types: [opened, edited]
|
||||
pull_request_review_comment:
|
||||
types: [created, edited]
|
||||
|
||||
jobs:
|
||||
translate:
|
||||
permissions:
|
||||
issues: write
|
||||
discussions: write
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: lizheming/[email protected]
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
IS_MODIFY_TITLE: true
|
||||
APPEND_TRANSLATION: true
|
||||
@@ -21,6 +21,7 @@ bin/
|
||||
|
||||
*.data
|
||||
*.db
|
||||
*.log
|
||||
|
||||
.DS_Store
|
||||
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
# Gopeed コントリビューターガイド
|
||||
|
||||
まず最初に、Gopeed への貢献に興味を持っていただきありがとうございます。このガイドは、あなたが Gopeed の
|
||||
開発に参加するための手助けとなるでしょう。
|
||||
|
||||
## ブランチの説明
|
||||
|
||||
このプロジェクトのメインブランチは `main` ブランチのみです。Gopeed の開発に参加したい場合は、
|
||||
まずこのプロジェクトをフォークし、フォークしたプロジェクトで開発を行ってください。開発が完了したら、
|
||||
このプロジェクトに PR を提出し、`main` ブランチにマージしてください。
|
||||
|
||||
## ローカル開発
|
||||
|
||||
開発およびデバッグはウェブ上で行うことを推奨する。まずバックエンドのサービスを起動し、
|
||||
コマンドライン `go run cmd/api/main.go` で起動する。サービスのデフォルトポートは `9999` で、
|
||||
次にフロントエンドの flutter プロジェクトを `debug` モードで起動して実行します。
|
||||
|
||||
## 翻訳
|
||||
|
||||
Gopeed の国際化ファイルは `ui/flutter/assets/locales` ディレクトリにあります。
|
||||
このディレクトリに対応する言語ファイルを追加するだけでよいです。
|
||||
|
||||
|
||||
ロケール編集後にロケールを生成:
|
||||
|
||||
|
||||
```
|
||||
get generate locales
|
||||
```
|
||||
|
||||
翻訳については `en_us.dart` を参照してください。
|
||||
|
||||
## flutter での開発
|
||||
|
||||
コミットする前に `dart format ./ui/flutter` を実行し、コードを標準の dart フォーマットにしておくことを忘れないでください
|
||||
|
||||
api/models を編集したい場合は build_runner watcher をオンにします:
|
||||
|
||||
```
|
||||
flutter pub run build_runner watch
|
||||
```
|
||||
|
||||
|
||||
get-cli コマンドの使用法:
|
||||
|
||||
```
|
||||
create:
|
||||
controller: Generate controller
|
||||
page: Use to generate pages
|
||||
view: Generate view
|
||||
generate:
|
||||
locales: Generate translation file from json files
|
||||
model: generate Class model from json
|
||||
```
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
FROM cirrusci/flutter:3.7.7 AS flutter
|
||||
FROM instrumentisto/flutter:3.10.6 AS flutter
|
||||
WORKDIR /app
|
||||
COPY ./ui/flutter/pubspec.yaml ./ui/flutter/pubspec.lock ./
|
||||
RUN flutter pub get
|
||||
@@ -18,4 +18,4 @@ LABEL maintainer="monkeyWie"
|
||||
WORKDIR /app
|
||||
COPY --from=go /app/dist/gopeed ./
|
||||
EXPOSE 9999
|
||||
ENTRYPOINT ["./gopeed"]
|
||||
ENTRYPOINT ["./gopeed"]
|
||||
@@ -1,12 +1,13 @@
|
||||
[](https://github.com/GopeedLab/gopeed/actions?query=workflow%3Atest)
|
||||
[](https://codecov.io/gh/GopeedLab/gopeed)
|
||||
[](https://github.com/GopeedLab/gopeed/releases)
|
||||
[](https://github.com/GopeedLab/gopeed/releases)
|
||||
[](https://github.com/GopeedLab/gopeed/releases)
|
||||
[](https://github.com/GopeedLab/gopeed/blob/main/.donate/index.md#donate)
|
||||
[](https://discord.gg/ZUJqJrwCGB)
|
||||
|
||||

|
||||
|
||||
[English](/README.md) | [中文](/README_zh-CN.md)
|
||||
[English](/README.md) | [中文](/README_zh-CN.md) | [日本語](/README_ja-JP.md)
|
||||
|
||||
## Introduction
|
||||
|
||||
@@ -43,7 +44,7 @@ go install github.com/GopeedLab/gopeed/cmd/gopeed@latest
|
||||
#### Start directly
|
||||
|
||||
```bash
|
||||
docker run -d -p 9999:9999 -v /path/to/download:/download liwei2633/gopeed
|
||||
docker run -d -p 9999:9999 -v /path/to/download:/root/Downloads liwei2633/gopeed
|
||||
```
|
||||
|
||||
#### Using Docker Compose
|
||||
@@ -56,8 +57,6 @@ docker-compose up -d
|
||||
|
||||
When the docker container is running, you can access the web page through `http://localhost:9999`.
|
||||
|
||||
> Tip: Modify the download path to `/download` on the setting page to access the downloaded files on the host.
|
||||
|
||||
## Donate
|
||||
|
||||
If you like this project, please consider [donating](/.donate/index.md#donate) to support the development of this project, thank you!
|
||||
@@ -75,7 +74,7 @@ This project is divided into two parts, the front end uses `flutter`, the back e
|
||||
### Environment
|
||||
|
||||
1. Golang 1.19+
|
||||
2. Flutter 3.7+
|
||||
2. Flutter 3.10+
|
||||
|
||||
### Clone
|
||||
|
||||
@@ -91,7 +90,7 @@ Please refer to [CONTRIBUTING.md](/CONTRIBUTING.md)
|
||||
|
||||
#### Desktop
|
||||
|
||||
First, you need to configure the environment according to the [flutter desktop official website document](https://docs.flutter.dev/development/platform-integration/desktop), and then you need to prepare the `cgo` environment, which can be searched for yourself.
|
||||
First, you need to configure the environment according to the official [Flutter desktop website documention](https://docs.flutter.dev/development/platform-integration/desktop), then you will need to ensure the cgo environment is set up accordingly. For detailed instructions on setting up the cgo environment, please refer to relevant resources available online.
|
||||
|
||||
command:
|
||||
|
||||
|
||||
+169
@@ -0,0 +1,169 @@
|
||||
[](https://github.com/GopeedLab/gopeed/actions?query=workflow%3Atest)
|
||||
[](https://codecov.io/gh/GopeedLab/gopeed)
|
||||
[](https://github.com/GopeedLab/gopeed/releases)
|
||||
[](https://github.com/GopeedLab/gopeed/releases)
|
||||
[](https://github.com/GopeedLab/gopeed/blob/main/.donate/index.md#donate)
|
||||
[](https://discord.gg/ZUJqJrwCGB)
|
||||
|
||||

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

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

|
||||
|
||||
[English](/README.md) | [中文](/README_zh-CN.md)
|
||||
[English](/README.md) | [中文](/README_zh-CN.md) | [日本語](/README_ja-JP.md)
|
||||
|
||||
## 介绍
|
||||
|
||||
@@ -43,7 +44,7 @@ go install github.com/GopeedLab/gopeed/cmd/gopeed@latest
|
||||
#### 直接运行
|
||||
|
||||
```bash
|
||||
docker run -d -p 9999:9999 -v /path/to/download:/download liwei2633/gopeed
|
||||
docker run -d -p 9999:9999 -v /path/to/download:/root/Downloads liwei2633/gopeed
|
||||
```
|
||||
|
||||
#### 使用 Docker Compose
|
||||
@@ -55,7 +56,6 @@ docker-compose up -d
|
||||
#### 访问服务
|
||||
|
||||
当 docker 容器运行时,可以通过 `http://localhost:9999` 访问 web 页面。
|
||||
> 提示:在设置页面把下载路径修改为 `/download` 以便在宿主机访问下载完的文件。
|
||||
|
||||
## 打赏
|
||||
|
||||
@@ -74,7 +74,7 @@ docker-compose up -d
|
||||
### 环境要求
|
||||
|
||||
1. Golang 1.19+
|
||||
2. Flutter 3.7+
|
||||
2. Flutter 3.10+
|
||||
|
||||
### 克隆项目
|
||||
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type args struct {
|
||||
Address *string `json:"address"`
|
||||
Port *int `json:"port"`
|
||||
Username *string `json:"username"`
|
||||
Password *string `json:"password"`
|
||||
ApiToken *string `json:"apiToken"`
|
||||
|
||||
configPath *string
|
||||
}
|
||||
|
||||
func parse() *args {
|
||||
var cliArgs args
|
||||
cliArgs.Address = flag.String("A", "0.0.0.0", "Bind Address")
|
||||
cliArgs.Port = flag.Int("P", 9999, "Bind Port")
|
||||
cliArgs.Username = flag.String("u", "gopeed", "HTTP Basic Auth Username")
|
||||
cliArgs.Password = flag.String("p", "", "HTTP Basic Auth Password")
|
||||
cliArgs.ApiToken = flag.String("T", "", "API token, that can only be used when basic authentication is enabled.")
|
||||
cliArgs.configPath = flag.String("c", "./config.json", "Config file path")
|
||||
flag.Parse()
|
||||
|
||||
fmt.Println("parse")
|
||||
|
||||
// args priority: config file > cli args
|
||||
cfgArgs := loadConfig(*cliArgs.configPath)
|
||||
if cfgArgs.Address == nil {
|
||||
cfgArgs.Address = cliArgs.Address
|
||||
}
|
||||
if cfgArgs.Port == nil {
|
||||
cfgArgs.Port = cliArgs.Port
|
||||
}
|
||||
if cfgArgs.Username == nil {
|
||||
cfgArgs.Username = cliArgs.Username
|
||||
}
|
||||
if cfgArgs.Password == nil {
|
||||
cfgArgs.Password = cliArgs.Password
|
||||
}
|
||||
if cfgArgs.ApiToken == nil {
|
||||
cfgArgs.ApiToken = cliArgs.ApiToken
|
||||
}
|
||||
return cfgArgs
|
||||
}
|
||||
|
||||
func loadConfig(path string) *args {
|
||||
var args args
|
||||
|
||||
if !filepath.IsAbs(path) {
|
||||
dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
|
||||
if err != nil {
|
||||
return &args
|
||||
}
|
||||
path = filepath.Join(dir, path)
|
||||
}
|
||||
file, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return &args
|
||||
}
|
||||
fmt.Println("config file read failed, reason:" + err.Error())
|
||||
return &args
|
||||
}
|
||||
if err = json.Unmarshal(file, &args); err != nil {
|
||||
fmt.Println("config file parse failed, reason:" + err.Error())
|
||||
return &args
|
||||
}
|
||||
fmt.Printf("config file loaded: %s\n", path)
|
||||
return &args
|
||||
}
|
||||
+22
-5
@@ -5,6 +5,7 @@ package main
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"fmt"
|
||||
"github.com/GopeedLab/gopeed/cmd"
|
||||
"github.com/GopeedLab/gopeed/pkg/rest/model"
|
||||
"io/fs"
|
||||
@@ -18,12 +19,28 @@ func main() {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
args := parse()
|
||||
var webBasicAuth *model.WebBasicAuth
|
||||
if isNotBlank(args.Username) && isNotBlank(args.Password) {
|
||||
webBasicAuth = &model.WebBasicAuth{
|
||||
Username: *args.Username,
|
||||
Password: *args.Password,
|
||||
}
|
||||
}
|
||||
|
||||
cfg := &model.StartConfig{
|
||||
Network: "tcp",
|
||||
Address: "0.0.0.0:9999",
|
||||
Storage: model.StorageBolt,
|
||||
WebEnable: true,
|
||||
WebFS: sub,
|
||||
Network: "tcp",
|
||||
Address: fmt.Sprintf("%s:%d", *args.Address, *args.Port),
|
||||
Storage: model.StorageBolt,
|
||||
ApiToken: *args.ApiToken,
|
||||
WebEnable: true,
|
||||
WebFS: sub,
|
||||
WebBasicAuth: webBasicAuth,
|
||||
}
|
||||
cmd.Start(cfg)
|
||||
}
|
||||
|
||||
func isNotBlank(str *string) bool {
|
||||
return str != nil && *str != ""
|
||||
}
|
||||
+1
-1
@@ -7,5 +7,5 @@ services:
|
||||
- 9999:9999 # HTTP port (host:container)
|
||||
image: liwei2633/gopeed
|
||||
volumes:
|
||||
- ~/Downloads:/download # replace with your prefer path
|
||||
- ~/Downloads:/root/Downloads # replace with your prefer path
|
||||
restart: unless-stopped
|
||||
@@ -3,71 +3,77 @@ module github.com/GopeedLab/gopeed
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/anacrolix/torrent v1.46.0
|
||||
github.com/anacrolix/missinggo/v2 v2.7.2-0.20230527121029-a582b4f397b9
|
||||
github.com/anacrolix/torrent v1.52.5
|
||||
github.com/gorilla/handlers v1.5.1
|
||||
github.com/gorilla/mux v1.8.0
|
||||
github.com/matoous/go-nanoid/v2 v2.0.0
|
||||
github.com/virtuald/go-paniclog v0.0.0-20190812204905-43a7fa316459
|
||||
go.etcd.io/bbolt v1.3.6
|
||||
golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde
|
||||
)
|
||||
|
||||
require (
|
||||
crawshaw.io/sqlite v0.3.3-0.20210127221821-98b1f83c5508 // indirect
|
||||
github.com/RoaringBitmap/roaring v1.0.1-0.20220510143707-3f418c4f42a4 // indirect
|
||||
crawshaw.io/sqlite v0.3.3-0.20220618202545-d1964889ea3c // indirect
|
||||
github.com/RoaringBitmap/roaring v1.2.3 // indirect
|
||||
github.com/ajwerner/btree v0.0.0-20211221152037-f427b3e689c0 // indirect
|
||||
github.com/alecthomas/atomic v0.1.0-alpha2 // indirect
|
||||
github.com/anacrolix/chansync v0.3.0 // indirect
|
||||
github.com/anacrolix/dht/v2 v2.16.2-0.20220311024416-dd658f18fd51 // indirect
|
||||
github.com/anacrolix/dht/v2 v2.19.2-0.20221121215055-066ad8494444 // indirect
|
||||
github.com/anacrolix/envpprof v1.2.1 // indirect
|
||||
github.com/anacrolix/generics v0.0.0-20220618083756-f99e35403a60 // indirect
|
||||
github.com/anacrolix/go-libutp v1.2.0 // indirect
|
||||
github.com/anacrolix/log v0.13.1 // indirect
|
||||
github.com/anacrolix/generics v0.0.0-20230428105757-683593396d68 // indirect
|
||||
github.com/anacrolix/go-libutp v1.3.1 // indirect
|
||||
github.com/anacrolix/log v0.14.0 // indirect
|
||||
github.com/anacrolix/missinggo v1.3.0 // indirect
|
||||
github.com/anacrolix/missinggo/perf v1.0.0 // indirect
|
||||
github.com/anacrolix/missinggo/v2 v2.7.0 // indirect
|
||||
github.com/anacrolix/mmsg v1.0.0 // indirect
|
||||
github.com/anacrolix/multiless v0.2.1-0.20211218050420-533661eef5dc // indirect
|
||||
github.com/anacrolix/stm v0.3.0 // indirect
|
||||
github.com/anacrolix/multiless v0.3.0 // indirect
|
||||
github.com/anacrolix/stm v0.4.0 // indirect
|
||||
github.com/anacrolix/sync v0.4.0 // indirect
|
||||
github.com/anacrolix/upnp v0.1.3-0.20220123035249-922794e51c96 // indirect
|
||||
github.com/anacrolix/utp v0.1.0 // indirect
|
||||
github.com/bahlo/generic-list-go v0.2.0 // indirect
|
||||
github.com/benbjohnson/immutable v0.3.0 // indirect
|
||||
github.com/bits-and-blooms/bitset v1.2.0 // indirect
|
||||
github.com/bits-and-blooms/bitset v1.2.2 // indirect
|
||||
github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dustin/go-humanize v1.0.0 // indirect
|
||||
github.com/edsrzf/mmap-go v1.1.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.1 // indirect
|
||||
github.com/google/btree v1.0.1 // indirect
|
||||
github.com/go-logr/logr v1.2.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/google/btree v1.1.2 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/gorilla/websocket v1.4.2 // indirect
|
||||
github.com/gorilla/websocket v1.5.0 // indirect
|
||||
github.com/huandu/xstrings v1.3.2 // indirect
|
||||
github.com/lispad/go-generics-tools v1.0.0 // indirect
|
||||
github.com/lispad/go-generics-tools v1.1.0 // indirect
|
||||
github.com/mschoch/smat v0.2.0 // indirect
|
||||
github.com/pion/datachannel v1.5.2 // indirect
|
||||
github.com/pion/dtls/v2 v2.1.2 // indirect
|
||||
github.com/pion/ice/v2 v2.1.20 // indirect
|
||||
github.com/pion/interceptor v0.1.7 // indirect
|
||||
github.com/pion/dtls/v2 v2.2.4 // indirect
|
||||
github.com/pion/ice/v2 v2.2.6 // indirect
|
||||
github.com/pion/interceptor v0.1.11 // indirect
|
||||
github.com/pion/logging v0.2.2 // indirect
|
||||
github.com/pion/mdns v0.0.5 // indirect
|
||||
github.com/pion/randutil v0.1.0 // indirect
|
||||
github.com/pion/rtcp v1.2.9 // indirect
|
||||
github.com/pion/rtp v1.7.4 // indirect
|
||||
github.com/pion/rtp v1.7.13 // indirect
|
||||
github.com/pion/sctp v1.8.2 // indirect
|
||||
github.com/pion/sdp/v3 v3.0.4 // indirect
|
||||
github.com/pion/srtp/v2 v2.0.5 // indirect
|
||||
github.com/pion/sdp/v3 v3.0.5 // indirect
|
||||
github.com/pion/srtp/v2 v2.0.9 // indirect
|
||||
github.com/pion/stun v0.3.5 // indirect
|
||||
github.com/pion/transport v0.13.0 // indirect
|
||||
github.com/pion/turn/v2 v2.0.6 // indirect
|
||||
github.com/pion/udp v0.1.1 // indirect
|
||||
github.com/pion/webrtc/v3 v3.1.24-0.20220208053747-94262c1b2b38 // indirect
|
||||
github.com/pion/transport v0.13.1 // indirect
|
||||
github.com/pion/transport/v2 v2.0.0 // indirect
|
||||
github.com/pion/turn/v2 v2.0.8 // indirect
|
||||
github.com/pion/udp v0.1.4 // indirect
|
||||
github.com/pion/webrtc/v3 v3.1.42 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/rs/dnscache v0.0.0-20210201191234-295bba877686 // indirect
|
||||
github.com/tidwall/btree v1.3.1 // indirect
|
||||
golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838 // indirect
|
||||
golang.org/x/exp v0.0.0-20220428152302-39d4317da171 // indirect
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect
|
||||
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
github.com/rs/dnscache v0.0.0-20211102005908-e0241e321417 // indirect
|
||||
github.com/tidwall/btree v1.6.0 // indirect
|
||||
go.opentelemetry.io/otel v1.8.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.8.0 // indirect
|
||||
golang.org/x/crypto v0.5.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20220613132600-b0d781184e0d // indirect
|
||||
golang.org/x/net v0.7.0 // indirect
|
||||
golang.org/x/sys v0.5.0 // indirect
|
||||
golang.org/x/time v0.0.0-20220609170525-579cf78fd858 // indirect
|
||||
)
|
||||
@@ -3,42 +3,46 @@ cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
|
||||
crawshaw.io/iox v0.0.0-20181124134642-c51c3df30797 h1:yDf7ARQc637HoxDho7xjqdvO5ZA2Yb+xzv/fOnnvZzw=
|
||||
crawshaw.io/iox v0.0.0-20181124134642-c51c3df30797/go.mod h1:sXBiorCo8c46JlQV3oXPKINnZ8mcqnye1EkVkqsectk=
|
||||
crawshaw.io/sqlite v0.3.2/go.mod h1:igAO5JulrQ1DbdZdtVq48mnZUBAPOeFzer7VhDWNtW4=
|
||||
crawshaw.io/sqlite v0.3.3-0.20210127221821-98b1f83c5508 h1:fILCBBFnjnrQ0whVJlGhfv1E/QiaFDNtGFBObEVRnYg=
|
||||
crawshaw.io/sqlite v0.3.3-0.20210127221821-98b1f83c5508/go.mod h1:igAO5JulrQ1DbdZdtVq48mnZUBAPOeFzer7VhDWNtW4=
|
||||
crawshaw.io/sqlite v0.3.3-0.20220618202545-d1964889ea3c h1:wvzox0eLO6CKQAMcOqz7oH3UFqMpMmK7kwmwV+22HIs=
|
||||
crawshaw.io/sqlite v0.3.3-0.20220618202545-d1964889ea3c/go.mod h1:igAO5JulrQ1DbdZdtVq48mnZUBAPOeFzer7VhDWNtW4=
|
||||
filippo.io/edwards25519 v1.0.0-rc.1 h1:m0VOOB23frXZvAOK44usCgLWvtsxIoMCTBGJZlpmGfU=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/RoaringBitmap/roaring v0.4.7/go.mod h1:8khRDP4HmeXns4xIj9oGrKSz7XTQiJx2zgh7AcNke4w=
|
||||
github.com/RoaringBitmap/roaring v0.4.17/go.mod h1:D3qVegWTmfCaX4Bl5CrBE9hfrSrrXIr8KVNvRsDi1NI=
|
||||
github.com/RoaringBitmap/roaring v0.4.23/go.mod h1:D0gp8kJQgE1A4LQ5wFLggQEyvDi06Mq5mKs52e1TwOo=
|
||||
github.com/RoaringBitmap/roaring v1.0.1-0.20220510143707-3f418c4f42a4 h1:LxR70Si8X8IlYTeKiPReOBt9pla6wEJDlkawfwDdrB0=
|
||||
github.com/RoaringBitmap/roaring v1.0.1-0.20220510143707-3f418c4f42a4/go.mod h1:icnadbWcNyfEHlYdr+tDlOTih1Bf/h+rzPpv4sbomAA=
|
||||
github.com/RoaringBitmap/roaring v1.2.3 h1:yqreLINqIrX22ErkKI0vY47/ivtJr6n+kMhVOVmhWBY=
|
||||
github.com/RoaringBitmap/roaring v1.2.3/go.mod h1:plvDsJQpxOC5bw8LRteu/MLWHsHez/3y6cubLI4/1yE=
|
||||
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
|
||||
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
|
||||
github.com/ajwerner/btree v0.0.0-20211221152037-f427b3e689c0 h1:byYvvbfSo3+9efR4IeReh77gVs4PnNDR3AMOE9NJ7a0=
|
||||
github.com/ajwerner/btree v0.0.0-20211221152037-f427b3e689c0/go.mod h1:q37NoqncT41qKc048STsifIt69LfUJ8SrWWcz/yam5k=
|
||||
github.com/alecthomas/assert/v2 v2.0.0-alpha3 h1:pcHeMvQ3OMstAWgaeaXIAL8uzB9xMm2zlxt+/4ml8lk=
|
||||
github.com/alecthomas/atomic v0.1.0-alpha2 h1:dqwXmax66gXvHhsOS4pGPZKqYOlTkapELkLb3MNdlH8=
|
||||
github.com/alecthomas/atomic v0.1.0-alpha2/go.mod h1:zD6QGEyw49HIq19caJDc2NMXAy8rNi9ROrxtMXATfyI=
|
||||
github.com/alecthomas/repr v0.0.0-20210801044451-80ca428c5142 h1:8Uy0oSf5co/NZXje7U1z8Mpep++QJOldL2hs/sBQf48=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/anacrolix/chansync v0.3.0 h1:lRu9tbeuw3wl+PhMu/r+JJCRu5ArFXIluOgdF0ao6/U=
|
||||
github.com/anacrolix/chansync v0.3.0/go.mod h1:DZsatdsdXxD0WiwcGl0nJVwyjCKMDv+knl1q2iBjA2k=
|
||||
github.com/anacrolix/dht/v2 v2.16.2-0.20220311024416-dd658f18fd51 h1:issCwqC43gQ7n0gg9rn0EeVYXnQMI7vlnWub4oidtlU=
|
||||
github.com/anacrolix/dht/v2 v2.16.2-0.20220311024416-dd658f18fd51/go.mod h1:osiyaNrMLG9dw7wUtVMaII/NdCjlXeHjUcYzXnmop68=
|
||||
github.com/anacrolix/dht/v2 v2.19.2-0.20221121215055-066ad8494444 h1:8V0K09lrGoeT2KRJNOtspA7q+OMxGwQqK/Ug0IiaaRE=
|
||||
github.com/anacrolix/dht/v2 v2.19.2-0.20221121215055-066ad8494444/go.mod h1:MctKM1HS5YYDb3F30NGJxLE+QPuqWoT5ReW/4jt8xew=
|
||||
github.com/anacrolix/envpprof v0.0.0-20180404065416-323002cec2fa/go.mod h1:KgHhUaQMc8cC0+cEflSgCFNFbKwi5h54gqtVn8yhP7c=
|
||||
github.com/anacrolix/envpprof v1.0.0/go.mod h1:KgHhUaQMc8cC0+cEflSgCFNFbKwi5h54gqtVn8yhP7c=
|
||||
github.com/anacrolix/envpprof v1.1.0/go.mod h1:My7T5oSqVfEn4MD4Meczkw/f5lSIndGAKu/0SM/rkf4=
|
||||
github.com/anacrolix/envpprof v1.2.1 h1:25TJe6t/i0AfzzldiGFKCpD+s+dk8lONBcacJZB2rdE=
|
||||
github.com/anacrolix/envpprof v1.2.1/go.mod h1:My7T5oSqVfEn4MD4Meczkw/f5lSIndGAKu/0SM/rkf4=
|
||||
github.com/anacrolix/generics v0.0.0-20220618083756-f99e35403a60 h1:k4/h2B1gGF+PJGyGHxs8nmHHt1pzWXZWBj6jn4OBlRc=
|
||||
github.com/anacrolix/generics v0.0.0-20220618083756-f99e35403a60/go.mod h1:ff2rHB/joTV03aMSSn/AZNnaIpUw0h3njetGsaXcMy8=
|
||||
github.com/anacrolix/go-libutp v1.2.0 h1:sjxoB+/ARiKUR7IK/6wLWyADIBqGmu1fm0xo+8Yy7u0=
|
||||
github.com/anacrolix/go-libutp v1.2.0/go.mod h1:RrJ3KcaDcf9Jqp33YL5V/5CBEc6xMc7aJL8wXfuWL50=
|
||||
github.com/anacrolix/generics v0.0.0-20230428105757-683593396d68 h1:fyXlBfnlFzZSFckJ8QLb2lfmWfY++4RiUnae7ZMuv0A=
|
||||
github.com/anacrolix/generics v0.0.0-20230428105757-683593396d68/go.mod h1:ff2rHB/joTV03aMSSn/AZNnaIpUw0h3njetGsaXcMy8=
|
||||
github.com/anacrolix/go-libutp v1.3.1 h1:idJzreNLl+hNjGC3ZnUOjujEaryeOGgkwHLqSGoige0=
|
||||
github.com/anacrolix/go-libutp v1.3.1/go.mod h1:heF41EC8kN0qCLMokLBVkB8NXiLwx3t8R8810MTNI5o=
|
||||
github.com/anacrolix/log v0.3.0/go.mod h1:lWvLTqzAnCWPJA08T2HCstZi0L1y2Wyvm3FJgwU9jwU=
|
||||
github.com/anacrolix/log v0.6.0/go.mod h1:lWvLTqzAnCWPJA08T2HCstZi0L1y2Wyvm3FJgwU9jwU=
|
||||
github.com/anacrolix/log v0.10.0/go.mod h1:s5yBP/j046fm9odtUTbHOfDUq/zh1W8OkPpJtnX0oQI=
|
||||
github.com/anacrolix/log v0.10.1-0.20220123034749-3920702c17f8/go.mod h1:GmnE2c0nvz8pOIPUSC9Rawgefy1sDXqposC2wgtBZE4=
|
||||
github.com/anacrolix/log v0.13.1 h1:BmVwTdxHd5VcNrLylgKwph4P4wf+5VvPgOK4yi91fTY=
|
||||
github.com/anacrolix/log v0.13.1/go.mod h1:D4+CvN8SnruK6zIFS/xPoRJmtvtnxs+CSfDQ+BFxZ68=
|
||||
github.com/anacrolix/log v0.14.0 h1:mYhTSemILe/Z8tIxbGdTIWWpPspI8W/fhZHpoFbDaL0=
|
||||
github.com/anacrolix/log v0.14.0/go.mod h1:1OmJESOtxQGNMlUO5rcv96Vpp9mfMqXXbe2RdinFLdY=
|
||||
github.com/anacrolix/lsan v0.0.0-20211126052245-807000409a62 h1:P04VG6Td13FHMgS5ZBcJX23NPC/fiC4cp9bXwYujdYM=
|
||||
github.com/anacrolix/lsan v0.0.0-20211126052245-807000409a62/go.mod h1:66cFKPCO7Sl4vbFnAaSq7e4OXtdMhRSBagJGWgmpJbM=
|
||||
github.com/anacrolix/missinggo v0.0.0-20180725070939-60ef2fbf63df/go.mod h1:kwGiTUTZ0+p4vAz3VbAI5a30t2YbvemcmspjKwrAz5s=
|
||||
@@ -52,16 +56,16 @@ github.com/anacrolix/missinggo/perf v1.0.0/go.mod h1:ljAFWkBuzkO12MQclXzZrosP5ur
|
||||
github.com/anacrolix/missinggo/v2 v2.2.0/go.mod h1:o0jgJoYOyaoYQ4E2ZMISVa9c88BbUBVQQW4QeRkNCGY=
|
||||
github.com/anacrolix/missinggo/v2 v2.5.1/go.mod h1:WEjqh2rmKECd0t1VhQkLGTdIWXO6f6NLjp5GlMZ+6FA=
|
||||
github.com/anacrolix/missinggo/v2 v2.5.2/go.mod h1:yNvsLrtZYRYCOI+KRH/JM8TodHjtIE/bjOGhQaLOWIE=
|
||||
github.com/anacrolix/missinggo/v2 v2.7.0 h1:4fzOAAn/VCvfWGviLmh64MPMttrlYew81JdPO7nSHvI=
|
||||
github.com/anacrolix/missinggo/v2 v2.7.0/go.mod h1:2IZIvmRTizALNYFYXsPR7ofXPzJgyBpKZ4kMqMEICkI=
|
||||
github.com/anacrolix/missinggo/v2 v2.7.2-0.20230527121029-a582b4f397b9 h1:W/oGeHhYwxueeiDjQfmK9G+X9M2xJgfTtow62v0TWAs=
|
||||
github.com/anacrolix/missinggo/v2 v2.7.2-0.20230527121029-a582b4f397b9/go.mod h1:mIEtp9pgaXqt8VQ3NQxFOod/eQ1H0D1XsZzKUQfwtac=
|
||||
github.com/anacrolix/mmsg v0.0.0-20180515031531-a4a3ba1fc8bb/go.mod h1:x2/ErsYUmT77kezS63+wzZp8E3byYB0gzirM/WMBLfw=
|
||||
github.com/anacrolix/mmsg v1.0.0 h1:btC7YLjOn29aTUAExJiVUhQOuf/8rhm+/nWCMAnL3Hg=
|
||||
github.com/anacrolix/mmsg v1.0.0/go.mod h1:x8kRaJY/dCrY9Al0PEcj1mb/uFHwP6GCJ9fLl4thEPc=
|
||||
github.com/anacrolix/multiless v0.2.1-0.20211218050420-533661eef5dc h1:K047jUtd0Xv4SEpv/5DoBgDvj4ZNpT1SOVtMlFpRrh0=
|
||||
github.com/anacrolix/multiless v0.2.1-0.20211218050420-533661eef5dc/go.mod h1:TrCLEZfIDbMVfLoQt5tOoiBS/uq4y8+ojuEVVvTNPX4=
|
||||
github.com/anacrolix/multiless v0.3.0 h1:5Bu0DZncjE4e06b9r1Ap2tUY4Au0NToBP5RpuEngSis=
|
||||
github.com/anacrolix/multiless v0.3.0/go.mod h1:TrCLEZfIDbMVfLoQt5tOoiBS/uq4y8+ojuEVVvTNPX4=
|
||||
github.com/anacrolix/stm v0.2.0/go.mod h1:zoVQRvSiGjGoTmbM0vSLIiaKjWtNPeTvXUSdJQA4hsg=
|
||||
github.com/anacrolix/stm v0.3.0 h1:peQncJSNJtk1YBrFbW0DLKYqll+sa0kOk8EvXRcO+wA=
|
||||
github.com/anacrolix/stm v0.3.0/go.mod h1:spImf/rXwiAUoYYJK1YCZeWkpaHZ3kzjGFjwK5OStfU=
|
||||
github.com/anacrolix/stm v0.4.0 h1:tOGvuFwaBjeu1u9X1eIh9TX8OEedEiEQ1se1FjhFnXY=
|
||||
github.com/anacrolix/stm v0.4.0/go.mod h1:GCkwqWoAsP7RfLW+jw+Z0ovrt2OO7wRzcTtFYMYY5t8=
|
||||
github.com/anacrolix/sync v0.0.0-20180808010631-44578de4e778/go.mod h1:s735Etp3joe/voe2sdaXLcqDdJSay1O0OPnM0ystjqk=
|
||||
github.com/anacrolix/sync v0.3.0/go.mod h1:BbecHL6jDSExojhNtgTFSBcdGerzNc64tz3DCOj/I0g=
|
||||
github.com/anacrolix/sync v0.4.0 h1:T+MdO/u87ir/ijWsTFsPYw5jVm0SMm4kVpg8t4KF38o=
|
||||
@@ -69,8 +73,8 @@ github.com/anacrolix/sync v0.4.0/go.mod h1:BbecHL6jDSExojhNtgTFSBcdGerzNc64tz3DC
|
||||
github.com/anacrolix/tagflag v0.0.0-20180109131632-2146c8d41bf0/go.mod h1:1m2U/K6ZT+JZG0+bdMK6qauP49QT4wE5pmhJXOKKCHw=
|
||||
github.com/anacrolix/tagflag v1.0.0/go.mod h1:1m2U/K6ZT+JZG0+bdMK6qauP49QT4wE5pmhJXOKKCHw=
|
||||
github.com/anacrolix/tagflag v1.1.0/go.mod h1:Scxs9CV10NQatSmbyjqmqmeQNwGzlNe0CMUMIxqHIG8=
|
||||
github.com/anacrolix/torrent v1.46.0 h1:m5SGlW4p0dJkqrIh4bCqzcKbKFFmfJorf9LSpSM5dEc=
|
||||
github.com/anacrolix/torrent v1.46.0/go.mod h1:3DE+VA4AgyfKDPjZcIo70D3VFZRo3bfdEBn70CGjca4=
|
||||
github.com/anacrolix/torrent v1.52.5 h1:jWowdx+EU6zFVfBwmnL0d3H4J6vTFEGOrHI35YdfIT8=
|
||||
github.com/anacrolix/torrent v1.52.5/go.mod h1:CcM8oPMYye5J42cSqJrmUpqwRFgSsJQ1jCEHwygqnqQ=
|
||||
github.com/anacrolix/upnp v0.1.3-0.20220123035249-922794e51c96 h1:QAVZ3pN/J4/UziniAhJR2OZ9Ox5kOY2053tBbbqUPYA=
|
||||
github.com/anacrolix/upnp v0.1.3-0.20220123035249-922794e51c96/go.mod h1:Wa6n8cYIdaG35x15aH3Zy6d03f7P728QfdcDeD/IEOs=
|
||||
github.com/anacrolix/utp v0.1.0 h1:FOpQOmIwYsnENnz7tAGohA+r6iXpRjrq8ssKSre2Cp4=
|
||||
@@ -84,8 +88,9 @@ github.com/benbjohnson/immutable v0.3.0/go.mod h1:uc6OHo6PN2++n98KHLxW8ef4W42ylH
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bits-and-blooms/bitset v1.2.0 h1:Kn4yilvwNtMACtf1eYDlG8H77R07mZSPbMjLyS07ChA=
|
||||
github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
|
||||
github.com/bits-and-blooms/bitset v1.2.2 h1:J5gbX05GpMdBjCvQ9MteIg2KKDExr7DrgK+Yc15FvIk=
|
||||
github.com/bits-and-blooms/bitset v1.2.2/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
|
||||
github.com/bradfitz/iter v0.0.0-20140124041915-454541ec3da2/go.mod h1:PyRFw1Lt2wKX4ZVSQ2mk+PeDa1rxyObEDlApuIsUKuo=
|
||||
github.com/bradfitz/iter v0.0.0-20190303215204-33e6a9893b0c/go.mod h1:PyRFw1Lt2wKX4ZVSQ2mk+PeDa1rxyObEDlApuIsUKuo=
|
||||
github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 h1:GKTyiRCL6zVf5wWaqKnf+7Qs6GbEPfd4iMOitWzXJx8=
|
||||
@@ -108,7 +113,7 @@ github.com/edsrzf/mmap-go v1.1.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8E
|
||||
github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ=
|
||||
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/frankban/quicktest v1.9.0/go.mod h1:ui7WezCLWMWxVWr1GETZY3smRy0G4KWq9vcPtJmFl7Y=
|
||||
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
|
||||
github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE=
|
||||
@@ -121,6 +126,11 @@ github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2
|
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
|
||||
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
@@ -144,14 +154,14 @@ github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8l
|
||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/btree v0.0.0-20180124185431-e89373fe6b4a/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
|
||||
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
|
||||
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
|
||||
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
@@ -165,9 +175,10 @@ github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv
|
||||
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/huandu/xstrings v1.0.0/go.mod h1:4qWG/gcEcfX4z/mBDHJ++3ReCw9ibxbsNJbcucJdbSo=
|
||||
github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4=
|
||||
@@ -185,14 +196,14 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxv
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/lispad/go-generics-tools v1.0.0 h1:pPtt9QERE5sGQfwvLakKe2sjhcNKKsDSnr3939ofj1E=
|
||||
github.com/lispad/go-generics-tools v1.0.0/go.mod h1:stn7X24ZIyFvaSyttafq3VlJzGJJJkUtLYdbgi/gopM=
|
||||
github.com/lispad/go-generics-tools v1.1.0 h1:mbSgcxdFVmpoyso1X/MJHXbSbSL3dD+qhRryyxk+/XY=
|
||||
github.com/lispad/go-generics-tools v1.1.0/go.mod h1:2csd1EJljo/gy5qG4khXol7ivCPptNjG5Uv2X8MgK84=
|
||||
github.com/matoous/go-nanoid v1.5.0/go.mod h1:zyD2a71IubI24efhpvkJz+ZwfwagzgSO6UNiFsZKN7U=
|
||||
github.com/matoous/go-nanoid/v2 v2.0.0 h1:d19kur2QuLeHmJBkvYkFdhFBzLoo1XVm2GgTpL+9Tj0=
|
||||
github.com/matoous/go-nanoid/v2 v2.0.0/go.mod h1:FtS4aGPVfEkxKxhdWPAspZpZSh1cOjtM7Ej/So3hR0g=
|
||||
@@ -221,44 +232,47 @@ github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG
|
||||
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||
github.com/pion/datachannel v1.5.2 h1:piB93s8LGmbECrpO84DnkIVWasRMk3IimbcXkTQLE6E=
|
||||
github.com/pion/datachannel v1.5.2/go.mod h1:FTGQWaHrdCwIJ1rw6xBIfZVkslikjShim5yr05XFuCQ=
|
||||
github.com/pion/dtls/v2 v2.1.1/go.mod h1:qG3gA7ZPZemBqpEFqRKyURYdKEwFZQCGb7gv9T3ON3Y=
|
||||
github.com/pion/dtls/v2 v2.1.2 h1:22Q1Jk9L++Yo7BIf9130MonNPfPVb+YgdYLeyQotuAA=
|
||||
github.com/pion/dtls/v2 v2.1.2/go.mod h1:o6+WvyLDAlXF7YiPB/RlskRoeK+/JtuaZa5emwQcWus=
|
||||
github.com/pion/ice/v2 v2.1.20 h1:xpxXyX5b4WjCh/D905gzBeW/hbJxMEPx2ptVfrhVE6M=
|
||||
github.com/pion/ice/v2 v2.1.20/go.mod h1:hEAldRzBhTtAfvlU1V/2/nLCMvveQWFKPNCop+63/Iw=
|
||||
github.com/pion/interceptor v0.1.7 h1:HThW0tIIKT9RRoDWGURe8rlZVOx0fJHxBHpA0ej0+bo=
|
||||
github.com/pion/interceptor v0.1.7/go.mod h1:Lh3JSl/cbJ2wP8I3ccrjh1K/deRGRn3UlSPuOTiHb6U=
|
||||
github.com/pion/dtls/v2 v2.1.3/go.mod h1:o6+WvyLDAlXF7YiPB/RlskRoeK+/JtuaZa5emwQcWus=
|
||||
github.com/pion/dtls/v2 v2.1.5/go.mod h1:BqCE7xPZbPSubGasRoDFJeTsyJtdD1FanJYL0JGheqY=
|
||||
github.com/pion/dtls/v2 v2.2.4 h1:YSfYwDQgrxMYXLBc/m7PFY5BVtWlNm/DN4qoU2CbcWg=
|
||||
github.com/pion/dtls/v2 v2.2.4/go.mod h1:WGKfxqhrddne4Kg3p11FUMJrynkOY4lb25zHNO49wuw=
|
||||
github.com/pion/ice/v2 v2.2.6 h1:R/vaLlI1J2gCx141L5PEwtuGAGcyS6e7E0hDeJFq5Ig=
|
||||
github.com/pion/ice/v2 v2.2.6/go.mod h1:SWuHiOGP17lGromHTFadUe1EuPgFh/oCU6FCMZHooVE=
|
||||
github.com/pion/interceptor v0.1.11 h1:00U6OlqxA3FFB50HSg25J/8cWi7P6FbSzw4eFn24Bvs=
|
||||
github.com/pion/interceptor v0.1.11/go.mod h1:tbtKjZY14awXd7Bq0mmWvgtHB5MDaRN7HV3OZ/uy7s8=
|
||||
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
|
||||
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
|
||||
github.com/pion/mdns v0.0.5 h1:Q2oj/JB3NqfzY9xGZ1fPzZzK7sDSD8rZPOvcIQ10BCw=
|
||||
github.com/pion/mdns v0.0.5/go.mod h1:UgssrvdD3mxpi8tMxAXbsppL3vJ4Jipw1mTCW+al01g=
|
||||
github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
|
||||
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
|
||||
github.com/pion/rtcp v1.2.6/go.mod h1:52rMNPWFsjr39z9B9MhnkqhPLoeHTv1aN63o/42bWE0=
|
||||
github.com/pion/rtcp v1.2.9 h1:1ujStwg++IOLIEoOiIQ2s+qBuJ1VN81KW+9pMPsif+U=
|
||||
github.com/pion/rtcp v1.2.9/go.mod h1:qVPhiCzAm4D/rxb6XzKeyZiQK69yJpbUDJSF7TgrqNo=
|
||||
github.com/pion/rtp v1.7.0/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
|
||||
github.com/pion/rtp v1.7.4 h1:4dMbjb1SuynU5OpA3kz1zHK+u+eOCQjW3MAeVHf1ODA=
|
||||
github.com/pion/rtp v1.7.4/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
|
||||
github.com/pion/rtp v1.7.13 h1:qcHwlmtiI50t1XivvoawdCGTP4Uiypzfrsap+bijcoA=
|
||||
github.com/pion/rtp v1.7.13/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
|
||||
github.com/pion/sctp v1.8.0/go.mod h1:xFe9cLMZ5Vj6eOzpyiKjT9SwGM4KpK/8Jbw5//jc+0s=
|
||||
github.com/pion/sctp v1.8.2 h1:yBBCIrUMJ4yFICL3RIvR4eh/H2BTTvlligmSTy+3kiA=
|
||||
github.com/pion/sctp v1.8.2/go.mod h1:xFe9cLMZ5Vj6eOzpyiKjT9SwGM4KpK/8Jbw5//jc+0s=
|
||||
github.com/pion/sdp/v3 v3.0.4 h1:2Kf+dgrzJflNCSw3TV5v2VLeI0s/qkzy2r5jlR0wzf8=
|
||||
github.com/pion/sdp/v3 v3.0.4/go.mod h1:bNiSknmJE0HYBprTHXKPQ3+JjacTv5uap92ueJZKsRk=
|
||||
github.com/pion/srtp/v2 v2.0.5 h1:ks3wcTvIUE/GHndO3FAvROQ9opy0uLELpwHJaQ1yqhQ=
|
||||
github.com/pion/srtp/v2 v2.0.5/go.mod h1:8k6AJlal740mrZ6WYxc4Dg6qDqqhxoRG2GSjlUhDF0A=
|
||||
github.com/pion/sdp/v3 v3.0.5 h1:ouvI7IgGl+V4CrqskVtr3AaTrPvPisEOxwgpdktctkU=
|
||||
github.com/pion/sdp/v3 v3.0.5/go.mod h1:iiFWFpQO8Fy3S5ldclBkpXqmWy02ns78NOKoLLL0YQw=
|
||||
github.com/pion/srtp/v2 v2.0.9 h1:JJq3jClmDFBPX/F5roEb0U19jSU7eUhyDqR/NZ34EKQ=
|
||||
github.com/pion/srtp/v2 v2.0.9/go.mod h1:5TtM9yw6lsH0ppNCehB/EjEUli7VkUgKSPJqWVqbhQ4=
|
||||
github.com/pion/stun v0.3.5 h1:uLUCBCkQby4S1cf6CGuR9QrVOKcvUwFeemaC865QHDg=
|
||||
github.com/pion/stun v0.3.5/go.mod h1:gDMim+47EeEtfWogA37n6qXZS88L5V6LqFcf+DZA2UA=
|
||||
github.com/pion/transport v0.12.2/go.mod h1:N3+vZQD9HlDP5GWkZ85LohxNsDcNgofQmyL6ojX5d8Q=
|
||||
github.com/pion/transport v0.12.3/go.mod h1:OViWW9SP2peE/HbwBvARicmAVnesphkNkCVZIWJ6q9A=
|
||||
github.com/pion/transport v0.13.0 h1:KWTA5ZrQogizzYwPEciGtHPLwpAjE91FgXnyu+Hv2uY=
|
||||
github.com/pion/transport v0.13.0/go.mod h1:yxm9uXpK9bpBBWkITk13cLo1y5/ur5VQpG22ny6EP7g=
|
||||
github.com/pion/turn/v2 v2.0.6 h1:AsXjSPR6Im15DMTB39NlfdTY9BQfieANPBjdg/aVNwY=
|
||||
github.com/pion/turn/v2 v2.0.6/go.mod h1:+y7xl719J8bAEVpSXBXvTxStjJv3hbz9YFflvkpcGPw=
|
||||
github.com/pion/udp v0.1.1 h1:8UAPvyqmsxK8oOjloDk4wUt63TzFe9WEJkg5lChlj7o=
|
||||
github.com/pion/transport v0.13.1 h1:/UH5yLeQtwm2VZIPjxwnNFxjS4DFhyLfS4GlfuKUzfA=
|
||||
github.com/pion/transport v0.13.1/go.mod h1:EBxbqzyv+ZrmDb82XswEE0BjfQFtuw1Nu6sjnjWCsGg=
|
||||
github.com/pion/transport/v2 v2.0.0 h1:bsMYyqHCbkvHwj+eNCFBuxtlKndKfyGI2vaQmM3fIE4=
|
||||
github.com/pion/transport/v2 v2.0.0/go.mod h1:HS2MEBJTwD+1ZI2eSXSvHJx/HnzQqRy2/LXxt6eVMHc=
|
||||
github.com/pion/turn/v2 v2.0.8 h1:KEstL92OUN3k5k8qxsXHpr7WWfrdp7iJZHx99ud8muw=
|
||||
github.com/pion/turn/v2 v2.0.8/go.mod h1:+y7xl719J8bAEVpSXBXvTxStjJv3hbz9YFflvkpcGPw=
|
||||
github.com/pion/udp v0.1.1/go.mod h1:6AFo+CMdKQm7UiA0eUPA8/eVCTx8jBIITLZHc9DWX5M=
|
||||
github.com/pion/webrtc/v3 v3.1.24-0.20220208053747-94262c1b2b38 h1:+IEql+S+YAj3S5e7Ftl/u4xPcZGG0WwLFsyFj6NRTz4=
|
||||
github.com/pion/webrtc/v3 v3.1.24-0.20220208053747-94262c1b2b38/go.mod h1:L5S/oAhL0Fzt/rnftVQRrP80/j5jygY7XRZzWwFx6P4=
|
||||
github.com/pion/udp v0.1.4 h1:OowsTmu1Od3sD6i3fQUJxJn2fEvJO6L1TidgadtbTI8=
|
||||
github.com/pion/udp v0.1.4/go.mod h1:G8LDo56HsFwC24LIcnT4YIDU5qcB6NepqqjP0keL2us=
|
||||
github.com/pion/webrtc/v3 v3.1.42 h1:wJEQFIXVanptnQcHOLTuIo4AtGB2+mG2x4OhIhnITOA=
|
||||
github.com/pion/webrtc/v3 v3.1.42/go.mod h1:ffD9DulDrPxyWvDPUIPAOSAWx9GUlOExiJPf7cCcMLA=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
@@ -284,10 +298,10 @@ github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+Gx
|
||||
github.com/prometheus/procfs v0.0.11/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
|
||||
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
||||
github.com/rs/dnscache v0.0.0-20210201191234-295bba877686 h1:IJ6Df0uxPDtNoByV0KkzVKNseWvZFCNM/S9UoyOMCSI=
|
||||
github.com/rs/dnscache v0.0.0-20210201191234-295bba877686/go.mod h1:qe5TWALJ8/a1Lqznoc5BDHpYX/8HU60Hm2AwRmqzxqA=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/rs/dnscache v0.0.0-20211102005908-e0241e321417 h1:Lt9DzQALzHoDwMBGJ6v8ObDPR0dzr2a6sXTB1Fq7IHs=
|
||||
github.com/rs/dnscache v0.0.0-20211102005908-e0241e321417/go.mod h1:qe5TWALJ8/a1Lqznoc5BDHpYX/8HU60Hm2AwRmqzxqA=
|
||||
github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46 h1:GHRpF1pTW19a8tTFrMLUcfWwyC0pnifVo2ClaLq+hP8=
|
||||
github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46/go.mod h1:uAQ5PCi+MFsC7HjREoAz1BU+Mq60+05gifQSsHSDG/8=
|
||||
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
|
||||
@@ -299,6 +313,8 @@ github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:X
|
||||
github.com/smartystreets/goconvey v0.0.0-20190306220146-200a235640ff/go.mod h1:KSQcGKpxUMHk3nbYzs/tIBAM2iDooCn0BmttHOJEbLs=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
@@ -306,36 +322,49 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
|
||||
github.com/tidwall/btree v1.3.1 h1:636+tdVDs8Hjcf35Di260W2xCW4KuoXOKyk9QWOvCpA=
|
||||
github.com/tidwall/btree v1.3.1/go.mod h1:LGm8L/DZjPLmeWGjv5kFrY8dL4uVhMmzmmLYmsObdKE=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/tidwall/btree v1.6.0 h1:LDZfKfQIBHGHWSwckhXI0RPSXzlo+KYdjK7FWSqOzzg=
|
||||
github.com/tidwall/btree v1.6.0/go.mod h1:twD9XRA5jj9VUQGELzDO4HPQTNJsoWWfYEL+EUQ2cKY=
|
||||
github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
|
||||
github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
|
||||
github.com/tinylib/msgp v1.1.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
|
||||
github.com/virtuald/go-paniclog v0.0.0-20190812204905-43a7fa316459 h1:x9pIfbdIjnw+Ylb2vE27Gtqb7BDmfR+nLcJwvbJh98U=
|
||||
github.com/virtuald/go-paniclog v0.0.0-20190812204905-43a7fa316459/go.mod h1:nFvuG3SWu3VWqobG3cX8nt57wXU0OOFapeCs/8axIuM=
|
||||
github.com/willf/bitset v1.1.9/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
|
||||
github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU=
|
||||
go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
|
||||
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
||||
go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opentelemetry.io/otel v1.8.0 h1:zcvBFizPbpa1q7FehvFiHbQwGzmPILebO0tyqIR5Djg=
|
||||
go.opentelemetry.io/otel v1.8.0/go.mod h1:2pkj+iMj0o03Y+cW6/m8Y4WkRdYN3AvCXCnzRMp9yvM=
|
||||
go.opentelemetry.io/otel/trace v1.8.0 h1:cSy0DF9eGI5WIfNwZ1q2iUyGj00tGzP24dE1lOlHrfY=
|
||||
go.opentelemetry.io/otel/trace v1.8.0/go.mod h1:0Bt3PXY8w+3pheS3hQUt+wow8b1ojPaTBoTCh2zIFI4=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838 h1:71vQrMauZZhcTVK6KdYM+rklehEEwb3E+ZhaE5jrPrE=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220516162934-403b01795ae8/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE=
|
||||
golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20220428152302-39d4317da171 h1:TfdoLivD44QwvssI9Sv1xwa5DcL5XQr4au4sZ2F2NV4=
|
||||
golang.org/x/exp v0.0.0-20220428152302-39d4317da171/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE=
|
||||
golang.org/x/exp v0.0.0-20220613132600-b0d781184e0d h1:vtUKgx8dahOomfFzLREU8nSv25YHnTgLBn4rDnWZdU0=
|
||||
golang.org/x/exp v0.0.0-20220613132600-b0d781184e0d/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180524181706-dfa909b99c79/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -351,13 +380,19 @@ golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201201195509-5d6afe98e0b7/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211201190559-0a0e4e1bb54c/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0=
|
||||
golang.org/x/net v0.0.0-20220401154927-543a649e0bdd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220531201128-c960675eff93/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
||||
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
|
||||
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -367,6 +402,7 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde h1:ejfdSekXMDxDLbRrJMwUk6KnSLZ2McaUCVcIKM+N6jc=
|
||||
golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -392,17 +428,29 @@ golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220608164250-635b8c9b7f68/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac h1:7zkz7BUtwNFFqcowJ+RIgu2MaV/MapERkDIy+mwPyjs=
|
||||
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/time v0.0.0-20220609170525-579cf78fd858 h1:Dpdu/EMxGMFgq0CeYMh4fazTD2vtlZRYE7wyynxJb9U=
|
||||
golang.org/x/time v0.0.0-20220609170525-579cf78fd858/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@@ -411,10 +459,10 @@ golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
@@ -446,7 +494,8 @@ gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
+101
-49
@@ -1,13 +1,14 @@
|
||||
package bt
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/GopeedLab/gopeed/internal/controller"
|
||||
"github.com/GopeedLab/gopeed/internal/fetcher"
|
||||
"github.com/GopeedLab/gopeed/pkg/base"
|
||||
"github.com/GopeedLab/gopeed/pkg/protocol/bt"
|
||||
"github.com/GopeedLab/gopeed/pkg/util"
|
||||
"github.com/anacrolix/torrent"
|
||||
"github.com/anacrolix/torrent/metainfo"
|
||||
"github.com/anacrolix/torrent/storage"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
@@ -15,7 +16,12 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
var client *torrent.Client
|
||||
var (
|
||||
client *torrent.Client
|
||||
lock sync.Mutex
|
||||
torrentDirMap = make(map[string]string)
|
||||
ftMap = make(map[string]*fileTorrentImpl)
|
||||
)
|
||||
|
||||
type Fetcher struct {
|
||||
ctl *controller.Controller
|
||||
@@ -23,10 +29,9 @@ type Fetcher struct {
|
||||
torrent *torrent.Torrent
|
||||
meta *fetcher.FetcherMeta
|
||||
|
||||
ready atomic.Bool
|
||||
progress fetcher.Progress
|
||||
|
||||
torrentPaths map[string]string
|
||||
torrentReady atomic.Bool
|
||||
create atomic.Bool
|
||||
progress fetcher.Progress
|
||||
}
|
||||
|
||||
func (f *Fetcher) Name() string {
|
||||
@@ -38,42 +43,48 @@ func (f *Fetcher) Setup(ctl *controller.Controller) (err error) {
|
||||
if f.meta == nil {
|
||||
f.meta = &fetcher.FetcherMeta{}
|
||||
}
|
||||
var once sync.Once
|
||||
once.Do(func() {
|
||||
cfg := torrent.NewDefaultClientConfig()
|
||||
cfg.ListenPort = 0
|
||||
return
|
||||
}
|
||||
|
||||
// Support custom download path for each torrent
|
||||
pieceCompletion, err := storage.NewDefaultPieceCompletionForDir(cfg.DataDir)
|
||||
if err != nil {
|
||||
pieceCompletion = storage.NewMapPieceCompletion()
|
||||
}
|
||||
clientImplCloser := storage.NewFileOpts(storage.NewFileClientOpts{
|
||||
ClientBaseDir: cfg.DataDir,
|
||||
TorrentDirMaker: func(baseDir string, info *metainfo.Info, infoHash metainfo.Hash) string {
|
||||
if dir, ok := f.torrentPaths[infoHash.String()]; ok {
|
||||
return dir
|
||||
}
|
||||
return baseDir
|
||||
},
|
||||
PieceCompletion: pieceCompletion,
|
||||
})
|
||||
cfg.DefaultStorage = clientImplCloser
|
||||
client, err = torrent.NewClient(cfg)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
func (f *Fetcher) initClient() (err error) {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
|
||||
f.torrentPaths = make(map[string]string)
|
||||
if client != nil {
|
||||
return
|
||||
}
|
||||
|
||||
cfg := torrent.NewDefaultClientConfig()
|
||||
cfg.ListenPort = 0
|
||||
cfg.DefaultStorage = newFileOpts(newFileClientOpts{
|
||||
ClientBaseDir: cfg.DataDir,
|
||||
HandleFileTorrent: func(infoHash metainfo.Hash, ft *fileTorrentImpl) {
|
||||
if dir, ok := torrentDirMap[infoHash.String()]; ok {
|
||||
ft.setTorrentDir(dir)
|
||||
}
|
||||
ftMap[infoHash.String()] = ft
|
||||
},
|
||||
})
|
||||
client, err = torrent.NewClient(cfg)
|
||||
return
|
||||
}
|
||||
|
||||
func (f *Fetcher) Resolve(req *base.Request) error {
|
||||
if err := f.addTorrent(req.URL); err != nil {
|
||||
if err := base.ParseReqExtra[bt.ReqExtra](req); err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.torrent.Drop()
|
||||
|
||||
if err := f.addTorrent(req); err != nil {
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
// recycle unused torrent resource
|
||||
time.Sleep(time.Minute * 3)
|
||||
if !f.create.Load() {
|
||||
f.torrentReady.Store(false)
|
||||
f.safeDrop()
|
||||
}
|
||||
}()
|
||||
res := &base.Resource{
|
||||
Name: f.torrent.Name(),
|
||||
Range: true,
|
||||
@@ -95,6 +106,7 @@ func (f *Fetcher) Resolve(req *base.Request) error {
|
||||
}
|
||||
|
||||
func (f *Fetcher) Create(opts *base.Options) (err error) {
|
||||
f.create.Store(true)
|
||||
f.meta.Opts = opts
|
||||
if len(opts.SelectFiles) == 0 {
|
||||
opts.SelectFiles = make([]int, 0)
|
||||
@@ -103,17 +115,20 @@ func (f *Fetcher) Create(opts *base.Options) (err error) {
|
||||
}
|
||||
}
|
||||
if opts.Path != "" {
|
||||
f.torrentPaths[f.meta.Res.Hash] = path.Join(f.meta.Opts.Path, f.meta.Res.RootDir)
|
||||
torrentDirMap[f.meta.Res.Hash] = path.Join(f.meta.Opts.Path, f.meta.Res.RootDir)
|
||||
if ft, ok := ftMap[f.meta.Res.Hash]; ok {
|
||||
// reuse resolve fetcher
|
||||
ft.setTorrentDir(torrentDirMap[f.meta.Res.Hash])
|
||||
}
|
||||
}
|
||||
|
||||
f.progress = make(fetcher.Progress, len(f.meta.Opts.SelectFiles))
|
||||
f.ready.Store(false)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *Fetcher) Start() (err error) {
|
||||
if !f.ready.Load() {
|
||||
if err = f.addTorrent(f.meta.Req.URL); err != nil {
|
||||
if !f.torrentReady.Load() {
|
||||
if err = f.addTorrent(f.meta.Req); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -130,8 +145,8 @@ func (f *Fetcher) Start() (err error) {
|
||||
}
|
||||
|
||||
func (f *Fetcher) Pause() (err error) {
|
||||
f.ready.Store(false)
|
||||
f.torrent.Drop()
|
||||
f.torrentReady.Store(false)
|
||||
f.safeDrop()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -140,13 +155,22 @@ func (f *Fetcher) Continue() (err error) {
|
||||
}
|
||||
|
||||
func (f *Fetcher) Close() (err error) {
|
||||
f.torrent.Drop()
|
||||
f.safeDrop()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *Fetcher) safeDrop() {
|
||||
defer func() {
|
||||
// ignore panic
|
||||
_ = recover()
|
||||
}()
|
||||
|
||||
f.torrent.Drop()
|
||||
}
|
||||
|
||||
func (f *Fetcher) Wait() (err error) {
|
||||
for {
|
||||
if f.ready.Load() {
|
||||
if f.torrentReady.Load() {
|
||||
done := true
|
||||
for _, selectIndex := range f.meta.Opts.SelectFiles {
|
||||
file := f.torrent.Files()[selectIndex]
|
||||
@@ -182,7 +206,7 @@ func (f *Fetcher) Meta() *fetcher.FetcherMeta {
|
||||
}
|
||||
|
||||
func (f *Fetcher) Progress() fetcher.Progress {
|
||||
if !f.ready.Load() {
|
||||
if !f.torrentReady.Load() {
|
||||
return f.progress
|
||||
}
|
||||
for i := range f.progress {
|
||||
@@ -193,12 +217,24 @@ func (f *Fetcher) Progress() fetcher.Progress {
|
||||
return f.progress
|
||||
}
|
||||
|
||||
func (f *Fetcher) addTorrent(url string) (err error) {
|
||||
schema := util.ParseSchema(url)
|
||||
func (f *Fetcher) addTorrent(req *base.Request) (err error) {
|
||||
if err = f.initClient(); err != nil {
|
||||
return
|
||||
}
|
||||
schema := util.ParseSchema(req.URL)
|
||||
if schema == "MAGNET" {
|
||||
f.torrent, err = client.AddMagnet(url)
|
||||
f.torrent, err = client.AddMagnet(req.URL)
|
||||
} else if schema == "APPLICATION/X-BITTORRENT" {
|
||||
_, data := util.ParseDataUri(req.URL)
|
||||
buf := bytes.NewBuffer(data)
|
||||
var metaInfo *metainfo.MetaInfo
|
||||
metaInfo, err = metainfo.Load(buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f.torrent, err = client.AddTorrent(metaInfo)
|
||||
} else {
|
||||
f.torrent, err = client.AddTorrentFromFile(url)
|
||||
f.torrent, err = client.AddTorrentFromFile(req.URL)
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
@@ -208,22 +244,38 @@ func (f *Fetcher) addTorrent(url string) (err error) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// use map to deduplicate
|
||||
trackers := make(map[string]bool)
|
||||
if req.Extra != nil {
|
||||
extra := req.Extra.(*bt.ReqExtra)
|
||||
if len(extra.Trackers) > 0 {
|
||||
for _, tracker := range extra.Trackers {
|
||||
trackers[tracker] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if exist && len(cfg.Trackers) > 0 {
|
||||
announceList := make([][]string, 0)
|
||||
for _, tracker := range cfg.Trackers {
|
||||
trackers[tracker] = true
|
||||
}
|
||||
}
|
||||
if len(trackers) > 0 {
|
||||
announceList := make([][]string, 0)
|
||||
for tracker := range trackers {
|
||||
announceList = append(announceList, []string{tracker})
|
||||
}
|
||||
f.torrent.AddTrackers(announceList)
|
||||
}
|
||||
<-f.torrent.GotInfo()
|
||||
f.ready.Store(true)
|
||||
f.torrentReady.Store(true)
|
||||
return
|
||||
}
|
||||
|
||||
type FetcherBuilder struct {
|
||||
}
|
||||
|
||||
var schemes = []string{"FILE", "MAGNET"}
|
||||
var schemes = []string{"FILE", "MAGNET", "APPLICATION/X-BITTORRENT"}
|
||||
|
||||
func (fb *FetcherBuilder) Schemes() []string {
|
||||
return schemes
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
package bt
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"github.com/GopeedLab/gopeed/internal/controller"
|
||||
"github.com/GopeedLab/gopeed/internal/fetcher"
|
||||
"github.com/GopeedLab/gopeed/internal/test"
|
||||
"github.com/GopeedLab/gopeed/pkg/base"
|
||||
"github.com/GopeedLab/gopeed/pkg/protocol/bt"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
@@ -14,6 +17,39 @@ func TestFetcher_Resolve_Torrent(t *testing.T) {
|
||||
doResolve(t, buildFetcher())
|
||||
}
|
||||
|
||||
func TestFetcher_Resolve_DataUri_Torrent(t *testing.T) {
|
||||
fetcher := buildFetcher()
|
||||
buf, err := os.ReadFile("./testdata/ubuntu-22.04-live-server-amd64.iso.torrent")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// convert to data uri
|
||||
dataUri := "data:application/x-bittorrent;base64," + base64.StdEncoding.EncodeToString(buf)
|
||||
err = fetcher.Resolve(&base.Request{
|
||||
URL: dataUri,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
want := &base.Resource{
|
||||
Name: "ubuntu-22.04-live-server-amd64.iso",
|
||||
Size: 1466714112,
|
||||
Range: true,
|
||||
RootDir: "ubuntu-22.04-live-server-amd64.iso",
|
||||
Files: []*base.FileInfo{
|
||||
{
|
||||
Name: "ubuntu-22.04-live-server-amd64.iso",
|
||||
Size: 1466714112,
|
||||
},
|
||||
},
|
||||
Hash: "8a55cfbd5ca5d11507364765936c4f9e55b253ed",
|
||||
}
|
||||
if !reflect.DeepEqual(want, fetcher.Meta().Res) {
|
||||
t.Errorf("Resolve() got = %v, want %v", fetcher.Meta().Res, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFetcher_Config(t *testing.T) {
|
||||
doResolve(t, buildConfigFetcher())
|
||||
}
|
||||
@@ -21,6 +57,12 @@ func TestFetcher_Config(t *testing.T) {
|
||||
func doResolve(t *testing.T, fetcher fetcher.Fetcher) {
|
||||
err := fetcher.Resolve(&base.Request{
|
||||
URL: "./testdata/ubuntu-22.04-live-server-amd64.iso.torrent",
|
||||
Extra: bt.ReqExtra{
|
||||
Trackers: []string{
|
||||
"udp://tracker.birkenwald.de:6969/announce",
|
||||
"udp://tracker.bitsearch.to:1337/announce",
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
||||
@@ -0,0 +1,214 @@
|
||||
// modify from github.com/anacrolix/torrent/storage/file.go
|
||||
|
||||
package bt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/anacrolix/torrent/storage"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/anacrolix/missinggo/v2"
|
||||
|
||||
"github.com/anacrolix/torrent/common"
|
||||
"github.com/anacrolix/torrent/metainfo"
|
||||
"github.com/anacrolix/torrent/segments"
|
||||
)
|
||||
|
||||
// File-based storage for torrents, that isn't yet bound to a particular torrent.
|
||||
type fileClientImpl struct {
|
||||
opts newFileClientOpts
|
||||
}
|
||||
|
||||
type newFileClientOpts struct {
|
||||
// The base directory for all downloads.
|
||||
ClientBaseDir string
|
||||
HandleFileTorrent func(infoHash metainfo.Hash, ft *fileTorrentImpl)
|
||||
FilePathMaker storage.FilePathMaker
|
||||
PieceCompletion storage.PieceCompletion
|
||||
}
|
||||
|
||||
func newFileOpts(opts newFileClientOpts) storage.ClientImplCloser {
|
||||
if opts.FilePathMaker == nil {
|
||||
opts.FilePathMaker = func(opts storage.FilePathMakerOpts) string {
|
||||
var parts []string
|
||||
if opts.Info.Name != metainfo.NoName {
|
||||
parts = append(parts, opts.Info.Name)
|
||||
}
|
||||
return filepath.Join(append(parts, opts.File.Path...)...)
|
||||
}
|
||||
}
|
||||
if opts.PieceCompletion == nil {
|
||||
ret, err := storage.NewDefaultPieceCompletionForDir(opts.ClientBaseDir)
|
||||
if err != nil {
|
||||
ret = storage.NewMapPieceCompletion()
|
||||
}
|
||||
opts.PieceCompletion = ret
|
||||
}
|
||||
return fileClientImpl{opts}
|
||||
}
|
||||
|
||||
func (fs fileClientImpl) Close() error {
|
||||
return fs.opts.PieceCompletion.Close()
|
||||
}
|
||||
|
||||
func (fs fileClientImpl) OpenTorrent(info *metainfo.Info, infoHash metainfo.Hash) (_ storage.TorrentImpl, err error) {
|
||||
upvertedFiles := info.UpvertedFiles()
|
||||
files := make([]file, 0, len(upvertedFiles))
|
||||
for _, fileInfo := range upvertedFiles {
|
||||
filePath := filepath.Join("", fs.opts.FilePathMaker(storage.FilePathMakerOpts{
|
||||
Info: info,
|
||||
File: &fileInfo,
|
||||
}))
|
||||
f := file{
|
||||
rawPath: filePath,
|
||||
path: filePath,
|
||||
length: fileInfo.Length,
|
||||
}
|
||||
if f.length == 0 {
|
||||
err = CreateNativeZeroLengthFile(f.path)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("creating zero length file: %w", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
files = append(files, f)
|
||||
}
|
||||
t := &fileTorrentImpl{
|
||||
files,
|
||||
segments.NewIndex(common.LengthIterFromUpvertedFiles(upvertedFiles)),
|
||||
infoHash,
|
||||
fs.opts.PieceCompletion,
|
||||
}
|
||||
fs.opts.HandleFileTorrent(infoHash, t)
|
||||
return storage.TorrentImpl{
|
||||
Piece: t.Piece,
|
||||
Close: t.Close,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type file struct {
|
||||
// The safe, OS-local file path.
|
||||
rawPath string
|
||||
path string
|
||||
length int64
|
||||
}
|
||||
|
||||
type fileTorrentImpl struct {
|
||||
files []file
|
||||
segmentLocater segments.Index
|
||||
infoHash metainfo.Hash
|
||||
completion storage.PieceCompletion
|
||||
}
|
||||
|
||||
func (fts *fileTorrentImpl) Piece(p metainfo.Piece) storage.PieceImpl {
|
||||
// Create a view onto the file-based torrent storage.
|
||||
_io := fileTorrentImplIO{fts}
|
||||
// Return the appropriate segments of this.
|
||||
return &filePieceImpl{
|
||||
fts,
|
||||
p,
|
||||
missinggo.NewSectionWriter(_io, p.Offset(), p.Length()),
|
||||
io.NewSectionReader(_io, p.Offset(), p.Length()),
|
||||
}
|
||||
}
|
||||
|
||||
func (fts *fileTorrentImpl) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fts *fileTorrentImpl) setTorrentDir(dir string) {
|
||||
for i, f := range fts.files {
|
||||
fts.files[i].path = filepath.Join(dir, f.rawPath)
|
||||
}
|
||||
}
|
||||
|
||||
// A helper to create zero-length files which won't appear for file-orientated storage since no
|
||||
// writes will ever occur to them (no torrent data is associated with a zero-length file). The
|
||||
// caller should make sure the file name provided is safe/sanitized.
|
||||
func CreateNativeZeroLengthFile(name string) error {
|
||||
os.MkdirAll(filepath.Dir(name), 0o777)
|
||||
var f io.Closer
|
||||
f, err := os.Create(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return f.Close()
|
||||
}
|
||||
|
||||
// Exposes file-based storage of a torrent, as one big ReadWriterAt.
|
||||
type fileTorrentImplIO struct {
|
||||
fts *fileTorrentImpl
|
||||
}
|
||||
|
||||
// Returns EOF on short or missing file.
|
||||
func (fst *fileTorrentImplIO) readFileAt(file file, b []byte, off int64) (n int, err error) {
|
||||
f, err := os.Open(file.path)
|
||||
if os.IsNotExist(err) {
|
||||
// File missing is treated the same as a short file.
|
||||
err = io.EOF
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
// Limit the read to within the expected bounds of this file.
|
||||
if int64(len(b)) > file.length-off {
|
||||
b = b[:file.length-off]
|
||||
}
|
||||
for off < file.length && len(b) != 0 {
|
||||
n1, err1 := f.ReadAt(b, off)
|
||||
b = b[n1:]
|
||||
n += n1
|
||||
off += int64(n1)
|
||||
if n1 == 0 {
|
||||
err = err1
|
||||
break
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Only returns EOF at the end of the torrent. Premature EOF is ErrUnexpectedEOF.
|
||||
func (fst fileTorrentImplIO) ReadAt(b []byte, off int64) (n int, err error) {
|
||||
fst.fts.segmentLocater.Locate(segments.Extent{off, int64(len(b))}, func(i int, e segments.Extent) bool {
|
||||
n1, err1 := fst.readFileAt(fst.fts.files[i], b[:e.Length], e.Start)
|
||||
n += n1
|
||||
b = b[n1:]
|
||||
err = err1
|
||||
return err == nil // && int64(n1) == e.Length
|
||||
})
|
||||
if len(b) != 0 && err == nil {
|
||||
err = io.EOF
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (fst fileTorrentImplIO) WriteAt(p []byte, off int64) (n int, err error) {
|
||||
// log.Printf("write at %v: %v bytes", off, len(p))
|
||||
fst.fts.segmentLocater.Locate(segments.Extent{off, int64(len(p))}, func(i int, e segments.Extent) bool {
|
||||
name := fst.fts.files[i].path
|
||||
os.MkdirAll(filepath.Dir(name), 0o777)
|
||||
var f *os.File
|
||||
f, err = os.OpenFile(name, os.O_WRONLY|os.O_CREATE, 0o666)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
var n1 int
|
||||
n1, err = f.WriteAt(p[:e.Length], e.Start)
|
||||
// log.Printf("%v %v wrote %v: %v", i, e, n1, err)
|
||||
closeErr := f.Close()
|
||||
n += n1
|
||||
p = p[n1:]
|
||||
if err == nil {
|
||||
err = closeErr
|
||||
}
|
||||
if err == nil && int64(n1) != e.Length {
|
||||
err = io.ErrShortWrite
|
||||
}
|
||||
return err == nil
|
||||
})
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
// modify from github.com/anacrolix/torrent/storage/file_misc.go
|
||||
|
||||
package bt
|
||||
|
||||
import "github.com/anacrolix/torrent/metainfo"
|
||||
|
||||
type requiredLength struct {
|
||||
fileIndex int
|
||||
length int64
|
||||
}
|
||||
|
||||
func extentCompleteRequiredLengths(info *metainfo.Info, off, n int64) (ret []requiredLength) {
|
||||
if n == 0 {
|
||||
return
|
||||
}
|
||||
for i, fi := range info.UpvertedFiles() {
|
||||
if off >= fi.Length {
|
||||
off -= fi.Length
|
||||
continue
|
||||
}
|
||||
n1 := n
|
||||
if off+n1 > fi.Length {
|
||||
n1 = fi.Length - off
|
||||
}
|
||||
ret = append(ret, requiredLength{
|
||||
fileIndex: i,
|
||||
length: off + n1,
|
||||
})
|
||||
n -= n1
|
||||
if n == 0 {
|
||||
return
|
||||
}
|
||||
off = 0
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
// modify from github.com/anacrolix/torrent/storage/file_piece.go
|
||||
|
||||
package bt
|
||||
|
||||
import (
|
||||
"github.com/anacrolix/torrent/metainfo"
|
||||
"github.com/anacrolix/torrent/storage"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
type filePieceImpl struct {
|
||||
*fileTorrentImpl
|
||||
p metainfo.Piece
|
||||
io.WriterAt
|
||||
io.ReaderAt
|
||||
}
|
||||
|
||||
var _ storage.PieceImpl = (*filePieceImpl)(nil)
|
||||
|
||||
func (fs *filePieceImpl) pieceKey() metainfo.PieceKey {
|
||||
return metainfo.PieceKey{InfoHash: fs.infoHash, Index: fs.p.Index()}
|
||||
}
|
||||
|
||||
func (fs *filePieceImpl) Completion() storage.Completion {
|
||||
c, err := fs.completion.Get(fs.pieceKey())
|
||||
if err != nil {
|
||||
log.Printf("error getting piece completion: %s", err)
|
||||
c.Ok = false
|
||||
return c
|
||||
}
|
||||
|
||||
verified := true
|
||||
if c.Complete {
|
||||
// If it's allegedly complete, check that its constituent files have the necessary length.
|
||||
for _, fi := range extentCompleteRequiredLengths(fs.p.Info, fs.p.Offset(), fs.p.Length()) {
|
||||
s, err := os.Stat(fs.files[fi.fileIndex].path)
|
||||
if err != nil || s.Size() < fi.length {
|
||||
verified = false
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !verified {
|
||||
// The completion was wrong, fix it.
|
||||
c.Complete = false
|
||||
fs.completion.Set(fs.pieceKey(), false)
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func (fs *filePieceImpl) MarkComplete() error {
|
||||
return fs.completion.Set(fs.pieceKey(), true)
|
||||
}
|
||||
|
||||
func (fs *filePieceImpl) MarkNotComplete() error {
|
||||
return fs.completion.Set(fs.pieceKey(), false)
|
||||
}
|
||||
@@ -17,7 +17,6 @@ import (
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type RequestError struct {
|
||||
@@ -85,13 +84,14 @@ func (f *Fetcher) Resolve(req *base.Request) error {
|
||||
return err
|
||||
}
|
||||
client := buildClient()
|
||||
// 只访问一个字节,测试资源是否支持Range请求
|
||||
// send Range request to check whether the server supports breakpoint continuation
|
||||
// just test one byte, Range: bytes=0-0
|
||||
httpReq.Header.Set(base.HttpHeaderRange, fmt.Sprintf(base.HttpHeaderRangeFormat, 0, 0))
|
||||
httpResp, err := client.Do(httpReq)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// 拿到响应头就关闭,不用加defer
|
||||
// close response body immediately
|
||||
httpResp.Body.Close()
|
||||
res := &base.Resource{
|
||||
Range: false,
|
||||
@@ -395,8 +395,7 @@ func buildClient() *http.Client {
|
||||
// Cookie handle
|
||||
jar, _ := cookiejar.New(nil)
|
||||
return &http.Client{
|
||||
Jar: jar,
|
||||
Timeout: time.Second * 60,
|
||||
Jar: jar,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,9 +7,11 @@ import (
|
||||
"github.com/GopeedLab/gopeed/pkg/base"
|
||||
"github.com/GopeedLab/gopeed/pkg/util"
|
||||
gonanoid "github.com/matoous/go-nanoid/v2"
|
||||
"github.com/virtuald/go-paniclog"
|
||||
"math"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -45,8 +47,9 @@ type Downloader struct {
|
||||
tasks []*Task
|
||||
listener Listener
|
||||
|
||||
lock *sync.Mutex
|
||||
closed atomic.Bool
|
||||
lock *sync.Mutex
|
||||
fetcherMapLock *sync.RWMutex
|
||||
closed atomic.Bool
|
||||
}
|
||||
|
||||
func NewDownloader(cfg *DownloaderConfig) *Downloader {
|
||||
@@ -61,14 +64,17 @@ func NewDownloader(cfg *DownloaderConfig) *Downloader {
|
||||
fetchBuilders: make(map[string]fetcher.FetcherBuilder),
|
||||
fetcherMap: make(map[string]fetcher.Fetcher),
|
||||
|
||||
storage: cfg.Storage,
|
||||
lock: &sync.Mutex{},
|
||||
storage: cfg.Storage,
|
||||
lock: &sync.Mutex{},
|
||||
fetcherMapLock: &sync.RWMutex{},
|
||||
}
|
||||
for _, f := range cfg.FetchBuilders {
|
||||
for _, p := range f.Schemes() {
|
||||
d.fetchBuilders[strings.ToUpper(p)] = f
|
||||
}
|
||||
}
|
||||
|
||||
logPanic(filepath.Join(cfg.StorageDir, "logs"))
|
||||
return d
|
||||
}
|
||||
|
||||
@@ -165,9 +171,6 @@ func (d *Downloader) Setup() error {
|
||||
func (d *Downloader) parseFb(url string) (fetcher.FetcherBuilder, error) {
|
||||
schema := util.ParseSchema(url)
|
||||
fetchBuilder, ok := d.fetchBuilders[schema]
|
||||
if !ok {
|
||||
fetchBuilder, ok = d.fetchBuilders[util.FileSchema]
|
||||
}
|
||||
if ok {
|
||||
return fetchBuilder, nil
|
||||
}
|
||||
@@ -197,7 +200,9 @@ func (d *Downloader) Resolve(req *base.Request) (rr *ResolveResult, err error) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
d.fetcherMapLock.Lock()
|
||||
d.fetcherMap[rrId] = fetcher
|
||||
d.fetcherMapLock.Unlock()
|
||||
rr = &ResolveResult{
|
||||
ID: rrId,
|
||||
Res: fetcher.Meta().Res,
|
||||
@@ -217,11 +222,17 @@ func (d *Downloader) Create(rrId string, opts *base.Options) (taskId string, err
|
||||
if opts == nil {
|
||||
opts = &base.Options{}
|
||||
}
|
||||
d.fetcherMapLock.RLock()
|
||||
fetcher, ok := d.fetcherMap[rrId]
|
||||
d.fetcherMapLock.RUnlock()
|
||||
if !ok {
|
||||
return "", errors.New("invalid resource id")
|
||||
}
|
||||
defer delete(d.fetcherMap, rrId)
|
||||
defer func() {
|
||||
d.fetcherMapLock.Lock()
|
||||
delete(d.fetcherMap, rrId)
|
||||
d.fetcherMapLock.Unlock()
|
||||
}()
|
||||
|
||||
meta := fetcher.Meta()
|
||||
meta.Opts = opts
|
||||
@@ -517,7 +528,9 @@ func (d *Downloader) watch(task *Task) {
|
||||
d.emit(EventKeyDone, task)
|
||||
}
|
||||
d.emit(EventKeyFinally, task, err)
|
||||
d.notifyRunning()
|
||||
if task.Status == EventKeyDone {
|
||||
d.notifyRunning()
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Downloader) restoreFetcher(task *Task) error {
|
||||
@@ -634,6 +647,18 @@ func (d *Downloader) doContinue(task *Task) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// redirect stderr to log file, when panic happened log it
|
||||
func logPanic(logDir string) {
|
||||
if err := util.CreateDirIfNotExist(logDir); err != nil {
|
||||
return
|
||||
}
|
||||
f, err := os.Create(filepath.Join(logDir, "crash.log"))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
paniclog.RedirectStderr(f)
|
||||
}
|
||||
|
||||
func initTask(task *Task) {
|
||||
task.timer = util.NewTimer(task.Progress.Used)
|
||||
task.lock = &sync.Mutex{}
|
||||
|
||||
@@ -73,6 +73,7 @@ type DownloaderConfig struct {
|
||||
|
||||
RefreshInterval int `json:"refreshInterval"` // RefreshInterval time duration to refresh task progress(ms)
|
||||
Storage Storage
|
||||
StorageDir string
|
||||
|
||||
*DownloaderStoreConfig
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
package bt
|
||||
|
||||
type ReqExtra struct {
|
||||
Trackers []string `json:"trackers"`
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
package model
|
||||
|
||||
import "io/fs"
|
||||
import (
|
||||
"encoding/base64"
|
||||
"io/fs"
|
||||
)
|
||||
|
||||
type Storage string
|
||||
|
||||
@@ -17,8 +20,9 @@ type StartConfig struct {
|
||||
StorageDir string `json:"storageDir"`
|
||||
ApiToken string `json:"apiToken"`
|
||||
|
||||
WebEnable bool
|
||||
WebFS fs.FS
|
||||
WebEnable bool
|
||||
WebFS fs.FS
|
||||
WebBasicAuth *WebBasicAuth
|
||||
}
|
||||
|
||||
func (cfg *StartConfig) Init() *StartConfig {
|
||||
@@ -39,3 +43,14 @@ func (cfg *StartConfig) Init() *StartConfig {
|
||||
}
|
||||
return cfg
|
||||
}
|
||||
|
||||
type WebBasicAuth struct {
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
// Authorization returns the value of the Authorization header to be used in HTTP requests.
|
||||
func (cfg *WebBasicAuth) Authorization() string {
|
||||
userId := cfg.Username + ":" + cfg.Password
|
||||
return "Basic " + base64.StdEncoding.EncodeToString([]byte(userId))
|
||||
}
|
||||
+17
-5
@@ -65,6 +65,7 @@ func BuildServer(startCfg *model.StartConfig) (*http.Server, net.Listener, error
|
||||
} else {
|
||||
downloadCfg.Storage = download.NewMemStorage()
|
||||
}
|
||||
downloadCfg.StorageDir = startCfg.StorageDir
|
||||
downloadCfg.Init()
|
||||
Downloader = download.NewDownloader(downloadCfg)
|
||||
if err := Downloader.Setup(); err != nil {
|
||||
@@ -97,14 +98,21 @@ func BuildServer(startCfg *model.StartConfig) (*http.Server, net.Listener, error
|
||||
r.PathPrefix("/").Handler(http.FileServer(http.FS(startCfg.WebFS)))
|
||||
}
|
||||
|
||||
if startCfg.ApiToken != "" {
|
||||
if startCfg.ApiToken != "" || (startCfg.WebEnable && startCfg.WebBasicAuth != nil) {
|
||||
r.Use(func(h http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Header.Get("X-Api-Token") != startCfg.ApiToken {
|
||||
WriteJson(w, model.NewErrorResult("invalid token", model.CodeUnauthorized))
|
||||
if startCfg.ApiToken != "" && r.Header.Get("X-Api-Token") == startCfg.ApiToken {
|
||||
h.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
h.ServeHTTP(w, r)
|
||||
if startCfg.WebEnable && startCfg.WebBasicAuth != nil {
|
||||
if r.Header.Get("Authorization") == startCfg.WebBasicAuth.Authorization() {
|
||||
h.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
w.Header().Set("WWW-Authenticate", "Basic realm=\"gopeed web\"")
|
||||
}
|
||||
WriteStatusJson(w, http.StatusUnauthorized, model.NewErrorResult("unauthorized", model.CodeUnauthorized))
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -126,7 +134,11 @@ func ReadJson(r *http.Request, w http.ResponseWriter, v any) bool {
|
||||
}
|
||||
|
||||
func WriteJson(w http.ResponseWriter, v any) {
|
||||
WriteStatusJson(w, http.StatusOK, v)
|
||||
}
|
||||
|
||||
func WriteStatusJson(w http.ResponseWriter, statusCode int, v any) {
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.WriteHeader(statusCode)
|
||||
json.NewEncoder(w).Encode(v)
|
||||
}
|
||||
+47
-6
@@ -153,7 +153,7 @@ func TestPauseAllAndContinueALLTasks(t *testing.T) {
|
||||
|
||||
createAndPause := func() {
|
||||
taskId := httpRequestCheckOk[string](http.MethodPost, "/api/v1/tasks", createReq)
|
||||
httpRequestCheckOk[*download.Task](http.MethodGet, "/api/v1/tasks/"+taskId, nil)
|
||||
httpRequestCheckOk[*download.Task](http.MethodPut, "/api/v1/tasks/"+taskId+"/pause", nil)
|
||||
}
|
||||
|
||||
total := cfg.MaxRunning + 2
|
||||
@@ -273,7 +273,7 @@ func TestDoProxy(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestAuthorization(t *testing.T) {
|
||||
func TestApiToken(t *testing.T) {
|
||||
var cfg = &model.StartConfig{}
|
||||
cfg.Init()
|
||||
cfg.ApiToken = "123456"
|
||||
@@ -285,16 +285,57 @@ func TestAuthorization(t *testing.T) {
|
||||
Stop()
|
||||
}()
|
||||
|
||||
code, _ := doHttpRequest[any](http.MethodGet, "/api/v1/config", nil, nil)
|
||||
checkCode(code, model.CodeUnauthorized)
|
||||
status, _ := doHttpRequest0(http.MethodGet, "/api/v1/config", nil, nil)
|
||||
if status != http.StatusUnauthorized {
|
||||
t.Errorf("TestApiToken() got = %v, want %v", status, http.StatusUnauthorized)
|
||||
}
|
||||
|
||||
code, _ = doHttpRequest[any](http.MethodGet, "/api/v1/config", map[string]string{
|
||||
status, _ = doHttpRequest0(http.MethodGet, "/api/v1/config", map[string]string{
|
||||
"X-Api-Token": cfg.ApiToken,
|
||||
}, nil)
|
||||
checkCode(code, model.CodeOk)
|
||||
if status != http.StatusOK {
|
||||
t.Errorf("TestApiToken() got = %v, want %v", status, http.StatusOK)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestAuthorization(t *testing.T) {
|
||||
var cfg = &model.StartConfig{}
|
||||
cfg.Init()
|
||||
cfg.ApiToken = "123456"
|
||||
cfg.WebEnable = true
|
||||
cfg.WebBasicAuth = &model.WebBasicAuth{
|
||||
Username: "admin",
|
||||
Password: "123456",
|
||||
}
|
||||
fileListener := doStart(cfg)
|
||||
defer func() {
|
||||
if err := fileListener.Close(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
Stop()
|
||||
}()
|
||||
|
||||
status, _ := doHttpRequest0(http.MethodGet, "/api/v1/config", nil, nil)
|
||||
if status != http.StatusUnauthorized {
|
||||
t.Errorf("TestAuthorization() got = %v, want %v", status, http.StatusUnauthorized)
|
||||
}
|
||||
|
||||
status, _ = doHttpRequest0(http.MethodGet, "/api/v1/config", map[string]string{
|
||||
"Authorization": cfg.WebBasicAuth.Authorization(),
|
||||
}, nil)
|
||||
if status != http.StatusOK {
|
||||
t.Errorf("TestAuthorization() got = %v, want %v", status, http.StatusOK)
|
||||
}
|
||||
|
||||
status, _ = doHttpRequest0(http.MethodGet, "/api/v1/config", map[string]string{
|
||||
"X-Api-Token": cfg.ApiToken,
|
||||
}, nil)
|
||||
if status != http.StatusOK {
|
||||
t.Errorf("TestAuthorization() got = %v, want %v", status, http.StatusOK)
|
||||
}
|
||||
}
|
||||
|
||||
func doTest(handler func()) {
|
||||
testFunc := func(storage model.Storage) {
|
||||
var cfg = &model.StartConfig{}
|
||||
|
||||
+12
-11
@@ -3,7 +3,6 @@ package util
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
syspath "path"
|
||||
"strings"
|
||||
@@ -83,17 +82,19 @@ func GetSingleDir(paths []string) string {
|
||||
return dir
|
||||
}
|
||||
|
||||
// check directory is empty
|
||||
func isEmpty(name string) (bool, error) {
|
||||
f, err := os.Open(name)
|
||||
if err != nil {
|
||||
return false, err
|
||||
func CreateDirIfNotExist(dir string) error {
|
||||
if _, err := os.Stat(dir); os.IsNotExist(err) {
|
||||
return os.MkdirAll(dir, 0o777)
|
||||
}
|
||||
defer f.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err = f.Readdirnames(1) // Or f.Readdir(1)
|
||||
if err == io.EOF {
|
||||
return true, nil
|
||||
// IsExistsFile check file exists and is a file
|
||||
func IsExistsFile(path string) bool {
|
||||
info, err := os.Stat(path)
|
||||
// 判断路径是否存在,并且是一个文件
|
||||
if err == nil && !info.IsDir() {
|
||||
return true
|
||||
}
|
||||
return false, err // Either not empty or error, suits both cases
|
||||
return false
|
||||
}
|
||||
@@ -199,3 +199,43 @@ func doCheckDuplicateAndRename(t *testing.T, exitsPaths []string, path string, e
|
||||
t.Errorf("CheckDuplicateAndRename() = %v, want %v", got, except)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsExistsFile(t *testing.T) {
|
||||
type args struct {
|
||||
path string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "exist",
|
||||
args: args{
|
||||
path: "./path.go",
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "not exist",
|
||||
args: args{
|
||||
path: "./path_not_exist.go",
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "is dir",
|
||||
args: args{
|
||||
path: "../util",
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := IsExistsFile(tt.args.path); got != tt.want {
|
||||
t.Errorf("IsExistsFile() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
+29
-3
@@ -1,13 +1,39 @@
|
||||
package util
|
||||
|
||||
import "strings"
|
||||
import (
|
||||
"encoding/base64"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const FileSchema = "FILE"
|
||||
|
||||
func ParseSchema(url string) string {
|
||||
index := strings.Index(url, ":")
|
||||
if index == -1 {
|
||||
// if no schema or windows path like C:\a.txt, return FILE
|
||||
if index == -1 || index == 1 {
|
||||
return FileSchema
|
||||
}
|
||||
return strings.ToUpper(url[:index])
|
||||
schema := url[:index]
|
||||
if schema == "data" {
|
||||
schema, _ = ParseDataUri(url)
|
||||
}
|
||||
return strings.ToUpper(schema)
|
||||
}
|
||||
|
||||
// ParseDataUri parses a data URI and returns the MIME type and decode data.
|
||||
func ParseDataUri(uri string) (string, []byte) {
|
||||
re := regexp.MustCompile(`^data:(.*);base64,(.*)$`)
|
||||
matches := re.FindStringSubmatch(uri)
|
||||
if len(matches) != 3 {
|
||||
return "", nil
|
||||
}
|
||||
mime := matches[1]
|
||||
base64Data := matches[2]
|
||||
// 解码Base64数据
|
||||
data, err := base64.StdEncoding.DecodeString(base64Data)
|
||||
if err != nil {
|
||||
return "", nil
|
||||
}
|
||||
return mime, data
|
||||
}
|
||||
+82
-2
@@ -1,6 +1,10 @@
|
||||
package util
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"encoding/base64"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseSchema(t *testing.T) {
|
||||
type args struct {
|
||||
@@ -35,7 +39,21 @@ func TestParseSchema(t *testing.T) {
|
||||
{
|
||||
name: "file-no-scheme",
|
||||
args: args{
|
||||
url: "/home/bt.torrent",
|
||||
url: "./url.go",
|
||||
},
|
||||
want: "FILE",
|
||||
},
|
||||
{
|
||||
name: "data-uri",
|
||||
args: args{
|
||||
url: "data:application/x-bittorrent;base64,test",
|
||||
},
|
||||
want: "APPLICATION/X-BITTORRENT",
|
||||
},
|
||||
{
|
||||
name: "windows-path",
|
||||
args: args{
|
||||
url: "D:\\bt.torrent",
|
||||
},
|
||||
want: "FILE",
|
||||
},
|
||||
@@ -48,3 +66,65 @@ func TestParseSchema(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseDataUri(t *testing.T) {
|
||||
type args struct {
|
||||
uri string
|
||||
}
|
||||
type result struct {
|
||||
mime string
|
||||
data []byte
|
||||
}
|
||||
|
||||
testData := []byte("test")
|
||||
testData64 := base64.StdEncoding.EncodeToString(testData)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want result
|
||||
}{
|
||||
{
|
||||
name: "success",
|
||||
args: args{
|
||||
uri: "data:application/x-bittorrent;base64," + testData64,
|
||||
},
|
||||
want: result{
|
||||
mime: "application/x-bittorrent",
|
||||
data: testData,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "fail-dirty-data",
|
||||
args: args{
|
||||
uri: "data::application/x-bittorrent;base64,!@$",
|
||||
},
|
||||
want: result{
|
||||
mime: "",
|
||||
data: nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "fail-miss-data",
|
||||
args: args{
|
||||
uri: ":application/x-bittorrent;base64," + testData64,
|
||||
},
|
||||
want: result{
|
||||
mime: "",
|
||||
data: nil,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
mime, data := ParseDataUri(tt.args.uri)
|
||||
got := result{
|
||||
mime: mime,
|
||||
data: data,
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("ParseDataUri() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,6 @@ subprojects {
|
||||
project.evaluationDependsOn(':app')
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
tasks.register("clean", Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
||||
@@ -12,6 +12,7 @@
|
||||
"donate": "Donate",
|
||||
"exit": "Exit",
|
||||
"create": "Create Task",
|
||||
"advancedOptions": "Advanced Options",
|
||||
"downloadLink": "Download Link",
|
||||
"downloadLinkValid": "Please enter the download link",
|
||||
"downloadLinkHit": "Please enter the download link, HTTP/HTTPS/MAGNET supported@append",
|
||||
@@ -20,6 +21,7 @@
|
||||
"noFileSelected": "Please select at least one file to continue.",
|
||||
"noStoragePermission": "Storage permission required",
|
||||
"selectFile": "Select File",
|
||||
"rename": "Rename",
|
||||
"basic": "Basic",
|
||||
"advanced": "Advanced",
|
||||
"general": "General",
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"label": "日本語",
|
||||
"error": "エラー",
|
||||
"tip": "ヒント",
|
||||
"confirm": "確認",
|
||||
"cancel": "キャンセル",
|
||||
"selectAll": "すべてを選択",
|
||||
"task": "タスク",
|
||||
"downloading": "ダウンロード中",
|
||||
"downloaded": "ダウンロード済",
|
||||
"setting": "セッティング",
|
||||
"donate": "寄付する",
|
||||
"exit": "終了",
|
||||
"create": "タスクを作成",
|
||||
"downloadLink": "ダウンロードリンク",
|
||||
"downloadLinkValid": "ダウンロードリンクを入力してください",
|
||||
"downloadLinkHit": "ダウンロード リンクを入力してください、HTTP/HTTPS/MAGNET support@append",
|
||||
"downloadLinkHitDesktop": "または、torrent ファイルを直接ここにドラッグします",
|
||||
"download": "ダウンロード",
|
||||
"noFileSelected": "続行するには少なくとも 1 つのファイルを選択してください。",
|
||||
"noStoragePermission": "ストレージのパーミッションが必要",
|
||||
"selectFile": "ファイルを選択",
|
||||
"basic": "ベーシック",
|
||||
"advanced": "アドバンスド",
|
||||
"general": "ジェネラル",
|
||||
"downloadDir": "ディレクトリのダウンロード",
|
||||
"downloadDirValid": "ダウンロードディレクトリを選択してください",
|
||||
"connections": "接続",
|
||||
"maxRunning": "最大実行タスク",
|
||||
"items": "@count アイテム",
|
||||
"subscribeTracker": "トラッカーを購読",
|
||||
"subscribeFail": "登録に失敗しました。ネットワークを確認するか、後でもう一度お試しください",
|
||||
"update": "アップデート",
|
||||
"updateDaily": "毎日アップデート",
|
||||
"lastUpdate": "最終アップデート: @time",
|
||||
"addTracker": "トラッカーを追加",
|
||||
"addTrackerHit": "トラッカーサーバーの URL を 1 行に 1 つずつ入力してください",
|
||||
"ui": "UI",
|
||||
"theme": "テーマ",
|
||||
"themeSystem": "システム",
|
||||
"themeLight": "ライト",
|
||||
"themeDark": "ダーク",
|
||||
"locale": "言語",
|
||||
"about": "概要",
|
||||
"homepage": "ホームページ",
|
||||
"version": "バージョン",
|
||||
"protocol": "プロトコル",
|
||||
"port": "ポート",
|
||||
"apiToken": "API トークン",
|
||||
"notSet": "未設置",
|
||||
"set": "設置",
|
||||
"effectAfterRestart": "再起動後の効果",
|
||||
"startAll": "すべてを開始",
|
||||
"pauseAll": "すべてを一時停止",
|
||||
"deleteTask": "タスクを削除",
|
||||
"deleteTaskTip": "ダウンロードしたファイルを保持",
|
||||
"delete": "削除",
|
||||
"newVersionTitle": "新しいバージョン @version を発見する",
|
||||
"newVersionUpdate": "今すぐダウンロード",
|
||||
"newVersionLater": "後で"
|
||||
}
|
||||
@@ -12,6 +12,7 @@
|
||||
"donate": "打赏",
|
||||
"exit": "退出",
|
||||
"create": "创建任务",
|
||||
"advancedOptions": "高级选项",
|
||||
"downloadLink": "下载链接",
|
||||
"downloadLinkValid": "请输入下载链接",
|
||||
"downloadLinkHit": "请输入下载链接,支持 HTTP/HTTPS/MAGNET@append",
|
||||
@@ -20,6 +21,7 @@
|
||||
"noFileSelected": "请至少选择一个文件下载",
|
||||
"noStoragePermission": "需要开启存储权限",
|
||||
"selectFile": "选择文件",
|
||||
"rename": "重命名",
|
||||
"basic": "基础",
|
||||
"advanced": "高级",
|
||||
"general": "通用",
|
||||
|
||||
@@ -34,6 +34,7 @@ class _Client {
|
||||
}
|
||||
dio.options.baseUrl = baseUrl;
|
||||
dio.options.contentType = Headers.jsonContentType;
|
||||
dio.options.sendTimeout = const Duration(seconds: 5);
|
||||
dio.options.connectTimeout = const Duration(seconds: 5);
|
||||
dio.options.receiveTimeout = const Duration(seconds: 60);
|
||||
dio.interceptors.add(InterceptorsWrapper(onRequest: (options, handler) {
|
||||
@@ -46,7 +47,8 @@ class _Client {
|
||||
_instance!.dio = dio;
|
||||
if (isUnixSocket) {
|
||||
(_instance!.dio.httpClientAdapter as IOHttpClientAdapter)
|
||||
.onHttpClientCreate = (client) {
|
||||
.createHttpClient = () {
|
||||
final client = HttpClient();
|
||||
client.connectionFactory =
|
||||
(Uri uri, String? proxyHost, int? proxyPort) {
|
||||
return Socket.startConnect(
|
||||
@@ -79,7 +81,12 @@ Future<T> _parse<T>(
|
||||
} else {
|
||||
throw Exception(result);
|
||||
}
|
||||
} on DioError catch (e) {
|
||||
} on DioException catch (e) {
|
||||
if (e.type == DioExceptionType.sendTimeout ||
|
||||
e.type == DioExceptionType.receiveTimeout ||
|
||||
e.type == DioExceptionType.connectionTimeout) {
|
||||
throw Exception(Result(code: 1000, msg: "request timeout"));
|
||||
}
|
||||
throw Exception(Result(code: 1000, msg: e.message));
|
||||
}
|
||||
}
|
||||
@@ -140,6 +147,8 @@ Future<Response<String>> proxyRequest<T>(String uri,
|
||||
options.headers!["X-Target-Uri"] = uri;
|
||||
|
||||
// add timestamp to avoid cache
|
||||
return _client.dio.request("/api/v1/proxy?t=${DateTime.now()}",
|
||||
data: data, options: options);
|
||||
return _client.dio.request(
|
||||
"/api/v1/proxy?t=${DateTime.now().millisecondsSinceEpoch}",
|
||||
data: data,
|
||||
options: options);
|
||||
}
|
||||
@@ -2,17 +2,18 @@ import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
part 'options.g.dart';
|
||||
|
||||
@JsonSerializable(explicitToJson: true, genericArgumentFactories: true)
|
||||
@JsonSerializable(explicitToJson: true)
|
||||
class Options {
|
||||
String name;
|
||||
String path;
|
||||
List<int> selectFiles;
|
||||
Map<String, dynamic>? extra;
|
||||
Object? extra;
|
||||
|
||||
Options({
|
||||
required this.name,
|
||||
required this.path,
|
||||
required this.selectFiles,
|
||||
this.extra,
|
||||
});
|
||||
|
||||
factory Options.fromJson(Map<String, dynamic> json) =>
|
||||
|
||||
@@ -11,7 +11,8 @@ Options _$OptionsFromJson(Map<String, dynamic> json) => Options(
|
||||
path: json['path'] as String,
|
||||
selectFiles:
|
||||
(json['selectFiles'] as List<dynamic>).map((e) => e as int).toList(),
|
||||
)..extra = json['extra'] as Map<String, dynamic>?;
|
||||
extra: json['extra'],
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$OptionsToJson(Options instance) {
|
||||
final val = <String, dynamic>{
|
||||
|
||||
@@ -5,10 +5,11 @@ part 'request.g.dart';
|
||||
@JsonSerializable(explicitToJson: true)
|
||||
class Request {
|
||||
String url;
|
||||
Map<String, dynamic>? extra;
|
||||
Object? extra;
|
||||
|
||||
Request({
|
||||
required this.url,
|
||||
this.extra,
|
||||
});
|
||||
|
||||
factory Request.fromJson(Map<String, dynamic> json) =>
|
||||
@@ -20,7 +21,7 @@ class Request {
|
||||
@JsonSerializable()
|
||||
class ReqExtraHttp {
|
||||
String method = 'GET';
|
||||
Map<String, String> headers = {};
|
||||
Map<String, String> header = {};
|
||||
String body = '';
|
||||
|
||||
ReqExtraHttp();
|
||||
@@ -30,3 +31,15 @@ class ReqExtraHttp {
|
||||
|
||||
Map<String, dynamic> toJson() => _$ReqExtraHttpToJson(this);
|
||||
}
|
||||
|
||||
@JsonSerializable()
|
||||
class ReqExtraBt {
|
||||
List<String> trackers = [];
|
||||
|
||||
ReqExtraBt();
|
||||
|
||||
factory ReqExtraBt.fromJson(Map<String, dynamic> json) =>
|
||||
_$ReqExtraBtFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$ReqExtraBtToJson(this);
|
||||
}
|
||||
@@ -8,7 +8,8 @@ part of 'request.dart';
|
||||
|
||||
Request _$RequestFromJson(Map<String, dynamic> json) => Request(
|
||||
url: json['url'] as String,
|
||||
)..extra = json['extra'] as Map<String, dynamic>?;
|
||||
extra: json['extra'],
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$RequestToJson(Request instance) {
|
||||
final val = <String, dynamic>{
|
||||
@@ -27,12 +28,21 @@ Map<String, dynamic> _$RequestToJson(Request instance) {
|
||||
|
||||
ReqExtraHttp _$ReqExtraHttpFromJson(Map<String, dynamic> json) => ReqExtraHttp()
|
||||
..method = json['method'] as String
|
||||
..headers = Map<String, String>.from(json['headers'] as Map)
|
||||
..header = Map<String, String>.from(json['header'] as Map)
|
||||
..body = json['body'] as String;
|
||||
|
||||
Map<String, dynamic> _$ReqExtraHttpToJson(ReqExtraHttp instance) =>
|
||||
<String, dynamic>{
|
||||
'method': instance.method,
|
||||
'headers': instance.headers,
|
||||
'header': instance.header,
|
||||
'body': instance.body,
|
||||
};
|
||||
|
||||
ReqExtraBt _$ReqExtraBtFromJson(Map<String, dynamic> json) => ReqExtraBt()
|
||||
..trackers =
|
||||
(json['trackers'] as List<dynamic>).map((e) => e as String).toList();
|
||||
|
||||
Map<String, dynamic> _$ReqExtraBtToJson(ReqExtraBt instance) =>
|
||||
<String, dynamic>{
|
||||
'trackers': instance.trackers,
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:ui' as ui;
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:app_links/app_links.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -16,7 +16,7 @@ import '../../../../api/api.dart';
|
||||
import '../../../../api/model/downloader_config.dart';
|
||||
import '../../../../core/common/start_config.dart';
|
||||
import '../../../../generated/locales.g.dart';
|
||||
import '../../../../util/localeManager.dart';
|
||||
import '../../../../util/locale_manager.dart';
|
||||
import '../../../../util/log_util.dart';
|
||||
import '../../../../util/package_info.dart';
|
||||
import '../../../../util/util.dart';
|
||||
@@ -42,10 +42,13 @@ const allTrackerSubscribeUrls = [
|
||||
'https://github.com/XIU2/TrackersListCollection/raw/master/http.txt',
|
||||
];
|
||||
const allTrackerCdns = [
|
||||
// jsdelivr: https://cdn.jsdelivr.net/gh/ngosang/trackerslist/trackers_all.txt
|
||||
["https://cdn.jsdelivr.net/gh", r".*github.com(/.*)/raw/master(/.*)"],
|
||||
// nuaa: https://hub.nuaa.cf/ngosang/trackerslist/raw/master/trackers_all.txt
|
||||
["https://hub.nuaa.cf", r".*github.com(/.*)"]
|
||||
// jsdelivr: https://fastly.jsdelivr.net/gh/ngosang/trackerslist/trackers_all.txt
|
||||
["https://fastly.jsdelivr.net/gh", r".*github.com(/.*)/raw/master(/.*)"],
|
||||
// ghproxy: https://ghproxy.com/https://raw.githubusercontent.com/ngosang/trackerslist/master/trackers_all.txt
|
||||
[
|
||||
"https://ghproxy.com/https://raw.githubusercontent.com",
|
||||
r".*github.com(/.*)/raw(/.*)"
|
||||
]
|
||||
];
|
||||
final allTrackerSubscribeUrlCdns = Map.fromIterable(allTrackerSubscribeUrls,
|
||||
key: (v) => v as String,
|
||||
@@ -265,18 +268,16 @@ class AppController extends GetxController with WindowListener, TrayListener {
|
||||
final btExtConfig = downloaderConfig.value.extra.bt;
|
||||
final result = <String>[];
|
||||
for (var u in btExtConfig.trackerSubscribeUrls) {
|
||||
var allFail = true;
|
||||
for (var cdn in allTrackerSubscribeUrlCdns[u]!) {
|
||||
try {
|
||||
result.addAll(await _fetchTrackers(cdn));
|
||||
allFail = false;
|
||||
break;
|
||||
} catch (e) {
|
||||
logger.w("subscribe trackers fail, url: $cdn", e);
|
||||
}
|
||||
final cdns = allTrackerSubscribeUrlCdns[u];
|
||||
if (cdns == null) {
|
||||
continue;
|
||||
}
|
||||
if (allFail) {
|
||||
throw Exception('subscribe trackers fail, network error');
|
||||
try {
|
||||
final trackers =
|
||||
await Util.anyOk(cdns.map((cdn) => _fetchTrackers(cdn)));
|
||||
result.addAll(trackers);
|
||||
} catch (e) {
|
||||
logger.w("subscribe trackers fail, url: $u", e);
|
||||
}
|
||||
}
|
||||
btExtConfig.subscribeTrackers.clear();
|
||||
@@ -336,7 +337,7 @@ class AppController extends GetxController with WindowListener, TrayListener {
|
||||
extra.themeMode = ThemeMode.system.name;
|
||||
}
|
||||
if (extra.locale.isEmpty) {
|
||||
final systemLocale = getLocaleKey(ui.window.locale);
|
||||
final systemLocale = getLocaleKey(PlatformDispatcher.instance.locale);
|
||||
extra.locale = AppTranslation.translations.containsKey(systemLocale)
|
||||
? systemLocale
|
||||
: getLocaleKey(fallbackLocale);
|
||||
@@ -365,6 +366,7 @@ class AppController extends GetxController with WindowListener, TrayListener {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setString(_startConfigNetwork, startConfig.value.network);
|
||||
await prefs.setString(_startConfigAddress, startConfig.value.address);
|
||||
await prefs.setString(_startConfigApiToken, startConfig.value.apiToken);
|
||||
await putConfig(downloaderConfig.value);
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import 'package:get/get.dart';
|
||||
|
||||
import '../../../../generated/locales.g.dart';
|
||||
import '../../../../theme/theme.dart';
|
||||
import '../../../../util/localeManager.dart';
|
||||
import '../../../../util/locale_manager.dart';
|
||||
import '../../../routes/app_pages.dart';
|
||||
import '../controllers/app_controller.dart';
|
||||
|
||||
|
||||
@@ -1,9 +1,38 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class CreateController extends GetxController {
|
||||
class CreateController extends GetxController
|
||||
with GetSingleTickerProviderStateMixin {
|
||||
// final files = [].obs;
|
||||
final RxList fileInfos = [].obs;
|
||||
final RxList selectedIndexes = [].obs;
|
||||
final RxList openedFolders = [].obs;
|
||||
final isResolving = false.obs;
|
||||
final showAdvanced = false.obs;
|
||||
late TabController advancedTabController;
|
||||
final fileDataUri = "".obs;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
advancedTabController = TabController(length: 2, vsync: this);
|
||||
}
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
advancedTabController.dispose();
|
||||
super.onClose();
|
||||
}
|
||||
|
||||
void setFileDataUri(Uint8List bytes) {
|
||||
fileDataUri.value =
|
||||
"data:application/x-bittorrent;base64,${base64.encode(bytes)}";
|
||||
}
|
||||
|
||||
void clearFileDataUri() {
|
||||
fileDataUri.value = "";
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
import 'package:autoscale_tabbarview/autoscale_tabbarview.dart';
|
||||
import 'package:desktop_drop/desktop_drop.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:get/get.dart';
|
||||
import '../../../../api/model/resolve_result.dart';
|
||||
import 'package:rounded_loading_button/rounded_loading_button.dart';
|
||||
@@ -9,6 +11,7 @@ import '../../../../api/api.dart';
|
||||
import '../../../../api/model/create_task.dart';
|
||||
import '../../../../api/model/options.dart';
|
||||
import '../../../../api/model/request.dart';
|
||||
import '../../../../util/input_formatter.dart';
|
||||
import '../../../../util/message.dart';
|
||||
import '../../../../util/util.dart';
|
||||
import '../../../routes/app_pages.dart';
|
||||
@@ -22,6 +25,10 @@ class CreateView extends GetView<CreateController> {
|
||||
|
||||
final _urlController = TextEditingController();
|
||||
final _confirmController = RoundedLoadingButtonController();
|
||||
final _httpUaController = TextEditingController();
|
||||
final _httpCookieController = TextEditingController();
|
||||
final _httpRefererController = TextEditingController();
|
||||
final _btTrackerController = TextEditingController();
|
||||
|
||||
CreateView({Key? key}) : super(key: key);
|
||||
|
||||
@@ -44,39 +51,51 @@ class CreateView extends GetView<CreateController> {
|
||||
title: Text('create'.tr),
|
||||
),
|
||||
body: DropTarget(
|
||||
onDragDone: (details) {
|
||||
_urlController.text = details.files[0].path;
|
||||
onDragDone: (details) async {
|
||||
if (!Util.isWeb()) {
|
||||
_urlController.text = details.files[0].path;
|
||||
return;
|
||||
}
|
||||
_urlController.text = details.files[0].name;
|
||||
final bytes = await details.files[0].readAsBytes();
|
||||
controller.setFileDataUri(bytes);
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16.0, horizontal: 24.0),
|
||||
child: Form(
|
||||
key: _resolveFormKey,
|
||||
autovalidateMode: AutovalidateMode.always,
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
padding:
|
||||
const EdgeInsets.symmetric(vertical: 16.0, horizontal: 24.0),
|
||||
child: Form(
|
||||
key: _resolveFormKey,
|
||||
autovalidateMode: AutovalidateMode.onUserInteraction,
|
||||
child: Column(children: [
|
||||
Row(children: [
|
||||
Expanded(
|
||||
child: TextFormField(
|
||||
autofocus: true,
|
||||
controller: _urlController,
|
||||
minLines: 1,
|
||||
maxLines: 30,
|
||||
decoration: InputDecoration(
|
||||
hintText: _hitText(),
|
||||
hintStyle: const TextStyle(fontSize: 12),
|
||||
labelText: 'downloadLink'.tr,
|
||||
icon: const Icon(Icons.link),
|
||||
suffixIcon: IconButton(
|
||||
onPressed: _urlController.clear,
|
||||
icon: const Icon(Icons.clear),
|
||||
),
|
||||
autofocus: true,
|
||||
controller: _urlController,
|
||||
minLines: 1,
|
||||
maxLines: 5,
|
||||
decoration: InputDecoration(
|
||||
hintText: _hitText(),
|
||||
hintStyle: const TextStyle(fontSize: 12),
|
||||
labelText: 'downloadLink'.tr,
|
||||
icon: const Icon(Icons.link),
|
||||
suffixIcon: IconButton(
|
||||
onPressed: () {
|
||||
_urlController.clear();
|
||||
controller.clearFileDataUri();
|
||||
},
|
||||
icon: const Icon(Icons.clear),
|
||||
),
|
||||
validator: (v) {
|
||||
return v!.trim().isNotEmpty
|
||||
? null
|
||||
: 'downloadLinkValid'.tr;
|
||||
}),
|
||||
),
|
||||
validator: (v) {
|
||||
return v!.trim().isNotEmpty
|
||||
? null
|
||||
: 'downloadLinkValid'.tr;
|
||||
},
|
||||
onChanged: (v) async {
|
||||
controller.clearFileDataUri();
|
||||
},
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.folder_open),
|
||||
@@ -85,31 +104,110 @@ class CreateView extends GetView<CreateController> {
|
||||
type: FileType.custom,
|
||||
allowedExtensions: ["torrent"]);
|
||||
if (pr != null) {
|
||||
_urlController.text = pr.files[0].path ?? "";
|
||||
if (!Util.isWeb()) {
|
||||
_urlController.text = pr.files[0].path ?? "";
|
||||
return;
|
||||
}
|
||||
_urlController.text = pr.files[0].name;
|
||||
controller.setFileDataUri(pr.files[0].bytes!);
|
||||
}
|
||||
}),
|
||||
],
|
||||
),
|
||||
Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(15.0),
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints.tightFor(
|
||||
]
|
||||
//.where((e) => e != null).map((e) => e!).toList(),
|
||||
),
|
||||
Obx(() => Visibility(
|
||||
visible: controller.showAdvanced.value,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 40, top: 15),
|
||||
child: Column(
|
||||
children: [
|
||||
TabBar(
|
||||
controller: controller.advancedTabController,
|
||||
tabs: const [
|
||||
Tab(
|
||||
text: 'HTTP',
|
||||
),
|
||||
Tab(
|
||||
text: 'BitTorrent',
|
||||
)
|
||||
],
|
||||
),
|
||||
AutoScaleTabBarView(
|
||||
controller: controller.advancedTabController,
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
TextFormField(
|
||||
controller: _httpUaController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'User-Agent',
|
||||
)),
|
||||
TextFormField(
|
||||
controller: _httpCookieController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Cookie',
|
||||
)),
|
||||
TextFormField(
|
||||
controller: _httpRefererController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Referer',
|
||||
)),
|
||||
],
|
||||
),
|
||||
Column(
|
||||
children: [
|
||||
TextFormField(
|
||||
controller: _btTrackerController,
|
||||
maxLines: 5,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Trakers',
|
||||
hintText: 'addTrackerHit'.tr,
|
||||
)),
|
||||
],
|
||||
)
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
))),
|
||||
Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 15),
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 10),
|
||||
child: TextButton(
|
||||
onPressed: () {
|
||||
controller.showAdvanced.value =
|
||||
!controller.showAdvanced.value;
|
||||
},
|
||||
child:
|
||||
Row(mainAxisSize: MainAxisSize.min, children: [
|
||||
Obx(() => Checkbox(
|
||||
value: controller.showAdvanced.value,
|
||||
onChanged: (bool? value) {
|
||||
controller.showAdvanced.value =
|
||||
value ?? false;
|
||||
},
|
||||
)),
|
||||
Text('advancedOptions'.tr),
|
||||
]),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: 150,
|
||||
height: 40,
|
||||
child: RoundedLoadingButton(
|
||||
color: Get.theme.colorScheme.secondary,
|
||||
onPressed: _doResolve,
|
||||
controller: _confirmController,
|
||||
child: Text('confirm'.tr),
|
||||
),
|
||||
),
|
||||
child: RoundedLoadingButton(
|
||||
color: Get.theme.colorScheme.secondary,
|
||||
onPressed: _doResolve,
|
||||
controller: _confirmController,
|
||||
child: Text('confirm'.tr),
|
||||
),
|
||||
)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)),
|
||||
]))),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -122,8 +220,27 @@ class CreateView extends GetView<CreateController> {
|
||||
try {
|
||||
_confirmController.start();
|
||||
if (_resolveFormKey.currentState!.validate()) {
|
||||
Object? extra;
|
||||
final submitUrl = Util.isWeb() && controller.fileDataUri.isNotEmpty
|
||||
? controller.fileDataUri.value
|
||||
: _urlController.text;
|
||||
if (controller.showAdvanced.value) {
|
||||
final u = Uri.parse(submitUrl);
|
||||
if (u.scheme.startsWith("http")) {
|
||||
extra = ReqExtraHttp()
|
||||
..header = {
|
||||
"User-Agent": _httpUaController.text,
|
||||
"Cookie": _httpCookieController.text,
|
||||
"Referer": _httpRefererController.text,
|
||||
};
|
||||
} else {
|
||||
extra = ReqExtraBt()
|
||||
..trackers = Util.textToLines(_btTrackerController.text);
|
||||
}
|
||||
}
|
||||
final rr = await resolve(Request(
|
||||
url: _urlController.text,
|
||||
url: submitUrl,
|
||||
extra: extra,
|
||||
));
|
||||
await _showResolveDialog(rr);
|
||||
}
|
||||
@@ -137,7 +254,8 @@ class CreateView extends GetView<CreateController> {
|
||||
|
||||
String _hitText() {
|
||||
return 'downloadLinkHit'.trParams({
|
||||
'append': Util.isDesktop() ? 'downloadLinkHitDesktop'.tr : '',
|
||||
'append':
|
||||
Util.isDesktop() || Util.isWeb() ? 'downloadLinkHitDesktop'.tr : '',
|
||||
});
|
||||
}
|
||||
|
||||
@@ -146,6 +264,8 @@ class CreateView extends GetView<CreateController> {
|
||||
final appController = Get.find<AppController>();
|
||||
|
||||
final createFormKey = GlobalKey<FormState>();
|
||||
final nameController = TextEditingController();
|
||||
final connectionsController = TextEditingController();
|
||||
final pathController = TextEditingController(
|
||||
text: appController.downloaderConfig.value.downloadDir);
|
||||
final downloadController = RoundedLoadingButtonController();
|
||||
@@ -168,6 +288,23 @@ class CreateView extends GetView<CreateController> {
|
||||
child: Column(
|
||||
children: [
|
||||
Expanded(child: FileListView(files: files)),
|
||||
TextFormField(
|
||||
controller: nameController,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'rename'.tr,
|
||||
),
|
||||
),
|
||||
TextFormField(
|
||||
controller: connectionsController,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'connections'.tr,
|
||||
),
|
||||
keyboardType: TextInputType.number,
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.digitsOnly,
|
||||
NumericalRangeFormatter(min: 1, max: 256),
|
||||
],
|
||||
),
|
||||
DirectorySelector(
|
||||
controller: pathController,
|
||||
),
|
||||
@@ -209,20 +346,18 @@ class CreateView extends GetView<CreateController> {
|
||||
return;
|
||||
}
|
||||
if (createFormKey.currentState!.validate()) {
|
||||
// if (Util.isAndroid()) {
|
||||
// if (!await Permission.storage.request().isGranted) {
|
||||
// Get.snackbar('error'.tr,
|
||||
// 'noStoragePermission'.tr);
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
await createTask(CreateTask(
|
||||
rid: rr.id,
|
||||
opts: Options(
|
||||
name: '',
|
||||
name: nameController.text,
|
||||
path: pathController.text,
|
||||
selectFiles: controller.selectedIndexes
|
||||
.cast<int>())));
|
||||
selectFiles:
|
||||
controller.selectedIndexes.cast<int>(),
|
||||
extra: connectionsController.text.isEmpty
|
||||
? null
|
||||
: (OptsExtraHttp()
|
||||
..connections = int.parse(
|
||||
connectionsController.text)))));
|
||||
Get.back();
|
||||
Get.rootDelegate.offNamed(Routes.TASK);
|
||||
}
|
||||
|
||||
@@ -28,17 +28,21 @@ class SettingController extends GetxController {
|
||||
void fetchLatestVersion() async {
|
||||
String? releaseDataStr;
|
||||
try {
|
||||
releaseDataStr =
|
||||
(await proxyRequest("https://gopeed.com/api/release")).data;
|
||||
} catch (e) {
|
||||
releaseDataStr = (await proxyRequest(
|
||||
"https://api.github.com/repos/GopeedLab/gopeed/releases/latest"))
|
||||
.data;
|
||||
} catch (e) {
|
||||
releaseDataStr =
|
||||
(await proxyRequest("https://gopeed.com/api/release")).data;
|
||||
}
|
||||
if (releaseDataStr == null) {
|
||||
return;
|
||||
}
|
||||
final releaseData = jsonDecode(releaseDataStr);
|
||||
final tagName = releaseData["tag_name"];
|
||||
if (tagName == null) {
|
||||
return;
|
||||
}
|
||||
latestVersion.value = releaseData["tag_name"].substring(1);
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,8 @@ import 'package:intl/intl.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
import '../../../../generated/locales.g.dart';
|
||||
import '../../../../util/localeManager.dart';
|
||||
import '../../../../util/input_formatter.dart';
|
||||
import '../../../../util/locale_manager.dart';
|
||||
import '../../../../util/message.dart';
|
||||
import '../../../../util/package_info.dart';
|
||||
import '../../../../util/util.dart';
|
||||
@@ -35,7 +36,7 @@ class SettingView extends GetView<SettingController> {
|
||||
debounceSave({bool needRestart = false}) {
|
||||
var completer = Completer<void>();
|
||||
timer?.cancel();
|
||||
timer = Timer(const Duration(milliseconds: 500), () {
|
||||
timer = Timer(const Duration(milliseconds: 1000), () {
|
||||
appController
|
||||
.saveConfig()
|
||||
.then(completer.complete)
|
||||
@@ -89,7 +90,7 @@ class SettingView extends GetView<SettingController> {
|
||||
keyboardType: TextInputType.number,
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.digitsOnly,
|
||||
_NumericalRangeFormatter(min: 1, max: 256),
|
||||
NumericalRangeFormatter(min: 1, max: 256),
|
||||
],
|
||||
);
|
||||
});
|
||||
@@ -116,7 +117,7 @@ class SettingView extends GetView<SettingController> {
|
||||
keyboardType: TextInputType.number,
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.digitsOnly,
|
||||
_NumericalRangeFormatter(min: 1, max: 256),
|
||||
NumericalRangeFormatter(min: 1, max: 256),
|
||||
],
|
||||
);
|
||||
});
|
||||
@@ -187,7 +188,6 @@ class SettingView extends GetView<SettingController> {
|
||||
(Key key) {
|
||||
final trackersController = TextEditingController(
|
||||
text: btExtConfig.customTrackers.join('\r\n').toString());
|
||||
const ls = LineSplitter();
|
||||
return TextField(
|
||||
key: key,
|
||||
focusNode: FocusNode(),
|
||||
@@ -198,7 +198,7 @@ class SettingView extends GetView<SettingController> {
|
||||
hintText: 'addTrackerHit'.tr,
|
||||
),
|
||||
onChanged: (value) async {
|
||||
btExtConfig.customTrackers = ls.convert(value);
|
||||
btExtConfig.customTrackers = Util.textToLines(value);
|
||||
appController.refreshTrackers();
|
||||
|
||||
await debounceSave();
|
||||
@@ -408,7 +408,7 @@ class SettingView extends GetView<SettingController> {
|
||||
keyboardType: TextInputType.number,
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.digitsOnly,
|
||||
_NumericalRangeFormatter(min: 0, max: 65535),
|
||||
NumericalRangeFormatter(min: 0, max: 65535),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -423,7 +423,7 @@ class SettingView extends GetView<SettingController> {
|
||||
},
|
||||
);
|
||||
final buildApiToken = _buildConfigItem('apiToken',
|
||||
() => startCfg.value.apiToken.isEmpty ? 'notSet'.tr : 'seted'.tr,
|
||||
() => startCfg.value.apiToken.isEmpty ? 'notSet'.tr : 'set'.tr,
|
||||
(Key key) {
|
||||
final apiTokenController =
|
||||
TextEditingController(text: startCfg.value.apiToken);
|
||||
@@ -434,7 +434,6 @@ class SettingView extends GetView<SettingController> {
|
||||
await debounceSave(needRestart: true);
|
||||
}
|
||||
});
|
||||
apiTokenController.addListener(() async {});
|
||||
return TextField(
|
||||
key: key,
|
||||
obscureText: true,
|
||||
@@ -606,39 +605,3 @@ class SettingView extends GetView<SettingController> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// class _ConfigItem {
|
||||
// late String label;
|
||||
// late String Function() text;
|
||||
// late Widget Function(Key key) inputItem;
|
||||
//
|
||||
// _ConfigItem(this.label, this.text, this.inputItem);
|
||||
// }
|
||||
|
||||
class _NumericalRangeFormatter extends TextInputFormatter {
|
||||
final int min;
|
||||
final int max;
|
||||
|
||||
_NumericalRangeFormatter({required this.min, required this.max});
|
||||
|
||||
@override
|
||||
TextEditingValue formatEditUpdate(
|
||||
TextEditingValue oldValue,
|
||||
TextEditingValue newValue,
|
||||
) {
|
||||
if (newValue.text.isEmpty) {
|
||||
return newValue;
|
||||
}
|
||||
var intVal = int.tryParse(newValue.text);
|
||||
if (intVal == null) {
|
||||
return oldValue;
|
||||
}
|
||||
if (intVal < min) {
|
||||
return newValue.copyWith(text: min.toString());
|
||||
} else if (intVal > max) {
|
||||
return oldValue.copyWith(text: max.toString());
|
||||
} else {
|
||||
return newValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:gopeed/api/model/meta.dart';
|
||||
import 'package:gopeed/api/model/resource.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
import '../../api/api.dart';
|
||||
@@ -190,7 +191,7 @@ class BuildTaskListView extends GetView {
|
||||
: const SizedBox.shrink(),
|
||||
ListTile(
|
||||
// isThreeLine: true,
|
||||
title: Text(task.meta.res.name,
|
||||
title: Text(fileName(task.meta),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: Get.textTheme.titleSmall),
|
||||
@@ -199,9 +200,9 @@ class BuildTaskListView extends GetView {
|
||||
style: context.textTheme.bodyLarge
|
||||
?.copyWith(color: Get.theme.disabledColor),
|
||||
),
|
||||
leading: task.meta.res.name.lastIndexOf('.') == -1
|
||||
? const Icon(FaIcons.file)
|
||||
: Icon(FaIcons.allIcons[findIcon(task.meta.res.name)]),
|
||||
leading: (task.meta.res.rootDir.isNotEmpty
|
||||
? const Icon(FaIcons.folder)
|
||||
: Icon(FaIcons.allIcons[findIcon(fileName(task.meta))])),
|
||||
|
||||
trailing: SizedBox(
|
||||
width: 180,
|
||||
@@ -220,4 +221,11 @@ class BuildTaskListView extends GetView {
|
||||
])
|
||||
]));
|
||||
}
|
||||
|
||||
String fileName(Meta meta) {
|
||||
if (meta.res.files.length > 1) {
|
||||
return meta.res.name;
|
||||
}
|
||||
return meta.opts.name.isEmpty ? meta.res.files[0].name : meta.opts.name;
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,6 @@ LibgopeedBoot create() => LibgopeedBootNative();
|
||||
|
||||
class LibgopeedBootNative implements LibgopeedBoot {
|
||||
late LibgopeedInterface _libgopeed;
|
||||
late SendPort _childSendPort;
|
||||
|
||||
LibgopeedBootNative() {
|
||||
if (!Util.isDesktop()) {
|
||||
@@ -44,11 +43,7 @@ class LibgopeedBootNative implements LibgopeedBoot {
|
||||
|
||||
@override
|
||||
Future<void> stop() async {
|
||||
if (Util.isDesktop()) {
|
||||
await _ffiStop();
|
||||
} else {
|
||||
await _libgopeed.stop();
|
||||
}
|
||||
await _libgopeed.stop();
|
||||
}
|
||||
|
||||
_ffiInit() {
|
||||
@@ -67,90 +62,9 @@ class LibgopeedBootNative implements LibgopeedBoot {
|
||||
|
||||
// FFI run in isolate
|
||||
Future<int> _ffiStart(StartConfig cfg) async {
|
||||
var startCompleter = Completer<int>();
|
||||
|
||||
var mainReceive = ReceivePort();
|
||||
var isolate = await Isolate.spawn((SendPort sendPort) {
|
||||
return await Isolate.run(() async {
|
||||
_ffiInit();
|
||||
var childReceive = ReceivePort();
|
||||
sendPort.send(childReceive.sendPort);
|
||||
|
||||
childReceive.listen((message) async {
|
||||
late _Result result;
|
||||
try {
|
||||
if (message is _StartParam) {
|
||||
result = _StartResult();
|
||||
(result as _StartResult).port = await _libgopeed.start(message.cfg);
|
||||
}
|
||||
if (message is _StopParam) {
|
||||
result = _StopResult();
|
||||
await _libgopeed.stop();
|
||||
childReceive.close();
|
||||
Isolate.exit(sendPort);
|
||||
}
|
||||
} on Exception catch (e) {
|
||||
result.exception = e;
|
||||
}
|
||||
sendPort.send(result);
|
||||
});
|
||||
}, mainReceive.sendPort);
|
||||
|
||||
var childCompleter = Completer<void>();
|
||||
mainReceive.listen((message) {
|
||||
if (message is SendPort) {
|
||||
_childSendPort = message;
|
||||
childCompleter.complete();
|
||||
}
|
||||
if (message is _StartResult) {
|
||||
if (message.exception != null) {
|
||||
startCompleter.completeError(message.exception!);
|
||||
} else {
|
||||
startCompleter.complete(message.port!);
|
||||
}
|
||||
}
|
||||
if (message is _StopResult) {
|
||||
isolate.kill();
|
||||
if (message.exception != null) {
|
||||
_stopCompleter.completeError(message.exception!);
|
||||
} else {
|
||||
_stopCompleter.complete();
|
||||
}
|
||||
}
|
||||
return await _libgopeed.start(cfg);
|
||||
});
|
||||
await childCompleter.future;
|
||||
_childSendPort.send(_StartParam(cfg: cfg));
|
||||
return startCompleter.future;
|
||||
}
|
||||
|
||||
late Completer<void> _stopCompleter;
|
||||
|
||||
Future<void> _ffiStop() async {
|
||||
_stopCompleter = Completer<void>();
|
||||
_childSendPort.send(_StopParam());
|
||||
return _stopCompleter.future;
|
||||
}
|
||||
}
|
||||
|
||||
class _Result {
|
||||
Exception? exception;
|
||||
|
||||
_Result();
|
||||
}
|
||||
|
||||
class _StartParam {
|
||||
final StartConfig cfg;
|
||||
|
||||
_StartParam({required this.cfg});
|
||||
}
|
||||
|
||||
class _StartResult extends _Result {
|
||||
int? port;
|
||||
|
||||
_StartResult();
|
||||
}
|
||||
|
||||
class _StopParam {}
|
||||
|
||||
class _StopResult extends _Result {
|
||||
_StopResult();
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -6,6 +6,7 @@ class AppTranslation {
|
||||
static Map<String, Map<String, String>> translations = {
|
||||
'en_US': Locales.en_US,
|
||||
'fa_IR': Locales.fa_IR,
|
||||
'ja_JP': Locales.ja_JP,
|
||||
'ru_RU': Locales.ru_RU,
|
||||
'zh_CN': Locales.zh_CN,
|
||||
};
|
||||
@@ -26,6 +27,7 @@ class LocaleKeys {
|
||||
static const donate = 'donate';
|
||||
static const exit = 'exit';
|
||||
static const create = 'create';
|
||||
static const advancedOptions = 'advancedOptions';
|
||||
static const downloadLink = 'downloadLink';
|
||||
static const downloadLinkValid = 'downloadLinkValid';
|
||||
static const downloadLinkHit = 'downloadLinkHit';
|
||||
@@ -34,6 +36,7 @@ class LocaleKeys {
|
||||
static const noFileSelected = 'noFileSelected';
|
||||
static const noStoragePermission = 'noStoragePermission';
|
||||
static const selectFile = 'selectFile';
|
||||
static const rename = 'rename';
|
||||
static const basic = 'basic';
|
||||
static const advanced = 'advanced';
|
||||
static const general = 'general';
|
||||
@@ -89,6 +92,7 @@ class Locales {
|
||||
'donate': 'Donate',
|
||||
'exit': 'Exit',
|
||||
'create': 'Create Task',
|
||||
'advancedOptions': 'Advanced Options',
|
||||
'downloadLink': 'Download Link',
|
||||
'downloadLinkValid': 'Please enter the download link',
|
||||
'downloadLinkHit':
|
||||
@@ -98,6 +102,7 @@ class Locales {
|
||||
'noFileSelected': 'Please select at least one file to continue.',
|
||||
'noStoragePermission': 'Storage permission required',
|
||||
'selectFile': 'Select File',
|
||||
'rename': 'Rename',
|
||||
'basic': 'Basic',
|
||||
'advanced': 'Advanced',
|
||||
'general': 'General',
|
||||
@@ -197,6 +202,67 @@ class Locales {
|
||||
'newVersionUpdate': 'دریافت اکنون',
|
||||
'newVersionLater': 'بعداً',
|
||||
};
|
||||
static const ja_JP = {
|
||||
'label': '日本語',
|
||||
'error': 'エラー',
|
||||
'tip': 'ヒント',
|
||||
'confirm': '確認',
|
||||
'cancel': 'キャンセル',
|
||||
'selectAll': 'すべてを選択',
|
||||
'task': 'タスク',
|
||||
'downloading': 'ダウンロード中',
|
||||
'downloaded': 'ダウンロード済',
|
||||
'setting': 'セッティング',
|
||||
'donate': '寄付する',
|
||||
'exit': '終了',
|
||||
'create': 'タスクを作成',
|
||||
'downloadLink': 'ダウンロードリンク',
|
||||
'downloadLinkValid': 'ダウンロードリンクを入力してください',
|
||||
'downloadLinkHit': 'ダウンロード リンクを入力してください、HTTP/HTTPS/MAGNET support@append',
|
||||
'downloadLinkHitDesktop': 'または、torrent ファイルを直接ここにドラッグします',
|
||||
'download': 'ダウンロード',
|
||||
'noFileSelected': '続行するには少なくとも 1 つのファイルを選択してください。',
|
||||
'noStoragePermission': 'ストレージのパーミッションが必要',
|
||||
'selectFile': 'ファイルを選択',
|
||||
'basic': 'ベーシック',
|
||||
'advanced': 'アドバンスド',
|
||||
'general': 'ジェネラル',
|
||||
'downloadDir': 'ディレクトリのダウンロード',
|
||||
'downloadDirValid': 'ダウンロードディレクトリを選択してください',
|
||||
'connections': '接続',
|
||||
'maxRunning': '最大実行タスク',
|
||||
'items': '@count アイテム',
|
||||
'subscribeTracker': 'トラッカーを購読',
|
||||
'subscribeFail': '登録に失敗しました。ネットワークを確認するか、後でもう一度お試しください',
|
||||
'update': 'アップデート',
|
||||
'updateDaily': '毎日アップデート',
|
||||
'lastUpdate': '最終アップデート: @time',
|
||||
'addTracker': 'トラッカーを追加',
|
||||
'addTrackerHit': 'トラッカーサーバーの URL を 1 行に 1 つずつ入力してください',
|
||||
'ui': 'UI',
|
||||
'theme': 'テーマ',
|
||||
'themeSystem': 'システム',
|
||||
'themeLight': 'ライト',
|
||||
'themeDark': 'ダーク',
|
||||
'locale': '言語',
|
||||
'about': '概要',
|
||||
'homepage': 'ホームページ',
|
||||
'version': 'バージョン',
|
||||
'protocol': 'プロトコル',
|
||||
'port': 'ポート',
|
||||
'apiToken': 'API トークン',
|
||||
'notSet': '未設置',
|
||||
'set': '設置',
|
||||
'effectAfterRestart': '再起動後の効果',
|
||||
'startAll': 'すべてを開始',
|
||||
'pauseAll': 'すべてを一時停止',
|
||||
'deleteTask': 'タスクを削除',
|
||||
'deleteTaskTip': 'ダウンロードしたファイルを保持',
|
||||
'delete': '削除',
|
||||
'newVersionTitle': '新しいバージョン @version を発見する',
|
||||
'newVersionUpdate': '今すぐダウンロード',
|
||||
'newVersionLater': '後で',
|
||||
};
|
||||
static const ru_RU = {
|
||||
'label': 'Русский(Россия)',
|
||||
'error': 'Ошибка',
|
||||
@@ -274,6 +340,7 @@ class Locales {
|
||||
'donate': '打赏',
|
||||
'exit': '退出',
|
||||
'create': '创建任务',
|
||||
'advancedOptions': '高级选项',
|
||||
'downloadLink': '下载链接',
|
||||
'downloadLinkValid': '请输入下载链接',
|
||||
'downloadLinkHit': '请输入下载链接,支持 HTTP/HTTPS/MAGNET@append',
|
||||
@@ -282,6 +349,7 @@ class Locales {
|
||||
'noFileSelected': '请至少选择一个文件下载',
|
||||
'noStoragePermission': '需要开启存储权限',
|
||||
'selectFile': '选择文件',
|
||||
'rename': '重命名',
|
||||
'basic': '基础',
|
||||
'advanced': '高级',
|
||||
'general': '通用',
|
||||
|
||||
@@ -8,7 +8,7 @@ import 'app/modules/app/controllers/app_controller.dart';
|
||||
import 'app/modules/app/views/app_view.dart';
|
||||
import 'core/libgopeed_boot.dart';
|
||||
import 'generated/locales.g.dart';
|
||||
import 'util/localeManager.dart';
|
||||
import 'util/locale_manager.dart';
|
||||
import 'util/log_util.dart';
|
||||
import 'util/mac_secure_util.dart';
|
||||
import 'util/package_info.dart';
|
||||
@@ -26,7 +26,7 @@ Future<void> init() async {
|
||||
if (Util.isDesktop()) {
|
||||
await windowManager.ensureInitialized();
|
||||
const windowOptions = WindowOptions(
|
||||
size: Size(1024, 768),
|
||||
size: Size(800, 600),
|
||||
center: true,
|
||||
skipTaskbar: false,
|
||||
);
|
||||
|
||||
@@ -2,27 +2,37 @@ 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.isFile(filePath)) {
|
||||
_openFile(filePath);
|
||||
} else if (await FileSystemEntity.isDirectory(filePath)) {
|
||||
_openDirectory(filePath);
|
||||
}
|
||||
if (await FileSystemEntity.isDirectory(filePath)) {
|
||||
if (Platform.isWindows) {
|
||||
Process.run('explorer.exe', [filePath]);
|
||||
} else if (Platform.isMacOS || Platform.isLinux) {
|
||||
Process.run('xdg-open', [filePath]);
|
||||
}
|
||||
|
||||
static Future<void> _openDirectory(String directoryPath) async {
|
||||
if (Platform.isWindows) {
|
||||
Process.run('explorer.exe', [directoryPath]);
|
||||
} else if (Platform.isMacOS) {
|
||||
Process.run('open', [directoryPath]);
|
||||
} else if (Platform.isLinux) {
|
||||
if (await _isUbuntuOrDebian()) {
|
||||
Process.run('xdg-open', [directoryPath]);
|
||||
} else if (await _isCentOS()) {
|
||||
Process.run('nautilus', [directoryPath]);
|
||||
}
|
||||
} 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<void> _openFile(String filePath) async {
|
||||
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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
findIcon(String filename) {
|
||||
String res = 'doc';
|
||||
String res = 'file';
|
||||
String ext = filename.substring(filename.lastIndexOf('.') + 1);
|
||||
for (var iconMap in iconMaps) {
|
||||
if (iconMap['extensions'].contains(ext)) {
|
||||
|
||||
@@ -28,6 +28,7 @@ class FaIcons {
|
||||
|
||||
static const _kFontFam = 'FontAwesome';
|
||||
static const Map<String, IconData> allIcons = {
|
||||
'file': file,
|
||||
'doc': doc,
|
||||
'file_image': file_image,
|
||||
'file_video': file_video,
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
/// A [TextInputFormatter] that restricts input to a numerical range between [min] and [max].
|
||||
class NumericalRangeFormatter extends TextInputFormatter {
|
||||
final int min;
|
||||
final int max;
|
||||
|
||||
NumericalRangeFormatter({required this.min, required this.max});
|
||||
|
||||
@override
|
||||
TextEditingValue formatEditUpdate(
|
||||
TextEditingValue oldValue,
|
||||
TextEditingValue newValue,
|
||||
) {
|
||||
if (newValue.text.isEmpty) {
|
||||
return newValue;
|
||||
}
|
||||
var intVal = int.tryParse(newValue.text);
|
||||
if (intVal == null) {
|
||||
return oldValue;
|
||||
}
|
||||
if (intVal < min) {
|
||||
return newValue.copyWith(text: min.toString());
|
||||
} else if (intVal > max) {
|
||||
return oldValue.copyWith(text: max.toString());
|
||||
} else {
|
||||
return newValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
File renamed without changes.
@@ -1,19 +1,23 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:logger/logger.dart';
|
||||
|
||||
import 'util.dart';
|
||||
|
||||
final logger = Logger(
|
||||
printer: PrettyPrinter(),
|
||||
filter: ProductionFilter(),
|
||||
printer: LogfmtPrinter(),
|
||||
output: buildOutput(),
|
||||
);
|
||||
|
||||
buildOutput() {
|
||||
// if is debug mode, output to console
|
||||
// if is debug mode, don't log to file
|
||||
if (!kDebugMode && Util.isDesktop()) {
|
||||
return FileOutput(file: File('log.txt'));
|
||||
const logDirPath = 'logs';
|
||||
var logDir = Directory(logDirPath);
|
||||
if (!logDir.existsSync()) {
|
||||
logDir.createSync();
|
||||
}
|
||||
return FileOutput(file: File('$logDirPath/client.log'));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -12,7 +12,7 @@ void showErrorMessage(msg) {
|
||||
Get.snackbar(title, ((msg as dynamic).message as Result).msg!);
|
||||
return;
|
||||
}
|
||||
Get.snackbar(title, msg);
|
||||
Get.snackbar(title, msg.toString());
|
||||
}
|
||||
|
||||
void showMessage(title, msg) {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
@@ -11,7 +13,9 @@ class Util {
|
||||
}
|
||||
|
||||
static String fmtByte(int byte) {
|
||||
if (byte < 1024) {
|
||||
if (byte < 0) {
|
||||
return "0 B";
|
||||
} else if (byte < 1024) {
|
||||
return "$byte B";
|
||||
} else if (byte < 1024 * 1024) {
|
||||
return "${(byte / 1024).toStringAsFixed(2)} KB";
|
||||
@@ -59,4 +63,33 @@ class Util {
|
||||
static isWeb() {
|
||||
return kIsWeb;
|
||||
}
|
||||
|
||||
static List<String> textToLines(String text) {
|
||||
if (text.isEmpty) {
|
||||
return [];
|
||||
}
|
||||
const ls = LineSplitter();
|
||||
return ls.convert(text);
|
||||
}
|
||||
|
||||
// if one future complete, return the result, only all future error, return the last error
|
||||
static anyOk<T>(Iterable<Future<T>> futures) {
|
||||
final completer = Completer<T>();
|
||||
Object lastError;
|
||||
var count = futures.length;
|
||||
for (var future in futures) {
|
||||
future.then((value) {
|
||||
if (!completer.isCompleted) {
|
||||
completer.complete(value);
|
||||
}
|
||||
}).catchError((e) {
|
||||
lastError = e;
|
||||
count--;
|
||||
if (count == 0) {
|
||||
completer.completeError(lastError);
|
||||
}
|
||||
});
|
||||
}
|
||||
return completer.future;
|
||||
}
|
||||
}
|
||||
+231
-216
File diff suppressed because it is too large.
Load diff
+10
-11
@@ -15,10 +15,10 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
|
||||
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
||||
# Read more about iOS versioning at
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
version: 1.3.3+1
|
||||
version: 1.3.10+1
|
||||
|
||||
environment:
|
||||
sdk: ">=2.17.6 <3.0.0"
|
||||
sdk: '>=3.0.0 <4.0.0'
|
||||
|
||||
# Dependencies specify other packages that your package needs in order to work.
|
||||
# To automatically upgrade your package dependencies to the latest versions
|
||||
@@ -40,15 +40,15 @@ dependencies:
|
||||
styled_widget: ^0.4.0+3
|
||||
context_menus: ^1.0.2
|
||||
json_annotation: ^4.6.0
|
||||
dio: ^5.1.1
|
||||
dio: ^5.2.1
|
||||
path_provider: ^2.0.11
|
||||
file_picker: ^5.2.5
|
||||
file_picker: 5.3.1
|
||||
macos_secure_bookmarks: ^0.2.1
|
||||
rounded_loading_button: ^2.1.0
|
||||
url_launcher: ^6.1.6
|
||||
shared_preferences: ^2.0.15
|
||||
shared_preferences: ^2.2.0
|
||||
logger: ^1.3.0
|
||||
desktop_drop: ^0.3.3
|
||||
desktop_drop: ^0.4.3
|
||||
package_info_plus: ^4.0.0
|
||||
fluent_ui: ^4.4.0
|
||||
path: any
|
||||
@@ -57,6 +57,7 @@ dependencies:
|
||||
uri_to_file: ^0.2.0
|
||||
window_manager: ^0.3.4
|
||||
tray_manager: ^0.2.0
|
||||
autoscale_tabbarview: ^1.0.2
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
@@ -67,14 +68,11 @@ dev_dependencies:
|
||||
# package. See that file for information about deactivating specific lint
|
||||
# rules and activating additional ones.
|
||||
flutter_lints: ^2.0.0
|
||||
ffigen: ^6.1.2
|
||||
ffigen: ^8.0.2
|
||||
# build json model: flutter pub run build_runner build --delete-conflicting-outputs
|
||||
build_runner: ^2.2.1
|
||||
json_serializable: ^6.3.2
|
||||
flutter_launcher_icons:
|
||||
git:
|
||||
url: https://github.com/fluttercommunity/flutter_launcher_icons.git
|
||||
ref: v0.11.0
|
||||
flutter_launcher_icons: ^0.13.1
|
||||
|
||||
# For information on the generic Dart part of this file, see the
|
||||
# following page: https://dart.dev/tools/pub/pubspec
|
||||
@@ -128,6 +126,7 @@ flutter_icons:
|
||||
generate: true
|
||||
image_path: "assets/icon/icon_macos_1024.png"
|
||||
|
||||
# flutter pub run ffigen
|
||||
ffigen:
|
||||
name: LibgopeedBind
|
||||
description: Bindings to gopeed library.
|
||||
|
||||
Reference in new issue
Block a user