mirror of
https://github.com/GopeedLab/gopeed.git
synced 2026-11-06 08:30:29 +00:00
feat: enhance deep link handling for install extensions (#1249)
This commit is contained in:
7 files changed
+77
-51
No files matched your search
+14
-10
@@ -3,6 +3,14 @@ package download
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/GopeedLab/gopeed/internal/logger"
|
||||
"github.com/GopeedLab/gopeed/pkg/base"
|
||||
"github.com/GopeedLab/gopeed/pkg/download/engine"
|
||||
@@ -12,13 +20,6 @@ import (
|
||||
"github.com/dop251/goja"
|
||||
"github.com/go-git/go-git/v5"
|
||||
"github.com/go-git/go-git/v5/plumbing/transport"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -200,10 +201,14 @@ func (d *Downloader) fetchExtensionByGit(url string, handler func(tempExtPath st
|
||||
subPath = pathArr[1]
|
||||
}
|
||||
|
||||
tempExtDir := filepath.Join(d.cfg.StorageDir, tempExtensionsDir, projectPath)
|
||||
if err := util.RmAndMkDirAll(tempExtDir); err != nil {
|
||||
// Use unique suffix to avoid concurrent conflicts when multiple git operations
|
||||
// target the same extension (e.g., install and update check happening simultaneously)
|
||||
tempExtDir := filepath.Join(d.cfg.StorageDir, tempExtensionsDir, fmt.Sprintf("%s_%d", projectPath, time.Now().UnixNano()))
|
||||
if err := os.MkdirAll(tempExtDir, 0755); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer os.RemoveAll(tempExtDir)
|
||||
|
||||
proxyOptions := transport.ProxyOptions{}
|
||||
proxyUrl := d.cfg.DownloaderStoreConfig.Proxy.ToUrl()
|
||||
if proxyUrl != nil {
|
||||
@@ -220,7 +225,6 @@ func (d *Downloader) fetchExtensionByGit(url string, handler func(tempExtPath st
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer os.RemoveAll(tempExtDir)
|
||||
|
||||
return handler(filepath.Join(tempExtDir, subPath), false)
|
||||
}
|
||||
|
||||
@@ -141,17 +141,6 @@ func CopyDir(source string, target string, excludeDir ...string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// RmAndMkDirAll remove and create directory, if the directory already exists, it will be overwritten.
|
||||
func RmAndMkDirAll(path string) error {
|
||||
if err := os.RemoveAll(path); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.MkdirAll(path, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// copy file, if the target file already exists, it will be overwritten.
|
||||
func copyForce(source string, target string) error {
|
||||
sourceFile, err := os.Open(source)
|
||||
|
||||
@@ -20,6 +20,7 @@ import 'package:window_manager/window_manager.dart';
|
||||
import '../../../../api/api.dart';
|
||||
import '../../../../api/model/create_task.dart';
|
||||
import '../../../../api/model/downloader_config.dart';
|
||||
import '../../../../api/model/install_extension.dart';
|
||||
import '../../../../api/model/request.dart';
|
||||
import '../../../../api/model/result.dart';
|
||||
import '../../../../core/common/start_config.dart';
|
||||
@@ -293,7 +294,7 @@ class AppController extends GetxController with WindowListener, TrayListener {
|
||||
final jsonMeta = jsonDecode(meta);
|
||||
final silent = jsonMeta['silent'] as bool? ?? false;
|
||||
final params = await ctx.readText();
|
||||
final createTaskParams = _decodeToCreatTaskParams(params);
|
||||
final createTaskParams = CreateTask.fromJson(_decodeParams(params));
|
||||
if (!silent) {
|
||||
await windowManager.show();
|
||||
_handleToCreate0(createTaskParams);
|
||||
@@ -382,6 +383,7 @@ class AppController extends GetxController with WindowListener, TrayListener {
|
||||
|
||||
Future<void> _handleDeepLink(Uri uri) async {
|
||||
if (uri.scheme == "gopeed") {
|
||||
// gopeed:///create?params=eyJyZXEiOnsidXJsIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9maWxlLnR4dCJ9fQ==
|
||||
if (uri.path == "/create") {
|
||||
final params = uri.queryParameters["params"];
|
||||
if (params?.isNotEmpty == true) {
|
||||
@@ -391,6 +393,16 @@ class AppController extends GetxController with WindowListener, TrayListener {
|
||||
Get.rootDelegate.offAndToNamed(Routes.CREATE);
|
||||
return;
|
||||
}
|
||||
// gopeed:///extension?params=eyJ1cmwiOiJodHRwczovL2dpdGh1Yi5jb20vbW9ua2V5V2llL2dvcGVlZC1leHRlbnNpb24tYmlsaWJpbGkiLCJkZXZNb2RlIjpmYWxzZX0=
|
||||
if (uri.path == "/extension") {
|
||||
final params = uri.queryParameters["params"];
|
||||
if (params?.isNotEmpty == true) {
|
||||
_handleToExtension(params!);
|
||||
return;
|
||||
}
|
||||
Get.rootDelegate.offAndToNamed(Routes.EXTENSION);
|
||||
return;
|
||||
}
|
||||
Get.rootDelegate.offAndToNamed(Routes.HOME);
|
||||
return;
|
||||
}
|
||||
@@ -650,15 +662,15 @@ class AppController extends GetxController with WindowListener, TrayListener {
|
||||
await putConfig(downloaderConfig.value);
|
||||
}
|
||||
|
||||
CreateTask _decodeToCreatTaskParams(String params) {
|
||||
Map<String, dynamic> _decodeParams(String params) {
|
||||
final safeParams = params.replaceAll('"', "").replaceAll(" ", "+");
|
||||
final paramsJson =
|
||||
String.fromCharCodes(base64Decode(base64.normalize(safeParams)));
|
||||
return CreateTask.fromJson(jsonDecode(paramsJson));
|
||||
return jsonDecode(paramsJson);
|
||||
}
|
||||
|
||||
_handleToCreate(String params) {
|
||||
final createTaskParams = _decodeToCreatTaskParams(params);
|
||||
final createTaskParams = CreateTask.fromJson(_decodeParams(params));
|
||||
_handleToCreate0(createTaskParams);
|
||||
}
|
||||
|
||||
@@ -666,4 +678,10 @@ class AppController extends GetxController with WindowListener, TrayListener {
|
||||
Get.rootDelegate.offAndToNamed(Routes.REDIRECT,
|
||||
arguments: RedirectArgs(Routes.CREATE, arguments: createTaskParams));
|
||||
}
|
||||
|
||||
_handleToExtension(String params) {
|
||||
final installExtension = InstallExtension.fromJson(_decodeParams(params));
|
||||
Get.rootDelegate.offAndToNamed(Routes.REDIRECT,
|
||||
arguments: RedirectArgs(Routes.EXTENSION, arguments: installExtension));
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,9 @@ class CreateController extends GetxController
|
||||
final oldUrl = "".obs;
|
||||
final fileDataUri = "".obs;
|
||||
|
||||
/// Flag to prevent handling pending create task from deep link multiple times
|
||||
bool pendingCreateHandled = false;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
|
||||
@@ -82,17 +82,16 @@ class CreateView extends GetView<CreateController> {
|
||||
_pathController.text = renderPathPlaceholders(downloadDir);
|
||||
}
|
||||
// Initialize archive settings from global config if not already set
|
||||
if (_autoExtractController.value == null) {
|
||||
_autoExtractController.value =
|
||||
appController.downloaderConfig.value.archive.autoExtract;
|
||||
}
|
||||
if (_deleteAfterExtractController.value == null) {
|
||||
_deleteAfterExtractController.value =
|
||||
appController.downloaderConfig.value.archive.deleteAfterExtract;
|
||||
}
|
||||
_autoExtractController.value ??=
|
||||
appController.downloaderConfig.value.archive.autoExtract;
|
||||
_deleteAfterExtractController.value ??=
|
||||
appController.downloaderConfig.value.archive.deleteAfterExtract;
|
||||
|
||||
// Handle pending create task from deep link
|
||||
final CreateTask? routerParams = Get.rootDelegate.arguments();
|
||||
if (routerParams?.req?.url.isNotEmpty ?? false) {
|
||||
if ((routerParams?.req?.url.isNotEmpty ?? false) &&
|
||||
!controller.pendingCreateHandled) {
|
||||
controller.pendingCreateHandled = true;
|
||||
// get url from route arguments
|
||||
final url = routerParams!.req!.url;
|
||||
_urlController.text = url;
|
||||
|
||||
@@ -9,6 +9,9 @@ class ExtensionController extends GetxController {
|
||||
final devMode = false.obs;
|
||||
var _devModeCount = 0;
|
||||
|
||||
/// Flag to prevent handling pending install multiple times
|
||||
bool pendingInstallHandled = false;
|
||||
|
||||
@override
|
||||
void onInit() async {
|
||||
super.onInit();
|
||||
|
||||
@@ -30,8 +30,34 @@ class ExtensionView extends GetView<ExtensionController> {
|
||||
final _installUrlController = TextEditingController();
|
||||
final _installBtnController = IconButtonLoadingController();
|
||||
|
||||
Future<void> _doInstall() async {
|
||||
if (_installUrlController.text.isEmpty) {
|
||||
controller.tryOpenDevMode();
|
||||
return;
|
||||
}
|
||||
_installBtnController.start();
|
||||
try {
|
||||
await installExtension(InstallExtension(url: _installUrlController.text));
|
||||
Get.snackbar('tip'.tr, 'extensionInstallSuccess'.tr);
|
||||
await controller.load();
|
||||
} catch (e) {
|
||||
showErrorMessage(e);
|
||||
} finally {
|
||||
_installBtnController.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Handle pending install extension from deep link
|
||||
final args = Get.rootDelegate.arguments();
|
||||
if (args is InstallExtension && !controller.pendingInstallHandled) {
|
||||
controller.pendingInstallHandled = true;
|
||||
_installUrlController.text = args.url;
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_doInstall();
|
||||
});
|
||||
}
|
||||
return Scaffold(
|
||||
body: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@@ -71,23 +97,7 @@ class ExtensionView extends GetView<ExtensionController> {
|
||||
const SizedBox(width: 10),
|
||||
IconButtonLoading(
|
||||
controller: _installBtnController,
|
||||
onPressed: () async {
|
||||
if (_installUrlController.text.isEmpty) {
|
||||
controller.tryOpenDevMode();
|
||||
return;
|
||||
}
|
||||
_installBtnController.start();
|
||||
try {
|
||||
await installExtension(InstallExtension(
|
||||
url: _installUrlController.text));
|
||||
Get.snackbar('tip'.tr, 'extensionInstallSuccess'.tr);
|
||||
await controller.load();
|
||||
} catch (e) {
|
||||
showErrorMessage(e);
|
||||
} finally {
|
||||
_installBtnController.stop();
|
||||
}
|
||||
},
|
||||
onPressed: _doInstall,
|
||||
icon: const Icon(Icons.download)),
|
||||
controller.devMode.value && Util.isDesktop()
|
||||
? IconButton(
|
||||
|
||||
Reference in new issue
Block a user