Skip to content

Commit 2777ebc

Browse files
ror3dcalixtusSiedlerchr
authored
Keep source urls on file download (#10855)
* Add source URL to the linked files and update it when a file is initially downloaded * Add context menu action to redownload a file * Add main menu tools action to redownload missing files Refactor redownload code into an action * Don't change the local link name when redownloading, the link should already have an appropriate file name, so use that for the downloaded file. * Update CHANGELOG.md * revert method change * Revert changes in property files * Improve extension check * Fix issue where action would only be executed once per jabref execution. * Make string empty check more readable * Fix test for FileFieldParser * Small refactoring and language fix * Refactor LinkedFileViewModel to use the DownloadLinkedFileAction for downloading Change test to use that too. * Fix rewrite check * Change file field format to be backwards compatible with old versions with mediaType. Recover/add tests for the file field. * Small language fix and reduced public fingerprint * Extract downloadProgress * DownloadLinkedFileActionTest Co-authored-by: Christoph <[email protected]> * Fixed checkstyle * Revert "Change file field format to be backwards compatible with old versions with mediaType." This reverts commit 2230310. * Reapply and modify good tests --------- Co-authored-by: Carl Christian Snethlage <[email protected]> Co-authored-by: Christoph <[email protected]>
1 parent 4194a09 commit 2777ebc

19 files changed

+611
-246
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv
1717
- We added a new group icon column to the main table showing the icons of the entry's groups. [#10801](https://github.com/JabRef/jabref/pull/10801)
1818
- We added ability to jump to an entry in the command line using `-j CITATIONKEY`. [koppor#540](https://github.com/koppor/jabref/issues/540)
1919
- We added a new boolean to the style files for Openoffice/Libreoffice integration to switch between ZERO_WIDTH_SPACE (default) and no space. [#10843](https://github.com/JabRef/jabref/pull/10843)
20+
- 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)
2021

2122
### Changed
2223

src/main/java/org/jabref/gui/MainMenu.java

+6-1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
import org.jabref.gui.integrity.IntegrityCheckAction;
4545
import org.jabref.gui.journals.AbbreviateAction;
4646
import org.jabref.gui.libraryproperties.LibraryPropertiesAction;
47+
import org.jabref.gui.linkedfile.RedownloadMissingFilesAction;
4748
import org.jabref.gui.mergeentries.MergeEntriesAction;
4849
import org.jabref.gui.preferences.ShowPreferencesAction;
4950
import org.jabref.gui.preview.CopyCitationAction;
@@ -287,7 +288,11 @@ private void createMenu() {
287288

288289
new SeparatorMenuItem(),
289290

290-
factory.createMenuItem(StandardActions.REBUILD_FULLTEXT_SEARCH_INDEX, new RebuildFulltextSearchIndexAction(stateManager, frame::getCurrentLibraryTab, dialogService, preferencesService.getFilePreferences(), taskExecutor))
291+
factory.createMenuItem(StandardActions.REBUILD_FULLTEXT_SEARCH_INDEX, new RebuildFulltextSearchIndexAction(stateManager, frame::getCurrentLibraryTab, dialogService, preferencesService.getFilePreferences(), taskExecutor)),
292+
293+
new SeparatorMenuItem(),
294+
295+
factory.createMenuItem(StandardActions.REDOWNLOAD_MISSING_FILES, new RedownloadMissingFilesAction(stateManager, dialogService, preferencesService.getFilePreferences(), taskExecutor))
291296
);
292297
SidePaneType webSearchPane = SidePaneType.WEB_SEARCH;
293298
SidePaneType groupsPane = SidePaneType.GROUPS;

src/main/java/org/jabref/gui/actions/StandardActions.java

+2
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ public enum StandardActions implements Action {
3030
SEND_AS_EMAIL(Localization.lang("As Email")),
3131
SEND_TO_KINDLE(Localization.lang("To Kindle")),
3232
REBUILD_FULLTEXT_SEARCH_INDEX(Localization.lang("Rebuild fulltext search index"), IconTheme.JabRefIcons.FILE),
33+
REDOWNLOAD_MISSING_FILES(Localization.lang("Redownload missing files"), IconTheme.JabRefIcons.DOWNLOAD),
3334
OPEN_EXTERNAL_FILE(Localization.lang("Open file"), IconTheme.JabRefIcons.FILE, KeyBinding.OPEN_FILE),
3435
OPEN_URL(Localization.lang("Open URL or DOI"), IconTheme.JabRefIcons.WWW, KeyBinding.OPEN_URL_OR_DOI),
3536
SEARCH_SHORTSCIENCE(Localization.lang("Search ShortScience")),
@@ -149,6 +150,7 @@ public enum StandardActions implements Action {
149150

150151
EDIT_FILE_LINK(Localization.lang("Edit"), IconTheme.JabRefIcons.EDIT, KeyBinding.EDIT_ENTRY),
151152
DOWNLOAD_FILE(Localization.lang("Download file"), IconTheme.JabRefIcons.DOWNLOAD_FILE),
153+
REDOWNLOAD_FILE(Localization.lang("Redownload file"), IconTheme.JabRefIcons.DOWNLOAD_FILE),
152154
RENAME_FILE_TO_PATTERN(Localization.lang("Rename file to defined pattern"), IconTheme.JabRefIcons.AUTO_RENAME),
153155
RENAME_FILE_TO_NAME(Localization.lang("Rename file to a given name"), IconTheme.JabRefIcons.RENAME, KeyBinding.REPLACE_STRING),
154156
MOVE_FILE_TO_FOLDER(Localization.lang("Move file to file directory"), IconTheme.JabRefIcons.MOVE_TO_FOLDER),

src/main/java/org/jabref/gui/externalfiles/FileDownloadTask.java

-40
This file was deleted.

src/main/java/org/jabref/gui/fieldeditors/LinkedFileViewModel.java

+32-143
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package org.jabref.gui.fieldeditors;
22

33
import java.io.IOException;
4-
import java.net.MalformedURLException;
54
import java.nio.file.Files;
65
import java.nio.file.Path;
76
import java.util.ArrayList;
@@ -11,11 +10,6 @@
1110
import java.util.function.BiPredicate;
1211
import java.util.function.Supplier;
1312

14-
import javax.net.ssl.HostnameVerifier;
15-
import javax.net.ssl.HttpsURLConnection;
16-
import javax.net.ssl.SSLHandshakeException;
17-
import javax.net.ssl.SSLSocketFactory;
18-
1913
import javafx.beans.Observable;
2014
import javafx.beans.binding.Bindings;
2115
import javafx.beans.binding.ObjectBinding;
@@ -32,20 +26,16 @@
3226
import org.jabref.gui.AbstractViewModel;
3327
import org.jabref.gui.DialogService;
3428
import org.jabref.gui.desktop.JabRefDesktop;
35-
import org.jabref.gui.externalfiles.FileDownloadTask;
3629
import org.jabref.gui.externalfiletype.ExternalFileType;
3730
import org.jabref.gui.externalfiletype.ExternalFileTypes;
38-
import org.jabref.gui.externalfiletype.StandardExternalFileType;
3931
import org.jabref.gui.icon.IconTheme;
4032
import org.jabref.gui.icon.JabRefIcon;
33+
import org.jabref.gui.linkedfile.DownloadLinkedFileAction;
4134
import org.jabref.gui.linkedfile.LinkedFileEditDialogView;
4235
import org.jabref.gui.mergeentries.MultiMergeEntriesView;
43-
import org.jabref.gui.util.BackgroundTask;
4436
import org.jabref.gui.util.ControlHelper;
4537
import org.jabref.gui.util.TaskExecutor;
4638
import org.jabref.logic.externalfiles.LinkedFileHandler;
47-
import org.jabref.logic.importer.FetcherClientException;
48-
import org.jabref.logic.importer.FetcherServerException;
4939
import org.jabref.logic.importer.Importer;
5040
import org.jabref.logic.importer.ParserResult;
5141
import org.jabref.logic.importer.fileformat.PdfContentImporter;
@@ -54,8 +44,6 @@
5444
import org.jabref.logic.importer.fileformat.PdfVerbatimBibTextImporter;
5545
import org.jabref.logic.importer.fileformat.PdfXmpImporter;
5646
import org.jabref.logic.l10n.Localization;
57-
import org.jabref.logic.net.URLDownload;
58-
import org.jabref.logic.util.io.FileNameUniqueness;
5947
import org.jabref.logic.util.io.FileUtil;
6048
import org.jabref.model.database.BibDatabaseContext;
6149
import org.jabref.model.entry.BibEntry;
@@ -421,146 +409,47 @@ public void edit() {
421409
this.linkedFile.setLink(file.getLink());
422410
this.linkedFile.setDescription(file.getDescription());
423411
this.linkedFile.setFileType(file.getFileType());
412+
this.linkedFile.setSourceURL(file.getSourceUrl());
424413
});
425414
}
426415

427-
public void download() {
428-
if (!linkedFile.isOnlineLink()) {
429-
throw new UnsupportedOperationException("In order to download the file it has to be an online link");
430-
}
431-
try {
432-
Optional<Path> targetDirectory = databaseContext.getFirstExistingFileDir(preferencesService.getFilePreferences());
433-
if (targetDirectory.isEmpty()) {
434-
dialogService.showErrorDialogAndWait(Localization.lang("Download file"), Localization.lang("File directory is not set or does not exist!"));
435-
return;
436-
}
437-
438-
URLDownload urlDownload = new URLDownload(linkedFile.getLink());
439-
if (!checkSSLHandshake(urlDownload)) {
440-
return;
441-
}
442-
443-
BackgroundTask<Path> downloadTask = prepareDownloadTask(targetDirectory.get(), urlDownload);
444-
downloadTask.onSuccess(destination -> {
445-
boolean isDuplicate;
446-
try {
447-
isDuplicate = FileNameUniqueness.isDuplicatedFile(targetDirectory.get(), destination.getFileName(), dialogService);
448-
} catch (IOException e) {
449-
LOGGER.error("FileNameUniqueness.isDuplicatedFile failed", e);
450-
return;
451-
}
452-
453-
if (!isDuplicate) {
454-
// we need to call LinkedFileViewModel#fromFile, because we need to make the path relative to the configured directories
455-
LinkedFile newLinkedFile = LinkedFilesEditorViewModel.fromFile(
456-
destination,
457-
databaseContext.getFileDirectories(preferencesService.getFilePreferences()),
458-
preferencesService.getFilePreferences());
459-
entry.replaceDownloadedFile(linkedFile.getLink(), newLinkedFile);
460-
461-
// Notify in bar when the file type is HTML.
462-
if (newLinkedFile.getFileType().equals(StandardExternalFileType.URL.getName())) {
463-
dialogService.notify(Localization.lang("Downloaded website as an HTML file."));
464-
LOGGER.debug("Downloaded website {} as an HTML file at {}", linkedFile.getLink(), destination);
465-
}
466-
}
467-
});
468-
downloadProgress.bind(downloadTask.workDonePercentageProperty());
469-
downloadTask.titleProperty().set(Localization.lang("Downloading"));
470-
downloadTask.messageProperty().set(
471-
Localization.lang("Fulltext for") + ": " + entry.getCitationKey().orElse(Localization.lang("New entry")));
472-
downloadTask.showToUser(true);
473-
downloadTask.onFailure(ex -> {
474-
LOGGER.error("Error downloading from URL: " + urlDownload, ex);
475-
String fetcherExceptionMessage = ex.getMessage();
476-
int statusCode;
477-
if (ex instanceof FetcherClientException clientException) {
478-
statusCode = clientException.getStatusCode();
479-
if (statusCode == 401) {
480-
dialogService.showInformationDialogAndWait(Localization.lang("Failed to download from URL"), Localization.lang("401 Unauthorized: Access Denied. You are not authorized to access this resource. Please check your credentials and try again. If you believe you should have access, please contact the administrator for assistance.\nURL: %0 \n %1", urlDownload.getSource(), fetcherExceptionMessage));
481-
} else if (statusCode == 403) {
482-
dialogService.showInformationDialogAndWait(Localization.lang("Failed to download from URL"), Localization.lang("403 Forbidden: Access Denied. You do not have permission to access this resource. Please contact the administrator for assistance or try a different action.\nURL: %0 \n %1", urlDownload.getSource(), fetcherExceptionMessage));
483-
} else if (statusCode == 404) {
484-
dialogService.showInformationDialogAndWait(Localization.lang("Failed to download from URL"), Localization.lang("404 Not Found Error: The requested resource could not be found. It seems that the file you are trying to download is not available or has been moved. Please verify the URL and try again. If you believe this is an error, please contact the administrator for further assistance.\nURL: %0 \n %1", urlDownload.getSource(), fetcherExceptionMessage));
485-
}
486-
} else if (ex instanceof FetcherServerException serverException) {
487-
statusCode = serverException.getStatusCode();
488-
dialogService.showInformationDialogAndWait(Localization.lang("Failed to download from URL"),
489-
Localization.lang("Error downloading from URL. Cause is likely the server side. HTTP Error %0 \n %1 \nURL: %2 \nPlease try again later or contact the server administrator.", statusCode, fetcherExceptionMessage, urlDownload.getSource()));
490-
} else {
491-
dialogService.showErrorDialogAndWait(Localization.lang("Failed to download from URL"), Localization.lang("Error message: %0 \nURL: %1 \nPlease check the URL and try again.", fetcherExceptionMessage, urlDownload.getSource()));
492-
}
493-
});
494-
taskExecutor.execute(downloadTask);
495-
} catch (MalformedURLException exception) {
496-
dialogService.showErrorDialogAndWait(Localization.lang("Invalid URL"), exception);
497-
}
416+
public void redownload() {
417+
LOGGER.info("Redownloading file from " + linkedFile.getSourceUrl());
418+
if (linkedFile.getSourceUrl().isEmpty() || !LinkedFile.isOnlineLink(linkedFile.getSourceUrl())) {
419+
throw new UnsupportedOperationException("In order to download the file, the source url has to be an online link");
498420
}
499421

500-
public boolean checkSSLHandshake(URLDownload urlDownload) {
501-
try {
502-
urlDownload.canBeReached();
503-
} catch (kong.unirest.UnirestException ex) {
504-
if (ex.getCause() instanceof SSLHandshakeException) {
505-
if (dialogService.showConfirmationDialogAndWait(Localization.lang("Download file"),
506-
Localization.lang("Unable to find valid certification path to requested target(%0), download anyway?",
507-
urlDownload.getSource().toString()))) {
508-
return true;
509-
} else {
510-
dialogService.notify(Localization.lang("Download operation canceled."));
511-
return false;
512-
}
513-
} else {
514-
LOGGER.error("Error while checking if the file can be downloaded", ex);
515-
dialogService.notify(Localization.lang("Error downloading"));
516-
return false;
517-
}
518-
}
519-
return true;
520-
}
422+
String fileName = Path.of(linkedFile.getLink()).getFileName().toString();
521423

522-
public BackgroundTask<Path> prepareDownloadTask(Path targetDirectory, URLDownload urlDownload) {
523-
SSLSocketFactory defaultSSLSocketFactory = HttpsURLConnection.getDefaultSSLSocketFactory();
524-
HostnameVerifier defaultHostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier();
525-
return BackgroundTask
526-
.wrap(() -> {
527-
Optional<ExternalFileType> suggestedType = inferFileType(urlDownload);
528-
ExternalFileType externalFileType = suggestedType.orElse(StandardExternalFileType.PDF);
529-
530-
String suggestedName = linkedFileHandler.getSuggestedFileName(externalFileType.getExtension());
531-
String fulltextDir = FileUtil.createDirNameFromPattern(databaseContext.getDatabase(), entry, preferencesService.getFilePreferences().getFileDirectoryPattern());
532-
suggestedName = FileNameUniqueness.getNonOverWritingFileName(targetDirectory.resolve(fulltextDir), suggestedName);
533-
return targetDirectory.resolve(fulltextDir).resolve(suggestedName);
534-
})
535-
.then(destination -> new FileDownloadTask(urlDownload.getSource(), destination))
536-
.onFailure(ex -> LOGGER.error("Error in download", ex))
537-
.onFinished(() -> URLDownload.setSSLVerification(defaultSSLSocketFactory, defaultHostnameVerifier));
538-
}
539-
540-
private Optional<ExternalFileType> inferFileType(URLDownload urlDownload) {
541-
Optional<ExternalFileType> suggestedType = inferFileTypeFromMimeType(urlDownload);
542-
543-
// If we did not find a file type from the MIME type, try based on extension:
544-
if (suggestedType.isEmpty()) {
545-
suggestedType = inferFileTypeFromURL(urlDownload.getSource().toExternalForm());
546-
}
547-
return suggestedType;
424+
DownloadLinkedFileAction downloadLinkedFileAction = new DownloadLinkedFileAction(
425+
databaseContext,
426+
entry,
427+
linkedFile,
428+
linkedFile.getSourceUrl(),
429+
dialogService,
430+
preferencesService.getFilePreferences(),
431+
taskExecutor,
432+
fileName);
433+
downloadProgress.bind(downloadLinkedFileAction.downloadProgress());
434+
downloadLinkedFileAction.execute();
548435
}
549436

550-
private Optional<ExternalFileType> inferFileTypeFromMimeType(URLDownload urlDownload) {
551-
String mimeType = urlDownload.getMimeType();
552-
553-
if (mimeType != null) {
554-
LOGGER.debug("MIME Type suggested: " + mimeType);
555-
return ExternalFileTypes.getExternalFileTypeByMimeType(mimeType, preferencesService.getFilePreferences());
556-
} else {
557-
return Optional.empty();
437+
public void download() {
438+
LOGGER.info("Downloading file from " + linkedFile.getSourceUrl());
439+
if (!linkedFile.isOnlineLink()) {
440+
throw new UnsupportedOperationException("In order to download the file it has to be an online link");
558441
}
559-
}
560442

561-
private Optional<ExternalFileType> inferFileTypeFromURL(String url) {
562-
return URLUtil.getSuffix(url, preferencesService.getFilePreferences())
563-
.flatMap(extension -> ExternalFileTypes.getExternalFileTypeByExt(extension, preferencesService.getFilePreferences()));
443+
DownloadLinkedFileAction downloadLinkedFileAction = new DownloadLinkedFileAction(
444+
databaseContext,
445+
entry,
446+
linkedFile,
447+
linkedFile.getLink(),
448+
dialogService,
449+
preferencesService.getFilePreferences(),
450+
taskExecutor);
451+
downloadProgress.bind(downloadLinkedFileAction.downloadProgress());
452+
downloadLinkedFileAction.execute();
564453
}
565454

566455
public LinkedFile getFile() {

0 commit comments

Comments
 (0)