feat: direct call download api by cgo dart

This commit is contained in:
liwei committed 2024-12-02 00:39:28 +08:00
1 parent 467ada5500
commit 91dfd78b77
71 files changed
+6936 -1883

No files matched your search

+6 -2
View File
@@ -51,7 +51,7 @@ func Invoke(index int, request *api.Request) string {
if !ok {
return BuildResult(errors.New("instance not found"))
}
return BuildResult(api.Invoke(instance, request))
return jsonEncode(api.Invoke(instance, request))
}
func BuildResult(data any) string {
@@ -59,6 +59,10 @@ func BuildResult(data any) string {
buf, _ := json.Marshal(model.NewErrorResult[any](err.Error()))
return string(buf)
}
buf, _ := json.Marshal(model.NewOkResult(data))
return jsonEncode(model.NewOkResult(data))
}
func jsonEncode(data any) string {
buf, _ := json.Marshal(data)
return string(buf)
}
-6
View File
@@ -1,15 +1,11 @@
package main
/*
#include <stdlib.h>
*/
import "C"
import (
"encoding/json"
"github.com/GopeedLab/gopeed/bind"
"github.com/GopeedLab/gopeed/pkg/api"
"github.com/GopeedLab/gopeed/pkg/api/model"
"unsafe"
)
func main() {}
@@ -20,7 +16,6 @@ func Create(cfg *C.char) *C.char {
if err := json.Unmarshal([]byte(C.GoString(cfg)), &config); err != nil {
return C.CString(bind.BuildResult(err))
}
defer C.free(unsafe.Pointer(cfg))
return C.CString(bind.Create(&config))
}
@@ -31,7 +26,6 @@ func Invoke(index int, req *C.char) *C.char {
if err := json.Unmarshal([]byte(C.GoString(req)), &request); err != nil {
return C.CString(bind.BuildResult(err))
}
defer C.free(unsafe.Pointer(req))
return C.CString(bind.Invoke(index, &request))
}
+2 -2
View File
@@ -8,8 +8,8 @@ import (
"github.com/GopeedLab/gopeed/pkg/api/model"
)
func Create(config *model.StartConfig) string {
return bind.Create(config)
func Create(cfg *model.StartConfig) string {
return bind.Create(cfg)
}
func Invoke(index int, request *api.Request) string {
+9 -3
View File
@@ -3,14 +3,20 @@ package main
import (
"github.com/GopeedLab/gopeed/cmd"
"github.com/GopeedLab/gopeed/pkg/api/model"
"github.com/GopeedLab/gopeed/pkg/base"
)
// only for local development
func main() {
cfg := &model.StartConfig{
Network: "tcp",
Address: "127.0.0.1:9999",
Storage: model.StorageBolt,
Storage: model.StorageBolt,
DownloadConfig: &base.DownloaderStoreConfig{
Http: &base.DownloaderHttpConfig{
Enable: true,
Host: "127.0.0.1",
Port: 9999,
},
},
WebEnable: true,
}
cmd.Start(cfg)
+9 -6
View File
@@ -21,6 +21,7 @@ func Start(cfg *model.StartConfig) {
if err != nil {
panic(err)
}
downloadCfgResult := instance.GetConfig()
if downloadCfgResult.HasError() {
panic(downloadCfgResult.Msg)
@@ -52,13 +53,15 @@ func Start(cfg *model.StartConfig) {
}
}
}
startResult := instance.Start()
if startResult.HasError() {
panic(startResult.Msg)
}
fmt.Printf("Server start success on http://%s:%s\n", startResult.Data.Host, startResult.Data.Port)
watchExit(instance)
result, start, err := api.ListenHttp(cfg.DownloadConfig.Http, instance)
if err != nil {
panic(err)
}
fmt.Printf("Server start success on http://%s:%d\n", result.Host, result.Port)
start()
}
func watchExit(instance *api.Instance) {
@@ -67,7 +70,7 @@ func watchExit(instance *api.Instance) {
go func() {
sig := <-sigs
fmt.Printf("Server is shutting down due to signal: %s\n", sig)
instance.Stop()
instance.Close()
os.Exit(0)
}()
}
+2 -2
View File
@@ -35,8 +35,8 @@ func parse() *args {
// args priority: config file > cli args
cfgArgs := loadConfig(*cliArgs.configPath)
if cfgArgs.Address == nil {
cfgArgs.Address = cliArgs.Address
if cfgArgs.Host == nil {
cfgArgs.Host = cliArgs.Host
}
if cfgArgs.Port == nil {
cfgArgs.Port = cliArgs.Port
+1
View File
@@ -46,6 +46,7 @@ func main() {
args.DownloadConfig = &base.DownloaderStoreConfig{}
}
args.DownloadConfig.Http = &base.DownloaderHttpConfig{
Enable: true,
Host: *args.Host,
Port: *args.Port,
ApiToken: *args.ApiToken,
+41 -73
View File
@@ -7,7 +7,6 @@ import (
"github.com/GopeedLab/gopeed/pkg/api/model"
"github.com/GopeedLab/gopeed/pkg/base"
"github.com/GopeedLab/gopeed/pkg/download"
"github.com/pkg/errors"
"net"
"net/http"
"reflect"
@@ -36,24 +35,6 @@ func Create(startCfg *model.StartConfig) (*Instance, error) {
startCfg: startCfg,
}
return i, nil
}
func (i *Instance) Start() *model.Result[*model.StartResult] {
// avoid repeat start
if i.running {
// If the http server is not enabled, return nil
if i.runningPort == 0 {
return model.NewOkResult[*model.StartResult](nil)
}
host, _, _ := net.SplitHostPort(i.listener.Addr().String())
return model.NewOkResult(&model.StartResult{
Host: host,
Port: i.runningPort,
})
}
startCfg := i.startCfg
downloadCfg := &download.DownloaderConfig{
ProductionMode: startCfg.ProductionMode,
RefreshInterval: startCfg.RefreshInterval,
@@ -67,69 +48,47 @@ func (i *Instance) Start() *model.Result[*model.StartResult] {
downloadCfg.Init()
downloader := download.NewDownloader(downloadCfg)
if err := downloader.Setup(); err != nil {
return model.NewErrorResult[*model.StartResult](err.Error())
return nil, err
}
i.downloader = downloader
i.running = true
config, err := i.downloader.GetConfig()
if err != nil {
return model.NewErrorResult[*model.StartResult](err.Error())
}
httpCfg := config.Http
if httpCfg == nil || !httpCfg.Enable {
return model.NewOkResult[*model.StartResult](nil)
}
i.httpLock = sync.Mutex{}
startResult := i.StartHttp(httpCfg)
if startResult.HasError() {
i.downloader.Logger.Error().Err(err).Msgf("listen http failed: %s", startResult.Msg)
return model.NewOkResult[*model.StartResult](nil)
}
go func() {
if err := i.srv.Serve(i.listener); err != nil && !errors.Is(err, http.ErrServerClosed) {
panic(err)
}
}()
addr, _ := i.listener.Addr().(*net.TCPAddr)
i.runningPort = addr.Port
return model.NewOkResult(&model.StartResult{
Host: httpCfg.Host,
Port: i.runningPort,
})
return i, nil
}
func (i *Instance) Stop() *model.Result[any] {
i.StopHttp()
if i.downloader != nil {
if err := i.downloader.Close(); err != nil {
i.downloader.Logger.Warn().Err(err).Msg("close downloader failed")
}
i.downloader = nil
i.running = false
}
return model.NewNilResult()
}
func (i *Instance) StartHttp(httpCfg *base.DownloaderHttpConfig) *model.Result[int] {
func (i *Instance) StartHttp() *model.Result[*model.HttpListenResult] {
i.httpLock.Lock()
defer i.httpLock.Unlock()
return i.startHttp(httpCfg)
return i.startHttp()
}
func (i *Instance) startHttp(httpCfg *base.DownloaderHttpConfig) *model.Result[int] {
func (i *Instance) startHttp() *model.Result[*model.HttpListenResult] {
var httpCfg *base.DownloaderHttpConfig
// if startCfg has http config, first use it
if i.startCfg.DownloadConfig != nil && i.startCfg.DownloadConfig.Http != nil {
httpCfg = i.startCfg.DownloadConfig.Http
} else {
cfg, err := i.downloader.GetConfig()
if err != nil {
return model.NewErrorResult[*model.HttpListenResult](err.Error())
}
httpCfg = cfg.Http
}
if httpCfg == nil || !httpCfg.Enable {
return model.NewErrorResult[*model.HttpListenResult]("HTTP API server not enabled")
}
if i.srv != nil {
return model.NewErrorResult[int]("server already started")
return model.NewErrorResult[*model.HttpListenResult]("HTTP API server already started")
}
if err := ListenHttp(httpCfg, i); err != nil {
return model.NewErrorResult[int](err.Error())
result, start, err := ListenHttp(httpCfg, i)
if err != nil {
return model.NewErrorResult[*model.HttpListenResult](err.Error())
}
return model.NewOkResult(i.runningPort)
go start()
return model.NewOkResult[*model.HttpListenResult](result)
}
func (i *Instance) StopHttp() *model.Result[any] {
@@ -151,16 +110,12 @@ func (i *Instance) stopHttp() *model.Result[any] {
return model.NewNilResult()
}
func (i *Instance) RestartHttp(httpCfg *base.DownloaderHttpConfig) *model.Result[int] {
func (i *Instance) RestartHttp() *model.Result[*model.HttpListenResult] {
i.httpLock.Lock()
defer i.httpLock.Unlock()
return i.restartHttp(httpCfg)
}
func (i *Instance) restartHttp(httpCfg *base.DownloaderHttpConfig) *model.Result[int] {
i.stopHttp()
return i.startHttp(httpCfg)
return i.startHttp()
}
func (i *Instance) Info() *model.Result[map[string]any] {
@@ -375,6 +330,19 @@ func (i *Instance) UpgradeExtension(identity string) *model.Result[any] {
return model.NewNilResult()
}
func (i *Instance) Close() *model.Result[any] {
i.StopHttp()
if i.downloader != nil {
if err := i.downloader.Close(); err != nil {
i.downloader.Logger.Warn().Err(err).Msg("close downloader failed")
}
i.downloader = nil
i.running = false
}
return model.NewNilResult()
}
type Request struct {
Method string `json:"method"`
Params []string `json:"params"`
+11 -4
View File
@@ -22,7 +22,7 @@ import (
const instanceKey string = "apiInstance"
func ListenHttp(httpCfg *base.DownloaderHttpConfig, ai *Instance) error {
func ListenHttp(httpCfg *base.DownloaderHttpConfig, ai *Instance) (*model.HttpListenResult, func(), error) {
var r = mux.NewRouter()
r.Methods(http.MethodGet).Path("/api/v1/info").HandlerFunc(Info)
r.Methods(http.MethodPost).Path("/api/v1/resolve").HandlerFunc(Resolve)
@@ -106,12 +106,19 @@ func ListenHttp(httpCfg *base.DownloaderHttpConfig, ai *Instance) error {
listener, err := net.Listen("tcp", net.JoinHostPort(httpCfg.Host, fmt.Sprintf("%d", httpCfg.Port)))
if err != nil {
return err
return nil, nil, err
}
ai.srv = srv
ai.listener = listener
return nil
return &model.HttpListenResult{
Host: httpCfg.Host,
Port: listener.Addr().(*net.TCPAddr).Port,
}, func() {
if err := srv.Serve(listener); err != nil && !errors.Is(err, http.ErrServerClosed) {
ai.downloader.Logger.Error().Err(err).Msg("http server listen error")
}
}, nil
}
func getInstance(r *http.Request) *Instance {
+1 -1
View File
@@ -51,7 +51,7 @@ func (cfg *WebBasicAuth) Authorization() string {
return "Basic " + base64.StdEncoding.EncodeToString([]byte(userId))
}
type StartResult struct {
type HttpListenResult struct {
Host string `json:"host"`
// Port is the http server real listen port.
Port int `json:"port"`
+3 -3
View File
@@ -113,9 +113,9 @@ func (t *Task) calcSpeed(speedArr []int64, downloaded int64, usedTime float64) i
}
type TaskFilter struct {
IDs []string
Statuses []base.Status
NotStatuses []base.Status
IDs []string `json:"ids"`
Statuses []base.Status `json:"statuses"`
NotStatuses []base.Status `json:"notStatuses"`
}
func (f *TaskFilter) IsEmpty() bool {
+19 -14
View File
@@ -2,7 +2,6 @@
/* package github.com/GopeedLab/gopeed/bind/desktop */
#line 1 "cgo-builtin-export-prolog"
#include <stddef.h>
@@ -11,19 +10,24 @@
#define GO_CGO_EXPORT_PROLOGUE_H
#ifndef GO_CGO_GOSTRING_TYPEDEF
typedef struct { const char *p; ptrdiff_t n; } _GoString_;
typedef struct {
const char *p;
ptrdiff_t n;
} _GoString_;
#endif
#endif
/* Start of preamble from import "C" comments. */
#line 3 "main.go"
#include <stdlib.h>
#line 1 "cgo-generated-wrapper"
/* End of preamble from import "C" comments. */
/* Start of boilerplate cgo prologue. */
#line 1 "cgo-gcc-export-header-prolog"
@@ -56,15 +60,22 @@ typedef double _Complex GoComplex128;
static assertion to make sure the file is being used on architecture
at least with matching size of GoInt.
*/
typedef char _check_for_64_bit_pointer_matching_GoInt[sizeof(void*)==64/8 ? 1:-1];
typedef char _check_for_64_bit_pointer_matching_GoInt[sizeof(void *) == 64 / 8 ? 1 : -1];
#ifndef GO_CGO_GOSTRING_TYPEDEF
typedef _GoString_ GoString;
#endif
typedef void *GoMap;
typedef void *GoChan;
typedef struct { void *t; void *v; } GoInterface;
typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;
typedef struct {
void *t;
void *v;
} GoInterface;
typedef struct {
void *data;
GoInt len;
GoInt cap;
} GoSlice;
#endif
@@ -74,14 +85,8 @@ typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;
extern "C" {
#endif
/* Return type for Start */
struct Start_return {
GoInt r0;
char* r1;
};
extern struct Start_return Start(char* cfg);
extern void Stop();
extern __declspec(dllexport) char *Create(char *cfg);
extern __declspec(dllexport) char *Invoke(GoInt index, char *req);
#ifdef __cplusplus
}
-210
View File
@@ -1,210 +0,0 @@
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:dio/io.dart';
import 'package:flutter/foundation.dart';
import 'package:gopeed/api/model/extension.dart';
import 'package:gopeed/api/model/install_extension.dart';
import 'package:gopeed/api/model/switch_extension.dart';
import '../util/util.dart';
import 'model/create_task.dart';
import 'model/downloader_config.dart';
import 'model/request.dart';
import 'model/resolve_result.dart';
import 'model/result.dart';
import 'model/task.dart';
import 'model/update_extension_settings.dart';
import 'model/update_check_extension_resp.dart';
class _Client {
static _Client? _instance;
late Dio dio;
_Client._internal();
factory _Client(String network, String address, String apiToken) {
if (_instance == null) {
_instance = _Client._internal();
var dio = Dio();
final isUnixSocket = network == 'unix';
var baseUrl = 'http://127.0.0.1';
if (!isUnixSocket) {
if (Util.isWeb()) {
baseUrl = kDebugMode ? 'http://127.0.0.1:9999' : '';
} else {
baseUrl = 'http://$address';
}
}
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) {
if (apiToken.isNotEmpty) {
options.headers['X-Api-Token'] = apiToken;
}
handler.next(options);
}));
_instance!.dio = dio;
if (isUnixSocket) {
(_instance!.dio.httpClientAdapter as IOHttpClientAdapter)
.createHttpClient = () {
final client = HttpClient();
client.connectionFactory =
(Uri uri, String? proxyHost, int? proxyPort) {
return Socket.startConnect(
InternetAddress(address, type: InternetAddressType.unix), 0);
};
return client;
};
}
}
return _instance!;
}
}
class TimeoutException implements Exception {
final String message;
TimeoutException(this.message);
}
late _Client _client;
void init(String network, String address, String apiToken) {
_client = _Client(network, address, apiToken);
}
Future<T> _parse<T>(
Future<Response> Function() fetch,
T Function(dynamic json)? fromJsonT,
) async {
try {
var resp = await fetch();
fromJsonT ??= (json) => null as T;
final result = Result<T>.fromJson(resp.data, fromJsonT);
if (result.code == 0) {
return result.data as T;
} else {
throw Exception(result);
}
} on DioException catch (e) {
if (e.type == DioExceptionType.sendTimeout ||
e.type == DioExceptionType.receiveTimeout ||
e.type == DioExceptionType.connectionTimeout) {
throw TimeoutException("request timeout");
}
throw Exception(Result(code: 1000, msg: e.message));
}
}
Future<ResolveResult> resolve(Request request) async {
return _parse<ResolveResult>(
() => _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),
(data) => data as String);
}
Future<List<Task>> getTasks(List<Status> statuses) async {
return _parse<List<Task>>(
() => _client.dio.get(
"/api/v1/tasks?${statuses.map((e) => "status=${e.name}").join("&")}"),
(data) => (data as List).map((e) => Task.fromJson(e)).toList());
}
Future<void> pauseTask(String id) async {
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);
}
Future<void> pauseAllTasks() async {
return _parse(() => _client.dio.put("/api/v1/tasks/pause"), null);
}
Future<void> continueAllTasks() async {
return _parse(() => _client.dio.put("/api/v1/tasks/continue"), null);
}
Future<void> deleteTask(String id, bool force) async {
return _parse(
() => _client.dio.delete("/api/v1/tasks/$id?force=$force"), null);
}
Future<DownloaderConfig> getConfig() async {
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);
}
Future<void> installExtension(InstallExtension installExtension) async {
return _parse(
() => _client.dio.post("/api/v1/extensions", data: installExtension),
null);
}
Future<List<Extension>> getExtensions() async {
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",
data: updateExtensionSettings),
null);
}
Future<void> switchExtension(
String identity, SwitchExtension switchExtension) async {
return _parse(
() => _client.dio
.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);
}
Future<UpdateCheckExtensionResp> upgradeCheckExtension(String identity) async {
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);
}
Future<Response<String>> proxyRequest<T>(String uri,
{data, Options? options}) async {
options ??= Options();
options.headers ??= {};
options.headers!["X-Target-Uri"] = uri;
// add timestamp to avoid cache
return _client.dio.request(
"/api/v1/proxy?t=${DateTime.now().millisecondsSinceEpoch}",
data: data,
options: options);
}
String join(String path) {
return "${_client.dio.options.baseUrl}/${Util.cleanPath(path)}";
}
+6
View File
@@ -0,0 +1,6 @@
class ApiException implements Exception {
final int code;
final String message;
ApiException(this.code, this.message);
}
@@ -0,0 +1,25 @@
import 'package:dio/dio.dart';
import '../api_exception.dart';
import '../model/result.dart';
mixin LibgopeedBootBase {
Dio createDio() {
Dio dio = Dio();
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);
return dio;
}
T handleResult<T>(
Result<T> result,
) {
if (result.code == 0) {
return result.data as T;
} else {
throw ApiException(result.code, result.msg ?? '');
}
}
}
@@ -0,0 +1,245 @@
import 'dart:async';
import 'dart:convert';
import 'dart:ffi';
import 'dart:io';
import 'package:dio/dio.dart';
import '../../api/model/create_task.dart';
import '../../api/model/create_task_batch.dart';
import '../../api/model/downloader_config.dart';
import '../../api/model/extension.dart';
import '../../api/model/http_listen_result.dart';
import '../../api/model/info.dart';
import '../../api/model/install_extension.dart';
import '../../api/model/request.dart';
import '../../api/model/resolve_result.dart';
import '../../api/model/result.dart';
import '../../api/model/switch_extension.dart';
import '../../api/model/task.dart';
import '../../api/model/task_filter.dart';
import '../../api/model/update_check_extension_resp.dart';
import '../../api/model/update_extension_settings.dart';
import '../../util/extensions.dart';
import '../../util/util.dart';
import '../libgopeed_boot.dart';
import '../native/channel/libgopeed_channel.dart';
import '../native/ffi/libgopeed_bind.dart';
import '../native/ffi/libgopeed_ffi.dart';
import '../native/libgopeed_interface.dart';
import '../native/model/invoke_request.dart';
import '../native/model/start_config.dart';
import 'libgopeed_boot_base.dart';
LibgopeedBoot create() => LibgopeedBootNative();
class LibgopeedBootNative with LibgopeedBootBase implements LibgopeedBoot {
late LibgopeedAbi _libgopeedAbi;
late int _apiInstance;
late Dio _dio;
LibgopeedBootNative() {
if (Util.isDesktop()) {
var libName = "libgopeed.";
if (Platform.isWindows) {
libName += "dll";
}
if (Platform.isMacOS) {
libName += "dylib";
}
if (Platform.isLinux) {
libName += "so";
}
_libgopeedAbi = LibgopeedFFi(LibgopeedBind(DynamicLibrary.open(libName)));
} else {
_libgopeedAbi = LibgopeedChannel();
}
}
Future<T> _invoke<T>(
String method,
List<dynamic> params,
T Function(dynamic json)? fromJsonT,
) async {
final invokeRequest = InvokeRequest(
method: method,
params: params.map((e) {
if (e.toJson != null) {
return jsonEncode(e.toJson());
}
return jsonEncode(e);
}).toList());
final resp =
await _libgopeedAbi.invoke(_apiInstance, jsonEncode(invokeRequest));
final result =
Result<T>.fromJson(jsonDecode(resp), fromJsonT ??= (json) => null as T);
return handleResult(result);
}
List<dynamic> _parseTaskFilter(TaskFilter? filter) {
if (filter == null) {
return [];
}
return [filter];
}
@override
Future<void> init(StartConfig cfg) async {
final result = await _libgopeedAbi.create(jsonEncode(cfg.toJson()));
_apiInstance =
Result.fromJson(jsonDecode(result), (data) => data as int).data!;
_dio = createDio();
}
@override
Future<HttpListenResult> startHttp() async {
return _invoke<HttpListenResult>(
"startHttp", [], (json) => HttpListenResult.fromJson(json));
}
@override
Future<void> stopHttp() async {
await _invoke<void>("stopHttp", [], null);
}
@override
Future<void> restartHttp() async {
await _invoke<void>("restartHttp", [], null);
}
@override
Future<Info> info() async {
return _invoke<Info>("info", [], (json) => Info.fromJson(json));
}
@override
Future<ResolveResult> resolve(Request request) async {
return _invoke<ResolveResult>(
"resolve", [request], (json) => ResolveResult.fromJson(json));
}
@override
Future<String> createTask(CreateTask createTask) async {
return _invoke<String>(
"createTask", [createTask], (json) => json as String);
}
@override
Future<String> createTaskBatch(CreateTaskBatch createTask) async {
return _invoke<String>(
"createTaskBatch", [createTask], (json) => json as String);
}
@override
Future<void> pauseTask(String id) async {
await _invoke<void>("pauseTask", [id], null);
}
@override
Future<void> pauseTasks(TaskFilter? filter) async {
await _invoke<void>("pauseTasks", _parseTaskFilter(filter), null);
}
@override
Future<void> continueTask(String id) async {
await _invoke<void>("continueTask", [id], null);
}
@override
Future<void> continueTasks(TaskFilter? filter) async {
await _invoke<void>("continueTasks", _parseTaskFilter(filter), null);
}
@override
Future<void> deleteTask(String id, bool force) async {
await _invoke<void>("deleteTask", [id, force], null);
}
@override
Future<void> deleteTasks(TaskFilter? filter, bool force) async {
await _invoke<void>(
"deleteTasks",
_parseTaskFilter(filter).apply((it) {
it.add(force);
}),
null);
}
@override
Future<Task> getTask(String id) async {
return _invoke<Task>("getTask", [id], (json) => Task.fromJson(json));
}
@override
Future<List<Task>> getTasks(TaskFilter? filter) async {
return _invoke<List<Task>>("getTasks", _parseTaskFilter(filter),
(json) => json.map((e) => Task.fromJson(e)).toList());
}
@override
Future<Map<String, dynamic>> getTaskStats(String id) async {
return _invoke<Map<String, dynamic>>(
"getTaskStats", [id], (json) => json as Map<String, dynamic>);
}
@override
Future<DownloaderConfig> getConfig() async {
return _invoke<DownloaderConfig>(
"getConfig", [], (json) => DownloaderConfig.fromJson(json));
}
@override
Future<void> putConfig(DownloaderConfig config) async {
await _invoke<void>("putConfig", [config], null);
}
@override
Future<void> installExtension(InstallExtension installExtension) async {
await _invoke<void>("installExtension", [installExtension], null);
}
@override
Future<List<Extension>> getExtensions() async {
return _invoke<List<Extension>>("getExtensions", [],
(json) => json.map((e) => Extension.fromJson(e)).toList());
}
@override
Future<void> updateExtensionSettings(
String identity, UpdateExtensionSettings updateExtensionSettings) async {
await _invoke<void>(
"updateExtensionSettings", [identity, updateExtensionSettings], null);
}
@override
Future<void> switchExtension(
String identity, SwitchExtension switchExtension) async {}
@override
Future<void> deleteExtension(String identity) async {
await _invoke<void>("deleteExtension", [identity], null);
}
@override
Future<UpdateCheckExtensionResp> upgradeCheckExtension(
String identity) async {
return _invoke<UpdateCheckExtensionResp>("upgradeCheckExtension",
[identity], (json) => UpdateCheckExtensionResp.fromJson(json));
}
@override
Future<void> upgradeExtension(String identity) async {
await _invoke<void>("upgradeExtension", [identity], null);
}
@override
Future<void> close() async {
await _invoke<void>("close", [], null);
}
@override
Future<Response<String>> proxyRequest<T>(String uri,
{data, Options? options}) {
return _dio.request(uri, data: data, options: options);
}
}
@@ -0,0 +1,275 @@
import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
import '../../api/model/create_task.dart';
import '../../api/model/create_task_batch.dart';
import '../../api/model/downloader_config.dart';
import '../../api/model/extension.dart';
import '../../api/model/http_listen_result.dart';
import '../../api/model/info.dart';
import '../../api/model/install_extension.dart';
import '../../api/model/request.dart';
import '../../api/model/resolve_result.dart';
import '../../api/model/result.dart';
import '../../api/model/switch_extension.dart';
import '../../api/model/task.dart';
import '../../api/model/task_filter.dart';
import '../../api/model/update_check_extension_resp.dart';
import '../../api/model/update_extension_settings.dart';
import '../../util/extensions.dart';
import '../../util/util.dart';
import '../api_exception.dart';
import '../libgopeed_boot.dart';
import '../native/model/start_config.dart';
import 'libgopeed_boot_base.dart';
LibgopeedBoot create() => LibgopeedBootWeb();
class LibgopeedBootWeb with LibgopeedBootBase implements LibgopeedBoot {
late Dio _dio;
static const String _apiPrefix = '/api/v1';
LibgopeedBootWeb();
Future<T> _fetch<T>(
Future<Response> Function() fetch,
T Function(dynamic json)? fromJsonT,
) async {
try {
var resp = await fetch();
fromJsonT ??= (json) => null as T;
final result = Result<T>.fromJson(resp.data, fromJsonT);
return handleResult(result);
} on DioException catch (e) {
if (e.type == DioExceptionType.sendTimeout ||
e.type == DioExceptionType.receiveTimeout ||
e.type == DioExceptionType.connectionTimeout) {
throw ApiException(1000, 'request timeout');
}
throw ApiException(1000, e.message ?? "");
}
}
Map<String, dynamic>? _parseTaskFilter(TaskFilter? filter) {
if (filter == null) {
return null;
}
Map<String, dynamic> params = {};
if (filter.ids != null) {
params['ids'] = filter.ids;
}
if (filter.statuses != null) {
params['statuses'] = filter.statuses;
}
if (filter.notStatuses != null) {
params['notStatuses'] = filter.notStatuses;
}
if (params.isEmpty) {
return null;
}
return params;
}
@override
Future<void> init(StartConfig cfg) async {
_dio = createDio();
_dio.options.baseUrl = kDebugMode ? 'http://127.0.0.1:9999' : '';
}
@override
Future<HttpListenResult> startHttp() async {
throw UnimplementedError();
}
@override
Future<void> stopHttp() async {
throw UnimplementedError();
}
@override
Future<void> restartHttp() async {
throw UnimplementedError();
}
@override
Future<Info> info() {
return _fetch<Info>(
() => _dio.get("$_apiPrefix/info"), (data) => Info.fromJson(data));
}
@override
Future<ResolveResult> resolve(Request request) async {
return _fetch<ResolveResult>(
() => _dio.post("$_apiPrefix/resolve", data: request),
(data) => ResolveResult.fromJson(data));
}
@override
Future<String> createTask(CreateTask createTask) async {
return _fetch<String>(
() => _dio.post("$_apiPrefix/tasks", data: createTask),
(data) => data as String);
}
@override
Future<String> createTaskBatch(CreateTaskBatch createTask) async {
return _fetch<String>(
() => _dio.post("$_apiPrefix/tasks", data: createTask),
(data) => data as String);
}
@override
Future<void> pauseTask(String id) async {
await _fetch<void>(
() => _dio.put("$_apiPrefix/tasks/$id/pause", data: {}), null);
}
@override
Future<void> pauseTasks(TaskFilter? filter) async {
await _fetch<void>(
() => _dio.put("$_apiPrefix/tasks/pause",
queryParameters: _parseTaskFilter(filter)),
null);
}
@override
Future<void> continueTask(String id) async {
await _fetch<void>(
() => _dio.put("$_apiPrefix/tasks/$id/continue", data: {}), null);
}
@override
Future<void> continueTasks(TaskFilter? filter) async {
await _fetch<void>(
() => _dio.put("$_apiPrefix/tasks/continue",
queryParameters: _parseTaskFilter(filter)),
null);
}
@override
Future<void> deleteTask(String id, bool force) async {
await _fetch<void>(
() => _dio
.delete("$_apiPrefix/tasks/$id", queryParameters: {'force': force}),
null);
}
@override
Future<void> deleteTasks(TaskFilter? filter, bool force) async {
await _fetch<void>(
() => _dio.delete("$_apiPrefix/tasks",
queryParameters: _parseTaskFilter(filter)?.apply((it) {
it['force'] = force;
})),
null);
}
@override
Future<Task> getTask(String id) async {
return _fetch<Task>(
() => _dio.get("$_apiPrefix/tasks/$id"), (data) => Task.fromJson(data));
}
@override
Future<List<Task>> getTasks(TaskFilter? filter) async {
return _fetch<List<Task>>(
() => _dio.get("$_apiPrefix/tasks",
queryParameters: _parseTaskFilter(filter)),
(data) => (data as List).map((e) => Task.fromJson(e)).toList());
}
@override
Future<Map<String, dynamic>> getTaskStats(String id) async {
return _fetch<Map<String, dynamic>>(
() => _dio.get("$_apiPrefix/tasks/$id/stats"),
(data) => data as Map<String, dynamic>);
}
@override
Future<DownloaderConfig> getConfig() async {
return _fetch<DownloaderConfig>(() => _dio.get("$_apiPrefix/config"),
(data) => DownloaderConfig.fromJson(data));
}
@override
Future<void> putConfig(DownloaderConfig config) async {
await _fetch<void>(
() => _dio.put("$_apiPrefix/config", data: config), null);
}
@override
Future<void> installExtension(InstallExtension installExtension) async {
await _fetch<void>(
() => _dio.post("$_apiPrefix/extensions", data: installExtension),
null);
}
@override
Future<List<Extension>> getExtensions() async {
return _fetch<List<Extension>>(() => _dio.get("$_apiPrefix/extensions"),
(data) => (data as List).map((e) => Extension.fromJson(e)).toList());
}
@override
Future<void> updateExtensionSettings(
String identity, UpdateExtensionSettings updateExtensionSettings) async {
await _fetch<void>(
() => _dio.put("$_apiPrefix/extensions/$identity/settings",
data: updateExtensionSettings),
null);
}
@override
Future<void> switchExtension(
String identity, SwitchExtension switchExtension) async {
await _fetch<void>(
() => _dio.put("$_apiPrefix/extensions/$identity/switch",
data: switchExtension),
null);
}
@override
Future<void> deleteExtension(String identity) async {
await _fetch<void>(
() => _dio.delete("$_apiPrefix/extensions/$identity"), null);
}
@override
Future<UpdateCheckExtensionResp> upgradeCheckExtension(
String identity) async {
return _fetch<UpdateCheckExtensionResp>(
() => _dio.get("$_apiPrefix/extensions/$identity/upgrade"),
(data) => UpdateCheckExtensionResp.fromJson(data));
}
@override
Future<void> upgradeExtension(String identity) async {
await _fetch<void>(
() => _dio.post("$_apiPrefix/extensions/$identity/upgrade"), null);
}
@override
Future<void> close() async {
throw UnimplementedError();
}
// To avoid CORS, use a backend proxy to forward the request
@override
Future<Response<String>> proxyRequest<T>(String uri,
{data, Options? options}) async {
options ??= Options();
options.headers ??= {};
options.headers!["X-Target-Uri"] = uri;
// add timestamp to avoid cache
return _dio.request(
"/api/v1/proxy?t=${DateTime.now().millisecondsSinceEpoch}",
data: data,
options: options);
}
String join(String path) {
return "${_dio.options.baseUrl}/${Util.cleanPath(path)}";
}
}
@@ -1,9 +1,9 @@
import 'common/start_config.dart';
import "libgopeed_boot_stub.dart"
if (dart.library.html) 'entry/libgopeed_boot_browser.dart'
if (dart.library.io) 'entry/libgopeed_boot_native.dart';
import 'native/libgopeed_interface.dart';
abstract class LibgopeedBoot {
abstract class LibgopeedBoot implements LibgopeedApi {
static LibgopeedBoot? _instance;
static LibgopeedBoot get instance {
@@ -12,8 +12,4 @@ abstract class LibgopeedBoot {
}
factory LibgopeedBoot() => create();
Future<int> start(StartConfig cfg);
Future<void> stop();
}
@@ -0,0 +1,20 @@
import 'package:gopeed/api/model/extension.dart';
import 'package:gopeed/api/model/request.dart';
import 'package:json_annotation/json_annotation.dart';
part 'create_task_batch.g.dart';
@JsonSerializable()
class CreateTaskBatch {
List<Request> reqs;
Option? opt;
CreateTaskBatch({
this.reqs = const [],
this.opt,
});
factory CreateTaskBatch.fromJson(Map<String, dynamic> json) =>
_$CreateTaskBatchFromJson(json);
Map<String, dynamic> toJson() => _$CreateTaskBatchToJson(this);
}
@@ -0,0 +1,33 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'create_task_batch.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
CreateTaskBatch _$CreateTaskBatchFromJson(Map<String, dynamic> json) =>
CreateTaskBatch(
reqs: (json['reqs'] as List<dynamic>?)
?.map((e) => Request.fromJson(e as Map<String, dynamic>))
.toList() ??
const [],
opt: json['opt'] == null
? null
: Option.fromJson(json['opt'] as Map<String, dynamic>),
);
Map<String, dynamic> _$CreateTaskBatchToJson(CreateTaskBatch instance) {
final val = <String, dynamic>{
'reqs': instance.reqs,
};
void writeNotNull(String key, dynamic value) {
if (value != null) {
val[key] = value;
}
}
writeNotNull('opt', instance.opt);
return val;
}
@@ -0,0 +1,18 @@
import 'package:json_annotation/json_annotation.dart';
part 'http_listen_result.g.dart';
@JsonSerializable()
class HttpListenResult {
String host;
int port;
HttpListenResult({
this.host = '',
this.port = 0,
});
factory HttpListenResult.fromJson(Map<String, dynamic> json) =>
_$HttpListenResultFromJson(json);
Map<String, dynamic> toJson() => _$HttpListenResultToJson(this);
}
@@ -0,0 +1,19 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'http_listen_result.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
HttpListenResult _$HttpListenResultFromJson(Map<String, dynamic> json) =>
HttpListenResult(
host: json['host'] as String? ?? '',
port: (json['port'] as num?)?.toInt() ?? 0,
);
Map<String, dynamic> _$HttpListenResultToJson(HttpListenResult instance) =>
<String, dynamic>{
'host': instance.host,
'port': instance.port,
};
+23
View File
@@ -0,0 +1,23 @@
import 'package:json_annotation/json_annotation.dart';
part 'info.g.dart';
@JsonSerializable()
class Info {
String version;
String runtime;
String os;
String arch;
bool inDocker;
Info({
this.version = '',
this.runtime = '',
this.os = '',
this.arch = '',
this.inDocker = false,
});
factory Info.fromJson(Map<String, dynamic> json) => _$InfoFromJson(json);
Map<String, dynamic> toJson() => _$InfoToJson(this);
}
+23
View File
@@ -0,0 +1,23 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'info.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
Info _$InfoFromJson(Map<String, dynamic> json) => Info(
version: json['version'] as String? ?? '',
runtime: json['runtime'] as String? ?? '',
os: json['os'] as String? ?? '',
arch: json['arch'] as String? ?? '',
inDocker: json['inDocker'] as bool? ?? false,
);
Map<String, dynamic> _$InfoToJson(Info instance) => <String, dynamic>{
'version': instance.version,
'runtime': instance.runtime,
'os': instance.os,
'arch': instance.arch,
'inDocker': instance.inDocker,
};
+2 -2
View File
@@ -10,7 +10,7 @@ Options _$OptionsFromJson(Map<String, dynamic> json) => Options(
name: json['name'] as String,
path: json['path'] as String,
selectFiles: (json['selectFiles'] as List<dynamic>?)
?.map((e) => e as int)
?.map((e) => (e as num).toInt())
.toList() ??
const [],
extra: json['extra'],
@@ -35,7 +35,7 @@ Map<String, dynamic> _$OptionsToJson(Options instance) {
OptsExtraHttp _$OptsExtraHttpFromJson(Map<String, dynamic> json) =>
OptsExtraHttp()
..connections = json['connections'] as int
..connections = (json['connections'] as num).toInt()
..autoTorrent = json['autoTorrent'] as bool;
Map<String, dynamic> _$OptsExtraHttpToJson(OptsExtraHttp instance) =>
+1 -1
View File
@@ -11,7 +11,7 @@ Result<T> _$ResultFromJson<T>(
T Function(Object? json) fromJsonT,
) =>
Result<T>(
code: json['code'] as int,
code: (json['code'] as num).toInt(),
msg: json['msg'] as String?,
data: _$nullableGenericFromJson(json['data'], fromJsonT),
);
+17
View File
@@ -0,0 +1,17 @@
import 'package:gopeed/api/model/task.dart';
import 'package:json_annotation/json_annotation.dart';
part 'task_filter.g.dart';
@JsonSerializable()
class TaskFilter {
List<String>? ids;
List<Status>? statuses;
List<Status>? notStatuses;
TaskFilter({this.ids, this.statuses, this.notStatuses});
factory TaskFilter.fromJson(Map<String, dynamic> json) =>
_$TaskFilterFromJson(json);
Map<String, dynamic> toJson() => _$TaskFilterToJson(this);
}
@@ -0,0 +1,43 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'task_filter.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
TaskFilter _$TaskFilterFromJson(Map<String, dynamic> json) => TaskFilter(
ids: (json['ids'] as List<dynamic>?)?.map((e) => e as String).toList(),
statuses: (json['statuses'] as List<dynamic>?)
?.map((e) => $enumDecode(_$StatusEnumMap, e))
.toList(),
notStatuses: (json['notStatuses'] as List<dynamic>?)
?.map((e) => $enumDecode(_$StatusEnumMap, e))
.toList(),
);
Map<String, dynamic> _$TaskFilterToJson(TaskFilter instance) {
final val = <String, dynamic>{};
void writeNotNull(String key, dynamic value) {
if (value != null) {
val[key] = value;
}
}
writeNotNull('ids', instance.ids);
writeNotNull(
'statuses', instance.statuses?.map((e) => _$StatusEnumMap[e]!).toList());
writeNotNull('notStatuses',
instance.notStatuses?.map((e) => _$StatusEnumMap[e]!).toList());
return val;
}
const _$StatusEnumMap = {
Status.ready: 'ready',
Status.running: 'running',
Status.pause: 'pause',
Status.wait: 'wait',
Status.error: 'error',
Status.done: 'done',
};
@@ -0,0 +1,24 @@
import 'package:flutter/services.dart';
import '../libgopeed_interface.dart';
class LibgopeedChannel implements LibgopeedAbi {
static const _channel = MethodChannel('gopeed.com/libgopeed');
@override
Future<String> create(String cfg) async {
final result = await _channel.invokeMethod<String>('create', {
'cfg': cfg,
});
return result!;
}
@override
Future<String> invoke(int instance, String params) async {
final result = await _channel.invokeMethod<String>('invoke', {
'instance': instance,
'params': params,
});
return result!;
}
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,36 @@
import 'dart:async';
import 'package:ffi/ffi.dart';
import '../libgopeed_interface.dart';
import 'libgopeed_bind.dart';
class LibgopeedFFi implements LibgopeedAbi {
late LibgopeedBind _libgopeed;
LibgopeedFFi(LibgopeedBind libgopeed) {
_libgopeed = libgopeed;
}
@override
Future<String> create(String cfg) {
final cfgPtr = cfg.toNativeUtf8();
try {
final result = _libgopeed.Create(cfgPtr.cast());
return Future.value(result.cast<Utf8>().toDartString());
} finally {
calloc.free(cfgPtr);
}
}
@override
Future<String> invoke(int instance, String params) {
final paramsPtr = params.toNativeUtf8();
try {
final result = _libgopeed.Invoke(instance, paramsPtr.cast());
return Future.value(result.cast<Utf8>().toDartString());
} finally {
calloc.free(paramsPtr);
}
}
}
@@ -0,0 +1,83 @@
import 'package:dio/dio.dart';
import '../model/create_task.dart';
import '../model/create_task_batch.dart';
import '../model/downloader_config.dart';
import '../model/extension.dart';
import '../model/http_listen_result.dart';
import '../model/info.dart';
import '../model/install_extension.dart';
import '../model/request.dart';
import '../model/resolve_result.dart';
import '../model/switch_extension.dart';
import '../model/task.dart';
import '../model/task_filter.dart';
import '../model/update_check_extension_resp.dart';
import '../model/update_extension_settings.dart';
import 'model/start_config.dart';
abstract class LibgopeedAbi {
Future<String> create(String cfg);
Future<String> invoke(int instance, String params);
}
abstract class LibgopeedApi {
Future<void> init(StartConfig cfg);
Future<HttpListenResult> startHttp();
Future<void> stopHttp();
Future<void> restartHttp();
Future<Info> info();
Future<ResolveResult> resolve(Request request);
Future<String> createTask(CreateTask createTask);
Future<String> createTaskBatch(CreateTaskBatch createTask);
Future<void> pauseTask(String id);
Future<void> pauseTasks(TaskFilter? filter);
Future<void> continueTask(String id);
Future<void> continueTasks(TaskFilter? filter);
Future<void> deleteTask(String id, bool force);
Future<void> deleteTasks(TaskFilter? filter, bool force);
Future<Task> getTask(String id);
Future<List<Task>> getTasks(TaskFilter? filter);
Future<Map<String, dynamic>> getTaskStats(String id);
Future<DownloaderConfig> getConfig();
Future<void> putConfig(DownloaderConfig config);
Future<void> installExtension(InstallExtension installExtension);
Future<List<Extension>> getExtensions();
Future<void> updateExtensionSettings(
String identity, UpdateExtensionSettings updateExtensionSettings);
Future<void> switchExtension(
String identity, SwitchExtension switchExtension);
Future<void> deleteExtension(String identity);
Future<UpdateCheckExtensionResp> upgradeCheckExtension(String identity);
Future<void> upgradeExtension(String identity);
Future<void> close();
Future<Response<String>> proxyRequest<T>(String uri,
{data, Options? options});
}
@@ -0,0 +1,19 @@
import 'package:json_annotation/json_annotation.dart';
part 'invoke_request.g.dart';
@JsonSerializable()
class InvokeRequest {
String method;
List<String> params;
InvokeRequest({
this.method = '',
this.params = const [],
});
factory InvokeRequest.fromJson(Map<String, dynamic> json) =>
_$InvokeRequestFromJson(json);
Map<String, dynamic> toJson() => _$InvokeRequestToJson(this);
}
@@ -0,0 +1,22 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'invoke_request.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
InvokeRequest _$InvokeRequestFromJson(Map<String, dynamic> json) =>
InvokeRequest(
method: json['method'] as String? ?? '',
params: (json['params'] as List<dynamic>?)
?.map((e) => e as String)
.toList() ??
const [],
);
Map<String, dynamic> _$InvokeRequestToJson(InvokeRequest instance) =>
<String, dynamic>{
'method': instance.method,
'params': instance.params,
};
@@ -4,14 +4,13 @@ part 'start_config.g.dart';
@JsonSerializable()
class StartConfig {
late String network;
late String address;
late String storage;
late String storageDir;
late int refreshInterval;
late String apiToken;
String storage;
String storageDir;
StartConfig();
StartConfig({
this.storage = '',
this.storageDir = '',
});
factory StartConfig.fromJson(Map<String, dynamic> json) =>
_$StartConfigFromJson(json);
@@ -6,20 +6,13 @@ part of 'start_config.dart';
// JsonSerializableGenerator
// **************************************************************************
StartConfig _$StartConfigFromJson(Map<String, dynamic> json) => StartConfig()
..network = json['network'] as String
..address = json['address'] as String
..storage = json['storage'] as String
..storageDir = json['storageDir'] as String
..refreshInterval = json['refreshInterval'] as int
..apiToken = json['apiToken'] as String;
StartConfig _$StartConfigFromJson(Map<String, dynamic> json) => StartConfig(
storage: json['storage'] as String? ?? '',
storageDir: json['storageDir'] as String? ?? '',
);
Map<String, dynamic> _$StartConfigToJson(StartConfig instance) =>
<String, dynamic>{
'network': instance.network,
'address': instance.address,
'storage': instance.storage,
'storageDir': instance.storageDir,
'refreshInterval': instance.refreshInterval,
'apiToken': instance.apiToken,
};
@@ -14,10 +14,8 @@ import 'package:uri_to_file/uri_to_file.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:window_manager/window_manager.dart';
import '../../../../api/api.dart';
import '../../../../api/libgopeed_boot.dart';
import '../../../../api/model/downloader_config.dart';
import '../../../../core/common/start_config.dart';
import '../../../../core/libgopeed_boot.dart';
import '../../../../database/database.dart';
import '../../../../database/entity.dart';
import '../../../../i18n/message.dart';
@@ -74,12 +72,9 @@ final allTrackerSubscribeUrlCdns = Map.fromIterable(allTrackerSubscribeUrls,
});
class AppController extends GetxController with WindowListener, TrayListener {
static StartConfig? _defaultStartConfig;
final autoStartup = false.obs;
final startConfig = StartConfig().obs;
final runningPort = 0.obs;
final downloaderConfig = DownloaderConfig().obs;
final httpRunningPort = 0.obs;
late AppLinks _appLinks;
StreamSubscription<Uri>? _linkSubscription;
@@ -111,7 +106,7 @@ class AppController extends GetxController with WindowListener, TrayListener {
void onClose() {
_linkSubscription?.cancel();
trayManager.removeListener(this);
LibgopeedBoot.instance.stop();
LibgopeedBoot.instance.close();
}
@override
@@ -218,11 +213,12 @@ class AppController extends GetxController with WindowListener, TrayListener {
),
MenuItem(
label: "startAll".tr,
onClick: (menuItem) async => {continueAllTasks()},
onClick: (menuItem) async =>
{LibgopeedBoot.instance.continueTasks(null)},
),
MenuItem(
label: "pauseAll".tr,
onClick: (menuItem) async => {pauseAllTasks()},
onClick: (menuItem) async => {LibgopeedBoot.instance.pauseTasks(null)},
),
MenuItem(
label: 'setting'.tr,
@@ -247,7 +243,7 @@ class AppController extends GetxController with WindowListener, TrayListener {
label: 'exit'.tr,
onClick: (menuItem) async {
try {
await LibgopeedBoot.instance.stop();
await LibgopeedBoot.instance.close();
} catch (e) {
logger.w("libgopeed stop fail", e);
}
@@ -313,43 +309,9 @@ class AppController extends GetxController with WindowListener, TrayListener {
await Get.rootDelegate.offAndToNamed(Routes.CREATE, arguments: path);
}
String runningAddress() {
if (startConfig.value.network == 'unix') {
return startConfig.value.address;
}
return '${startConfig.value.address.split(':').first}:$runningPort';
}
Future<StartConfig> _initDefaultStartConfig() async {
if (_defaultStartConfig != null) {
return _defaultStartConfig!;
}
_defaultStartConfig = StartConfig();
if (!Util.supportUnixSocket()) {
// not support unix socket, use tcp
_defaultStartConfig!.network = "tcp";
_defaultStartConfig!.address = "127.0.0.1:0";
} else {
_defaultStartConfig!.network = "unix";
_defaultStartConfig!.address =
"${(await getTemporaryDirectory()).path}/$unixSocketPath";
}
_defaultStartConfig!.apiToken = '';
return _defaultStartConfig!;
}
Future<StartConfig> loadStartConfig() async {
final defaultCfg = await _initDefaultStartConfig();
final saveCfg = Database.instance.getStartConfig();
startConfig.value.network = saveCfg?.network ?? defaultCfg.network;
startConfig.value.address = saveCfg?.address ?? defaultCfg.address;
startConfig.value.apiToken = saveCfg?.apiToken ?? defaultCfg.apiToken;
return startConfig.value;
}
Future<DownloaderConfig> loadDownloaderConfig() async {
try {
downloaderConfig.value = await getConfig();
downloaderConfig.value = await LibgopeedBoot.instance.getConfig();
} catch (e) {
logger.w("load downloader config fail", e, StackTrace.current);
downloaderConfig.value = DownloaderConfig();
@@ -382,7 +344,7 @@ class AppController extends GetxController with WindowListener, TrayListener {
});
refreshTrackers();
await saveConfig();
await LibgopeedBoot.instance.putConfig(downloaderConfig.value);
}
refreshTrackers() {
@@ -411,7 +373,7 @@ class AppController extends GetxController with WindowListener, TrayListener {
}
Future<List<String>> _fetchTrackers(String subscribeUrl) async {
final resp = await proxyRequest(subscribeUrl);
final resp = await LibgopeedBoot.instance.proxyRequest(subscribeUrl);
if (resp.statusCode != 200) {
throw Exception(
'Failed to get trackers, status code: ${resp.statusCode}');
@@ -468,12 +430,4 @@ class AppController extends GetxController with WindowListener, TrayListener {
args: ['--${Args.flagHidden}']);
autoStartup.value = await launchAtStartup.isEnabled();
}
Future<void> saveConfig() async {
Database.instance.saveStartConfig(StartConfigEntity(
network: startConfig.value.network,
address: startConfig.value.address,
apiToken: startConfig.value.apiToken));
await putConfig(downloaderConfig.value);
}
}
@@ -7,7 +7,7 @@ import 'package:get/get.dart';
import 'package:path/path.dart' as path;
import 'package:rounded_loading_button_plus/rounded_loading_button.dart';
import '../../../../api/api.dart';
import '../../../../api/libgopeed_boot.dart';
import '../../../../api/model/create_task.dart';
import '../../../../api/model/options.dart';
import '../../../../api/model/request.dart';
@@ -594,7 +594,7 @@ class CreateView extends GetView<CreateController> {
final isDirect = controller.directDownload.value || isMultiLine;
if (isDirect) {
await Future.wait(urls.map((url) {
return createTask(CreateTask(
return LibgopeedBoot.instance.createTask(CreateTask(
req: Request(
url: url,
extra: parseReqExtra(url),
@@ -610,7 +610,7 @@ class CreateView extends GetView<CreateController> {
}));
Get.rootDelegate.offNamed(Routes.TASK);
} else {
final rr = await resolve(Request(
final rr = await LibgopeedBoot.instance.resolve(Request(
url: submitUrl,
extra: parseReqExtra(_urlController.text),
proxy: parseProxy(),
@@ -741,23 +741,28 @@ class CreateView extends GetView<CreateController> {
await Future.wait(
controller.selectedIndexes.map((index) {
final file = rr.res.files[index];
return createTask(CreateTask(
req: file.req!..proxy = parseProxy(),
opt: Options(
name: file.name,
path: path.join(_pathController.text,
rr.res.name, file.path),
selectFiles: [],
extra: optExtra)));
return LibgopeedBoot.instance.createTask(
CreateTask(
req: file.req!..proxy = parseProxy(),
opt: Options(
name: file.name,
path: path.join(
_pathController.text,
rr.res.name,
file.path),
selectFiles: [],
extra: optExtra)));
}));
} else {
await createTask(CreateTask(
rid: rr.id,
opt: Options(
name: _renameController.text,
path: _pathController.text,
selectFiles: controller.selectedIndexes,
extra: optExtra)));
await LibgopeedBoot.instance.createTask(
CreateTask(
rid: rr.id,
opt: Options(
name: _renameController.text,
path: _pathController.text,
selectFiles:
controller.selectedIndexes,
extra: optExtra)));
}
Get.back();
Get.rootDelegate.offNamed(Routes.TASK);
@@ -1,6 +1,6 @@
import 'package:get/get.dart';
import 'package:gopeed/api/api.dart';
import '../../../../api/libgopeed_boot.dart';
import '../../../../api/model/extension.dart';
class ExtensionController extends GetxController {
@@ -17,12 +17,13 @@ class ExtensionController extends GetxController {
}
Future<void> load() async {
extensions.value = await getExtensions();
extensions.value = await LibgopeedBoot.instance.getExtensions();
}
Future<void> checkUpdate() async {
for (final ext in extensions) {
final resp = await upgradeCheckExtension(ext.identity);
final resp =
await LibgopeedBoot.instance.upgradeCheckExtension(ext.identity);
if (resp.newVersion.isNotEmpty) {
updateFlags[ext.identity] = resp.newVersion;
}
@@ -11,7 +11,8 @@ import 'package:path/path.dart' as path;
import 'package:rounded_loading_button_plus/rounded_loading_button.dart';
import 'package:url_launcher/url_launcher.dart';
import '../../../../api/api.dart';
import '../../../../api/entry/libgopeed_boot_web.dart';
import '../../../../api/libgopeed_boot.dart';
import '../../../../api/model/extension.dart';
import '../../../../api/model/install_extension.dart';
import '../../../../api/model/switch_extension.dart';
@@ -78,8 +79,9 @@ class ExtensionView extends GetView<ExtensionController> {
}
_installBtnController.start();
try {
await installExtension(InstallExtension(
url: _installUrlController.text));
await LibgopeedBoot.instance.installExtension(
InstallExtension(
url: _installUrlController.text));
Get.snackbar('tip'.tr, 'extensionInstallSuccess'.tr);
await controller.load();
} catch (e) {
@@ -98,7 +100,7 @@ class ExtensionView extends GetView<ExtensionController> {
if (dir != null) {
MacSecureUtil.saveBookmark(dir);
try {
await installExtension(
await LibgopeedBoot.instance.installExtension(
InstallExtension(devMode: true, url: dir));
Get.snackbar(
'tip'.tr, 'extensionInstallSuccess'.tr);
@@ -133,8 +135,10 @@ class ExtensionView extends GetView<ExtensionController> {
)
: Util.isWeb()
? Image.network(
join(
'/fs/extensions/${extension.identity}/${extension.icon}'),
(LibgopeedBoot.instance
as LibgopeedBootWeb)
.join(
'/fs/extensions/${extension.identity}/${extension.icon}'),
width: 48,
height: 48,
)
@@ -155,9 +159,9 @@ class ExtensionView extends GetView<ExtensionController> {
value: !extension.disabled,
onChanged: (value) async {
try {
await switchExtension(
extension.identity,
SwitchExtension(status: value));
await LibgopeedBoot.instance
.switchExtension(extension.identity,
SwitchExtension(status: value));
await controller.load();
} catch (e) {
showErrorMessage(e);
@@ -354,10 +358,11 @@ class ExtensionView extends GetView<ExtensionController> {
try {
confrimController.start();
if (formKey.currentState?.saveAndValidate() == true) {
await updateExtensionSettings(
extension.identity,
UpdateExtensionSettings(
settings: formKey.currentState!.value));
await LibgopeedBoot.instance
.updateExtensionSettings(
extension.identity,
UpdateExtensionSettings(
settings: formKey.currentState!.value));
await controller.load();
Get.back();
}
@@ -454,7 +459,8 @@ class ExtensionView extends GetView<ExtensionController> {
),
onPressed: () async {
try {
await deleteExtension(extension.identity);
await LibgopeedBoot.instance
.deleteExtension(extension.identity);
await controller.load();
Get.back();
} catch (e) {
@@ -487,18 +493,19 @@ class ExtensionView extends GetView<ExtensionController> {
onPressed: () async {
confrimController.start();
try {
await updateExtension(extension.identity);
await LibgopeedBoot.instance
.upgradeExtension(extension.identity);
await controller.load();
controller.updateFlags.remove(extension.identity);
Get.back();
showMessage('tip'.tr, 'extensionUpdateSuccess'.tr);
showMessage('tip'.tr, 'extensionUpgradeSuccess'.tr);
} catch (e) {
showErrorMessage(e);
} finally {
confrimController.stop();
}
},
child: Text('newVersionUpdate'.tr),
child: Text('newVersionUpgrade'.tr),
),
],
));
@@ -1,7 +1,8 @@
import 'dart:convert';
import 'package:get/get.dart';
import 'package:gopeed/api/api.dart';
import '../../../../api/libgopeed_boot.dart';
class SettingController extends GetxController {
final tapStatues = <String, bool>{}.obs;
@@ -28,12 +29,13 @@ class SettingController extends GetxController {
void fetchLatestVersion() async {
String? releaseDataStr;
try {
releaseDataStr = (await proxyRequest(
releaseDataStr = (await LibgopeedBoot.instance.proxyRequest(
"https://api.github.com/repos/GopeedLab/gopeed/releases/latest"))
.data;
} catch (e) {
releaseDataStr =
(await proxyRequest("https://gopeed.com/api/release")).data;
releaseDataStr = (await LibgopeedBoot.instance
.proxyRequest("https://gopeed.com/api/release"))
.data;
}
if (releaseDataStr == null) {
return;
@@ -10,6 +10,7 @@ import 'package:intl/intl.dart';
import 'package:launch_at_startup/launch_at_startup.dart';
import 'package:url_launcher/url_launcher.dart';
import '../../../../api/libgopeed_boot.dart';
import '../../../../api/model/downloader_config.dart';
import '../../../../i18n/message.dart';
import '../../../../util/input_formatter.dart';
@@ -35,7 +36,6 @@ class SettingView extends GetView<SettingController> {
Widget build(BuildContext context) {
final appController = Get.find<AppController>();
final downloaderCfg = appController.downloaderConfig;
final startCfg = appController.startConfig;
Timer? timer;
Future<bool> debounceSave(
@@ -51,8 +51,8 @@ class SettingView extends GetView<SettingController> {
return;
}
}
appController
.saveConfig()
LibgopeedBoot.instance
.putConfig(downloaderCfg.value)
.then((_) => completer.complete(true))
.onError(completer.completeError);
if (needRestart) {
@@ -530,7 +530,7 @@ class SettingView extends GetView<SettingController> {
launchUrl(Uri.parse('https://gopeed.com'),
mode: LaunchMode.externalApplication);
},
child: Text('newVersionUpdate'.tr),
child: Text('newVersionUpgrade'.tr),
),
],
));
@@ -637,7 +637,7 @@ class SettingView extends GetView<SettingController> {
final portController = TextEditingController(text: port);
updateAddress() async {
final newAddress = '${ipController.text}:${portController.text}';
if (newAddress != startCfg.value.address) {
if (newAddress != proxy.host) {
proxy.host = newAddress;
await debounceSave();
@@ -718,152 +718,152 @@ class SettingView extends GetView<SettingController> {
);
// advanced config API items start
final buildApiProtocol = _buildConfigItem(
'protocol',
() => startCfg.value.network == 'tcp'
? 'TCP ${startCfg.value.address}'
: 'Unix',
(Key key) {
final items = <Widget>[
SizedBox(
width: 80,
child: DropdownButtonFormField<String>(
value: startCfg.value.network,
onChanged: Util.isDesktop()
? (value) async {
startCfg.update((val) {
val!.network = value!;
});
// final buildApiProtocol = _buildConfigItem(
// 'protocol',
// () => startCfg.value.host == 'tcp'
// ? 'TCP ${startCfg.value.address}'
// : 'Unix',
// (Key key) {
// final items = <Widget>[
// SizedBox(
// width: 80,
// child: DropdownButtonFormField<String>(
// value: startCfg.value.host,
// onChanged: Util.isDesktop()
// ? (value) async {
// startCfg.update((val) {
// val!.host = value!;
// });
await debounceSave(needRestart: true);
}
: null,
items: [
const DropdownMenuItem<String>(
value: 'tcp',
child: Text('TCP'),
),
Util.supportUnixSocket()
? const DropdownMenuItem<String>(
value: 'unix',
child: Text('Unix'),
)
: null,
].where((e) => e != null).map((e) => e!).toList(),
),
)
];
if (Util.isDesktop() && startCfg.value.network == 'tcp') {
final arr = startCfg.value.address.split(':');
var ip = '127.0.0.1';
var port = '0';
if (arr.length > 1) {
ip = arr[0];
port = arr[1];
}
// await debounceSave(needRestart: true);
// }
// : null,
// items: [
// const DropdownMenuItem<String>(
// value: 'tcp',
// child: Text('TCP'),
// ),
// Util.supportUnixSocket()
// ? const DropdownMenuItem<String>(
// value: 'unix',
// child: Text('Unix'),
// )
// : null,
// ].where((e) => e != null).map((e) => e!).toList(),
// ),
// )
// ];
// if (Util.isDesktop() && startCfg.value.host == 'tcp') {
// final arr = startCfg.value.address.split(':');
// var ip = '127.0.0.1';
// var port = '0';
// if (arr.length > 1) {
// ip = arr[0];
// port = arr[1];
// }
final ipController = TextEditingController(text: ip);
final portController = TextEditingController(text: port);
updateAddress() async {
if (ipController.text.isEmpty || portController.text.isEmpty) {
return;
}
final newAddress = '${ipController.text}:${portController.text}';
if (newAddress != startCfg.value.address) {
startCfg.value.address = newAddress;
// final ipController = TextEditingController(text: ip);
// final portController = TextEditingController(text: port);
// updateAddress() async {
// if (ipController.text.isEmpty || portController.text.isEmpty) {
// return;
// }
// final newAddress = '${ipController.text}:${portController.text}';
// if (newAddress != startCfg.value.address) {
// startCfg.value.address = newAddress;
final saved = await debounceSave(
check: () async {
// Check if address already in use
final configIp = ipController.text;
final configPort = int.parse(portController.text);
if (configPort == 0) {
return '';
}
try {
final socket = await Socket.connect(configIp, configPort,
timeout: const Duration(seconds: 3));
socket.close();
return 'portInUse'
.trParams({'port': configPort.toString()});
} catch (e) {
return '';
}
},
needRestart: true);
// final saved = await debounceSave(
// check: () async {
// // Check if address already in use
// final configIp = ipController.text;
// final configPort = int.parse(portController.text);
// if (configPort == 0) {
// return '';
// }
// try {
// final socket = await Socket.connect(configIp, configPort,
// timeout: const Duration(seconds: 3));
// socket.close();
// return 'portInUse'
// .trParams({'port': configPort.toString()});
// } catch (e) {
// return '';
// }
// },
// needRestart: true);
// If save failed, restore the old address
if (!saved) {
final oldAddress =
(await appController.loadStartConfig()).address;
startCfg.update((val) async {
val!.address = oldAddress;
});
}
}
}
// // If save failed, restore the old address
// if (!saved) {
// // final oldAddress =
// // (await appController.loadStartConfig()).address;
// // startCfg.update((val) async {
// // val!.address = oldAddress;
// // });
// }
// }
// }
ipController.addListener(updateAddress);
portController.addListener(updateAddress);
items.addAll([
const Padding(padding: EdgeInsets.only(left: 20)),
Flexible(
child: TextFormField(
controller: ipController,
decoration: const InputDecoration(
labelText: 'IP',
contentPadding: EdgeInsets.all(0.0),
),
keyboardType: TextInputType.number,
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp('[0-9.]')),
],
),
),
const Padding(padding: EdgeInsets.only(left: 10)),
Flexible(
child: TextFormField(
controller: portController,
decoration: InputDecoration(
labelText: 'port'.tr,
contentPadding: const EdgeInsets.all(0.0),
),
keyboardType: TextInputType.number,
inputFormatters: [
FilteringTextInputFormatter.digitsOnly,
NumericalRangeFormatter(min: 0, max: 65535),
],
),
),
]);
}
// ipController.addListener(updateAddress);
// portController.addListener(updateAddress);
// items.addAll([
// const Padding(padding: EdgeInsets.only(left: 20)),
// Flexible(
// child: TextFormField(
// controller: ipController,
// decoration: const InputDecoration(
// labelText: 'IP',
// contentPadding: EdgeInsets.all(0.0),
// ),
// keyboardType: TextInputType.number,
// inputFormatters: [
// FilteringTextInputFormatter.allow(RegExp('[0-9.]')),
// ],
// ),
// ),
// const Padding(padding: EdgeInsets.only(left: 10)),
// Flexible(
// child: TextFormField(
// controller: portController,
// decoration: InputDecoration(
// labelText: 'port'.tr,
// contentPadding: const EdgeInsets.all(0.0),
// ),
// keyboardType: TextInputType.number,
// inputFormatters: [
// FilteringTextInputFormatter.digitsOnly,
// NumericalRangeFormatter(min: 0, max: 65535),
// ],
// ),
// ),
// ]);
// }
return Form(
child: Row(
children: items,
),
);
},
);
final buildApiToken = _buildConfigItem('apiToken',
() => startCfg.value.apiToken.isEmpty ? 'notSet'.tr : 'set'.tr,
(Key key) {
final apiTokenController =
TextEditingController(text: startCfg.value.apiToken);
apiTokenController.addListener(() async {
if (apiTokenController.text != startCfg.value.apiToken) {
startCfg.value.apiToken = apiTokenController.text;
// return Form(
// child: Row(
// children: items,
// ),
// );
// },
// );
// final buildApiToken = _buildConfigItem('apiToken',
// () => startCfg.value.apiToken.isEmpty ? 'notSet'.tr : 'set'.tr,
// (Key key) {
// final apiTokenController =
// TextEditingController(text: startCfg.value.apiToken);
// apiTokenController.addListener(() async {
// if (apiTokenController.text != startCfg.value.apiToken) {
// startCfg.value.apiToken = apiTokenController.text;
await debounceSave(needRestart: true);
}
});
return TextField(
key: key,
obscureText: true,
controller: apiTokenController,
focusNode: FocusNode(),
);
});
// await debounceSave(needRestart: true);
// }
// });
// return TextField(
// key: key,
// obscureText: true,
// controller: apiTokenController,
// focusNode: FocusNode(),
// );
// });
// advanced config log items start
buildLogsDir() {
@@ -993,16 +993,16 @@ class SettingView extends GetView<SettingController> {
child: Column(
children: _addDivider([buildProxy()]),
)),
const Text('API'),
Card(
child: Column(
children: _addDivider([
buildApiProtocol(),
Util.isDesktop() && startCfg.value.network == 'tcp'
? buildApiToken()
: null,
]),
)),
// const Text('API'),
// Card(
// child: Column(
// children: _addDivider([
// buildApiProtocol(),
// Util.isDesktop() && startCfg.value.host == 'tcp'
// ? buildApiToken()
// : null,
// ]),
// )),
Text('developer'.tr),
Card(
child: Column(
@@ -1,5 +1,5 @@
import 'package:get/get.dart';
import '../../../../api/api.dart';
import '../../../../api/libgopeed_boot.dart';
import '../../../../api/model/resource.dart';
import '../../../../api/model/task.dart';
@@ -31,7 +31,7 @@ class TaskFilesController extends GetxController {
super.onInit();
final taskId = Get.rootDelegate.parameters['id'];
final tasks = await getTasks([]);
final tasks = await LibgopeedBoot.instance.getTasks(null);
task.value = tasks.firstWhere((element) => element.id == taskId);
parseDirMap(task.value!.meta.res!.files);
toDir("/");
@@ -2,8 +2,9 @@ import 'dart:async';
import 'package:get/get.dart';
import '../../../../api/api.dart';
import '../../../../api/libgopeed_boot.dart';
import '../../../../api/model/task.dart';
import '../../../../api/model/task_filter.dart';
abstract class TaskListController extends GetxController {
List<Status> statuses;
@@ -44,7 +45,8 @@ abstract class TaskListController extends GetxController {
}
getTasksState() async {
final tasks = await getTasks(statuses);
final tasks =
await LibgopeedBoot.instance.getTasks(TaskFilter(statuses: statuses));
// sort tasks by create time
tasks.sort(compare);
this.tasks.value = tasks;
@@ -5,7 +5,8 @@ import 'package:path/path.dart';
import 'package:share_plus/share_plus.dart';
import 'package:url_launcher/url_launcher.dart';
import '../../../../api/api.dart' as api;
import '../../../../api/entry/libgopeed_boot_web.dart';
import '../../../../api/libgopeed_boot.dart';
import '../../../../util/browser_download/browser_download.dart';
import '../../../../util/file_icon.dart';
import '../../../../util/icons.dart';
@@ -84,8 +85,10 @@ class TaskFilesView extends GetView<TaskFilesController> {
mainAxisAlignment: MainAxisAlignment.end,
children: Util.isWeb()
? () {
final accessUrl = api.join(
"/fs/tasks/${controller.task.value!.id}$fileRelativePath");
final accessUrl = (LibgopeedBoot
.instance as LibgopeedBootWeb)
.join(
"/fs/tasks/${controller.task.value!.id}$fileRelativePath");
return [
IconButton(
icon:
@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:styled_widget/styled_widget.dart';
import '../../api/api.dart';
import '../../api/libgopeed_boot.dart';
import '../../api/model/task.dart';
import '../../util/file_icon.dart';
import '../../util/icons.dart';
@@ -95,8 +95,9 @@ class BuildTaskListView extends GetView {
try {
final force = !appController
.downloaderConfig.value.extra.lastDeleteTaskKeep;
await appController.saveConfig();
await deleteTask(id, force);
LibgopeedBoot.instance
.putConfig(appController.downloaderConfig.value);
await LibgopeedBoot.instance.deleteTask(id, force);
Get.back();
} catch (e) {
showErrorMessage(e);
@@ -122,7 +123,7 @@ class BuildTaskListView extends GetView {
icon: const Icon(Icons.pause),
onPressed: () async {
try {
await pauseTask(task.id);
await LibgopeedBoot.instance.pauseTask(task.id);
} catch (e) {
showErrorMessage(e);
}
@@ -133,7 +134,7 @@ class BuildTaskListView extends GetView {
icon: const Icon(Icons.play_arrow),
onPressed: () async {
try {
await continueTask(task.id);
await LibgopeedBoot.instance.continueTask(task.id);
} catch (e) {
showErrorMessage(e);
}
@@ -1,23 +0,0 @@
import 'dart:convert';
import 'package:flutter/services.dart';
import 'libgopeed_interface.dart';
import 'start_config.dart';
class LibgopeedChannel implements LibgopeedInterface {
static const _channel = MethodChannel('gopeed.com/libgopeed');
@override
Future<int> start(StartConfig cfg) async {
final port = await _channel.invokeMethod('start', {
'cfg': jsonEncode(cfg),
});
return port as int;
}
@override
Future<void> stop() async {
return await _channel.invokeMethod('stop');
}
}
@@ -1,37 +0,0 @@
import 'dart:async';
import 'dart:convert';
import 'dart:ffi';
import 'package:ffi/ffi.dart';
import '../ffi/libgopeed_bind.dart';
import 'libgopeed_interface.dart';
import 'start_config.dart';
class LibgopeedFFi implements LibgopeedInterface {
late LibgopeedBind _libgopeed;
LibgopeedFFi(LibgopeedBind libgopeed) {
_libgopeed = libgopeed;
}
@override
Future<int> start(StartConfig cfg) {
var completer = Completer<int>();
var result = _libgopeed.Start(jsonEncode(cfg).toNativeUtf8().cast());
if (result.r1 != nullptr) {
completer.completeError(Exception(result.r1.cast<Utf8>().toDartString()));
} else {
completer.complete(result.r0);
}
return completer.future;
}
@override
Future<void> stop() {
var completer = Completer<void>();
_libgopeed.Stop();
completer.complete();
return completer.future;
}
}
@@ -1,7 +0,0 @@
import 'start_config.dart';
abstract class LibgopeedInterface {
Future<int> start(StartConfig cfg);
Future<void> stop();
}
@@ -1,18 +0,0 @@
import 'dart:async';
import '../common/start_config.dart';
import '../libgopeed_boot.dart';
LibgopeedBoot create() => LibgopeedBootBrowser();
class LibgopeedBootBrowser implements LibgopeedBoot {
// do nothing
@override
Future<int> start(StartConfig cfg) async {
return 0;
}
@override
Future<void> stop() async {}
}
@@ -1,49 +0,0 @@
import 'dart:async';
import 'dart:ffi';
import 'dart:io';
import '../../util/util.dart';
import '../common/libgopeed_channel.dart';
import '../common/libgopeed_ffi.dart';
import '../common/libgopeed_interface.dart';
import '../common/start_config.dart';
import '../ffi/libgopeed_bind.dart';
import '../libgopeed_boot.dart';
LibgopeedBoot create() => LibgopeedBootNative();
class LibgopeedBootNative implements LibgopeedBoot {
late LibgopeedInterface _libgopeed;
LibgopeedBootNative() {
if (Util.isDesktop()) {
var libName = "libgopeed.";
if (Platform.isWindows) {
libName += "dll";
}
if (Platform.isMacOS) {
libName += "dylib";
}
if (Platform.isLinux) {
libName += "so";
}
_libgopeed = LibgopeedFFi(LibgopeedBind(DynamicLibrary.open(libName)));
} else {
_libgopeed = LibgopeedChannel();
}
}
@override
Future<int> start(StartConfig cfg) async {
cfg.storage = 'bolt';
cfg.storageDir = Util.getStorageDir();
cfg.refreshInterval = 0;
var port = await _libgopeed.start(cfg);
return port;
}
@override
Future<void> stop() async {
await _libgopeed.stop();
}
}
File diff suppressed because it is too large. Load diff
+2 -2
View File
@@ -71,12 +71,12 @@ const enUS = {
'deleteTaskTip': 'Keep downloaded files',
'delete': 'Delete',
'newVersionTitle': 'Discover new version @version',
'newVersionUpdate': 'Update Now',
'newVersionUpgrade': 'Update Now',
'newVersionLater': 'Later',
'extensions': 'Extensions',
'extensionInstallUrl': 'Install URL',
'extensionInstallSuccess': 'Installed successfully',
'extensionUpdateSuccess': 'Updated successfully',
'extensionUpgradeSuccess': 'Updated successfully',
'extensionDelete': 'Delete Extension',
'extensionAlreadyLatest': 'It\'s already the latest version',
'extensionFind': 'Find Extensions',
+9 -6
View File
@@ -21,9 +21,11 @@ const esES = {
'downloadLinkValid': 'Por favor, introduce el enlace de descarga',
'downloadLinkHit':
'Por favor, introduce el enlace de descarga, se admite HTTP/HTTPS/MAGNET@append',
'downloadLinkHitDesktop': ', o arrastra el archivo torrent aquí directamente',
'downloadLinkHitDesktop':
', o arrastra el archivo torrent aquí directamente',
'download': 'Descargar',
'noFileSelected': 'Por favor, selecciona al menos un archivo para continuar.',
'noFileSelected':
'Por favor, selecciona al menos un archivo para continuar.',
'noStoragePermission': 'Se requiere permiso de almacenamiento',
'selectFile': 'Seleccionar Archivo',
'rename': 'Renombrar',
@@ -43,7 +45,8 @@ const esES = {
'updateDaily': 'Actualizar diariamente',
'lastUpdate': 'Última actualización: @time',
'addTracker': 'Añadir Tracker',
'addTrackerHit': 'Por favor, introduce la URL del servidor tracker, una por línea',
'addTrackerHit':
'Por favor, introduce la URL del servidor tracker, una por línea',
'ui': 'Interfaz',
'theme': 'Tema',
'themeSystem': 'Sistema',
@@ -67,12 +70,12 @@ const esES = {
'deleteTaskTip': 'Mantener archivos descargados',
'delete': 'Eliminar',
'newVersionTitle': 'Nueva versión @version disponible',
'newVersionUpdate': 'Actualizar Ahora',
'newVersionUpgrade': 'Actualizar Ahora',
'newVersionLater': 'Más tarde',
'extensions': 'Extensiones',
'extensionInstallUrl': 'URL de instalación',
'extensionInstallSuccess': 'Instalado con éxito',
'extensionUpdateSuccess': 'Actualizado con éxito',
'extensionUpgradeSuccess': 'Actualizado con éxito',
'extensionDelete': 'Eliminar Extensión',
'extensionAlreadyLatest': 'Ya es la versión más reciente',
'extensionFind': 'Buscar Extensiones',
@@ -105,4 +108,4 @@ const esES = {
'downloadPath': 'Ruta de Descarga',
'skipVerifyCert': 'Omitir Verificación de Certificado',
},
};
};
+1 -1
View File
@@ -55,7 +55,7 @@ const faIR = {
'deleteTaskTip': 'فایل های دانلود شده را نگه دارد',
'delete': 'پاک کردن',
'newVersionTitle': '@version عنوان: کشف نسخه جدید',
'newVersionUpdate': 'بروزرسانی',
'newVersionUpgrade': 'بروزرسانی',
'newVersionLater': 'بعداً',
'thanks': 'تشکر',
'thanksDesc':
+14 -8
View File
@@ -19,10 +19,12 @@ const frFR = {
'advancedOptions': 'Options avancées',
'downloadLink': 'Lien de téléchargement',
'downloadLinkValid': 'Veuillez entrer le lien de téléchargement',
'downloadLinkHit': 'Veuillez entrer le lien de téléchargement, HTTP/HTTPS/MAGNET pris en charge',
'downloadLinkHit':
'Veuillez entrer le lien de téléchargement, HTTP/HTTPS/MAGNET pris en charge',
'downloadLinkHitDesktop': ', ou glissez directement le fichier torrent ici',
'download': 'Télécharger',
'noFileSelected': 'Veuillez sélectionner au moins un fichier pour continuer.',
'noFileSelected':
'Veuillez sélectionner au moins un fichier pour continuer.',
'noStoragePermission': 'Permission de stockage requise',
'selectFile': 'Sélectionner un fichier',
'rename': 'Renommer',
@@ -32,16 +34,19 @@ const frFR = {
'downloadDir': 'Répertoire de téléchargement',
'downloadDirValid': 'Veuillez sélectionner le répertoire de téléchargement',
'connections': 'Connexions',
'useServerCtime': 'Utiliser l\'heure du serveur pour la création de fichier',
'useServerCtime':
'Utiliser l\'heure du serveur pour la création de fichier',
'maxRunning': 'Tâches en cours maximum',
'items': '@count éléments',
'subscribeTracker': 'S\'abonner au tracker',
'subscribeFail': 'Abonnement échoué, veuillez vérifier le réseau ou réessayer plus tard',
'subscribeFail':
'Abonnement échoué, veuillez vérifier le réseau ou réessayer plus tard',
'update': 'Mettre à jour',
'updateDaily': 'Mettre à jour quotidiennement',
'lastUpdate': 'Dernière mise à jour : @time',
'addTracker': 'Ajouter un tracker',
'addTrackerHit': 'Veuillez entrer l\'URL du serveur de tracker, une par ligne',
'addTrackerHit':
'Veuillez entrer l\'URL du serveur de tracker, une par ligne',
'ui': 'Interface utilisateur',
'theme': 'Thème',
'themeSystem': 'Système',
@@ -65,12 +70,12 @@ const frFR = {
'deleteTaskTip': 'Conserver les fichiers téléchargés',
'delete': 'Supprimer',
'newVersionTitle': 'Nouvelle version @version disponible',
'newVersionUpdate': 'Mettre à jour maintenant',
'newVersionUpgrade': 'Mettre à jour maintenant',
'newVersionLater': 'Plus tard',
'extensions': 'Extensions',
'extensionInstallUrl': 'URL d\'installation',
'extensionInstallSuccess': 'Installé avec succès',
'extensionUpdateSuccess': 'Mis à jour avec succès',
'extensionUpgradeSuccess': 'Mis à jour avec succès',
'extensionDelete': 'Supprimer l\'extension',
'extensionAlreadyLatest': 'C\'est déjà la dernière version',
'extensionFind': 'Trouver des extensions',
@@ -89,7 +94,8 @@ const frFR = {
'username': 'Nom d\'utilisateur',
'password': 'Mot de passe',
'thanks': 'Merci',
'thanksDesc': 'Merci à tous les contributeurs qui ont aidé à construire et développer la communauté Gopeed !',
'thanksDesc':
'Merci à tous les contributeurs qui ont aidé à construire et développer la communauté Gopeed !',
'browserExtension': 'Extension du navigateur',
'launchAtStartup': 'Lancer au démarrage',
'skipVerifyCert': 'Bypass Verifikasi Sertifikat',
+4 -3
View File
@@ -22,7 +22,8 @@ const idID = {
'downloadLinkValid': 'Silakan masukkan tautan unduhan',
'downloadLinkHit':
'Tolong masukkan tautan unduhan, HTTP/HTTPS/MAGNET didukung@append',
'downloadLinkHitDesktop': ', atau seret file torrent ke sini secara langsung',
'downloadLinkHitDesktop':
', atau seret file torrent ke sini secara langsung',
'download': 'Download',
'noFileSelected': 'Silakan pilih setidaknya satu file untuk melanjutkan.',
'noStoragePermission': 'Izin penyimpanan diperlukan',
@@ -71,12 +72,12 @@ const idID = {
'deleteTaskTip': 'Simpan file yang terunduh',
'delete': 'Hapus',
'newVersionTitle': 'Temukan versi batu @version',
'newVersionUpdate': 'Perbarui Sekarang',
'newVersionUpgrade': 'Perbarui Sekarang',
'newVersionLater': 'Nanti',
'extensions': 'Ekstensi',
'extensionInstallUrl': 'URL Instal',
'extensionInstallSuccess': 'Diinstal dengan sukses',
'extensionUpdateSuccess': 'Diperbaruhi dengan sukses',
'extensionUpgradeSuccess': 'Diperbaruhi dengan sukses',
'extensionDelete': 'Hapus Ekstensi',
'extensionAlreadyLatest': 'Ini sudah versi terbaru',
'extensionFind': 'Temukan Ekstensi',
+2 -2
View File
@@ -68,12 +68,12 @@ const itIT = {
'deleteTaskTip': 'Conserva i file scaricati',
'delete': 'Elimina',
'newVersionTitle': 'Scopri la nuova versione @version',
'newVersionUpdate': 'Aggiorna ora',
'newVersionUpgrade': 'Aggiorna ora',
'newVersionLater': 'Dopo',
'extensions': 'Estensioni',
'extensionInstallUrl': 'Installa URL',
'extensionInstallSuccess': 'Installato con successo',
'extensionUpdateSuccess': 'Aggiornato con successo',
'extensionUpgradeSuccess': 'Aggiornato con successo',
'extensionDelete': 'Elimina estensione',
'extensionAlreadyLatest': "È già l'ultima versione",
'extensionFind': 'Trova estensioni',
+1 -1
View File
@@ -57,7 +57,7 @@ const jaJP = {
'deleteTaskTip': 'ダウンロードしたファイルを保持',
'delete': '削除',
'newVersionTitle': '新しいバージョン @version を発見する',
'newVersionUpdate': 'アップデート',
'newVersionUpgrade': 'アップデート',
'newVersionLater': '後で',
'thanks': '感謝',
'thanksDesc': 'Gopeedコミュニティの建設に協力してくださったすべての貢献者の方々に感謝します!',
+2 -2
View File
@@ -66,12 +66,12 @@ const plPL = {
'deleteTaskTip': 'Zachowaj pobrane pliki',
'delete': 'Usuń',
'newVersionTitle': 'Sprawdź aktualizację @version',
'newVersionUpdate': 'Aktualizuj teraz',
'newVersionUpgrade': 'Aktualizuj teraz',
'newVersionLater': 'Później',
'extensions': 'Wtyczki',
'extensionInstallUrl': 'Instaluj z url',
'extensionInstallSuccess': 'Instalacja zakończona',
'extensionUpdateSuccess': 'Aktualizacja zakończona',
'extensionUpgradeSuccess': 'Aktualizacja zakończona',
'extensionDelete': 'Usuń wtyczkę',
'extensionAlreadyLatest': 'Wtyczka w aktualnej wersji',
'extensionFind': 'Wyszukaj wtyczkę',
+2 -2
View File
@@ -61,11 +61,11 @@ const ruRU = {
'deleteTaskTip': 'Сохранить загруженные файлы',
'delete': 'Удалить',
'newVersionTitle': 'Обнаружена новая версия @version',
'newVersionUpdate': 'Обновить',
'newVersionUpgrade': 'Обновить',
'newVersionLater': 'позже',
'extensions': 'Расширения',
'extensionInstallSuccess': 'Установка завершена',
'extensionUpdateSuccess': 'Обновление завершено',
'extensionUpgradeSuccess': 'Обновление завершено',
'extensionDelete': 'Удалить расширение',
'extensionAlreadyLatest': 'Это последняя версия',
'extensionFind': 'Найти расширения',
+4 -3
View File
@@ -21,7 +21,8 @@ const taTA = {
'downloadLinkValid': 'பதிவிறக்க இணைப்பை உள்ளிடவும்',
'downloadLinkHit':
'பதிவிறக்க இணைப்பை உள்ளிடவும், HTTP/HTTPS/MAGNET ஆகியவற்றை ஏற்கும்@append',
'downloadLinkHitDesktop': ', அல்லது torrent கோப்பை நேரடியாக இங்கே இழுக்கவும்',
'downloadLinkHitDesktop':
', அல்லது torrent கோப்பை நேரடியாக இங்கே இழுக்கவும்',
'download': 'பதிவிறக்கு',
'noFileSelected': 'குறைந்தபட்சம் ஒரு கோப்பைத் தேர்ந்தெடுக்கவும்.',
'noStoragePermission': 'சேமிப்பக அனுமதி தேவை',
@@ -67,12 +68,12 @@ const taTA = {
'deleteTaskTip': 'பதிவிறக்கம் செய்யப்பட்ட கோப்புகளை வைத்திரு',
'delete': 'நீக்கு',
'newVersionTitle': 'புதிய பதிப்பைக் கண்டறியவும் @version',
'newVersionUpdate': 'இப்பொழுது புதுப்பி',
'newVersionUpgrade': 'இப்பொழுது புதுப்பி',
'newVersionLater': 'பின்னர்',
'extensions': 'நீட்டிப்புகள்',
'extensionInstallUrl': 'நிறுவப்படும் URL',
'extensionInstallSuccess': 'வெற்றிகரமாக நிறுவப்பட்டது',
'extensionUpdateSuccess': 'வெற்றிகரமாக புதுப்பிக்கப்பட்டது',
'extensionUpgradeSuccess': 'வெற்றிகரமாக புதுப்பிக்கப்பட்டது',
'extensionDelete': 'நீட்டிப்பை நீக்கு',
'extensionAlreadyLatest': 'இது ஏற்கனவே சமீபத்திய பதிப்பாகும்',
'extensionFind': 'நீட்டிப்புகளைக் கண்டறியவும்',
+14 -8
View File
@@ -20,8 +20,10 @@ const trTR = {
'followSettings': 'Ayarları takip et',
'downloadLink': 'İndirme bağlantısı',
'downloadLinkValid': 'Lütfen indirme bağlantısını girin',
'downloadLinkHit': 'Lütfen indirme bağlantısını girin, HTTP/HTTPS/MAGNET desteklenir@append',
'downloadLinkHitDesktop': ', veya torrent dosyasını doğrudan buraya sürükleyin',
'downloadLinkHit':
'Lütfen indirme bağlantısını girin, HTTP/HTTPS/MAGNET desteklenir@append',
'downloadLinkHitDesktop':
', veya torrent dosyasını doğrudan buraya sürükleyin',
'download': 'İndir',
'noFileSelected': 'Devam etmek için lütfen en az bir dosya seçin.',
'noStoragePermission': 'Depolama izni gerekli',
@@ -38,12 +40,14 @@ const trTR = {
'defaultDirectDownload': 'Doğrudan indir seçili olsun',
'items': '@count tane',
'subscribeTracker': 'İzleyiciye abone ol',
'subscribeFail': 'Abone olunamadı, lütfen ağı kontrol edin veya daha sonra tekrar deneyin',
'subscribeFail':
'Abone olunamadı, lütfen ağı kontrol edin veya daha sonra tekrar deneyin',
'update': 'Güncelle',
'updateDaily': 'Günlük olarak güncelle',
'lastUpdate': 'Son güncellenme tarihi: @time',
'addTracker': 'İzleyici ekle',
'addTrackerHit': 'Lütfen her satıra bir tane olmak üzere izleyici sunucusu bağlantısını girin',
'addTrackerHit':
'Lütfen her satıra bir tane olmak üzere izleyici sunucusu bağlantısını girin',
'ui': 'Arayüz',
'theme': 'Tema',
'themeSystem': 'Sistem',
@@ -58,7 +62,8 @@ const trTR = {
'apiToken': 'API anahtarı',
'notSet': 'AYARLI DEĞİL',
'set': 'AYARLI',
'portInUse': 'Bağlantı noktası [@port] kullanımda, lütfen bağlantı noktasını değiştirin',
'portInUse':
'Bağlantı noktası [@port] kullanımda, lütfen bağlantı noktasını değiştirin',
'effectAfterRestart': 'Yeniden başlatma sonrası etki edecektir',
'developer': 'Geliştirici',
'logDirectory': 'Kayıt dizini',
@@ -69,12 +74,12 @@ const trTR = {
'deleteTaskTip': 'İndirilen dosyaları sakla',
'delete': 'Sil',
'newVersionTitle': 'Yeni sürümü keşfet @version',
'newVersionUpdate': 'Şimdi güncelle',
'newVersionUpgrade': 'Şimdi güncelle',
'newVersionLater': 'Sonra',
'extensions': 'Uzantılar',
'extensionInstallUrl': 'İndirme bağlantısı',
'extensionInstallSuccess': 'Başarıyla indirildi',
'extensionUpdateSuccess': 'Başarıyla güncellendi',
'extensionUpgradeSuccess': 'Başarıyla güncellendi',
'extensionDelete': 'Uzantıyı sil',
'extensionAlreadyLatest': 'Bu zaten en son sürüm',
'extensionFind': 'Uzantı ara',
@@ -93,7 +98,8 @@ const trTR = {
'username': 'Kullanıcı adı',
'password': 'Şifre',
'thanks': 'Teşekkürler',
'thanksDesc': 'Gopeed topluluğunun oluşmasına ve gelişmesine yardımcı olan tüm katılımcılara teşekkürler!',
'thanksDesc':
'Gopeed topluluğunun oluşmasına ve gelişmesine yardımcı olan tüm katılımcılara teşekkürler!',
'browserExtension': 'Tarayıcı uzantısı',
'launchAtStartup': 'Başlangıçta başlat',
'seedConfig': 'Seed ayarları',
+2 -2
View File
@@ -65,12 +65,12 @@ const viVN = {
'deleteTaskTip': 'Giữ các tệp đã tải về',
'delete': 'Xóa',
'newVersionTitle': 'Khám phá phiên bản mới @version',
'newVersionUpdate': 'Cập nhật ngay',
'newVersionUpgrade': 'Cập nhật ngay',
'newVersionLater': 'Sau',
'extensions': 'Tiện ích mở rộng',
'extensionInstallUrl': 'Liên kết cài đặt',
'extensionInstallSuccess': 'Cài đặt thành công',
'extensionUpdateSuccess': 'Cập nhật thành công',
'extensionUpgradeSuccess': 'Cập nhật thành công',
'extensionDelete': 'Xóa tiện ích mở rộng',
'extensionAlreadyLatest': 'Đây đã là phiên bản mới nhất',
'extensionFind': 'Tìm tiện ích mở rộng',
+2 -2
View File
@@ -69,12 +69,12 @@ const zhCN = {
'deleteTaskTip': '保留已下载的文件',
'delete': '删除',
'newVersionTitle': '发现新版本 @version',
'newVersionUpdate': '立即更新',
'newVersionUpgrade': '立即更新',
'newVersionLater': '稍后再说',
'extensions': '扩展',
'extensionInstallUrl': '安装链接',
'extensionInstallSuccess': '安装成功',
'extensionUpdateSuccess': '更新成功',
'extensionUpgradeSuccess': '更新成功',
'extensionDelete': '删除扩展',
'extensionAlreadyLatest': '已经是最新版本',
'extensionFind': '获取扩展',
+2 -2
View File
@@ -69,12 +69,12 @@ const zhTW = {
'deleteTaskTip': '保留已下載的檔案',
'delete': '刪除',
'newVersionTitle': '發現新版本 @version',
'newVersionUpdate': '立即更新',
'newVersionUpgrade': '立即更新',
'newVersionLater': '稍後',
'extensions': '擴充功能',
'extensionInstallUrl': '安裝 URL',
'extensionInstallSuccess': '安裝成功',
'extensionUpdateSuccess': '更新成功',
'extensionUpgradeSuccess': '更新成功',
'extensionDelete': '刪除擴充功能',
'extensionAlreadyLatest': '已是最新版本',
'extensionFind': '尋找擴充功能',
+6 -6
View File
@@ -2,12 +2,12 @@ import 'package:args/args.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:gopeed/api/native/model/start_config.dart';
import 'package:window_manager/window_manager.dart';
import 'api/api.dart' as api;
import 'app/modules/app/controllers/app_controller.dart';
import 'app/modules/app/views/app_view.dart';
import 'core/libgopeed_boot.dart';
import 'api/libgopeed_boot.dart';
import 'database/database.dart';
import 'i18n/message.dart';
import 'util/locale_manager.dart';
@@ -73,10 +73,10 @@ Future<void> init(Args args) async {
final controller = Get.put(AppController());
try {
await controller.loadStartConfig();
final startCfg = controller.startConfig.value;
controller.runningPort.value = await LibgopeedBoot.instance.start(startCfg);
api.init(startCfg.network, controller.runningAddress(), startCfg.apiToken);
await LibgopeedBoot.instance.init(StartConfig(
storage: "bolt",
storageDir: Util.getStorageDir(),
));
} catch (e) {
logger.e("libgopeed init fail", e);
}
+10
View File
@@ -0,0 +1,10 @@
extension ApplyExtension<T> on T {
T apply(void Function(T) fn) {
fn(this);
return this;
}
}
extension LetExtension<T> on T {
R let<R>(R Function(T) fn) => fn(this);
}
+12 -44
View File
@@ -293,18 +293,18 @@ packages:
dependency: "direct dev"
description:
name: ffigen
sha256: d3e76c2ad48a4e7f93a29a162006f00eba46ce7c08194a77bb5c5e97d1b5ff0a
sha256: dead012f29db2be71ea152458f5eab600de98fbc244e01088ae6bf2616bceca7
url: "https://pub.dev"
source: hosted
version: "8.0.2"
version: "11.0.0"
file:
dependency: transitive
description:
name: file
sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d"
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
url: "https://pub.dev"
source: hosted
version: "6.1.4"
version: "7.0.1"
file_picker:
dependency: "direct main"
description:
@@ -525,30 +525,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.2.2"
leak_tracker:
dependency: transitive
description:
name: leak_tracker
sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa"
url: "https://pub.dev"
source: hosted
version: "10.0.0"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0
url: "https://pub.dev"
source: hosted
version: "2.0.1"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47
url: "https://pub.dev"
source: hosted
version: "2.0.1"
lecle_downloads_path_provider:
dependency: "direct main"
description:
@@ -617,18 +593,18 @@ packages:
dependency: transitive
description:
name: matcher
sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e"
url: "https://pub.dev"
source: hosted
version: "0.12.16+1"
version: "0.12.16"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41"
url: "https://pub.dev"
source: hosted
version: "0.8.0"
version: "0.5.0"
math_expressions:
dependency: transitive
description:
@@ -649,10 +625,10 @@ packages:
dependency: transitive
description:
name: meta
sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04
sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e
url: "https://pub.dev"
source: hosted
version: "1.11.0"
version: "1.10.0"
mime:
dependency: transitive
description:
@@ -697,10 +673,10 @@ packages:
dependency: "direct main"
description:
name: path
sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917"
url: "https://pub.dev"
source: hosted
version: "1.9.0"
version: "1.8.3"
path_provider:
dependency: "direct main"
description:
@@ -1203,14 +1179,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.4"
vm_service:
dependency: transitive
description:
name: vm_service
sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957
url: "https://pub.dev"
source: hosted
version: "13.0.0"
watcher:
dependency: transitive
description:
+4 -4
View File
@@ -18,7 +18,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
version: 1.6.3+1
environment:
sdk: '>=3.0.0 <4.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
@@ -85,7 +85,7 @@ dev_dependencies:
# package. See that file for information about deactivating specific lint
# rules and activating additional ones.
flutter_lints: ^2.0.0
ffigen: ^8.0.2
ffigen: ^11.0.0
# build json model: dart run build_runner build --delete-conflicting-outputs
build_runner: ^2.2.1
json_serializable: ^6.3.2
@@ -150,7 +150,7 @@ flutter_icons:
ffigen:
name: LibgopeedBind
description: Bindings to gopeed library.
output: 'lib/core/ffi/libgopeed_bind.dart'
output: "lib/api/native/ffi/libgopeed_bind.dart"
headers:
entry-points:
- 'include/libgopeed.h'
- "include/libgopeed.h"