mirror of
https://github.com/GopeedLab/gopeed.git
synced 2026-11-13 03:43:41 +00:00
Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5a628bb44a | ||
|
|
9955e482a4 | ||
|
|
27dc7aea52 | ||
|
|
14df9490cf | ||
|
|
431fdd0d54 | ||
|
|
25bf3ca65b |
No files matched your search
@@ -508,7 +508,7 @@ jobs:
|
||||
overwrite: true
|
||||
build-ios:
|
||||
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'ios' }}
|
||||
runs-on: macos-14
|
||||
runs-on: macos-latest
|
||||
needs: [get-release]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
@@ -53,13 +53,7 @@ jobs:
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
- name: Run Unit tests.
|
||||
run: |
|
||||
# Get packages for testing and coverage (including cmd/web for flags_test.go)
|
||||
PACKAGES=$(go list ./... | grep -v /bind/ | grep -v -E '/cmd/(?!web)')
|
||||
go test -v -coverpkg=$(echo "$PACKAGES" | paste -sd "," -) -covermode=count -coverprofile=coverage.txt $PACKAGES
|
||||
# Filter out main.go from coverage report to avoid lowering coverage percentage
|
||||
grep -v "main.go" coverage.txt > coverage_filtered.txt || true
|
||||
mv coverage_filtered.txt coverage.txt
|
||||
run: go test -v -coverpkg=$(go list ./... | grep -v /cmd/ | grep -v /bind/ | paste -sd "," -) -covermode=count -coverprofile=coverage.txt $(go list ./... | grep -v /cmd/ | grep -v /bind/)
|
||||
- uses: codecov/codecov-action@v4
|
||||
with:
|
||||
files: ./coverage.txt
|
||||
@@ -69,8 +63,8 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [windows-latest, macos-latest, ubuntu-22.04, ubuntu-22.04-arm]
|
||||
needs: [test]
|
||||
os: [ windows-latest, macos-latest, ubuntu-22.04, ubuntu-22.04-arm ]
|
||||
needs: [ test ]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-go@v4
|
||||
@@ -102,8 +96,8 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [macos-14, ubuntu-latest]
|
||||
needs: [test]
|
||||
os: [ macos-latest, ubuntu-latest ]
|
||||
needs: [ test ]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-go@v4
|
||||
@@ -135,7 +129,7 @@ jobs:
|
||||
build-web:
|
||||
name: Build web check
|
||||
runs-on: ubuntu-latest
|
||||
needs: [test]
|
||||
needs: [ test ]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-go@v4
|
||||
@@ -155,7 +149,7 @@ jobs:
|
||||
build-docker:
|
||||
name: Build docker check
|
||||
runs-on: ubuntu-latest
|
||||
needs: [test]
|
||||
needs: [ test ]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-go@v4
|
||||
|
||||
+51
-170
@@ -3,23 +3,19 @@ package main
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/GopeedLab/gopeed/pkg/base"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/GopeedLab/gopeed/pkg/base"
|
||||
)
|
||||
|
||||
type args struct {
|
||||
Address *string `json:"address"`
|
||||
Port *int `json:"port"`
|
||||
Username *string `json:"username"`
|
||||
Password *string `json:"password"`
|
||||
ApiToken *string `json:"apiToken"`
|
||||
StorageDir *string `json:"storageDir"`
|
||||
WhiteDownloadDirs []string `json:"whiteDownloadDirs"`
|
||||
Address *string `json:"address"`
|
||||
Port *int `json:"port"`
|
||||
Username *string `json:"username"`
|
||||
Password *string `json:"password"`
|
||||
ApiToken *string `json:"apiToken"`
|
||||
StorageDir *string `json:"storageDir"`
|
||||
// DownloadConfig when the first time to start the server, it will be configured as initial value
|
||||
DownloadConfig *base.DownloaderStoreConfig `json:"downloadConfig"`
|
||||
|
||||
@@ -27,177 +23,62 @@ type args struct {
|
||||
}
|
||||
|
||||
func parse() *args {
|
||||
cfg := &args{}
|
||||
|
||||
cliConfig := loadCliArgs()
|
||||
loadConfigFile(cfg, *cliConfig.configPath)
|
||||
loadEnvVars(cfg)
|
||||
// override with non-default command line arguments
|
||||
overrideWithCliArgs(cfg, cliConfig)
|
||||
// set default values for any unset fields
|
||||
setDefaults(cfg)
|
||||
return cfg
|
||||
}
|
||||
|
||||
// loadCliArgs parses command line arguments and returns initial config
|
||||
func loadCliArgs() *args {
|
||||
cfg := &args{}
|
||||
cfg.Address = flag.String("A", "", "Bind Address")
|
||||
cfg.Port = flag.Int("P", 0, "Bind Port")
|
||||
cfg.Username = flag.String("u", "", "Web Authentication Username")
|
||||
cfg.Password = flag.String("p", "", "Web Authentication Password, if no password is set, web authentication will not be enabled")
|
||||
cfg.ApiToken = flag.String("T", "", "API token, it must be configured when using HTTP API in the case of enabling web authentication")
|
||||
cfg.StorageDir = flag.String("d", "", "Storage directory")
|
||||
whiteDownloadDirs := flag.String("w", "", "White download directories, comma-separated")
|
||||
cfg.configPath = flag.String("c", "./config.json", "Config file path")
|
||||
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", "Web Authentication Username")
|
||||
cliArgs.Password = flag.String("p", "", "Web Authentication Password, if no password is set, web authentication will not be enabled")
|
||||
cliArgs.ApiToken = flag.String("T", "", "API token, it must be configured when using HTTP API in the case of enabling web authentication")
|
||||
cliArgs.StorageDir = flag.String("d", "", "Storage directory")
|
||||
cliArgs.configPath = flag.String("c", "./config.json", "Config file path")
|
||||
flag.Parse()
|
||||
|
||||
// Parse white download directories from comma-separated string
|
||||
if whiteDownloadDirs != nil && *whiteDownloadDirs != "" {
|
||||
dirs := strings.Split(*whiteDownloadDirs, ",")
|
||||
for i := range dirs {
|
||||
dirs[i] = strings.TrimSpace(dirs[i])
|
||||
}
|
||||
cfg.WhiteDownloadDirs = dirs
|
||||
// args priority: config file > cli args
|
||||
cfgArgs := loadConfig(*cliArgs.configPath)
|
||||
if cfgArgs.Address == nil {
|
||||
cfgArgs.Address = cliArgs.Address
|
||||
}
|
||||
|
||||
return cfg
|
||||
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
|
||||
}
|
||||
if cfgArgs.StorageDir == nil {
|
||||
cfgArgs.StorageDir = cliArgs.StorageDir
|
||||
}
|
||||
return cfgArgs
|
||||
}
|
||||
|
||||
// overrideWithCliArgs overrides config with non-empty command line arguments
|
||||
func overrideWithCliArgs(cfg *args, cliConfig *args) {
|
||||
// Only override if the cli value is not empty/zero
|
||||
if cliConfig.Address != nil && *cliConfig.Address != "" {
|
||||
cfg.Address = cliConfig.Address
|
||||
}
|
||||
func loadConfig(path string) *args {
|
||||
var args args
|
||||
|
||||
if cliConfig.Port != nil && *cliConfig.Port != 0 {
|
||||
cfg.Port = cliConfig.Port
|
||||
}
|
||||
|
||||
if cliConfig.Username != nil && *cliConfig.Username != "" {
|
||||
cfg.Username = cliConfig.Username
|
||||
}
|
||||
|
||||
if cliConfig.Password != nil && *cliConfig.Password != "" {
|
||||
cfg.Password = cliConfig.Password
|
||||
}
|
||||
|
||||
if cliConfig.ApiToken != nil && *cliConfig.ApiToken != "" {
|
||||
cfg.ApiToken = cliConfig.ApiToken
|
||||
}
|
||||
|
||||
if cliConfig.StorageDir != nil && *cliConfig.StorageDir != "" {
|
||||
cfg.StorageDir = cliConfig.StorageDir
|
||||
}
|
||||
|
||||
if cliConfig.WhiteDownloadDirs != nil {
|
||||
cfg.WhiteDownloadDirs = cliConfig.WhiteDownloadDirs
|
||||
}
|
||||
}
|
||||
|
||||
// setDefaults sets default values for any unset configuration fields
|
||||
func setDefaults(cfg *args) {
|
||||
if cfg.Address == nil {
|
||||
address := "0.0.0.0"
|
||||
cfg.Address = &address
|
||||
}
|
||||
|
||||
if cfg.Port == nil {
|
||||
port := 9999
|
||||
cfg.Port = &port
|
||||
}
|
||||
|
||||
if cfg.Username == nil {
|
||||
username := "gopeed"
|
||||
cfg.Username = &username
|
||||
}
|
||||
}
|
||||
|
||||
// loadConfigFile loads configuration from file
|
||||
func loadConfigFile(cfg *args, configPath string) {
|
||||
if !filepath.IsAbs(configPath) {
|
||||
if !filepath.IsAbs(path) {
|
||||
dir, err := os.Getwd()
|
||||
if err != nil {
|
||||
return
|
||||
fmt.Println("config dir get failed, reason:" + err.Error())
|
||||
return &args
|
||||
}
|
||||
configPath = filepath.Join(dir, configPath)
|
||||
path = filepath.Join(dir, path)
|
||||
}
|
||||
|
||||
file, err := os.ReadFile(configPath)
|
||||
file, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return
|
||||
return &args
|
||||
}
|
||||
return
|
||||
fmt.Println("config file read failed, reason:" + err.Error())
|
||||
return &args
|
||||
}
|
||||
|
||||
if err = json.Unmarshal(file, cfg); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// loadEnvVars loads configuration from environment variables with prefix GOPEED_
|
||||
func loadEnvVars(cfg *args) {
|
||||
v := reflect.ValueOf(cfg).Elem()
|
||||
t := reflect.TypeOf(cfg).Elem()
|
||||
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
field := v.Field(i)
|
||||
fieldType := t.Field(i)
|
||||
|
||||
// Get json tag as environment variable suffix
|
||||
jsonTag := fieldType.Tag.Get("json")
|
||||
if jsonTag == "" || jsonTag == "-" {
|
||||
continue
|
||||
}
|
||||
|
||||
// Remove options like omitempty
|
||||
if commaIdx := strings.Index(jsonTag, ","); commaIdx != -1 {
|
||||
jsonTag = jsonTag[:commaIdx]
|
||||
}
|
||||
|
||||
// Convert to uppercase and add GOPEED_ prefix
|
||||
envKey := "GOPEED_" + strings.ToUpper(jsonTag)
|
||||
envValue := os.Getenv(envKey)
|
||||
|
||||
if envValue == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// Set value based on field type
|
||||
if field.Kind() == reflect.Ptr {
|
||||
if field.IsNil() {
|
||||
// Create new pointer instance
|
||||
newVal := reflect.New(field.Type().Elem())
|
||||
field.Set(newVal)
|
||||
}
|
||||
|
||||
switch field.Type().Elem().Kind() {
|
||||
case reflect.String:
|
||||
field.Elem().SetString(envValue)
|
||||
case reflect.Int:
|
||||
if intVal, err := strconv.Atoi(envValue); err == nil {
|
||||
field.Elem().SetInt(int64(intVal))
|
||||
}
|
||||
default:
|
||||
// For complex types like DownloadConfig, try JSON unmarshaling
|
||||
if field.Type().Elem() == reflect.TypeOf(base.DownloaderStoreConfig{}) {
|
||||
var config base.DownloaderStoreConfig
|
||||
if err := json.Unmarshal([]byte(envValue), &config); err == nil {
|
||||
field.Set(reflect.ValueOf(&config))
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if field.Kind() == reflect.Slice {
|
||||
// Handle non-pointer slice types (like []string for WhiteDownloadDirs)
|
||||
if field.Type().Elem().Kind() == reflect.String {
|
||||
dirs := strings.Split(envValue, ",")
|
||||
for i := range dirs {
|
||||
dirs[i] = strings.TrimSpace(dirs[i])
|
||||
}
|
||||
field.Set(reflect.ValueOf(dirs))
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
+15
-17
@@ -6,12 +6,11 @@ package main
|
||||
import (
|
||||
"embed"
|
||||
"fmt"
|
||||
"github.com/GopeedLab/gopeed/cmd"
|
||||
"github.com/GopeedLab/gopeed/pkg/rest/model"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/GopeedLab/gopeed/cmd"
|
||||
"github.com/GopeedLab/gopeed/pkg/rest/model"
|
||||
)
|
||||
|
||||
//go:embed dist/*
|
||||
@@ -32,29 +31,28 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
var storageDir string
|
||||
var dir string
|
||||
if args.StorageDir != nil && *args.StorageDir != "" {
|
||||
storageDir = *args.StorageDir
|
||||
dir = *args.StorageDir
|
||||
} else {
|
||||
exe, err := os.Executable()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
storageDir = filepath.Join(filepath.Dir(exe), "storage")
|
||||
dir = filepath.Dir(exe)
|
||||
}
|
||||
|
||||
cfg := &model.StartConfig{
|
||||
Network: "tcp",
|
||||
Address: fmt.Sprintf("%s:%d", *args.Address, *args.Port),
|
||||
Storage: model.StorageBolt,
|
||||
StorageDir: storageDir,
|
||||
WhiteDownloadDirs: args.WhiteDownloadDirs,
|
||||
ApiToken: *args.ApiToken,
|
||||
DownloadConfig: args.DownloadConfig,
|
||||
ProductionMode: true,
|
||||
WebEnable: true,
|
||||
WebFS: sub,
|
||||
WebAuth: webAuth,
|
||||
Network: "tcp",
|
||||
Address: fmt.Sprintf("%s:%d", *args.Address, *args.Port),
|
||||
Storage: model.StorageBolt,
|
||||
StorageDir: filepath.Join(dir, "storage"),
|
||||
ApiToken: *args.ApiToken,
|
||||
DownloadConfig: args.DownloadConfig,
|
||||
ProductionMode: true,
|
||||
WebEnable: true,
|
||||
WebFS: sub,
|
||||
WebAuth: webAuth,
|
||||
}
|
||||
cmd.Start(cfg)
|
||||
}
|
||||
|
||||
@@ -941,9 +941,9 @@ func (d *Downloader) doCreate(f fetcher.Fetcher, opts *base.Options) (taskId str
|
||||
}
|
||||
|
||||
// if enable white download directory, check if the download directory is in the white list
|
||||
if len(d.cfg.WhiteDownloadDirs) > 0 {
|
||||
if len(d.cfg.DownloadDirWhiteList) > 0 {
|
||||
inWhiteList := false
|
||||
for _, dir := range d.cfg.WhiteDownloadDirs {
|
||||
for _, dir := range d.cfg.DownloadDirWhiteList {
|
||||
if match, err := filepath.Match(dir, opts.Path); match && err == nil {
|
||||
inWhiteList = true
|
||||
break
|
||||
|
||||
@@ -91,7 +91,7 @@ func TestDownloader_CreateNotInWhite(t *testing.T) {
|
||||
defer listener.Close()
|
||||
|
||||
downloader := NewDownloader(&DownloaderConfig{
|
||||
WhiteDownloadDirs: []string{"./downloads"},
|
||||
DownloadDirWhiteList: []string{"./downloads"},
|
||||
})
|
||||
if err := downloader.Setup(); err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
@@ -137,10 +137,10 @@ type DownloaderConfig struct {
|
||||
Controller *controller.Controller
|
||||
FetchManagers []fetcher.FetcherManager
|
||||
|
||||
RefreshInterval int // RefreshInterval time duration to refresh task progress(ms)
|
||||
Storage Storage
|
||||
StorageDir string
|
||||
WhiteDownloadDirs []string
|
||||
RefreshInterval int `json:"refreshInterval"` // RefreshInterval time duration to refresh task progress(ms)
|
||||
Storage Storage
|
||||
StorageDir string
|
||||
DownloadDirWhiteList []string `json:"downloadDirWhiteList"`
|
||||
|
||||
ProductionMode bool
|
||||
|
||||
|
||||
+2
-3
@@ -83,9 +83,8 @@ func BuildServer(startCfg *model.StartConfig) (*http.Server, net.Listener, error
|
||||
startCfg.Init()
|
||||
|
||||
downloadCfg := &download.DownloaderConfig{
|
||||
ProductionMode: startCfg.ProductionMode,
|
||||
RefreshInterval: startCfg.RefreshInterval,
|
||||
WhiteDownloadDirs: startCfg.WhiteDownloadDirs,
|
||||
ProductionMode: startCfg.ProductionMode,
|
||||
RefreshInterval: startCfg.RefreshInterval,
|
||||
}
|
||||
if startCfg.Storage == model.StorageBolt {
|
||||
downloadCfg.Storage = download.NewBoltStorage(startCfg.StorageDir)
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../controllers/app_controller.dart';
|
||||
|
||||
class AppBinding extends Bindings {
|
||||
@override
|
||||
void dependencies() {
|
||||
Get.lazyPut<AppController>(
|
||||
() => AppController(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../../../../util/network_monitor.dart';
|
||||
import '../controllers/root_controller.dart';
|
||||
|
||||
class RootBinding extends Bindings {
|
||||
@override
|
||||
void dependencies() {
|
||||
Get.lazyPut<RootController>(() => RootController(), fenix: true);
|
||||
Get.put<NetworkMonitor>(NetworkMonitor(), permanent: true);
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,19 @@
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../../../../database/database.dart';
|
||||
import '../../../../util/updater.dart';
|
||||
|
||||
class SettingController extends GetxController {
|
||||
final tapStatues = <String, bool>{}.obs;
|
||||
final latestVersion = Rxn<VersionInfo>();
|
||||
final networkAutoControl = false.obs;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
fetchLatestVersion();
|
||||
// Initialize network auto control setting
|
||||
networkAutoControl.value = Database.instance.getNetworkAutoControl();
|
||||
}
|
||||
|
||||
// set all tap status to false
|
||||
@@ -27,4 +31,10 @@ class SettingController extends GetxController {
|
||||
void fetchLatestVersion() async {
|
||||
latestVersion.value = await checkUpdate();
|
||||
}
|
||||
|
||||
// update network auto control setting
|
||||
void updateNetworkAutoControl(bool value) {
|
||||
networkAutoControl.value = value;
|
||||
Database.instance.saveNetworkAutoControl(value);
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import 'package:launch_at_startup/launch_at_startup.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
import '../../../../api/model/downloader_config.dart';
|
||||
import '../../../../database/database.dart';
|
||||
import '../../../../i18n/message.dart';
|
||||
import '../../../../util/input_formatter.dart';
|
||||
import '../../../../util/locale_manager.dart';
|
||||
@@ -543,6 +544,24 @@ class SettingView extends GetView<SettingController> {
|
||||
);
|
||||
}
|
||||
|
||||
// Network auto control setting (mobile only)
|
||||
buildNetworkAutoControl() {
|
||||
if (!Platform.isAndroid && !Platform.isIOS) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Obx(() => ListTile(
|
||||
title: Text('networkAutoControl'.tr),
|
||||
subtitle: Text('networkAutoControlTip'.tr),
|
||||
trailing: Switch(
|
||||
value: controller.networkAutoControl.value,
|
||||
onChanged: (bool value) {
|
||||
controller.updateNetworkAutoControl(value);
|
||||
},
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
// advanced config proxy items start
|
||||
final proxy = downloaderCfg.value.proxy;
|
||||
final buildProxy = _buildConfigItem(
|
||||
@@ -985,7 +1004,10 @@ class SettingView extends GetView<SettingController> {
|
||||
Text('network'.tr),
|
||||
Card(
|
||||
child: Column(
|
||||
children: _addDivider([buildProxy()]),
|
||||
children: _addDivider([
|
||||
buildNetworkAutoControl(),
|
||||
buildProxy(),
|
||||
].where((e) => e != null).cast<Widget>().toList()),
|
||||
)),
|
||||
const Text('API'),
|
||||
Card(
|
||||
|
||||
@@ -10,6 +10,7 @@ const String _windowState = 'windowState';
|
||||
const String _bookmark = 'bookmark';
|
||||
const String _createHistory = 'createHistory';
|
||||
const String _webToken = 'webToken';
|
||||
const String _networkAutoControl = 'networkAutoControl';
|
||||
|
||||
class Database {
|
||||
static final Database _instance = Database._internal();
|
||||
@@ -109,4 +110,12 @@ class Database {
|
||||
void clearCreateHistory() {
|
||||
clear(_createHistory);
|
||||
}
|
||||
|
||||
void saveNetworkAutoControl(bool enabled) {
|
||||
save<bool>(_networkAutoControl, enabled);
|
||||
}
|
||||
|
||||
bool getNetworkAutoControl() {
|
||||
return get<bool>(_networkAutoControl, (json) => json as bool) ?? false;
|
||||
}
|
||||
}
|
||||
@@ -1,129 +0,0 @@
|
||||
const deDE = {
|
||||
'de_DE': {
|
||||
'label': 'Deutsch',
|
||||
'error': 'Fehler',
|
||||
'tip': 'Tipp',
|
||||
'confirm': 'Bestätigen',
|
||||
'cancel': 'Abbrechen',
|
||||
'on': 'An',
|
||||
'off': 'Aus',
|
||||
'selectAll': 'Alle Auswählen',
|
||||
'select': 'Auswählen',
|
||||
'task': 'Aufgaben',
|
||||
'downloading': 'Wird Heruntergeladen',
|
||||
'downloaded': 'Heruntergeladen',
|
||||
'setting': 'Einstellungen',
|
||||
'donate': 'Spenden',
|
||||
'exit': 'Beenden',
|
||||
'create': 'Aufgabe Erstellen',
|
||||
'directDownload': 'Direkter Download',
|
||||
'advancedOptions': 'Erweiterte Einstellungen',
|
||||
'followSettings': 'Einstellungen Folgen',
|
||||
'downloadLink': 'Download Link',
|
||||
'downloadLinkValid': 'Bitte geben Sie den Download-Link ein',
|
||||
'downloadLinkHit': 'Bitte geben Sie den Download-Link ein, einen pro Zeile@append',
|
||||
'downloadLinkHitDesktop': ', oder ziehen Sie die Torrent-Datei direkt hierher',
|
||||
'download': 'Download',
|
||||
'noFileSelected': 'Bitte wählen Sie mindestens eine Datei aus, um fortzufahren.',
|
||||
'noStoragePermission': 'Speicherberechtigung erforderlich',
|
||||
'selectFile': 'Datei auswählen',
|
||||
'rename': 'Umbenennen',
|
||||
'basic': 'Basic',
|
||||
'advanced': 'Fortschrittlich',
|
||||
'general': 'Allgemein',
|
||||
'downloadDir': 'Download-Verzeichnis',
|
||||
'downloadDirValid': 'Bitte wählen Sie das Download-Verzeichnis',
|
||||
'connections': 'Anschlüsse',
|
||||
'useServerCtime': 'Serverzeit für die Dateierstellung verwenden',
|
||||
'maxRunning': 'Maximale Anzahl ausgeführter Aufgaben',
|
||||
'defaultDirectDownload': 'Direkten Download standardmäßig aktivieren',
|
||||
'items': '@count Artikel',
|
||||
'subscribeTracker': 'Abonnement-Tracker',
|
||||
'subscribeFail':
|
||||
'Anmeldung fehlgeschlagen. Bitte überprüfen Sie das Netzwerk oder versuchen Sie es später erneut.',
|
||||
'update': 'Aktualisieren',
|
||||
'updateDaily': 'Tägliche Aktualisierung',
|
||||
'lastUpdate': 'Letzte Aktualisierung: @time',
|
||||
'addTracker': 'Tracker hinzufügen',
|
||||
'addTrackerHit': 'Bitte geben Sie die URL des Tracker-Servers ein, eine pro Zeile',
|
||||
'ui': 'Benutzeroberfläche',
|
||||
'theme': 'Thema',
|
||||
'themeSystem': 'System',
|
||||
'themeLight': 'Lichtmodus',
|
||||
'themeDark': 'Dunkler Modus',
|
||||
'locale': 'Sprache',
|
||||
'about': 'Über',
|
||||
'homepage': 'Homepage',
|
||||
'version': 'Version',
|
||||
'protocol': 'Protokoll',
|
||||
'port': 'Port',
|
||||
'apiToken': 'API Token',
|
||||
'notSet': 'NS',
|
||||
'set': 'SET',
|
||||
'portInUse': 'Port [@port] wird verwendet, bitte ändern Sie den Port',
|
||||
'effectAfterRestart': 'Wirkung nach Neustart',
|
||||
'developer': 'Entwickler-in',
|
||||
'logDirectory': 'Protokollverzeichnis',
|
||||
'show': 'Zeigen',
|
||||
'continue': 'Weitermachen',
|
||||
'pause': 'Pause',
|
||||
'startAll': 'Alles starten',
|
||||
'pauseAll': 'Alle pausieren',
|
||||
'deleteTask': '@count Aufgaben löschen',
|
||||
'deleteTaskTip': 'Heruntergeladene Dateien behalten',
|
||||
'delete': 'Löschen',
|
||||
'newVersionTitle': 'Entdecken Sie die neue Version @version',
|
||||
'newVersionUpdate': 'Jetzt aktualisieren',
|
||||
'newVersionLater': 'Später',
|
||||
'extensions': 'Erweiterungen',
|
||||
'extensionInstallUrl': 'Installations-URL',
|
||||
'extensionInstallSuccess': 'Erfolgreich installiert',
|
||||
'extensionUpdateSuccess': 'Erfolgreich aktualisiert',
|
||||
'extensionDelete': 'Erweiterung löschen',
|
||||
'extensionAlreadyLatest': 'Es ist bereits die neueste Version',
|
||||
'extensionFind': 'Erweiterungen suchen',
|
||||
'extensionDevelop': 'Erweiterungen entwickeln',
|
||||
'history': 'Verlauf',
|
||||
'clearHistory': 'Verlauf löschen',
|
||||
'noHistoryFound': 'Kein Verlauf gefunden',
|
||||
'serviceTitle': 'Download-Service',
|
||||
'serviceText': 'Läuft',
|
||||
'network': 'Netzwerk',
|
||||
'proxy': 'Proxy',
|
||||
'noProxy': 'Kein Proxy',
|
||||
'systemProxy': 'System-Proxy',
|
||||
'customProxy': 'Benutzerdefinierter Proxy',
|
||||
'server': 'Server',
|
||||
'username': 'Benutzername',
|
||||
'password': 'Passwort',
|
||||
'thanks': 'Danke',
|
||||
'thanksDesc':
|
||||
'Vielen Dank an alle Mitwirkenden, die beim Aufbau und der Entwicklung der Gopeed-Community geholfen haben!',
|
||||
'browserExtension': 'Browsererweiterung',
|
||||
'launchAtStartup': 'Beim Start starten',
|
||||
'seedConfig': 'Seed-Konfiguration',
|
||||
'seedKeep': 'Weiter-seeden bis manuell gestoppt',
|
||||
'seedRatio': 'Seed Verhältniss',
|
||||
'seedTime': 'Seed-Zeit (Minuten)',
|
||||
'setAsDefaultBtClient': 'Als Standard-BT-Client festlegen',
|
||||
'taskDetail': 'Aufgabendetails',
|
||||
'taskName': 'Aufgabenname',
|
||||
'taskUrl': 'Aufgaben-URL',
|
||||
'downloadPath': 'Download-Pfad',
|
||||
'skipVerifyCert': 'Zertifikatsüberprüfung überspringen',
|
||||
'name': 'Name',
|
||||
'size': 'Größe',
|
||||
'unknown': 'Unbekannt',
|
||||
'fileSelectedCount': 'Dateien: ',
|
||||
'fileSelectedSize': 'Größe: ',
|
||||
'httpHeaderName': 'Header-Name',
|
||||
'httpHeaderValue': 'Header-Wert',
|
||||
'login': 'Login',
|
||||
'username_required': 'Bitte geben Sie Ihren Benutzernamen ein',
|
||||
'password_required': 'Bitte geben Sie Ihr Passwort ein',
|
||||
'login_success': 'Anmeldung erfolgreich',
|
||||
'login_failed': 'Anmeldung fehlgeschlagen, bitte überprüfen Sie Ihren Benutzernamen und Ihr Passwort',
|
||||
'login_failed_network':
|
||||
'Anmeldung fehlgeschlagen, bitte überprüfen Sie Ihre Netzwerkverbindung',
|
||||
},
|
||||
};
|
||||
@@ -125,5 +125,7 @@ const enUS = {
|
||||
'login_failed': 'Login failed, please check your username and password',
|
||||
'login_failed_network':
|
||||
'Login failed, please check your network connection',
|
||||
'networkAutoControl': 'Network Auto Control',
|
||||
'networkAutoControlTip': 'Automatically pause downloads when switching from WiFi to mobile data, and resume when switching back to WiFi',
|
||||
},
|
||||
};
|
||||
@@ -104,5 +104,7 @@ const esES = {
|
||||
'taskUrl': 'URL de la Tarea',
|
||||
'downloadPath': 'Ruta de Descarga',
|
||||
'skipVerifyCert': 'Omitir Verificación de Certificado',
|
||||
'networkAutoControl': 'Control Automático de Red',
|
||||
'networkAutoControlTip': 'Pausar automáticamente las descargas al cambiar de WiFi a datos móviles y reanudar al volver a WiFi',
|
||||
},
|
||||
};
|
||||
@@ -93,5 +93,7 @@ const frFR = {
|
||||
'browserExtension': 'Extension du navigateur',
|
||||
'launchAtStartup': 'Lancer au démarrage',
|
||||
'skipVerifyCert': 'Bypass Verifikasi Sertifikat',
|
||||
'networkAutoControl': 'Contrôle automatique du réseau',
|
||||
'networkAutoControlTip': 'Mettre automatiquement en pause les téléchargements lors du passage du WiFi aux données mobiles et reprendre lors du retour au WiFi',
|
||||
},
|
||||
};
|
||||
@@ -97,5 +97,7 @@ const itIT = {
|
||||
'browserExtension': 'Estensione del browser',
|
||||
'launchAtStartup': "Lancia all'avvio",
|
||||
'skipVerifyCert': 'Salta la verifica del certificato',
|
||||
'networkAutoControl': 'Controllo automatico della rete',
|
||||
'networkAutoControlTip': 'Mette automaticamente in pausa i download quando si passa dal WiFi ai dati mobili e riprende quando si torna al WiFi',
|
||||
},
|
||||
};
|
||||
@@ -63,5 +63,7 @@ const jaJP = {
|
||||
'thanksDesc': 'Gopeedコミュニティの建設に協力してくださったすべての貢献者の方々に感謝します!',
|
||||
'browserExtension': 'ブラウザ拡張機能',
|
||||
'skipVerifyCert': '証明書の検証をスキップ',
|
||||
'networkAutoControl': 'ネットワーク自動制御',
|
||||
'networkAutoControlTip': 'WiFiからモバイルデータに切り替わった時に自動的にダウンロードを一時停止し、WiFiに戻った時に自動的に再開します',
|
||||
}
|
||||
};
|
||||
@@ -119,5 +119,7 @@ const ruRU = {
|
||||
'fileSelectedSize': 'Размер: ',
|
||||
'httpHeaderName': 'Название заголовка HTTP',
|
||||
'httpHeaderValue': 'Значение заголовка HTTP',
|
||||
'networkAutoControl': 'Автоматическое управление сетью',
|
||||
'networkAutoControlTip': 'Автоматически приостанавливать загрузки при переключении с WiFi на мобильные данные и возобновлять при возврате к WiFi',
|
||||
}
|
||||
};
|
||||
@@ -90,5 +90,7 @@ const viVN = {
|
||||
'Cảm ơn tất cả những người đóng góp đã giúp xây dựng và phát triển cộng đồng Gopeed!',
|
||||
'browserExtension': 'Tiện ích mở rộng trình duyệt',
|
||||
'skipVerifyCert': 'Bỏ qua xác thực chứng chỉ',
|
||||
'networkAutoControl': 'Điều khiển mạng tự động',
|
||||
'networkAutoControlTip': 'Tự động tạm dừng tải xuống khi chuyển từ WiFi sang dữ liệu di động và tiếp tục khi quay lại WiFi',
|
||||
},
|
||||
};
|
||||
@@ -122,5 +122,7 @@ const zhCN = {
|
||||
'login_success': '登录成功',
|
||||
'login_failed': '登录失败,请检查用户名和密码',
|
||||
'login_failed_network': '登录失败,请检查网络连接',
|
||||
'networkAutoControl': '网络状态自动控制',
|
||||
'networkAutoControlTip': '当网络从WiFi切换到移动数据时自动暂停下载,切换回WiFi时自动恢复下载',
|
||||
}
|
||||
};
|
||||
@@ -116,5 +116,7 @@ const zhTW = {
|
||||
'fileSelectedSize': '大小:',
|
||||
'httpHeaderName': '標頭名稱',
|
||||
'httpHeaderValue': '標頭值',
|
||||
'networkAutoControl': '網路狀態自動控制',
|
||||
'networkAutoControlTip': '當網路從WiFi切換到流動數據時自動暫停下載,切換回WiFi時自動恢復下載',
|
||||
}
|
||||
};
|
||||
@@ -1,6 +1,5 @@
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import 'langs/de_de.dart';
|
||||
import 'langs/en_us.dart';
|
||||
import 'langs/fa_ir.dart';
|
||||
import 'langs/fr_fr.dart';
|
||||
@@ -42,6 +41,5 @@ class _Messages extends Translations {
|
||||
...ukUA,
|
||||
...huHU,
|
||||
...ptBR,
|
||||
...deDE
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../api/api.dart';
|
||||
import '../database/database.dart';
|
||||
import '../util/log_util.dart';
|
||||
|
||||
enum NetworkType { wifi, mobile, other, none }
|
||||
|
||||
class NetworkMonitor extends GetxService {
|
||||
static NetworkMonitor get to => Get.find();
|
||||
|
||||
late final Connectivity _connectivity;
|
||||
late StreamSubscription<List<ConnectivityResult>> _connectivitySubscription;
|
||||
|
||||
final _isWiFiConnected = false.obs;
|
||||
bool get isWiFiConnected => _isWiFiConnected.value;
|
||||
|
||||
NetworkType _previousNetworkType = NetworkType.none;
|
||||
bool _isInitialized = false;
|
||||
|
||||
@override
|
||||
Future<void> onInit() async {
|
||||
super.onInit();
|
||||
|
||||
logInfo('NetworkMonitor.onInit() called');
|
||||
|
||||
// Only monitor network on mobile platforms
|
||||
if (!Platform.isAndroid && !Platform.isIOS) {
|
||||
logInfo('NetworkMonitor: Not on mobile platform, skipping initialization');
|
||||
return;
|
||||
}
|
||||
|
||||
_connectivity = Connectivity();
|
||||
|
||||
// Check initial connectivity state
|
||||
final initialResult = await _connectivity.checkConnectivity();
|
||||
logInfo('NetworkMonitor: Initial connectivity result: $initialResult');
|
||||
_updateNetworkStatus(initialResult);
|
||||
_previousNetworkType = _getNetworkType(initialResult);
|
||||
logInfo('NetworkMonitor: Initial network type: $_previousNetworkType');
|
||||
_isInitialized = true;
|
||||
|
||||
// Listen to connectivity changes
|
||||
_connectivitySubscription = _connectivity.onConnectivityChanged.listen(
|
||||
_onConnectivityChanged,
|
||||
onError: (error) {
|
||||
logError('Network monitoring error: $error');
|
||||
},
|
||||
);
|
||||
|
||||
logInfo('Network monitor initialized successfully');
|
||||
}
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
_connectivitySubscription.cancel();
|
||||
super.onClose();
|
||||
}
|
||||
|
||||
NetworkType _getNetworkType(List<ConnectivityResult> result) {
|
||||
if (result.contains(ConnectivityResult.wifi)) {
|
||||
return NetworkType.wifi;
|
||||
} else if (result.contains(ConnectivityResult.mobile)) {
|
||||
return NetworkType.mobile;
|
||||
} else if (result.contains(ConnectivityResult.none)) {
|
||||
return NetworkType.none;
|
||||
} else {
|
||||
// Ethernet, Bluetooth, VPN, or other connection types
|
||||
return NetworkType.other;
|
||||
}
|
||||
}
|
||||
|
||||
void _updateNetworkStatus(List<ConnectivityResult> result) {
|
||||
final hasWiFi = result.contains(ConnectivityResult.wifi);
|
||||
_isWiFiConnected.value = hasWiFi;
|
||||
}
|
||||
|
||||
void _onConnectivityChanged(List<ConnectivityResult> result) {
|
||||
logInfo('NetworkMonitor: Connectivity changed event received: $result');
|
||||
|
||||
if (!_isInitialized) {
|
||||
logInfo('NetworkMonitor: Not initialized yet, ignoring connectivity change');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if network auto control is enabled
|
||||
final isNetworkAutoControlEnabled = Database.instance.getNetworkAutoControl();
|
||||
logInfo('NetworkMonitor: Network auto control enabled: $isNetworkAutoControlEnabled');
|
||||
if (!isNetworkAutoControlEnabled) {
|
||||
logInfo('NetworkMonitor: Network auto control disabled, ignoring connectivity change');
|
||||
return;
|
||||
}
|
||||
|
||||
_updateNetworkStatus(result);
|
||||
final currentNetworkType = _getNetworkType(result);
|
||||
|
||||
logInfo('Network changed: previous=$_previousNetworkType, current=$currentNetworkType');
|
||||
|
||||
// Only handle WiFi ↔ Mobile transitions, ignore other connection types
|
||||
if (_previousNetworkType == NetworkType.wifi && currentNetworkType == NetworkType.mobile) {
|
||||
_pauseAllDownloads();
|
||||
logInfo('Switched from WiFi to mobile data - pausing all downloads');
|
||||
} else if (_previousNetworkType == NetworkType.mobile && currentNetworkType == NetworkType.wifi) {
|
||||
_resumeAllDownloads();
|
||||
logInfo('Switched from mobile data to WiFi - resuming all downloads');
|
||||
} else {
|
||||
logInfo('Network change ignored: not a WiFi ↔ Mobile transition');
|
||||
}
|
||||
|
||||
_previousNetworkType = currentNetworkType;
|
||||
}
|
||||
|
||||
Future<void> _pauseAllDownloads() async {
|
||||
try {
|
||||
await pauseAllTasks(null);
|
||||
logInfo('Successfully paused all downloads due to network change');
|
||||
} catch (e) {
|
||||
logError('Failed to pause all downloads: $e');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _resumeAllDownloads() async {
|
||||
try {
|
||||
await continueAllTasks(null);
|
||||
logInfo('Successfully resumed all downloads due to network change');
|
||||
} catch (e) {
|
||||
logError('Failed to resume all downloads: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -54,8 +54,7 @@ class Util {
|
||||
static Future<void> initStorageDir() async {
|
||||
var storageDir = "";
|
||||
if (Util.isWindows()) {
|
||||
storageDir =
|
||||
path.join(File(Platform.resolvedExecutable).parent.path, "storage");
|
||||
storageDir = File(Platform.resolvedExecutable).parent.path;
|
||||
} else if (!Util.isWeb()) {
|
||||
if (Util.isLinux()) {
|
||||
storageDir = File(Platform.resolvedExecutable).parent.path;
|
||||
|
||||
@@ -15,7 +15,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
|
||||
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
||||
# Read more about iOS versioning at
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
version: 1.8.0+1
|
||||
version: 1.7.1+1
|
||||
|
||||
environment:
|
||||
sdk: ">=3.0.0 <4.0.0"
|
||||
@@ -66,6 +66,7 @@ dependencies:
|
||||
toggle_switch: ^2.3.0
|
||||
permission_handler: ^11.3.1
|
||||
device_info_plus: ^11.1.0
|
||||
connectivity_plus: ^6.1.0
|
||||
checkable_treeview: ^1.3.1
|
||||
contextmenu_plus: ^1.0.1
|
||||
contentsize_tabbarview: ^0.0.2
|
||||
|
||||
Reference in new issue
Block a user