Compare commits

...
24 changed files with 1739 additions and 105 deletions

No files matched your search

+3 -9
View File
@@ -258,19 +258,14 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-22.04, ubuntu-22.04-arm]
os: [ubuntu-24.04, ubuntu-24.04-arm]
needs: [get-release]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v4
with:
go-version: ${{ env.GO_VERSION }}
- if: matrix.os == 'ubuntu-22.04'
uses: subosito/flutter-action@v2
with:
flutter-version: ${{ env.FLUTTER_VERSION }}
- if: matrix.os == 'ubuntu-22.04-arm'
uses: subosito/flutter-action@v2
- uses: subosito/flutter-action@v2
with:
flutter-version: ${{ env.FLUTTER_VERSION }}
channel: master
@@ -282,7 +277,6 @@ jobs:
sudo apt install -y libfuse2 locate
arch=$(uname -m)
wget -O appimagetool "https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-${arch}.AppImage"
wget -O appimagetool "https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-${arch}.AppImage"
chmod +x appimagetool
sudo mv appimagetool /usr/local/bin/
- name: Build
@@ -508,7 +502,7 @@ jobs:
overwrite: true
build-ios:
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'ios' }}
runs-on: macos-latest
runs-on: macos-14
needs: [get-release]
steps:
- uses: actions/checkout@v3
+13 -7
View File
@@ -53,7 +53,13 @@ jobs:
with:
go-version: ${{ env.GO_VERSION }}
- name: Run Unit tests.
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/)
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
- uses: codecov/codecov-action@v4
with:
files: ./coverage.txt
@@ -63,8 +69,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-24.04, ubuntu-24.04-arm]
needs: [test]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v4
@@ -96,8 +102,8 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ macos-latest, ubuntu-latest ]
needs: [ test ]
os: [macos-14, ubuntu-latest]
needs: [test]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v4
@@ -129,7 +135,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
@@ -149,7 +155,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
+166 -51
View File
@@ -3,19 +3,23 @@ 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"`
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"`
// DownloadConfig when the first time to start the server, it will be configured as initial value
DownloadConfig *base.DownloaderStoreConfig `json:"downloadConfig"`
@@ -23,62 +27,173 @@ type args struct {
}
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", "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")
cfg := &args{}
cliConfig := loadCliArgs()
loadConfigFile(cfg, *cliConfig.configPath)
loadEnvVars(cfg)
// override with non-default command line arguments
overrideWithCliArgs(cfg, cliConfig)
// set default values
setDefaults(cfg, cliConfig)
return cfg
}
// loadCliArgs parses command line arguments and returns initial config
func loadCliArgs() *args {
cfg := &args{}
cfg.Address = flag.String("A", "0.0.0.0", "Bind Address")
cfg.Port = flag.Int("P", 9999, "Bind Port")
cfg.Username = flag.String("u", "gopeed", "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")
flag.Parse()
// args priority: config file > cli args
cfgArgs := loadConfig(*cliArgs.configPath)
if cfgArgs.Address == nil {
cfgArgs.Address = cliArgs.Address
// 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
}
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
return cfg
}
func loadConfig(path string) *args {
var args args
// overrideWithCliArgs overrides config with non-empty command line arguments
func overrideWithCliArgs(cfg *args, cliConfig *args) {
flag.Visit(func(f *flag.Flag) {
switch f.Name {
case "A":
cfg.Address = cliConfig.Address
case "P":
cfg.Port = cliConfig.Port
case "u":
cfg.Username = cliConfig.Username
case "p":
cfg.Password = cliConfig.Password
case "T":
cfg.ApiToken = cliConfig.ApiToken
case "d":
cfg.StorageDir = cliConfig.StorageDir
case "w":
cfg.WhiteDownloadDirs = cliConfig.WhiteDownloadDirs
case "c":
cfg.configPath = cliConfig.configPath
}
})
}
if !filepath.IsAbs(path) {
// setDefaults sets default values for any unset configuration fields
func setDefaults(cfg *args, cliConfig *args) {
if cfg.Address == nil {
cfg.Address = cliConfig.Address
}
if cfg.Port == nil {
cfg.Port = cliConfig.Port
}
if cfg.Username == nil {
cfg.Username = cliConfig.Username
}
if cfg.Password == nil {
cfg.Password = cliConfig.Password
}
if cfg.ApiToken == nil {
cfg.ApiToken = cliConfig.ApiToken
}
if cfg.StorageDir == nil {
cfg.StorageDir = cliConfig.StorageDir
}
}
// loadConfigFile loads configuration from file
func loadConfigFile(cfg *args, configPath string) {
if !filepath.IsAbs(configPath) {
dir, err := os.Getwd()
if err != nil {
fmt.Println("config dir get failed, reason:" + err.Error())
return &args
return
}
path = filepath.Join(dir, path)
configPath = filepath.Join(dir, configPath)
}
file, err := os.ReadFile(path)
file, err := os.ReadFile(configPath)
if err != nil {
if os.IsNotExist(err) {
return &args
return
}
fmt.Println("config file read failed, reason:" + err.Error())
return &args
return
}
if err = json.Unmarshal(file, &args); err != nil {
fmt.Println("config file parse 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))
}
}
}
fmt.Printf("config file loaded: %s\n", path)
return &args
}
File diff suppressed because it is too large. Load diff
+17 -15
View File
@@ -6,11 +6,12 @@ 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/*
@@ -31,28 +32,29 @@ func main() {
}
}
var dir string
var storageDir string
if args.StorageDir != nil && *args.StorageDir != "" {
dir = *args.StorageDir
storageDir = *args.StorageDir
} else {
exe, err := os.Executable()
if err != nil {
panic(err)
}
dir = filepath.Dir(exe)
storageDir = filepath.Join(filepath.Dir(exe), "storage")
}
cfg := &model.StartConfig{
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,
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,
}
cmd.Start(cfg)
}
+1 -1
View File
@@ -34,7 +34,7 @@ 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 {
if opts.Info.Length == 0 {
parts = append(parts, opts.Info.Name)
}
return filepath.Join(append(parts, opts.File.Path...)...)
+3 -2
View File
@@ -23,6 +23,7 @@ import (
"github.com/GopeedLab/gopeed/internal/fetcher"
"github.com/GopeedLab/gopeed/pkg/base"
fhttp "github.com/GopeedLab/gopeed/pkg/protocol/http"
"github.com/GopeedLab/gopeed/pkg/util"
"github.com/xiaoqidun/setft"
"golang.org/x/sync/errgroup"
)
@@ -172,13 +173,13 @@ func (f *Fetcher) Resolve(req *base.Request) error {
filename = strings.Replace(filename, "UTF8", "UTF-8", 1)
file.Name, _ = decoder.Decode(filename)
} else {
file.Name = filename
file.Name = util.TryUrlQueryUnescape(filename)
}
} else {
substr := "attachment; filename="
index := strings.Index(contentDisposition, substr)
if index != -1 {
file.Name = contentDisposition[index+len(substr):]
file.Name = util.TryUrlQueryUnescape(contentDisposition[index+len(substr):])
}
}
}
+2 -2
View File
@@ -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.DownloadDirWhiteList) > 0 {
if len(d.cfg.WhiteDownloadDirs) > 0 {
inWhiteList := false
for _, dir := range d.cfg.DownloadDirWhiteList {
for _, dir := range d.cfg.WhiteDownloadDirs {
if match, err := filepath.Match(dir, opts.Path); match && err == nil {
inWhiteList = true
break
+1 -1
View File
@@ -91,7 +91,7 @@ func TestDownloader_CreateNotInWhite(t *testing.T) {
defer listener.Close()
downloader := NewDownloader(&DownloaderConfig{
DownloadDirWhiteList: []string{"./downloads"},
WhiteDownloadDirs: []string{"./downloads"},
})
if err := downloader.Setup(); err != nil {
t.Fatal(err)
+4 -4
View File
@@ -137,10 +137,10 @@ type DownloaderConfig struct {
Controller *controller.Controller
FetchManagers []fetcher.FetcherManager
RefreshInterval int `json:"refreshInterval"` // RefreshInterval time duration to refresh task progress(ms)
Storage Storage
StorageDir string
DownloadDirWhiteList []string `json:"downloadDirWhiteList"`
RefreshInterval int // RefreshInterval time duration to refresh task progress(ms)
Storage Storage
StorageDir string
WhiteDownloadDirs []string
ProductionMode bool
+7 -4
View File
@@ -65,7 +65,9 @@ func Stop() {
}()
if srv != nil {
if err := srv.Shutdown(context.TODO()); err != nil {
shutdownCtx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
if err := srv.Shutdown(shutdownCtx); err != nil {
Downloader.Logger.Warn().Err(err).Msg("shutdown server failed")
}
}
@@ -83,8 +85,9 @@ func BuildServer(startCfg *model.StartConfig) (*http.Server, net.Listener, error
startCfg.Init()
downloadCfg := &download.DownloaderConfig{
ProductionMode: startCfg.ProductionMode,
RefreshInterval: startCfg.RefreshInterval,
ProductionMode: startCfg.ProductionMode,
RefreshInterval: startCfg.RefreshInterval,
WhiteDownloadDirs: startCfg.WhiteDownloadDirs,
}
if startCfg.Storage == model.StorageBolt {
downloadCfg.Storage = download.NewBoltStorage(startCfg.StorageDir)
@@ -190,7 +193,7 @@ func BuildServer(startCfg *model.StartConfig) (*http.Server, net.Listener, error
}
if enableBasicAuth {
if r.URL.Path == "/api/web/login" {
if !strings.HasPrefix(r.URL.Path, "/api/") || r.URL.Path == "/api/web/login" {
h.ServeHTTP(w, r)
return
}
+10
View File
@@ -56,3 +56,13 @@ func ProxyUrlToHandler(proxyUrl *url.URL) func(*http.Request) (*url.URL, error)
}
return http.ProxyURL(proxyUrl)
}
// TryUrlQueryUnescape tries to unescape a URL-encoded string.
//
// If unescaping fails, it returns the original string.
func TryUrlQueryUnescape(s string) string {
if decoded, err := url.QueryUnescape(s); err == nil {
return decoded
}
return s
}
+21
View File
@@ -128,3 +128,24 @@ func TestParseDataUri(t *testing.T) {
})
}
}
func TestTryURLDecode(t *testing.T) {
tests := []struct {
input string
expected string
}{
{"normal.txt", "normal.txt"},
{"%E7%8F%80%E5%B0%94%E8%AF%BA.zip", "珀尔诺.zip"},
{"hello%20world.txt", "hello world.txt"},
{"bad%2-text", "bad%2-text"},
}
for _, tt := range tests {
t.Run(tt.input, func(t *testing.T) {
got := TryUrlQueryUnescape(tt.input)
if got != tt.expected {
t.Errorf("TryUrlQueryUnescape(%q) = %q, want %q", tt.input, got, tt.expected)
}
})
}
}
@@ -81,6 +81,7 @@ class ExtraConfig {
bool lastDeleteTaskKeep;
bool defaultDirectDownload;
bool defaultBtClient;
bool notifyWhenNewVersion;
ExtraConfigBt bt = ExtraConfigBt();
@@ -90,6 +91,7 @@ class ExtraConfig {
this.lastDeleteTaskKeep = false,
this.defaultDirectDownload = false,
this.defaultBtClient = true,
this.notifyWhenNewVersion = true,
});
factory ExtraConfig.fromJson(Map<String, dynamic>? json) =>
@@ -74,6 +74,7 @@ ExtraConfig _$ExtraConfigFromJson(Map<String, dynamic> json) => ExtraConfig(
lastDeleteTaskKeep: json['lastDeleteTaskKeep'] as bool? ?? false,
defaultDirectDownload: json['defaultDirectDownload'] as bool? ?? false,
defaultBtClient: json['defaultBtClient'] as bool? ?? true,
notifyWhenNewVersion: json['notifyWhenNewVersion'] as bool? ?? true,
)..bt = ExtraConfigBt.fromJson(json['bt'] as Map<String, dynamic>);
Map<String, dynamic> _$ExtraConfigToJson(ExtraConfig instance) =>
@@ -83,6 +84,7 @@ Map<String, dynamic> _$ExtraConfigToJson(ExtraConfig instance) =>
'lastDeleteTaskKeep': instance.lastDeleteTaskKeep,
'defaultDirectDownload': instance.defaultDirectDownload,
'defaultBtClient': instance.defaultBtClient,
'notifyWhenNewVersion': instance.notifyWhenNewVersion,
'bt': instance.bt.toJson(),
};
@@ -119,7 +119,7 @@ class AppController extends GetxController with WindowListener, TrayListener {
@override
void onTrayIconRightMouseDown() {
trayManager.popUpContextMenu();
trayManager.popUpContextMenu(bringAppToFront: true);
}
@override
@@ -506,6 +506,11 @@ class AppController extends GetxController with WindowListener, TrayListener {
}
Future<void> _initCheckUpdate() async {
// Check if auto check update is enabled
if (!downloaderConfig.value.extra.notifyWhenNewVersion) {
return;
}
final versionInfo = await checkUpdate();
if (versionInfo != null) {
await showUpdateDialog(Get.context!, versionInfo);
@@ -531,6 +531,25 @@ class SettingView extends GetView<SettingController> {
);
}
final buildAutoCheckUpdate = _buildConfigItem(
'notifyWhenNewVersion',
() =>
downloaderCfg.value.extra.notifyWhenNewVersion ? 'on'.tr : 'off'.tr,
(Key key) {
return Container(
alignment: Alignment.centerLeft,
child: Switch(
value: downloaderCfg.value.extra.notifyWhenNewVersion,
onChanged: (bool value) async {
downloaderCfg.update((val) {
val!.extra.notifyWhenNewVersion = value;
});
await debounceSave();
},
),
);
});
buildThanks() {
const thankPage =
'https://github.com/GopeedLab/gopeed/graphs/contributors';
@@ -957,6 +976,7 @@ class SettingView extends GetView<SettingController> {
children: _addDivider([
buildHomepage(),
buildVersion(),
buildAutoCheckUpdate(),
buildThanks(),
]),
)),
+129
View File
@@ -0,0 +1,129 @@
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',
},
};
+1
View File
@@ -52,6 +52,7 @@ const enUS = {
'themeLight': 'Light',
'themeDark': 'Dark',
'locale': 'Language',
'notifyWhenNewVersion': 'Notify for updates',
'about': 'About',
'homepage': 'Homepage',
'version': 'Version',
+1
View File
@@ -51,6 +51,7 @@ const zhCN = {
'themeLight': '明亮主题',
'themeDark': '暗黑主题',
'locale': '语言',
'notifyWhenNewVersion': '有新版本时提醒',
'about': '关于',
'homepage': '主页',
'version': '版本',
+2
View File
@@ -1,5 +1,6 @@
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';
@@ -41,5 +42,6 @@ class _Messages extends Translations {
...ukUA,
...huHU,
...ptBR,
...deDE
};
}
+2 -1
View File
@@ -54,7 +54,8 @@ class Util {
static Future<void> initStorageDir() async {
var storageDir = "";
if (Util.isWindows()) {
storageDir = File(Platform.resolvedExecutable).parent.path;
storageDir =
path.join(File(Platform.resolvedExecutable).parent.path, "storage");
} else if (!Util.isWeb()) {
if (Util.isLinux()) {
storageDir = File(Platform.resolvedExecutable).parent.path;
+6 -5
View File
@@ -1252,11 +1252,12 @@ packages:
tray_manager:
dependency: "direct main"
description:
name: tray_manager
sha256: bdc3ac6c36f3d12d871459e4a9822705ce5a1165a17fa837103bc842719bf3f7
url: "https://pub.dev"
source: hosted
version: "0.2.4"
path: "packages/tray_manager"
ref: main
resolved-ref: "91cdbdc12517d0d832aca715542b9d0329739d5d"
url: "https://github.com/monkeyWie/tray_manager.git"
source: git
version: "0.5.1"
typed_data:
dependency: transitive
description:
+6 -2
View File
@@ -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.7.1+1
version: 1.8.1+1
environment:
sdk: ">=3.0.0 <4.0.0"
@@ -58,7 +58,11 @@ dependencies:
form_builder_validators: ^11.0.0
flutter_foreground_task: ^8.2.0
open_filex: ^4.5.0
tray_manager: ^0.2.4
tray_manager:
git:
url: https://github.com/monkeyWie/tray_manager.git
ref: main
path: packages/tray_manager
lecle_downloads_path_provider: ^0.0.2+8
hive: ^2.2.3
launch_at_startup: ^0.3.1