Skip to content
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv
- We separated the "Clean up entries" dialog into three tabs for clarity [#13819](https://github.com/JabRef/jabref/issues/13819)
- `JabKit`: `--porcelain` does not output any logs to the console anymore. [#14244](https://github.com/JabRef/jabref/pull/14244)
- <kbd>Ctrl</kbd> + <kbd>Shift</kbd> + <kbd>L</kbd> now opens the terminal in the active library directory. [#14130](https://github.com/JabRef/jabref/issues/14130)
- We improved the handling of entries with broken links during the "Search for unlinked files" process. [#9798](https://github.com/JabRef/jabref/issues/9798)
- We changed fixed-value ComboBoxes to SearchableComboBox for better usability. [#14083](https://github.com/JabRef/jabref/issues/14083)

### Fixed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.stream.Stream;

Expand Down Expand Up @@ -96,7 +98,7 @@ public LinkFilesResult linkAssociatedFiles(List<BibEntry> entries, BiConsumer<Li
///
/// NOTE: This method does not check if the file is already linked to another entry.
public List<LinkedFile> findAssociatedNotLinkedFiles(BibEntry entry) throws IOException {
List<LinkedFile> linkedFiles = new ArrayList<>();
Set<LinkedFile> linkedFiles = new HashSet<>();

List<String> extensions = externalApplicationsPreferences.getExternalFileTypes().stream().map(ExternalFileType::getExtension).toList();

Expand Down Expand Up @@ -133,11 +135,11 @@ public List<LinkedFile> findAssociatedNotLinkedFiles(BibEntry entry) throws IOEx
}
}

return linkedFiles;
return linkedFiles.stream().toList();
Comment on lines -136 to +138
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels not OK - promising a list in the method contract, but internally a set with a random order.

Please convert the result type to Set and also adapt the callers. Then, you will see if there is a list assumption anywhere -- and if there should be an ordering done. If an ordering needs to be done, either do it here (Maybe, use SortedSet). Or do it at the place where sorting is needed.

}

private List<Path> findByBrokenLinkName(BibEntry entry) throws IOException {
List<Path> matches = new ArrayList<>();
Set<Path> matches = new HashSet<>();

for (LinkedFile brokenLink : entry.getFiles()) {
if (brokenLink.findIn(directories).isPresent()) {
Expand All @@ -150,12 +152,11 @@ private List<Path> findByBrokenLinkName(BibEntry entry) throws IOException {
try (Stream<Path> walk = Files.walk(directory)) {
walk.filter(path -> !Files.isDirectory(path))
.filter(path -> FileUtil.getBaseName(path).equalsIgnoreCase(wantedBase))
.findFirst()
.ifPresent(matches::add);
.forEach(matches::add);
}
}
}

return matches;
return matches.stream().toList();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,13 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import javax.swing.undo.UndoManager;

Expand Down Expand Up @@ -41,7 +45,10 @@
import org.jabref.logic.util.BackgroundTask;
import org.jabref.logic.util.StandardFileType;
import org.jabref.logic.util.TaskExecutor;
import org.jabref.logic.util.io.FileUtil;
import org.jabref.model.database.BibDatabaseContext;
import org.jabref.model.entry.BibEntry;
import org.jabref.model.entry.LinkedFile;
import org.jabref.model.util.FileUpdateMonitor;

import de.saxsys.mvvmfx.utils.validation.FunctionBasedValidator;
Expand Down Expand Up @@ -118,14 +125,82 @@ public UnlinkedFilesDialogViewModel(DialogService dialogService,
treeRootProperty.setValue(Optional.empty());
}

public void findAndFixBrokenLinks(Path searchDirectory) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method needs a JavaDoc comment

Maybe also the name needs to be adjusted --> fixBrokenLinksAndReturnStillBroken

Is it possible NOT to use a global variable resultList?

Finally, this should go to org.jabref.logic.util.io.FileUtil, because, it is NOT a UI thing, but a logic (which could be used by JabKit, too)

List<ImportFilesResultItemViewModel> results = new ArrayList<>();

// pre-build a map of all existing linked files for O(1) lookup
Set<Path> existingLinkedFiles = bibDatabase.getDatabase().getEntries().stream()
.flatMap(entry -> entry.getFiles().stream())
.map(linkedFile -> linkedFile.findIn(bibDatabase, preferences.getFilePreferences()))
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toSet());

for (BibEntry entry : bibDatabase.getDatabase().getEntries()) {
try {
List<LinkedFile> currentFiles = new ArrayList<>(entry.getFiles());
List<LinkedFile> filesToRemove = new ArrayList<>();
List<LinkedFile> filesToAdd = new ArrayList<>();

for (LinkedFile brokenLink : currentFiles) {
if (brokenLink.findIn(bibDatabase, preferences.getFilePreferences()).isPresent()) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this just duplicated? You add all those to entriesWithBrokenLInks that are not present so why checking them again here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not all.. only entries having at least one broken link... though it could be improved thnks for letting know

continue;
}

String exactFileName = Path.of(brokenLink.getLink()).getFileName().toString();
List<Path> matches = new ArrayList<>();

try (Stream<Path> walk = Files.walk(searchDirectory)) {
walk.filter(path -> !Files.isDirectory(path))
.filter(path -> path.getFileName().toString().equals(exactFileName))
.forEach(matches::add);
}

// check if matches found is only one
if (matches.size() != 1) {
continue;
}
Path foundFile = matches.getFirst();

// check if the potential file is not linked to other entries
if (existingLinkedFiles.contains(foundFile)) {
continue;
}

Path newFilePath = preferences.getFilePreferences().shouldStoreFilesRelativeToBibFile() ?
FileUtil.relativize(foundFile, bibDatabase.getFileDirectories(preferences.getFilePreferences())) :
foundFile;

filesToRemove.add(brokenLink);
filesToAdd.add(new LinkedFile(brokenLink.getDescription(), newFilePath, brokenLink.getFileType()));
existingLinkedFiles.add(foundFile);

results.add(new ImportFilesResultItemViewModel(foundFile, true,
Localization.lang("File relinked to entry %0.", exactFileName)));
}

if (!filesToRemove.isEmpty() || !filesToAdd.isEmpty()) {
currentFiles.removeAll(filesToRemove);
currentFiles.addAll(filesToAdd);
entry.setFiles(currentFiles);
}
} catch (IOException e) {
LOGGER.error("Error finding associated files for entry", e);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if the data structures are consistent in all error cases. Shouldn't the try/catch be more close to the points where the exception can occur and an appropriate handling take place?

Are results consistent in all cases? Maybe yes, but that deserves a code comment.

}
}

resultList.clear();
resultList.addAll(results);
Comment on lines +192 to +193
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs a Java comment - why this is done.

}

public void startSearch() {
Path directory = this.getSearchDirectory();
findAndFixBrokenLinks(directory);
Filter<Path> selectedFileFilter = selectedExtension.getValue().dirFilter();
DateRange selectedDateFilter = selectedDate.getValue();
ExternalFileSorter selectedSortFilter = selectedSort.getValue();
progressValueProperty.unbind();
progressTextProperty.unbind();

findUnlinkedFilesTask = new UnlinkedFilesCrawler(directory, selectedFileFilter, selectedDateFilter, selectedSortFilter, bibDatabase, preferences.getFilePreferences())
.onRunning(() -> {
progressValueProperty.set(ProgressIndicator.INDETERMINATE_PROGRESS);
Expand Down Expand Up @@ -296,4 +371,9 @@ public BooleanProperty taskActiveProperty() {
public SimpleListProperty<TreeItem<FileNodeViewModel>> checkedFileListProperty() {
return checkedFileListProperty;
}

// For testing purposes
public int resultListSize() {
Comment on lines +375 to +376
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// For testing purposes
public int resultListSize() {
// @VisibleForTesting
int resultListSize() {

return resultList.size();
}
}
Loading
Loading