diff --git a/jabgui/src/main/java/org/jabref/gui/fieldeditors/LinkedFileViewModel.java b/jabgui/src/main/java/org/jabref/gui/fieldeditors/LinkedFileViewModel.java index 0078883b683..6799149011e 100644 --- a/jabgui/src/main/java/org/jabref/gui/fieldeditors/LinkedFileViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/fieldeditors/LinkedFileViewModel.java @@ -1,6 +1,7 @@ package org.jabref.gui.fieldeditors; import java.io.IOException; +import java.nio.file.FileSystemException; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; @@ -270,12 +271,14 @@ public void renameFileToName(String targetFileName) { private void performRenameWithConflictCheck(String targetFileName) { // Check if a file with the same name already exists Optional existingFile = linkedFileHandler.findExistingFile(linkedFile, entry, targetFileName); + LOGGER.debug("file already exists: {}", existingFile.isPresent()); boolean overwriteFile = false; if (existingFile.isPresent()) { // Get existing file path and its directory Path existingFilePath = existingFile.get(); Path targetDirectory = existingFilePath.getParent(); + LOGGER.debug("existing file path: {} || target Directory: {}", existingFilePath, targetDirectory); // Suggest a non-conflicting file name String suggestedFileName = FileNameUniqueness.getNonOverWritingFileName(targetDirectory, targetFileName); @@ -316,10 +319,18 @@ private void performRenameWithConflictCheck(String targetFileName) { // Attempt the rename operation linkedFileHandler.renameToName(targetFileName, overwriteFile); } catch (IOException e) { - // Display an error dialog if file is locked or inaccessible - dialogService.showErrorDialogAndWait( - Localization.lang("Rename failed"), - Localization.lang("JabRef cannot access the file because it is being used by another process.")); + LOGGER.error("ERROR MESSAGE FOR RENAMING THE FILE: {}", e.getMessage()); + if (e instanceof FileSystemException fe) { + LOGGER.error(fe.getReason()); + dialogService.showErrorDialogAndWait( + Localization.lang("Rename failed"), + Localization.lang("JabRef could not rename the file. Please use a shorter filename or a shorter pattern or try changing the directory.")); + } else { + // Display an error dialog if file is locked or inaccessible + dialogService.showErrorDialogAndWait( + Localization.lang("Rename failed"), + Localization.lang("JabRef cannot access the file because it is being used by another process.")); + } } } diff --git a/jablib/src/main/java/org/jabref/logic/externalfiles/LinkedFileHandler.java b/jablib/src/main/java/org/jabref/logic/externalfiles/LinkedFileHandler.java index 499dd167f41..e6757779265 100644 --- a/jablib/src/main/java/org/jabref/logic/externalfiles/LinkedFileHandler.java +++ b/jablib/src/main/java/org/jabref/logic/externalfiles/LinkedFileHandler.java @@ -8,6 +8,7 @@ import java.util.stream.Stream; import org.jabref.logic.FilePreferences; +import org.jabref.logic.os.OS; import org.jabref.logic.util.io.FileNameUniqueness; import org.jabref.logic.util.io.FileUtil; import org.jabref.logic.util.strings.StringUtil; @@ -16,6 +17,7 @@ import org.jabref.model.entry.LinkedFile; import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -23,6 +25,9 @@ public class LinkedFileHandler { private static final Logger LOGGER = LoggerFactory.getLogger(LinkedFileHandler.class); + private static final int MAX_PATH_LENGTH_WINDOWS = 259; + private static final int SEPERATOR_WINDOWS = 1; + private final BibDatabaseContext databaseContext; private final FilePreferences filePreferences; private final BibEntry entry; @@ -111,7 +116,10 @@ public boolean copyOrMoveToDefaultDirectory(boolean shouldMove, boolean shouldRe /// If exists: the path already exists and has the same content as the given sourcePath /// /// @param renamed The original/suggested filename was adapted to fit it - private record GetTargetPathResult(boolean exists, boolean renamed, Path path) { + private record GetTargetPathResult( + boolean exists, + boolean renamed, + Path path) { } private GetTargetPathResult getTargetPath(Path sourcePath, Path targetDirectory, boolean useSuggestedName) throws IOException { @@ -193,14 +201,29 @@ public boolean renameToName(String targetFileName, boolean overwriteExistingFile } final Path oldPath = oldFile.get(); + final int parentPathLength = oldPath.getParent() == null ? 0 : oldPath.getParent().toString().length(); + + LOGGER.debug("PARENT: {}", oldPath.getParent()); Optional oldExtension = FileUtil.getFileExtension(oldPath); Optional newExtension = FileUtil.getFileExtension(targetFileName); Path newPath; if (newExtension.isPresent() || (oldExtension.isEmpty() && newExtension.isEmpty())) { + if (OS.WINDOWS && (parentPathLength + targetFileName.length() + SEPERATOR_WINDOWS) > MAX_PATH_LENGTH_WINDOWS) { + if (newExtension.isPresent()) { + targetFileName = truncateFileNameOnWindows(targetFileName, parentPathLength, newExtension.get(), null); + } else { + targetFileName = truncateFileNameOnWindows(targetFileName, parentPathLength, null, null); + } + } newPath = oldPath.resolveSibling(targetFileName); + + LOGGER.debug("NEW PATH WITH THE NEW FILENAME: {}", newPath); } else { assert oldExtension.isPresent() && newExtension.isEmpty(); + if (OS.WINDOWS && (parentPathLength + targetFileName.length() + SEPERATOR_WINDOWS) > MAX_PATH_LENGTH_WINDOWS) { + targetFileName = truncateFileNameOnWindows(targetFileName, parentPathLength, null, oldExtension.get()); + } newPath = oldPath.resolveSibling(targetFileName + "." + oldExtension.get()); } @@ -222,7 +245,9 @@ public boolean renameToName(String targetFileName, boolean overwriteExistingFile Files.move(oldPath, newPath, StandardCopyOption.REPLACE_EXISTING); } else { Files.createDirectories(newPath.getParent()); + LOGGER.debug("OVERWRITING FILENAME TO {}", newPath); Files.move(oldPath, newPath); + LOGGER.debug("OVERWRITING SUCCESSFUL"); } // Update path @@ -235,15 +260,48 @@ public boolean renameToName(String targetFileName, boolean overwriteExistingFile return true; } + /// Helper function which truncates a file name for Windows if length (path + filename) exceeds the max windows + /// path limit. + /// + /// @param targetFileName proposed file name (may include an extension; only the base name is truncated) + /// @param parentLength Length of the parent directory for the file being renamed + /// @param newExtension extension from the target name (no leading "."), or null if the target has none + /// @param oldExtension extension from the existing file when the target has no extension and the old extension is kept + /// @return the shortened file name; includes {@code .extension} when {@code newExtension} is not {@code null} + private String truncateFileNameOnWindows(String targetFileName, int parentLength, @Nullable String newExtension, @Nullable String oldExtension) { + String baseName = FileUtil.getBaseName(targetFileName); + LOGGER.debug("BASENAME: {}", baseName); + int extensionLength = 0; + int dot = 0; + String fileName; + + if (newExtension != null) { + extensionLength = newExtension.length(); + dot = 1; + fileName = baseName.substring(0, (MAX_PATH_LENGTH_WINDOWS - parentLength - extensionLength - dot - SEPERATOR_WINDOWS)) + "." + newExtension; + } else if (oldExtension != null) { + extensionLength = oldExtension.length(); + dot = 1; + fileName = baseName.substring(0, (MAX_PATH_LENGTH_WINDOWS - parentLength - extensionLength - dot - SEPERATOR_WINDOWS)); + } else { + fileName = baseName.substring(0, (MAX_PATH_LENGTH_WINDOWS - parentLength - extensionLength - dot - SEPERATOR_WINDOWS)); + } + LOGGER.debug("NEW FILE NAME: {}", fileName); + + return fileName; + } + /// Determines the suggested file name based on the pattern specified in the preferences and valid for the file system. /// Uses file extension from original file. /// /// @return the suggested filename, including extension public String getSuggestedFileName() { + LOGGER.debug("NORMAL getSuggestedFileName METHOD CALLED"); String filename = linkedFile.getFileName().orElse("file"); + LOGGER.debug("FILENAME: {}", filename); final String targetFileName = FileUtil.createFileNameFromPattern(databaseContext.getDatabase(), entry, filePreferences.getFileNamePattern()) .orElse(FileUtil.getBaseName(filename)); - + LOGGER.debug("TARGET FILE NAME: {}", targetFileName); return FileUtil.getValidFileName(FileUtil.getFileExtension(filename).map(ext -> targetFileName + "." + ext).orElse(targetFileName)); } diff --git a/jablib/src/main/java/org/jabref/logic/util/io/FileUtil.java b/jablib/src/main/java/org/jabref/logic/util/io/FileUtil.java index 2fa6ef748c3..1d4ed2a208e 100644 --- a/jablib/src/main/java/org/jabref/logic/util/io/FileUtil.java +++ b/jablib/src/main/java/org/jabref/logic/util/io/FileUtil.java @@ -383,6 +383,7 @@ public static List getListOfLinkedFiles(@NonNull List entries, @ public static Optional createFileNameFromPattern(BibDatabase database, BibEntry entry, String fileNamePattern) { String targetName = BracketedPattern.expandBrackets(fileNamePattern, ';', entry, database).trim(); + LOGGER.debug("TARGET NAME BEFORE CLEANUP: {}", targetName); if (targetName.isEmpty() || "-".equals(targetName)) { return entry.getCitationKey().map(FileNameCleaner::cleanFileName); } @@ -392,6 +393,7 @@ public static Optional createFileNameFromPattern(BibDatabase database, B targetName = REMOVE_LATEX_COMMANDS_FORMATTER.format(targetName); // Removes illegal characters from filename targetName = FileNameCleaner.cleanFileName(targetName); + LOGGER.debug("TARGET NAME AFTER CLEANUP: {}", targetName); return Optional.of(targetName); } diff --git a/jablib/src/main/resources/l10n/JabRef_en.properties b/jablib/src/main/resources/l10n/JabRef_en.properties index bbc02d71b78..a97aaaecad1 100644 --- a/jablib/src/main/resources/l10n/JabRef_en.properties +++ b/jablib/src/main/resources/l10n/JabRef_en.properties @@ -1984,6 +1984,7 @@ Could\ not\ copy\ file=Could not copy file Copied\ %0\ files\ of\ %1\ successfully\ to\ %2=Copied %0 files of %1 successfully to %2 Rename\ failed=Rename failed JabRef\ cannot\ access\ the\ file\ because\ it\ is\ being\ used\ by\ another\ process.=JabRef cannot access the file because it is being used by another process. +JabRef\ could\ not\ rename\ the\ file.\ Please\ use\ a\ shorter\ filename\ or\ a\ shorter\ pattern\ or\ try\ changing\ the\ directory.=JabRef could not rename the file. Please use a shorter filename or a shorter pattern or try changing the directory. Remove\ line\ breaks=Remove line breaks Removes\ all\ line\ breaks\ in\ the\ field\ content.=Removes all line breaks in the field content.