From 27f06514f57a818253d75ed7f60bf8b821cef70b Mon Sep 17 00:00:00 2001 From: Simon Hocker Date: Fri, 1 Mar 2024 11:17:57 +0100 Subject: [PATCH 1/5] feat: cloned u74981018's code and tested locally. Added FileNotFoundException and refactored. --- .../org/jabref/cli/ArgumentProcessor.java | 5 +- .../externalfiles/AutoLinkFilesAction.java | 3 +- .../externalfiles/AutoSetFileLinksUtil.java | 71 ++++++++++++++++--- .../jabref/gui/remote/CLIMessageHandler.java | 4 ++ .../AutoSetFileLinksUtilTest.java | 43 +++++++++-- 5 files changed, 110 insertions(+), 16 deletions(-) diff --git a/src/main/java/org/jabref/cli/ArgumentProcessor.java b/src/main/java/org/jabref/cli/ArgumentProcessor.java index d8272507450..1b7ba2e1990 100644 --- a/src/main/java/org/jabref/cli/ArgumentProcessor.java +++ b/src/main/java/org/jabref/cli/ArgumentProcessor.java @@ -1,5 +1,6 @@ package org.jabref.cli; +import java.io.FileNotFoundException; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; @@ -188,7 +189,7 @@ private Optional importFile(Path file, String importFormat) { } } - public void processArguments() { + public void processArguments() throws FileNotFoundException { uiCommands.clear(); if ((startupMode == Mode.INITIAL_START) && cli.isShowVersion()) { @@ -711,7 +712,7 @@ private void resetPreferences(String value) { } } - private void automaticallySetFileLinks(List loaded) { + private void automaticallySetFileLinks(List loaded) throws FileNotFoundException { for (ParserResult parserResult : loaded) { BibDatabase database = parserResult.getDatabase(); LOGGER.info(Localization.lang("Automatically setting file links")); diff --git a/src/main/java/org/jabref/gui/externalfiles/AutoLinkFilesAction.java b/src/main/java/org/jabref/gui/externalfiles/AutoLinkFilesAction.java index 689d79dca69..f8dac0eed60 100644 --- a/src/main/java/org/jabref/gui/externalfiles/AutoLinkFilesAction.java +++ b/src/main/java/org/jabref/gui/externalfiles/AutoLinkFilesAction.java @@ -1,5 +1,6 @@ package org.jabref.gui.externalfiles; +import java.io.FileNotFoundException; import java.util.List; import javax.swing.undo.UndoManager; @@ -55,7 +56,7 @@ public void execute() { Task linkFilesTask = new Task<>() { @Override - protected AutoSetFileLinksUtil.LinkFilesResult call() { + protected AutoSetFileLinksUtil.LinkFilesResult call() throws FileNotFoundException { return util.linkAssociatedFiles(entries, nc); } diff --git a/src/main/java/org/jabref/gui/externalfiles/AutoSetFileLinksUtil.java b/src/main/java/org/jabref/gui/externalfiles/AutoSetFileLinksUtil.java index d1fed3f9f0a..55a65c0d73a 100644 --- a/src/main/java/org/jabref/gui/externalfiles/AutoSetFileLinksUtil.java +++ b/src/main/java/org/jabref/gui/externalfiles/AutoSetFileLinksUtil.java @@ -1,6 +1,8 @@ package org.jabref.gui.externalfiles; +import java.io.FileNotFoundException; import java.io.IOException; +import java.nio.file.FileVisitOption; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; @@ -30,15 +32,17 @@ public class AutoSetFileLinksUtil { + record RelinkedResults(List relinkedFiles, List exceptions) { } + public static class LinkFilesResult { private final List changedEntries = new ArrayList<>(); - private final List fileExceptions = new ArrayList<>(); + private final List fileExceptions = new ArrayList(); protected void addBibEntry(BibEntry bibEntry) { changedEntries.add(bibEntry); } - protected void addFileException(IOException exception) { + protected void addFileException(String exception) { fileExceptions.add(exception); } @@ -46,7 +50,7 @@ public List getChangedEntries() { return changedEntries; } - public List getFileExceptions() { + public List getFileExceptions() { return fileExceptions; } } @@ -66,7 +70,7 @@ private AutoSetFileLinksUtil(List directories, FilePreferences filePrefere this.filePreferences = filePreferences; } - public LinkFilesResult linkAssociatedFiles(List entries, NamedCompound ce) { + public LinkFilesResult linkAssociatedFiles(List entries, NamedCompound ce) throws FileNotFoundException { LinkFilesResult result = new LinkFilesResult(); for (BibEntry entry : entries) { @@ -75,7 +79,7 @@ public LinkFilesResult linkAssociatedFiles(List entries, NamedCompound try { linkedFiles = findAssociatedNotLinkedFiles(entry); } catch (IOException e) { - result.addFileException(e); + result.addFileException(String.valueOf(e)); LOGGER.error("Problem finding files", e); } @@ -94,10 +98,18 @@ public LinkFilesResult linkAssociatedFiles(List entries, NamedCompound entry.addFile(linkedFile); }); } - if (changed) { result.addBibEntry(entry); } + // Run Relinking Process + RelinkedResults relink = relinkingFiles(entry.getFiles()); + entry.setFiles(relink.relinkedFiles); + if (!relink.relinkedFiles().isEmpty()) { + result.addBibEntry(entry); + } + for (String e : (relink.exceptions)) { + result.addFileException(e); + } } } return result; @@ -127,8 +139,8 @@ public List findAssociatedNotLinkedFiles(BibEntry entry) throws IOEx if (!fileAlreadyLinked) { Optional type = FileUtil.getFileExtension(foundFile) - .map(extension -> ExternalFileTypes.getExternalFileTypeByExt(extension, filePreferences)) - .orElse(Optional.of(new UnknownExternalFileType(""))); + .map(extension -> ExternalFileTypes.getExternalFileTypeByExt(extension, filePreferences)) + .orElse(Optional.of(new UnknownExternalFileType(""))); String strType = type.isPresent() ? type.get().getName() : ""; Path relativeFilePath = FileUtil.relativize(foundFile, directories); @@ -136,7 +148,48 @@ public List findAssociatedNotLinkedFiles(BibEntry entry) throws IOEx linkedFiles.add(linkedFile); } } - return linkedFiles; } + + public RelinkedResults relinkingFiles(List listlinked) throws FileNotFoundException { + List changedFiles = new ArrayList<>(); + List exceptions = new ArrayList<>(); + + for (LinkedFile file : listlinked) { + Path path = Path.of(file.getLink()); + if (!Files.exists(path)) { + Path filePath = Path.of(file.getLink()); + String directoryPath = filePath.getParent().getParent().toString(); + + String fileNameString = filePath.getFileName().toString(); + + List fileLocations = searchFileInDirectoryAndSubdirectories(Path.of(directoryPath), fileNameString); + if (!fileLocations.isEmpty()) { + file.setLink(fileLocations.get(0)); + changedFiles.add(file); + } else { + exceptions.add(fileNameString); + throw new FileNotFoundException(); + } + } + } + return new RelinkedResults(changedFiles, exceptions); + } + + public List searchFileInDirectoryAndSubdirectories(Path directory, String targetFileName) { + List paths = new ArrayList<>(); + try { + Files.walk(directory, Integer.MAX_VALUE, FileVisitOption.FOLLOW_LINKS) + .filter(path -> Files.isRegularFile(path)) + .filter(path -> path.getFileName().toString().equals(targetFileName)) + .forEach(paths::add); + } catch (IOException e) { + // Handle any exceptions here + } + List output = new ArrayList<>(); + for (Path p : paths) { + output.add(p.toString()); + } + return output; + } } diff --git a/src/main/java/org/jabref/gui/remote/CLIMessageHandler.java b/src/main/java/org/jabref/gui/remote/CLIMessageHandler.java index ed794039d97..ced49ce94d8 100644 --- a/src/main/java/org/jabref/gui/remote/CLIMessageHandler.java +++ b/src/main/java/org/jabref/gui/remote/CLIMessageHandler.java @@ -1,5 +1,6 @@ package org.jabref.gui.remote; +import java.io.FileNotFoundException; import java.util.Arrays; import javafx.application.Platform; @@ -42,6 +43,9 @@ public void handleCommandLineArguments(String[] message) { Platform.runLater(() -> JabRefGUI.getMainFrame().handleUiCommands(argumentProcessor.getUiCommands())); } catch (ParseException e) { LOGGER.error("Error when parsing CLI args", e); + } catch ( + FileNotFoundException e) { + throw new RuntimeException(e); } } } diff --git a/src/test/java/org/jabref/gui/externalfiles/AutoSetFileLinksUtilTest.java b/src/test/java/org/jabref/gui/externalfiles/AutoSetFileLinksUtilTest.java index 51a91c5bc80..603faa5ac09 100644 --- a/src/test/java/org/jabref/gui/externalfiles/AutoSetFileLinksUtilTest.java +++ b/src/test/java/org/jabref/gui/externalfiles/AutoSetFileLinksUtilTest.java @@ -1,7 +1,10 @@ package org.jabref.gui.externalfiles; +import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.TreeSet; @@ -26,7 +29,6 @@ import static org.mockito.Mockito.when; public class AutoSetFileLinksUtilTest { - private final FilePreferences filePreferences = mock(FilePreferences.class); private final AutoLinkPreferences autoLinkPrefs = new AutoLinkPreferences( AutoLinkPreferences.CitationKeyDependency.START, @@ -36,10 +38,16 @@ public class AutoSetFileLinksUtilTest { private final BibDatabaseContext databaseContext = mock(BibDatabaseContext.class); private final BibEntry entry = new BibEntry(StandardEntryType.Article); private Path path = null; + private Path A; + private Path B; @BeforeEach public void setUp(@TempDir Path folder) throws Exception { - path = folder.resolve("CiteKey.pdf"); + A = folder.resolve("A"); + Files.createDirectory(A); + B = folder.resolve("B"); + Files.createDirectory(B); + path = A.resolve("CiteKey.pdf"); Files.createFile(path); entry.setCitationKey("CiteKey"); when(filePreferences.getExternalFileTypes()) @@ -47,7 +55,7 @@ public void setUp(@TempDir Path folder) throws Exception { } @Test - public void findAssociatedNotLinkedFilesSuccess() throws Exception { + public void testFindAssociatedNotLinkedFilesSuccess() throws Exception { when(databaseContext.getFileDirectories(any())).thenReturn(Collections.singletonList(path.getParent())); List expected = Collections.singletonList(new LinkedFile("", Path.of("CiteKey.pdf"), "PDF")); AutoSetFileLinksUtil util = new AutoSetFileLinksUtil(databaseContext, filePreferences, autoLinkPrefs); @@ -56,10 +64,37 @@ public void findAssociatedNotLinkedFilesSuccess() throws Exception { } @Test - public void findAssociatedNotLinkedFilesForEmptySearchDir() throws Exception { + public void testFindAssociatedNotLinkedFilesForEmptySearchDir() throws Exception { when(filePreferences.shouldStoreFilesRelativeToBibFile()).thenReturn(false); AutoSetFileLinksUtil util = new AutoSetFileLinksUtil(databaseContext, filePreferences, autoLinkPrefs); List actual = util.findAssociatedNotLinkedFiles(entry); assertEquals(Collections.emptyList(), actual); } + + @Test + public void testFileLinksAfterMoving() throws Exception { + // Run "Automatically set file links" - check that the bib file was not modified + LinkedFile linkedFile = new LinkedFile("desc", path, "PDF"); + List listLinked = new ArrayList<>(); + listLinked.add(linkedFile); + entry.setFiles(listLinked); + + // Copy Bib file from A to B + Path destination = B.resolve("CiteKey.pdf"); + Files.copy(A.resolve("CiteKey.pdf"), destination, StandardCopyOption.REPLACE_EXISTING); + Files.deleteIfExists(A.resolve("CiteKey.pdf")); + + AutoSetFileLinksUtil util = new AutoSetFileLinksUtil(databaseContext, filePreferences, autoLinkPrefs); + AutoSetFileLinksUtil.RelinkedResults results = util.relinkingFiles(entry.getFiles()); + List list = results.relinkedFiles(); + List exceptions = results.exceptions(); + assertEquals(Collections.emptyList(), exceptions); + + // Change Entry to match required result and run method on bib + LinkedFile linkedDestFile = new LinkedFile("desc", B.resolve("CiteKey.pdf"), "PDF"); + List listLinked2 = new ArrayList<>(); + listLinked2.add(linkedDestFile); + entry.setFiles(listLinked2); + assertEquals(entry.getFiles(), list); + } } From 2d94893e92f068525e079b5b734387ce4ad96689 Mon Sep 17 00:00:00 2001 From: Simon Hocker Date: Fri, 1 Mar 2024 14:51:30 +0100 Subject: [PATCH 2/5] fix: addressed owner's feedback suggestions. Co-authored-by: Serhan cakmakserhan02@gmail.com --- .../externalfiles/AutoSetFileLinksUtil.java | 13 +++-- .../AutoSetFileLinksUtilTest.java | 48 +++++++++++++------ 2 files changed, 41 insertions(+), 20 deletions(-) diff --git a/src/main/java/org/jabref/gui/externalfiles/AutoSetFileLinksUtil.java b/src/main/java/org/jabref/gui/externalfiles/AutoSetFileLinksUtil.java index 55a65c0d73a..38b9ebfee77 100644 --- a/src/main/java/org/jabref/gui/externalfiles/AutoSetFileLinksUtil.java +++ b/src/main/java/org/jabref/gui/externalfiles/AutoSetFileLinksUtil.java @@ -159,13 +159,16 @@ public RelinkedResults relinkingFiles(List listlinked) throws FileNo Path path = Path.of(file.getLink()); if (!Files.exists(path)) { Path filePath = Path.of(file.getLink()); + // May need to put some limit since it can cause considerable performance problems. String directoryPath = filePath.getParent().getParent().toString(); String fileNameString = filePath.getFileName().toString(); - List fileLocations = searchFileInDirectoryAndSubdirectories(Path.of(directoryPath), fileNameString); + List fileLocations = searchFileInDirectoryAndSubdirectories(Path.of(directoryPath), fileNameString); if (!fileLocations.isEmpty()) { - file.setLink(fileLocations.get(0)); + // File locations is a list but as stated in the link below, it is a rare case. But can be solved if time allows. + // https://github.com/JabRef/jabref/issues/9798#issuecomment-1524155132 + file.setLink(fileLocations.get(0).toString()); changedFiles.add(file); } else { exceptions.add(fileNameString); @@ -176,7 +179,7 @@ public RelinkedResults relinkingFiles(List listlinked) throws FileNo return new RelinkedResults(changedFiles, exceptions); } - public List searchFileInDirectoryAndSubdirectories(Path directory, String targetFileName) { + public List searchFileInDirectoryAndSubdirectories(Path directory, String targetFileName) { List paths = new ArrayList<>(); try { Files.walk(directory, Integer.MAX_VALUE, FileVisitOption.FOLLOW_LINKS) @@ -186,9 +189,9 @@ public List searchFileInDirectoryAndSubdirectories(Path directory, Strin } catch (IOException e) { // Handle any exceptions here } - List output = new ArrayList<>(); + List output = new ArrayList<>(); for (Path p : paths) { - output.add(p.toString()); + output.add(p); } return output; } diff --git a/src/test/java/org/jabref/gui/externalfiles/AutoSetFileLinksUtilTest.java b/src/test/java/org/jabref/gui/externalfiles/AutoSetFileLinksUtilTest.java index 603faa5ac09..a63cb4f11ab 100644 --- a/src/test/java/org/jabref/gui/externalfiles/AutoSetFileLinksUtilTest.java +++ b/src/test/java/org/jabref/gui/externalfiles/AutoSetFileLinksUtilTest.java @@ -1,6 +1,5 @@ package org.jabref.gui.externalfiles; -import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; @@ -38,16 +37,10 @@ public class AutoSetFileLinksUtilTest { private final BibDatabaseContext databaseContext = mock(BibDatabaseContext.class); private final BibEntry entry = new BibEntry(StandardEntryType.Article); private Path path = null; - private Path A; - private Path B; @BeforeEach public void setUp(@TempDir Path folder) throws Exception { - A = folder.resolve("A"); - Files.createDirectory(A); - B = folder.resolve("B"); - Files.createDirectory(B); - path = A.resolve("CiteKey.pdf"); + path = folder.resolve("CiteKey.pdf"); Files.createFile(path); entry.setCitationKey("CiteKey"); when(filePreferences.getExternalFileTypes()) @@ -71,30 +64,55 @@ public void testFindAssociatedNotLinkedFilesForEmptySearchDir() throws Exception assertEquals(Collections.emptyList(), actual); } + /* @BeforeEach + public void setUp(@TempDir Path folder) throws Exception { + A = folder.resolve("A"); + Files.createDirectory(A); + B = folder.resolve("B"); + Files.createDirectory(B); + path = A.resolve("CiteKey.pdf"); + Files.createFile(path); + entry.setCitationKey("CiteKey"); + when(filePreferences.getExternalFileTypes()) + .thenReturn(FXCollections.observableSet(new TreeSet<>(ExternalFileTypes.getDefaultExternalFileTypes()))); + } */ + @Test public void testFileLinksAfterMoving() throws Exception { // Run "Automatically set file links" - check that the bib file was not modified - LinkedFile linkedFile = new LinkedFile("desc", path, "PDF"); + path = path.getParent(); + + Path A = path.resolve("A"); + Files.createDirectory(A); + Path B = path.resolve("B"); + Files.createDirectory(B); + Path filePath = A.resolve("Test.pdf"); + Files.createFile(filePath); + entry.setCitationKey("CiteKey"); + + LinkedFile linkedFile = new LinkedFile("desc", filePath, "PDF"); List listLinked = new ArrayList<>(); listLinked.add(linkedFile); entry.setFiles(listLinked); - // Copy Bib file from A to B - Path destination = B.resolve("CiteKey.pdf"); - Files.copy(A.resolve("CiteKey.pdf"), destination, StandardCopyOption.REPLACE_EXISTING); - Files.deleteIfExists(A.resolve("CiteKey.pdf")); + Path destination = B.resolve("Test.pdf"); + Files.move(A.resolve("Test.pdf"), destination, StandardCopyOption.REPLACE_EXISTING); AutoSetFileLinksUtil util = new AutoSetFileLinksUtil(databaseContext, filePreferences, autoLinkPrefs); AutoSetFileLinksUtil.RelinkedResults results = util.relinkingFiles(entry.getFiles()); List list = results.relinkedFiles(); - List exceptions = results.exceptions(); + List exceptions = results.exceptions(); assertEquals(Collections.emptyList(), exceptions); // Change Entry to match required result and run method on bib - LinkedFile linkedDestFile = new LinkedFile("desc", B.resolve("CiteKey.pdf"), "PDF"); + LinkedFile linkedDestFile = new LinkedFile("desc", B.resolve("Test.pdf"), "PDF"); List listLinked2 = new ArrayList<>(); listLinked2.add(linkedDestFile); entry.setFiles(listLinked2); assertEquals(entry.getFiles(), list); } + + // todo There should be another test where the file stays the same + // In test code, it is very OK to have duplicated code fragments. + } From 533eaa0890447374fd196fed08b2b5277142f69a Mon Sep 17 00:00:00 2001 From: Simon Hocker Date: Fri, 1 Mar 2024 15:47:30 +0100 Subject: [PATCH 3/5] feat: Added further tests. Co-authored-by: Serhan cakmakserhan02@gmail.com --- .../AutoSetFileLinksUtilTest.java | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/src/test/java/org/jabref/gui/externalfiles/AutoSetFileLinksUtilTest.java b/src/test/java/org/jabref/gui/externalfiles/AutoSetFileLinksUtilTest.java index a63cb4f11ab..54290120ced 100644 --- a/src/test/java/org/jabref/gui/externalfiles/AutoSetFileLinksUtilTest.java +++ b/src/test/java/org/jabref/gui/externalfiles/AutoSetFileLinksUtilTest.java @@ -88,7 +88,7 @@ public void testFileLinksAfterMoving() throws Exception { Files.createDirectory(B); Path filePath = A.resolve("Test.pdf"); Files.createFile(filePath); - entry.setCitationKey("CiteKey"); + entry.setCitationKey("Test"); LinkedFile linkedFile = new LinkedFile("desc", filePath, "PDF"); List listLinked = new ArrayList<>(); @@ -110,9 +110,30 @@ public void testFileLinksAfterMoving() throws Exception { listLinked2.add(linkedDestFile); entry.setFiles(listLinked2); assertEquals(entry.getFiles(), list); - } + + // After this point, tested different extension along with the case where document has multiple file references + Path filePath2 = B.resolve("Test1.txt"); + Files.createFile(filePath2); + linkedFile = new LinkedFile("desc", filePath2, "TXT"); + listLinked.add(linkedFile); + entry.setFiles(listLinked); + + destination = A.resolve("Test1.txt"); + Files.move(B.resolve("Test1.txt"), destination, StandardCopyOption.REPLACE_EXISTING); + util = new AutoSetFileLinksUtil(databaseContext, filePreferences, autoLinkPrefs); + results = util.relinkingFiles(entry.getFiles()); + list = results.relinkedFiles(); + exceptions = results.exceptions(); + + listLinked2.clear(); + listLinked2.add(new LinkedFile("desc", B.resolve("Test.pdf"), "PDF")); + listLinked2.add(new LinkedFile("desc", A.resolve("Test1.txt"), "TXT")); + + assertEquals(listLinked2, list); + } // todo There should be another test where the file stays the same // In test code, it is very OK to have duplicated code fragments. + } From ca0d08b354dacd28b9dfbd86c7c3af0acb9faddd Mon Sep 17 00:00:00 2001 From: Simon Hocker Date: Fri, 1 Mar 2024 15:49:25 +0100 Subject: [PATCH 4/5] fix: corrected checkstyle comments. Co-authored-by: Serhan cakmakserhan02@gmail.com --- .../jabref/gui/externalfiles/AutoSetFileLinksUtilTest.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/test/java/org/jabref/gui/externalfiles/AutoSetFileLinksUtilTest.java b/src/test/java/org/jabref/gui/externalfiles/AutoSetFileLinksUtilTest.java index 54290120ced..9c1226cb8a0 100644 --- a/src/test/java/org/jabref/gui/externalfiles/AutoSetFileLinksUtilTest.java +++ b/src/test/java/org/jabref/gui/externalfiles/AutoSetFileLinksUtilTest.java @@ -111,7 +111,6 @@ public void testFileLinksAfterMoving() throws Exception { entry.setFiles(listLinked2); assertEquals(entry.getFiles(), list); - // After this point, tested different extension along with the case where document has multiple file references Path filePath2 = B.resolve("Test1.txt"); Files.createFile(filePath2); @@ -132,8 +131,4 @@ public void testFileLinksAfterMoving() throws Exception { assertEquals(listLinked2, list); } - // todo There should be another test where the file stays the same - // In test code, it is very OK to have duplicated code fragments. - - } From dc57b2ba55e0fc7a09e5a4f00243c14aec008005 Mon Sep 17 00:00:00 2001 From: Simon Hocker <65558810+RiLoGosh@users.noreply.github.com> Date: Fri, 1 Mar 2024 16:06:11 +0100 Subject: [PATCH 5/5] Update CHANGELOG.md We added file relinking functionality after a file is moved when clicking the "Automatically set file links" button. Implementation based and improved upon work done in closed issue [#10526]. [#9798] --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9db672a5f8a..18e7a255640 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv - When pasting HTML into the abstract or a comment field, the hypertext is automatically converted to Markdown. [#10558](https://github.com/JabRef/jabref/issues/10558) - We added the possibility to redownload files that had been present but are no longer in the specified location. [#10848](https://github.com/JabRef/jabref/issues/10848) - We added the citation key pattern `[camelN]`. Equivalent to the first N words of the `[camel]` pattern. +- We added file relinking functionality after a file is moved when clicking the "Automatically set file links" button. Implementation based and improved upon work done in closed issue [#10526]. [#9798] ### Changed