Skip to content
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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<Path> 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);
Expand Down Expand Up @@ -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."));
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -16,13 +17,17 @@
import org.jabref.model.entry.LinkedFile;

import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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;
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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<String> oldExtension = FileUtil.getFileExtension(oldPath);
Optional<String> 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());
}

Expand All @@ -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
Expand All @@ -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));
}

Expand Down
2 changes: 2 additions & 0 deletions jablib/src/main/java/org/jabref/logic/util/io/FileUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,7 @@ public static List<Path> getListOfLinkedFiles(@NonNull List<BibEntry> entries, @
public static Optional<String> 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);
}
Expand All @@ -392,6 +393,7 @@ public static Optional<String> 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);
}
Expand Down
Loading