diff --git a/CHANGELOG.md b/CHANGELOG.md index 036cc5ce61d..76800e9a0e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,9 @@ Note that this project **does not** adhere to [Semantic Versioning](http://semve - We added a progress counter to the title bar in Possible Duplicates dialog window. [#7366](https://github.com/JabRef/jabref/issues/7366) - We added new "Customization" tab to the preferences which includes option to choose a custom address for DOI access. [#7337](https://github.com/JabRef/jabref/issues/7337) - We added zbmath to the public databases from which the bibliographic information of an existing entry can be updated. [#7437](https://github.com/JabRef/jabref/issues/7437) +- We showed to the find Unlinked Files Dialog the date of the files' most recent modification. [#4652](https://github.com/JabRef/jabref/issues/4652) +- We added to the find Unlinked Files function a filter to show only files based on date of last modification (Last Year, Last Month, Last Week, Last Day). [#4652](https://github.com/JabRef/jabref/issues/4652) +- We added to the find Unlinked Files function a filter that sorts the files based on the date of last modification(Sort by Newest, Sort by Oldest First). [#4652](https://github.com/JabRef/jabref/issues/4652) - We added the possibility to add a new entry via its zbMath ID (zbMATH can be chosen as ID type in the "Select entry type" window). [#7202](https://github.com/JabRef/jabref/issues/7202) - We added the extension support and the external application support (For Texshow, Texmaker and LyX) to the flatpak [#7248](https://github.com/JabRef/jabref/pull/7248) - We added some symbols and keybindings to the context menu in the entry editor. [#7268](https://github.com/JabRef/jabref/pull/7268) diff --git a/src/main/java/org/jabref/gui/externalfiles/DateRange.java b/src/main/java/org/jabref/gui/externalfiles/DateRange.java new file mode 100644 index 00000000000..b73f700481c --- /dev/null +++ b/src/main/java/org/jabref/gui/externalfiles/DateRange.java @@ -0,0 +1,21 @@ +package org.jabref.gui.externalfiles; + +import org.jabref.logic.l10n.Localization; + +public enum DateRange { + ALL_TIME(Localization.lang("All time")), + YEAR(Localization.lang("Last year")), + MONTH(Localization.lang("Last month")), + WEEK(Localization.lang("Last week")), + DAY(Localization.lang("Last day")); + + private final String dateRange; + + DateRange(String dateRange) { + this.dateRange = dateRange; + } + + public String getDateRange() { + return dateRange; + } +} diff --git a/src/main/java/org/jabref/gui/externalfiles/ExternalFileSorter.java b/src/main/java/org/jabref/gui/externalfiles/ExternalFileSorter.java new file mode 100644 index 00000000000..e425c642750 --- /dev/null +++ b/src/main/java/org/jabref/gui/externalfiles/ExternalFileSorter.java @@ -0,0 +1,19 @@ +package org.jabref.gui.externalfiles; + +import org.jabref.logic.l10n.Localization; + +public enum ExternalFileSorter { + DEFAULT(Localization.lang("Default")), + DATE_ASCENDING(Localization.lang("Newest first")), + DATE_DESCENDING(Localization.lang("Oldest first")); + + private final String sorter; + + ExternalFileSorter(String sorter) { + this.sorter = sorter; + } + + public String getSorter() { + return sorter; + } +} diff --git a/src/main/java/org/jabref/gui/externalfiles/FileFilterUtils.java b/src/main/java/org/jabref/gui/externalfiles/FileFilterUtils.java new file mode 100644 index 00000000000..28e1f20929a --- /dev/null +++ b/src/main/java/org/jabref/gui/externalfiles/FileFilterUtils.java @@ -0,0 +1,112 @@ +package org.jabref.gui.externalfiles; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.attribute.FileTime; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class FileFilterUtils { + + private static final Logger LOGGER = LoggerFactory.getLogger(FileFilterUtils.class); + + /* Returns the last edited time of a file as LocalDateTime. */ + public static LocalDateTime getFileTime(Path path) { + FileTime lastEditedTime = null; + try { + lastEditedTime = Files.getLastModifiedTime(path); + } catch (IOException e) { + LOGGER.error("Could not retrieve file time", e); + return LocalDateTime.now(); + } + LocalDateTime localDateTime = lastEditedTime + .toInstant() + .atZone(ZoneId.systemDefault()) + .toLocalDateTime(); + return localDateTime; + } + + /* Returns true if a file with a specific path + * was edited during the last 24 hours. */ + public boolean isDuringLastDay(LocalDateTime fileEditTime) { + LocalDateTime NOW = LocalDateTime.now(ZoneId.systemDefault()); + return fileEditTime.isAfter(NOW.minusHours(24)); + } + + /* Returns true if a file with a specific path + * was edited during the last 7 days. */ + public boolean isDuringLastWeek(LocalDateTime fileEditTime) { + LocalDateTime NOW = LocalDateTime.now(ZoneId.systemDefault()); + return fileEditTime.isAfter(NOW.minusDays(7)); + } + + /* Returns true if a file with a specific path + * was edited during the last 30 days. */ + public boolean isDuringLastMonth(LocalDateTime fileEditTime) { + LocalDateTime NOW = LocalDateTime.now(ZoneId.systemDefault()); + return fileEditTime.isAfter(NOW.minusDays(30)); + } + + /* Returns true if a file with a specific path + * was edited during the last 365 days. */ + public boolean isDuringLastYear(LocalDateTime fileEditTime) { + LocalDateTime NOW = LocalDateTime.now(ZoneId.systemDefault()); + return fileEditTime.isAfter(NOW.minusDays(365)); + } + + /* Returns true if a file is edited in the time margin specified by the given filter. */ + public static boolean filterByDate(Path path, DateRange filter) { + FileFilterUtils fileFilter = new FileFilterUtils(); + LocalDateTime fileTime = FileFilterUtils.getFileTime(path); + boolean isInDateRange = switch (filter) { + case DAY -> fileFilter.isDuringLastDay(fileTime); + case WEEK -> fileFilter.isDuringLastWeek(fileTime); + case MONTH -> fileFilter.isDuringLastMonth(fileTime); + case YEAR -> fileFilter.isDuringLastYear(fileTime); + case ALL_TIME -> true; + }; + return isInDateRange; + } + + /* Sorts a list of Path objects according to the last edited date + * of their corresponding files, from newest to oldest. */ + public List sortByDateAscending(List files) { + return files.stream() + .sorted(Comparator.comparingLong(file -> FileFilterUtils.getFileTime(file) + .atZone(ZoneId.systemDefault()) + .toInstant() + .toEpochMilli())) + .collect(Collectors.toList()); + } + + /* Sorts a list of Path objects according to the last edited date + * of their corresponding files, from oldest to newest. */ + public List sortByDateDescending(List files) { + return files.stream() + .sorted(Comparator.comparingLong(file -> -FileFilterUtils.getFileTime(file) + .atZone(ZoneId.systemDefault()) + .toInstant() + .toEpochMilli())) + .collect(Collectors.toList()); + } + + /* Sorts a list of Path objects according to the last edited date + * the order depends on the specified sorter type. */ + public static List sortByDate(List files, ExternalFileSorter sortType) { + FileFilterUtils fileFilter = new FileFilterUtils(); + List sortedFiles = switch (sortType) { + case DEFAULT -> files; + case DATE_ASCENDING -> fileFilter.sortByDateDescending(files); + case DATE_DESCENDING -> fileFilter.sortByDateAscending(files); + }; + return sortedFiles; + } +} + diff --git a/src/main/java/org/jabref/gui/externalfiles/UnlinkedFilesCrawler.java b/src/main/java/org/jabref/gui/externalfiles/UnlinkedFilesCrawler.java index 4b29a57204f..53ebad10fc5 100644 --- a/src/main/java/org/jabref/gui/externalfiles/UnlinkedFilesCrawler.java +++ b/src/main/java/org/jabref/gui/externalfiles/UnlinkedFilesCrawler.java @@ -32,12 +32,16 @@ public class UnlinkedFilesCrawler extends BackgroundTask { private final Path directory; private final Filter fileFilter; + private final DateRange dateFilter; + private final ExternalFileSorter sorter; private final BibDatabaseContext databaseContext; private final FilePreferences filePreferences; - public UnlinkedFilesCrawler(Path directory, Filter fileFilter, BibDatabaseContext databaseContext, FilePreferences filePreferences) { + public UnlinkedFilesCrawler(Path directory, Filter fileFilter, DateRange dateFilter, ExternalFileSorter sorter, BibDatabaseContext databaseContext, FilePreferences filePreferences) { this.directory = directory; this.fileFilter = fileFilter; + this.dateFilter = dateFilter; + this.sorter = sorter; this.databaseContext = databaseContext; this.filePreferences = filePreferences; } @@ -61,6 +65,9 @@ protected FileNodeViewModel call() throws IOException { * For ensuring the capability to cancel the work of this recursive method, the first position in the integer array * 'state' must be set to 1, to keep the recursion running. When the states value changes, the method will resolve * its recursion and return what it has saved so far. + *
+ * The files are filtered according to the {@link DateRange} filter value + * and then sorted according to the {@link ExternalFileSorter} value. * * @throws IOException if directory is not a directory or empty */ @@ -92,11 +99,19 @@ private FileNodeViewModel searchDirectory(Path directory, UnlinkedPDFFileFilter parent.getChildren().add(subRoot); } } - - parent.setFileCount(files.size() + fileCount); - parent.getChildren().addAll(files.stream() - .map(FileNodeViewModel::new) - .collect(Collectors.toList())); + // filter files according to last edited date. + List filteredFiles = new ArrayList(); + for (Path path : files) { + if (FileFilterUtils.filterByDate(path, dateFilter)) { + filteredFiles.add(path); + } + } + // sort files according to last edited date. + filteredFiles = FileFilterUtils.sortByDate(filteredFiles, sorter); + parent.setFileCount(filteredFiles.size() + fileCount); + parent.getChildren().addAll(filteredFiles.stream() + .map(FileNodeViewModel::new) + .collect(Collectors.toList())); return parent; } } diff --git a/src/main/java/org/jabref/gui/externalfiles/UnlinkedFilesDialog.fxml b/src/main/java/org/jabref/gui/externalfiles/UnlinkedFilesDialog.fxml index 1c7109f3504..e3d2b8b9133 100644 --- a/src/main/java/org/jabref/gui/externalfiles/UnlinkedFilesDialog.fxml +++ b/src/main/java/org/jabref/gui/externalfiles/UnlinkedFilesDialog.fxml @@ -52,8 +52,12 @@