diff --git a/app/src/main/java/io/xpipe/app/browser/file/BrowserAlerts.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserAlerts.java index 6f0350188..03d652798 100644 --- a/app/src/main/java/io/xpipe/app/browser/file/BrowserAlerts.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserAlerts.java @@ -30,15 +30,22 @@ public class BrowserAlerts { new ButtonType(AppI18n.get("replaceAll"), ButtonBar.ButtonData.OTHER), FileConflictChoice.REPLACE_ALL); } + map.put(new ButtonType(AppI18n.get("rename"), ButtonBar.ButtonData.OTHER), FileConflictChoice.RENAME); + if (multiple) { + map.put( + new ButtonType(AppI18n.get("renameAll"), ButtonBar.ButtonData.OTHER), + FileConflictChoice.RENAME_ALL); + } return AppWindowHelper.showBlockingAlert(alert -> { alert.setTitle(AppI18n.get("fileConflictAlertTitle")); alert.setHeaderText(AppI18n.get("fileConflictAlertHeader")); - AppWindowHelper.setContent( - alert, - AppI18n.get( - multiple ? "fileConflictAlertContentMultiple" : "fileConflictAlertContent", file)); alert.setAlertType(Alert.AlertType.CONFIRMATION); alert.getButtonTypes().clear(); + alert.getDialogPane().setContent(AppWindowHelper.alertContentText(AppI18n.get( + multiple ? "fileConflictAlertContentMultiple" : "fileConflictAlertContent", file), 655)); + alert.getDialogPane().setMinWidth(705); + alert.getDialogPane().setPrefWidth(705); + alert.getDialogPane().setMaxWidth(705); map.sequencedKeySet() .forEach(buttonType -> alert.getButtonTypes().add(buttonType)); }) @@ -97,6 +104,8 @@ public class BrowserAlerts { SKIP, SKIP_ALL, REPLACE, - REPLACE_ALL + REPLACE_ALL, + RENAME, + RENAME_ALL } } diff --git a/app/src/main/java/io/xpipe/app/browser/file/BrowserFileTransferOperation.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileTransferOperation.java index edad848fc..113afb304 100644 --- a/app/src/main/java/io/xpipe/app/browser/file/BrowserFileTransferOperation.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileTransferOperation.java @@ -12,6 +12,8 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.concurrent.atomic.AtomicLong; import java.util.function.Consumer; +import java.util.regex.Matcher; +import java.util.regex.Pattern; public class BrowserFileTransferOperation { @@ -63,41 +65,52 @@ public class BrowserFileTransferOperation { this.progress.accept(progress); } - private boolean handleChoice(FileSystem fileSystem, String target, boolean multiple) throws Exception { + private BrowserAlerts.FileConflictChoice handleChoice(FileSystem fileSystem, String target, boolean multiple) throws Exception { if (lastConflictChoice == BrowserAlerts.FileConflictChoice.CANCEL) { - return false; + return BrowserAlerts.FileConflictChoice.CANCEL; } if (lastConflictChoice == BrowserAlerts.FileConflictChoice.REPLACE_ALL) { - return true; + return BrowserAlerts.FileConflictChoice.REPLACE; + } + + if (lastConflictChoice == BrowserAlerts.FileConflictChoice.RENAME_ALL) { + return BrowserAlerts.FileConflictChoice.RENAME; } if (fileSystem.fileExists(target)) { if (lastConflictChoice == BrowserAlerts.FileConflictChoice.SKIP_ALL) { - return false; + return BrowserAlerts.FileConflictChoice.SKIP; } var choice = BrowserAlerts.showFileConflictAlert(target, multiple); if (choice == BrowserAlerts.FileConflictChoice.CANCEL) { lastConflictChoice = BrowserAlerts.FileConflictChoice.CANCEL; - return false; + return BrowserAlerts.FileConflictChoice.CANCEL; } if (choice == BrowserAlerts.FileConflictChoice.SKIP) { - return false; + return BrowserAlerts.FileConflictChoice.SKIP; } if (choice == BrowserAlerts.FileConflictChoice.SKIP_ALL) { lastConflictChoice = BrowserAlerts.FileConflictChoice.SKIP_ALL; - return false; + return BrowserAlerts.FileConflictChoice.SKIP; } if (choice == BrowserAlerts.FileConflictChoice.REPLACE_ALL) { lastConflictChoice = BrowserAlerts.FileConflictChoice.REPLACE_ALL; - return true; + return BrowserAlerts.FileConflictChoice.REPLACE; } + + if (choice == BrowserAlerts.FileConflictChoice.RENAME_ALL) { + lastConflictChoice = BrowserAlerts.FileConflictChoice.RENAME_ALL; + return BrowserAlerts.FileConflictChoice.RENAME; + } + + return choice; } - return true; + return BrowserAlerts.FileConflictChoice.REPLACE; } public void execute() throws Exception { @@ -152,8 +165,15 @@ public class BrowserFileTransferOperation { new IllegalArgumentException("Target directory " + targetFile + " does already exist")); } - if (checkConflicts && !handleChoice(target.getFileSystem(), targetFile, files.size() > 1)) { - return; + if (checkConflicts) { + var fileConflictChoice = handleChoice(target.getFileSystem(), targetFile, files.size() > 1); + if (fileConflictChoice == BrowserAlerts.FileConflictChoice.SKIP || fileConflictChoice == BrowserAlerts.FileConflictChoice.CANCEL) { + return; + } + + if (fileConflictChoice == BrowserAlerts.FileConflictChoice.RENAME) { + targetFile = renameFileLoop(target.getFileSystem(), targetFile, source.getKind() == FileKind.DIRECTORY); + } } var doesMove = transferMode == BrowserFileTransferMode.MOVE || transferMode == BrowserFileTransferMode.NORMAL; @@ -164,6 +184,33 @@ public class BrowserFileTransferOperation { } } + private String renameFileLoop(FileSystem fileSystem, String target, boolean dir) throws Exception { + // Who has more than 10 copies? + for (int i = 0; i < 10; i++) { + target = renameFile(target); + if ((dir && !fileSystem.directoryExists(target)) || (!dir && !fileSystem.fileExists(target))) { + return target; + } + } + return target; + } + + private String renameFile(String target) { + var targetFile = new FilePath(target); + var name = targetFile.getFileName(); + var pattern = Pattern.compile("(.+?) \\((\\d+)\\)\\.(.+)"); + var matcher = pattern.matcher(name); + if (matcher.matches()) { + try { + var number = Integer.parseInt(matcher.group(2)); + var newFile = targetFile.getParent().join(matcher.group(1) + " (" + (number + 1) + ")." + matcher.group(3)); + return newFile.toString(); + } catch (NumberFormatException e) {} + } + + return targetFile.getBaseName() + " (" + 1 + ")." + targetFile.getExtension(); + } + private void handleSingleAcrossFileSystems(FileEntry source) throws Exception { if (target.getKind() != FileKind.DIRECTORY) { throw new IllegalStateException("Target " + target.getPath() + " is not a directory"); @@ -225,10 +272,15 @@ public class BrowserFileTransferOperation { if (sourceFile.getKind() == FileKind.DIRECTORY) { target.getFileSystem().mkdirs(targetFile); } else if (sourceFile.getKind() == FileKind.FILE) { - if (checkConflicts - && !handleChoice( - target.getFileSystem(), targetFile, files.size() > 1 || flatFiles.size() > 1)) { - continue; + if (checkConflicts) { + var fileConflictChoice = handleChoice(target.getFileSystem(), targetFile, files.size() > 1 || flatFiles.size() > 1); + if (fileConflictChoice == BrowserAlerts.FileConflictChoice.SKIP || fileConflictChoice == BrowserAlerts.FileConflictChoice.CANCEL) { + continue; + } + + if (fileConflictChoice == BrowserAlerts.FileConflictChoice.RENAME) { + targetFile = renameFileLoop(target.getFileSystem(), targetFile, false); + } } transfer(sourceFile, targetFile, transferred, totalSize, start); diff --git a/app/src/main/java/io/xpipe/app/core/window/AppWindowHelper.java b/app/src/main/java/io/xpipe/app/core/window/AppWindowHelper.java index 79070c556..52040259e 100644 --- a/app/src/main/java/io/xpipe/app/core/window/AppWindowHelper.java +++ b/app/src/main/java/io/xpipe/app/core/window/AppWindowHelper.java @@ -41,8 +41,12 @@ import java.util.function.Supplier; public class AppWindowHelper { public static Node alertContentText(String s) { + return alertContentText(s, 450); + } + + public static Node alertContentText(String s, int width) { var text = new Text(s); - text.setWrappingWidth(450); + text.setWrappingWidth(width); AppFont.medium(text); var sp = new StackPane(text); sp.setPadding(new Insets(5)); diff --git a/lang/app/strings/translations_da.properties b/lang/app/strings/translations_da.properties index ff57075d5..9911e5a50 100644 --- a/lang/app/strings/translations_da.properties +++ b/lang/app/strings/translations_da.properties @@ -530,3 +530,4 @@ dontAllowTerminalRestart=Tillad ikke genstart af terminal dontAllowTerminalRestartDescription=Som standard kan terminalsessioner genstartes, når de er afsluttet inde fra terminalen. For at tillade dette accepterer XPipe disse eksterne anmodninger fra terminalen om at starte sessionen igen\n\nXPipe har ingen kontrol over terminalen, og hvor dette opkald kommer fra, så ondsindede lokale programmer kan også bruge denne funktion til at starte forbindelser gennem XPipe. Ved at deaktivere denne funktion forhindres dette scenarie. openDocumentation=Åben dokumentation openDocumentationDescription=Besøg XPipes dokumentationsside for dette problem +renameAll=Omdøb alle diff --git a/lang/app/strings/translations_de.properties b/lang/app/strings/translations_de.properties index 30988e65d..43c4947c3 100644 --- a/lang/app/strings/translations_de.properties +++ b/lang/app/strings/translations_de.properties @@ -524,3 +524,4 @@ dontAllowTerminalRestart=Terminal-Neustart nicht zulassen dontAllowTerminalRestartDescription=Standardmäßig können Terminalsitzungen neu gestartet werden, nachdem sie vom Terminal aus beendet wurden. Um dies zu ermöglichen, akzeptiert XPipe diese externen Anfragen vom Terminal, um die Sitzung erneut zu starten\n\nXPipe hat keine Kontrolle über das Terminal und darüber, woher dieser Aufruf kommt. Daher können böswillige lokale Anwendungen diese Funktion ebenfalls nutzen, um Verbindungen über XPipe zu starten. Die Deaktivierung dieser Funktion verhindert dieses Szenario. openDocumentation=Offene Dokumentation openDocumentationDescription=Besuche die XPipe-Dokumentationsseite zu diesem Thema +renameAll=Alle umbenennen diff --git a/lang/app/strings/translations_en.properties b/lang/app/strings/translations_en.properties index d5c8ea7c5..2ef94ec4a 100644 --- a/lang/app/strings/translations_en.properties +++ b/lang/app/strings/translations_en.properties @@ -530,3 +530,4 @@ dontAllowTerminalRestart=Don't allow terminal restart dontAllowTerminalRestartDescription=By default, terminal sessions can be restarted after they ended from within the terminal. To allow this, XPipe will accept these external requests from the terminal to launch the session again\n\nXPipe doesn't have any control over the terminal and where this call comes from, so malicious local applications can use this functionality as well to launch connections through XPipe. Disabling this functionality prevents this scenario. openDocumentation=Open documentation openDocumentationDescription=Visit the XPipe docs page for this issue +renameAll=Rename all diff --git a/lang/app/strings/translations_es.properties b/lang/app/strings/translations_es.properties index 7907f7f70..e9ae8f3d1 100644 --- a/lang/app/strings/translations_es.properties +++ b/lang/app/strings/translations_es.properties @@ -511,3 +511,4 @@ dontAllowTerminalRestart=No permitir el reinicio del terminal dontAllowTerminalRestartDescription=Por defecto, las sesiones de terminal pueden reiniciarse una vez finalizadas desde dentro del terminal. Para permitirlo, XPipe aceptará estas peticiones externas del terminal para iniciar de nuevo la sesión\n\nXPipe no tiene ningún control sobre el terminal y de dónde procede esta llamada, por lo que las aplicaciones locales maliciosas también pueden utilizar esta funcionalidad para lanzar conexiones a través de XPipe. Deshabilitar esta funcionalidad evita este escenario. openDocumentation=Documentación abierta openDocumentationDescription=Visita la página de documentación de XPipe sobre este tema +renameAll=Renombrar todo diff --git a/lang/app/strings/translations_fr.properties b/lang/app/strings/translations_fr.properties index f0ce8c972..fdcacf96c 100644 --- a/lang/app/strings/translations_fr.properties +++ b/lang/app/strings/translations_fr.properties @@ -511,3 +511,4 @@ dontAllowTerminalRestart=Ne pas autoriser le redémarrage du terminal dontAllowTerminalRestartDescription=Par défaut, les sessions de terminal peuvent être relancées après s'être terminées depuis le terminal. Pour permettre cela, XPipe acceptera ces demandes externes du terminal pour relancer la session\n\nXPipe n'a aucun contrôle sur le terminal et sur la provenance de cet appel, de sorte que des applications locales malveillantes peuvent également utiliser cette fonctionnalité pour lancer des connexions par l'intermédiaire de XPipe. La désactivation de cette fonctionnalité permet d'éviter ce scénario. openDocumentation=Documentation ouverte openDocumentationDescription=Visite la page de documentation de XPipe pour ce problème +renameAll=Renommer tout diff --git a/lang/app/strings/translations_it.properties b/lang/app/strings/translations_it.properties index bc2307ae6..0739361da 100644 --- a/lang/app/strings/translations_it.properties +++ b/lang/app/strings/translations_it.properties @@ -511,3 +511,4 @@ dontAllowTerminalRestart=Non consentire il riavvio del terminale dontAllowTerminalRestartDescription=Per impostazione predefinita, le sessioni del terminale possono essere riavviate dopo la loro conclusione dall'interno del terminale stesso. Per consentire ciò, XPipe accetterà le seguenti richieste esterne dal terminale per avviare nuovamente la sessione\n\nXPipe non ha alcun controllo sul terminale e sulla provenienza di questa chiamata, quindi anche le applicazioni locali malintenzionate possono utilizzare questa funzionalità per avviare connessioni attraverso XPipe. Disabilitando questa funzionalità si evita questo scenario. openDocumentation=Documentazione aperta openDocumentationDescription=Visita la pagina dei documenti di XPipe per questo problema +renameAll=Rinomina tutti diff --git a/lang/app/strings/translations_ja.properties b/lang/app/strings/translations_ja.properties index 44ea90fcb..ed762e96c 100644 --- a/lang/app/strings/translations_ja.properties +++ b/lang/app/strings/translations_ja.properties @@ -511,3 +511,4 @@ dontAllowTerminalRestart=端末の再起動を許可しない dontAllowTerminalRestartDescription=デフォルトでは、ターミナル・セッションはターミナル内から終了後に再開することができる。これを可能にするため、XPipeはターミナルからセッションを再び起動するための以下の外部リクエストを受け付ける。\n\nXPipeはターミナルとこの呼び出しの発信元を制御できないため、悪意のあるローカルアプリケーションはこの機能を使用してXPipe経由で接続を開始することができる。この機能を無効にすることで、このシナリオを防ぐことができる。 openDocumentation=ドキュメントを開く openDocumentationDescription=この問題のXPipeドキュメントページを見る +renameAll=すべての名前を変更する diff --git a/lang/app/strings/translations_nl.properties b/lang/app/strings/translations_nl.properties index 73c3cb6df..b25cafaed 100644 --- a/lang/app/strings/translations_nl.properties +++ b/lang/app/strings/translations_nl.properties @@ -511,3 +511,4 @@ dontAllowTerminalRestart=Terminal opnieuw opstarten niet toestaan dontAllowTerminalRestartDescription=Standaard kunnen terminalsessies opnieuw worden gestart nadat ze vanuit de terminal zijn beëindigd. Om dit mogelijk te maken, accepteert XPipe deze externe verzoeken van de terminal om de sessie opnieuw te starten\n\nXPipe heeft geen controle over de terminal en waar deze oproep vandaan komt, dus kwaadwillende lokale applicaties kunnen deze functionaliteit ook gebruiken om verbindingen via XPipe te starten. Het uitschakelen van deze functionaliteit voorkomt dit scenario. openDocumentation=Open documentatie openDocumentationDescription=Bezoek de XPipe docs pagina voor dit probleem +renameAll=Hernoem alle diff --git a/lang/app/strings/translations_pt.properties b/lang/app/strings/translations_pt.properties index 62fac86e2..00a657ed2 100644 --- a/lang/app/strings/translations_pt.properties +++ b/lang/app/strings/translations_pt.properties @@ -511,3 +511,4 @@ dontAllowTerminalRestart=Não permitir o reinício do terminal dontAllowTerminalRestartDescription=Por defeito, as sessões de terminal podem ser reiniciadas depois de terminarem a partir do terminal. Para permitir isso, o XPipe aceitará essas solicitações externas do terminal para iniciar a sessão novamente\n\nO XPipe não tem qualquer controlo sobre o terminal e sobre a origem desta chamada, pelo que as aplicações locais maliciosas também podem utilizar esta funcionalidade para iniciar ligações através do XPipe. Desativar esta funcionalidade evita este cenário. openDocumentation=Abre a documentação openDocumentationDescription=Visita a página de documentação do XPipe para esta questão +renameAll=Renomeia tudo diff --git a/lang/app/strings/translations_ru.properties b/lang/app/strings/translations_ru.properties index f91a20fe4..dc2cac910 100644 --- a/lang/app/strings/translations_ru.properties +++ b/lang/app/strings/translations_ru.properties @@ -511,3 +511,4 @@ dontAllowTerminalRestart=Не разрешайте перезагрузку те dontAllowTerminalRestartDescription=По умолчанию терминальные сессии могут быть перезапущены после их завершения изнутри терминала. Чтобы разрешить это, XPipe будет принимать такие внешние запросы от терминала, чтобы снова запустить сессию\n\nXPipe не имеет никакого контроля над терминалом и тем, откуда поступает этот вызов, поэтому вредоносные локальные приложения могут использовать эту функциональность и для запуска соединений через XPipe. Отключение этой функциональности предотвращает подобный сценарий. openDocumentation=Открытая документация openDocumentationDescription=Посетите страницу документации XPipe по этому вопросу +renameAll=Переименовать все diff --git a/lang/app/strings/translations_tr.properties b/lang/app/strings/translations_tr.properties index 494f83bdf..be51fd1a5 100644 --- a/lang/app/strings/translations_tr.properties +++ b/lang/app/strings/translations_tr.properties @@ -512,3 +512,4 @@ dontAllowTerminalRestart=Terminalin yeniden başlatılmasına izin verme dontAllowTerminalRestartDescription=Varsayılan olarak, terminal oturumları terminal içinden sonlandırıldıktan sonra yeniden başlatılabilir. Buna izin vermek için XPipe, oturumu tekrar başlatmak üzere terminalden gelen şu harici istekleri kabul edecektir\n\nXPipe terminal ve bu çağrının nereden geldiği üzerinde herhangi bir kontrole sahip değildir, bu nedenle kötü niyetli yerel uygulamalar XPipe üzerinden bağlantı başlatmak için bu işlevi de kullanabilir. Bu işlevselliğin devre dışı bırakılması bu senaryoyu önler. openDocumentation=Açık dokümantasyon openDocumentationDescription=Bu sorun için XPipe dokümanlar sayfasını ziyaret edin +renameAll=Tümünü yeniden adlandır diff --git a/lang/app/strings/translations_zh.properties b/lang/app/strings/translations_zh.properties index cdb731b99..f4bb880f0 100644 --- a/lang/app/strings/translations_zh.properties +++ b/lang/app/strings/translations_zh.properties @@ -511,3 +511,4 @@ dontAllowTerminalRestart=不允许终端重启 dontAllowTerminalRestartDescription=默认情况下,终端会话可以在终端内部结束后重新启动。为了做到这一点,XPipe 将接受来自终端的这些外部请求,以再次启动会话\n\nXPipe无法控制终端以及该调用的来源,因此恶意本地应用程序也可以使用该功能通过XPipe启动连接。禁用该功能可防止出现这种情况。 openDocumentation=开放文档 openDocumentationDescription=访问 XPipe 文档页面了解这一问题 +renameAll=重命名所有