Compare commits

...
8 Commits
11 changed files with 240 additions and 97 deletions

No files matched your search

+1 -1
View File
@@ -53,7 +53,7 @@ jobs:
with:
go-version: ${{ env.GO_VERSION }}
- name: Run Unit tests.
run: go test $(go list ./... | grep -v /cmd/ | grep -v /bind/) -v -covermode=count -coverprofile=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
+1 -1
View File
@@ -4,7 +4,7 @@ type config struct {
ListenPort int `json:"listenPort"`
Trackers []string `json:"trackers"`
// SeedKeep is always keep seeding after downloading is complete, unless manually stopped.
SeedKeep bool `json:"seedKeep`
SeedKeep bool `json:"seedKeep"`
// SeedRatio is the ratio of uploaded data to downloaded data to seed.
SeedRatio float64 `json:"seedRatio"`
// SeedTime is the time in seconds to seed after downloading is complete.
+38 -24
View File
@@ -4,13 +4,6 @@ import (
"bytes"
"context"
"fmt"
"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"
"io"
"net/url"
"os"
@@ -19,6 +12,14 @@ import (
"sync"
"sync/atomic"
"time"
"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"
)
var (
@@ -359,6 +360,7 @@ func (f *Fetcher) addTorrent(req *base.Request, fromUpload bool) (err error) {
return
}
schema := util.ParseSchema(req.URL)
privateTorrent := false
if schema == "MAGNET" {
f.torrent, err = client.AddMagnet(req.URL)
} else {
@@ -388,34 +390,46 @@ func (f *Fetcher) addTorrent(req *base.Request, fromUpload bool) (err error) {
if err != nil && !strings.Contains(err.Error(), "expected EOF") {
return err
}
info, er := metaInfo.UnmarshalInfo()
if er != nil {
return er
}
if info.Private != nil && *info.Private {
privateTorrent = true
}
f.torrent, err = client.AddTorrent(metaInfo)
}
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 {
// Do not add external tracker to a private torrent.
if !privateTorrent {
// 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 len(f.config.Trackers) > 0 {
for _, tracker := range f.config.Trackers {
trackers[tracker] = true
}
}
}
if len(f.config.Trackers) > 0 {
for _, tracker := range f.config.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)
}
}
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.torrentReady.Store(true)
+21 -21
View File
@@ -30,12 +30,12 @@ class _Client {
_instance = _Client._internal();
var dio = Dio();
final isUnixSocket = network == 'unix';
var baseUrl = 'http://127.0.0.1';
var baseUrl = 'http://127.0.0.1/';
if (!isUnixSocket) {
if (Util.isWeb()) {
baseUrl = kDebugMode ? 'http://127.0.0.1:9999' : '';
baseUrl = kDebugMode ? 'http://127.0.0.1:9999/' : '';
} else {
baseUrl = 'http://$address';
baseUrl = 'http://$address/';
}
}
dio.options.baseUrl = baseUrl;
@@ -105,19 +105,19 @@ Future<T> _parse<T>(
Future<ResolveResult> resolve(Request request) async {
return _parse<ResolveResult>(
() => _client.dio.post("/api/v1/resolve", data: request),
() => _client.dio.post("api/v1/resolve", data: request),
(data) => ResolveResult.fromJson(data));
}
Future<String> createTask(CreateTask createTask) async {
return _parse<String>(
() => _client.dio.post("/api/v1/tasks", data: createTask),
() => _client.dio.post("api/v1/tasks", data: createTask),
(data) => data as String);
}
Future<List<String>> createTaskBatch(CreateTaskBatch createTaskBatch) async {
return _parse<List<String>>(
() => _client.dio.post("/api/v1/tasks/batch", data: createTaskBatch),
() => _client.dio.post("api/v1/tasks/batch", data: createTaskBatch),
(data) => (data as List).map((e) => e as String).toList());
}
@@ -129,16 +129,16 @@ Future<List<Task>> getTasks(List<Status> statuses) async {
}
Future<void> pauseTask(String id) async {
return _parse(() => _client.dio.put("/api/v1/tasks/$id/pause"), null);
return _parse(() => _client.dio.put("api/v1/tasks/$id/pause"), null);
}
Future<void> continueTask(String id) async {
return _parse(() => _client.dio.put("/api/v1/tasks/$id/continue"), null);
return _parse(() => _client.dio.put("api/v1/tasks/$id/continue"), null);
}
Future<void> pauseAllTasks(List<String>? ids) async {
return _parse(
() => _client.dio.put("/api/v1/tasks/pause", queryParameters: {
() => _client.dio.put("api/v1/tasks/pause", queryParameters: {
"id": ids,
}),
null);
@@ -146,7 +146,7 @@ Future<void> pauseAllTasks(List<String>? ids) async {
Future<void> continueAllTasks(List<String>? ids) async {
return _parse(
() => _client.dio.put("/api/v1/tasks/continue", queryParameters: {
() => _client.dio.put("api/v1/tasks/continue", queryParameters: {
"id": ids,
}),
null);
@@ -154,12 +154,12 @@ Future<void> continueAllTasks(List<String>? ids) async {
Future<void> deleteTask(String id, bool force) async {
return _parse(
() => _client.dio.delete("/api/v1/tasks/$id?force=$force"), null);
() => _client.dio.delete("api/v1/tasks/$id?force=$force"), null);
}
Future<void> deleteTasks(List<String>? ids, bool force) async {
return _parse(
() => _client.dio.delete("/api/v1/tasks", queryParameters: {
() => _client.dio.delete("api/v1/tasks", queryParameters: {
"id": ids,
"force": force,
}),
@@ -167,29 +167,29 @@ Future<void> deleteTasks(List<String>? ids, bool force) async {
}
Future<DownloaderConfig> getConfig() async {
return _parse(() => _client.dio.get("/api/v1/config"),
return _parse(() => _client.dio.get("api/v1/config"),
(data) => DownloaderConfig.fromJson(data));
}
Future<void> putConfig(DownloaderConfig config) async {
return _parse(() => _client.dio.put("/api/v1/config", data: config), null);
return _parse(() => _client.dio.put("api/v1/config", data: config), null);
}
Future<void> installExtension(InstallExtension installExtension) async {
return _parse(
() => _client.dio.post("/api/v1/extensions", data: installExtension),
() => _client.dio.post("api/v1/extensions", data: installExtension),
null);
}
Future<List<Extension>> getExtensions() async {
return _parse<List<Extension>>(() => _client.dio.get("/api/v1/extensions"),
return _parse<List<Extension>>(() => _client.dio.get("api/v1/extensions"),
(data) => (data as List).map((e) => Extension.fromJson(e)).toList());
}
Future<void> updateExtensionSettings(
String identity, UpdateExtensionSettings updateExtensionSettings) async {
return _parse(
() => _client.dio.put("/api/v1/extensions/$identity/settings",
() => _client.dio.put("api/v1/extensions/$identity/settings",
data: updateExtensionSettings),
null);
}
@@ -198,22 +198,22 @@ Future<void> switchExtension(
String identity, SwitchExtension switchExtension) async {
return _parse(
() => _client.dio
.put("/api/v1/extensions/$identity/switch", data: switchExtension),
.put("api/v1/extensions/$identity/switch", data: switchExtension),
null);
}
Future<void> deleteExtension(String identity) async {
return _parse(() => _client.dio.delete("/api/v1/extensions/$identity"), null);
return _parse(() => _client.dio.delete("api/v1/extensions/$identity"), null);
}
Future<UpdateCheckExtensionResp> upgradeCheckExtension(String identity) async {
return _parse(() => _client.dio.get("/api/v1/extensions/$identity/update"),
return _parse(() => _client.dio.get("api/v1/extensions/$identity/update"),
(data) => UpdateCheckExtensionResp.fromJson(data));
}
Future<void> updateExtension(String identity) async {
return _parse(
() => _client.dio.post("/api/v1/extensions/$identity/update"), null);
() => _client.dio.post("api/v1/extensions/$identity/update"), null);
}
Future<Response<String>> proxyRequest<T>(String uri,
+122
View File
@@ -0,0 +1,122 @@
const ptBR = {
'pt_BR': {
'label': 'Português(Brasil)',
'error': 'Erro',
'tip': 'Dica',
'confirm': 'Confirmar',
'cancel': 'Cancelar',
'on': 'Ligado',
'off': 'Desligado',
'selectAll': 'Selecionar Tudo',
'select': 'Selecionar',
'task': 'Tarefas',
'downloading': 'baixando',
'downloaded': 'baixado',
'setting': 'Configurações',
'donate': 'Doação',
'exit': 'Sair',
'create': 'Criar Tarefa',
'directDownload': 'Download Direto',
'advancedOptions': 'Opções Avançadas',
'followSettings': 'Follow Settings',
'downloadLink': 'Link de Download',
'downloadLinkValid': 'Por favor, insira o link de download',
'downloadLinkHit': 'Por favor, insira o link de download, um por linha@append',
'downloadLinkHitDesktop': ', ou arraste o arquivo torrent aqui',
'download': 'Download',
'noFileSelected': 'Por favor, selecione ao menos um arquivo para continuar',
'noStoragePermission': 'Permissão de Armazenamento Necessária',
'selectFile': 'Selecionar Arquivo',
'rename': 'Renomear para',
'basic': 'Configurações Básica',
'advanced': 'Configurações Avançada',
'general': 'Principal',
'downloadDir': 'Onde você deseja salvar?',
'downloadDirValid': 'Por favor, selecione a pasta',
'connections': 'Conexões',
'useServerCtime': 'Use server time for file creation',
'maxRunning': 'Máximo de Tarefas Simultâneas',
'defaultDirectDownload': 'Marcar download direto por padrão',
'items': '@count itens',
'subscribeTracker': 'Assinar Rastreador',
'subscribeFail':
'Falha na Assinatura, verifique sua conexão ou tente novamente mais tarde',
'update': 'Atualizar',
'updateDaily': 'Atualizar Diariamente',
'lastUpdate': 'Última Atualização: @time',
'addTracker': 'Adicionar Rastreador',
'addTrackerHit': 'Por favor, insira a URL do rastreador, um por linha',
'ui': 'Interface',
'theme': 'Tema',
'themeSystem': 'Sistema',
'themeLight': 'Claro',
'themeDark': 'Escuro',
'locale': 'Idioma',
'about': 'Sobre',
'homepage': 'Site',
'version': 'Versão',
'protocol': 'Protocolo',
'port': 'Porta',
'apiToken': 'API Token',
'notSet': 'NS',
'set': 'SET',
'portInUse': 'Porta [@port] em uso, por favor utilize outra porta',
'effectAfterRestart': 'Effect after restart',
'developer': 'Desenvolvedor',
'logDirectory': 'Pasta de Logs',
'show': 'Mostrar',
'continue': 'Continuar',
'pause': 'Pausar',
'startAll': 'Iniciar Tudo',
'pauseAll': 'Pausar Tudo',
'deleteTask': 'Excluir @count tarefas',
'deleteTaskTip': 'Manter arquivos baixados',
'delete': 'Excluir',
'newVersionTitle': 'Nova Versão Disponível @version',
'newVersionUpdate': 'Atualizar Agora',
'newVersionLater': 'Mais Tarde',
'extensions': 'Extensões',
'extensionInstallUrl': 'Link de Instalação',
'extensionInstallSuccess': 'Instalado com Sucesso',
'extensionUpdateSuccess': 'Atualizado com Sucesso',
'extensionDelete': 'Excluir Extensão',
'extensionAlreadyLatest': 'It\'s already the latest version',
'extensionFind': 'Encontrar Extensões',
'extensionDevelop': 'Desenvolvedor',
'history': 'Histórico',
'clearHistory': 'Limpar Histórico',
'noHistoryFound': 'Nenhum Histório Encontrado',
'serviceTitle': 'Download Service',
'serviceText': 'Executando',
'network': 'Rede',
'proxy': 'Proxy',
'noProxy': 'Sem Proxy',
'systemProxy': 'Proxy do Sistema',
'customProxy': 'Proxy Personalizado',
'server': 'Servidor',
'username': 'Usuário',
'password': 'Senha',
'thanks': 'Obrigado',
'thanksDesc':
'Obrigado a todos os colaboradores que ajudaram a construir e desenvolver a comunidade Gopeed!',
'browserExtension': 'Extensões para Navegadores',
'launchAtStartup': 'Launch at Startup',
'seedConfig': 'Seed Config',
'seedKeep': 'Keep seeding until manually stopped',
'seedRatio': 'Seed ratio',
'seedTime': 'Seed time (minutes)',
'setAsDefaultBtClient': 'Set as the default BT client',
'taskDetail': 'Detalhes da Tarefa',
'taskName': 'Nome da Tarefa',
'taskUrl': 'Tarefa URL',
'downloadPath': 'Download Path',
'skipVerifyCert': 'Pular Verificação de Certificado',
'name': 'Nome',
'size': 'Tamanho',
'unknown': 'Desconhecido',
'fileSelectedCount': 'Arquivos: ',
'fileSelectedSize': 'Tamanho: ',
'httpHeaderName': 'Nome do Header',
'httpHeaderValue': 'Valor do Header',
},
};
+2
View File
@@ -16,6 +16,7 @@ import 'langs/zh_tw.dart';
import 'langs/es_es.dart';
import 'langs/uk_ua.dart';
import 'langs/hu_hu.dart';
import 'langs/pt_br.dart';
final messages = _Messages();
@@ -39,5 +40,6 @@ class _Messages extends Translations {
...esES,
...ukUA,
...huHU,
...ptBR,
};
}
+12 -34
View File
@@ -1,5 +1,6 @@
import 'dart:io';
import 'package:open_dir/open_dir.dart';
import 'package:path/path.dart' as path;
import 'package:url_launcher/url_launcher_string.dart';
class FileExplorer {
@@ -12,42 +13,19 @@ class FileExplorer {
}
static Future<void> _openDirectory(String directoryPath) async {
await launchUrlString("file://$directoryPath");
if (Platform.isWindows || Platform.isMacOS || Platform.isLinux) {
await OpenDir().openNativeDir(path: directoryPath);
} else {
await launchUrlString("file://$directoryPath");
}
}
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) {
_linuxOpen(filePath);
}
}
static Future<void> _linuxOpen(String filePath) async {
if (await Process.run('which', ['xdg-open'])
.then((value) => value.exitCode == 0)) {
final result = await Process.run('xdg-open', [filePath]);
if (result.exitCode != 0) {
_openWithFileManager(filePath);
}
} else {
_openWithFileManager(filePath);
}
}
static Future<void> _openWithFileManager(String filePath) async {
final desktop = Platform.environment['XDG_CURRENT_DESKTOP'];
if (desktop == null) {
throw Exception('XDG_CURRENT_DESKTOP is not set');
}
if (desktop == 'GNOME') {
await Process.run('nautilus', ['--select', filePath]);
} else if (desktop == 'KDE') {
await Process.run('dolphin', ['--select', filePath]);
} else {
throw Exception('Unsupported desktop environment');
if (Platform.isWindows || Platform.isMacOS || Platform.isLinux) {
final fileName = path.basename(filePath);
final parentPath = path.dirname(filePath);
await OpenDir()
.openNativeDir(path: parentPath, highlightedFileName: fileName);
}
}
}
+1
View File
@@ -42,6 +42,7 @@ endif()
function(APPLY_STANDARD_SETTINGS TARGET)
target_compile_features(${TARGET} PUBLIC cxx_std_14)
target_compile_options(${TARGET} PRIVATE -Wall -Werror)
target_compile_options(${TARGET} PRIVATE -Wno-error=deprecated-declarations) # Allow deprecated warnings
target_compile_options(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:-O3>")
target_compile_definitions(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:NDEBUG>")
endfunction()
+40
View File
@@ -723,6 +723,46 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.0.0"
open_dir:
dependency: "direct main"
description:
name: open_dir
sha256: a4884b00e5e5795a9b4b3d582ac6a66e9196795ed760dbc3c63b4837c70c5901
url: "https://pub.dev"
source: hosted
version: "0.0.2+1"
open_dir_linux:
dependency: transitive
description:
name: open_dir_linux
sha256: "566cd9e02403971be06af35e1abc8057a4f3f98888c1226042e96a2af333b8bc"
url: "https://pub.dev"
source: hosted
version: "0.0.2+1"
open_dir_macos:
dependency: transitive
description:
name: open_dir_macos
sha256: "51fdc8c3a06c9d571b599b5901045ada23d1440b24c3052c0a66cf3ee4ac901b"
url: "https://pub.dev"
source: hosted
version: "0.0.2"
open_dir_platform_interface:
dependency: transitive
description:
name: open_dir_platform_interface
sha256: ca189abb02d8e3320f9b2493b6d58e3a33f393d5eb4ccbbef02e0bc0fd393872
url: "https://pub.dev"
source: hosted
version: "0.0.2"
open_dir_windows:
dependency: transitive
description:
name: open_dir_windows
sha256: ec48df32ce61adb6f6cede0330d13b0d89714d2ee2df198f32ecd520e3a5d250
url: "https://pub.dev"
source: hosted
version: "0.0.2+1"
open_filex:
dependency: "direct main"
description:
+2 -1
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.0+1
version: 1.7.1+1
environment:
sdk: ">=3.0.0 <4.0.0"
@@ -72,6 +72,7 @@ dependencies:
win32_registry: ^1.1.5
share_handler: ^0.0.22
crypto: ^3.0.6
open_dir: ^0.0.2+1
install_plugin:
git:
url: https://github.com/hui-z/flutter_install_plugin.git
-15
View File
@@ -1,21 +1,6 @@
<!DOCTYPE html>
<html>
<head>
<!--
If you are serving your web app in a path other than the root, change the
href value below to reflect the base path you are serving from.
The path provided below has to start and end with a slash "/" in order for
it to work correctly.
For more details:
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
This is a placeholder for base href that will be replaced by the value of
the `--base-href` argument provided to `flutter build`.
-->
<base href="$FLUTTER_BASE_HREF">
<meta charset="UTF-8">
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
<meta name="description" content="A modern download manager.">