Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Keep source urls on file download #10855

Merged
merged 23 commits into from
Feb 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
ad95b64
Add source URL to the linked files and update it when a file is initi…
ror3d Feb 2, 2024
a692271
Add context menu action to redownload a file
ror3d Feb 2, 2024
a241b7c
Add main menu tools action to redownload missing files
ror3d Feb 2, 2024
b448217
Merge branch 'JabRef:main' into keep-source-urls-on-file-download
ror3d Feb 5, 2024
6219d01
Don't change the local link name when redownloading, the link should …
ror3d Feb 5, 2024
9dc0fb7
Update CHANGELOG.md
ror3d Feb 5, 2024
c9fb857
revert method change
ror3d Feb 6, 2024
bd1810d
Revert changes in property files
ror3d Feb 6, 2024
116a516
Improve extension check
ror3d Feb 6, 2024
6a7b8f9
Fix issue where action would only be executed once per jabref execution.
ror3d Feb 6, 2024
52777bf
Make string empty check more readable
ror3d Feb 6, 2024
90452c8
Fix test for FileFieldParser
ror3d Feb 6, 2024
8f07428
Merge branch 'keep-source-urls-on-file-download' of github.com:ror3d/…
ror3d Feb 6, 2024
f3f1b9c
Small refactoring and language fix
calixtus Feb 10, 2024
c4f3907
Refactor LinkedFileViewModel to use the DownloadLinkedFileAction for …
ror3d Feb 12, 2024
06c81bd
Fix rewrite check
ror3d Feb 13, 2024
2230310
Change file field format to be backwards compatible with old versions…
ror3d Feb 15, 2024
c61d149
Small language fix and reduced public fingerprint
calixtus Feb 15, 2024
03c8000
Extract downloadProgress
calixtus Feb 15, 2024
8f3f55b
DownloadLinkedFileActionTest
calixtus Feb 15, 2024
e39c8f5
Fixed checkstyle
calixtus Feb 15, 2024
7d019ae
Revert "Change file field format to be backwards compatible with old …
calixtus Feb 18, 2024
d0c7bbf
Reapply and modify good tests
calixtus Feb 18, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv
- 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)
- We added ability to jump to an entry in the command line using `-j CITATIONKEY`. [koppor#540](https://github.com/koppor/jabref/issues/540)
- 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)
- 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)

### Changed

Expand Down
7 changes: 6 additions & 1 deletion src/main/java/org/jabref/gui/MainMenu.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
import org.jabref.gui.integrity.IntegrityCheckAction;
import org.jabref.gui.journals.AbbreviateAction;
import org.jabref.gui.libraryproperties.LibraryPropertiesAction;
import org.jabref.gui.linkedfile.RedownloadMissingFilesAction;
import org.jabref.gui.mergeentries.MergeEntriesAction;
import org.jabref.gui.preferences.ShowPreferencesAction;
import org.jabref.gui.preview.CopyCitationAction;
Expand Down Expand Up @@ -287,7 +288,11 @@ private void createMenu() {

new SeparatorMenuItem(),

factory.createMenuItem(StandardActions.REBUILD_FULLTEXT_SEARCH_INDEX, new RebuildFulltextSearchIndexAction(stateManager, frame::getCurrentLibraryTab, dialogService, preferencesService.getFilePreferences(), taskExecutor))
factory.createMenuItem(StandardActions.REBUILD_FULLTEXT_SEARCH_INDEX, new RebuildFulltextSearchIndexAction(stateManager, frame::getCurrentLibraryTab, dialogService, preferencesService.getFilePreferences(), taskExecutor)),

new SeparatorMenuItem(),

factory.createMenuItem(StandardActions.REDOWNLOAD_MISSING_FILES, new RedownloadMissingFilesAction(stateManager, dialogService, preferencesService.getFilePreferences(), taskExecutor))
);
SidePaneType webSearchPane = SidePaneType.WEB_SEARCH;
SidePaneType groupsPane = SidePaneType.GROUPS;
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/org/jabref/gui/actions/StandardActions.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public enum StandardActions implements Action {
SEND_AS_EMAIL(Localization.lang("As Email")),
SEND_TO_KINDLE(Localization.lang("To Kindle")),
REBUILD_FULLTEXT_SEARCH_INDEX(Localization.lang("Rebuild fulltext search index"), IconTheme.JabRefIcons.FILE),
REDOWNLOAD_MISSING_FILES(Localization.lang("Redownload missing files"), IconTheme.JabRefIcons.DOWNLOAD),
OPEN_EXTERNAL_FILE(Localization.lang("Open file"), IconTheme.JabRefIcons.FILE, KeyBinding.OPEN_FILE),
OPEN_URL(Localization.lang("Open URL or DOI"), IconTheme.JabRefIcons.WWW, KeyBinding.OPEN_URL_OR_DOI),
SEARCH_SHORTSCIENCE(Localization.lang("Search ShortScience")),
Expand Down Expand Up @@ -149,6 +150,7 @@ public enum StandardActions implements Action {

EDIT_FILE_LINK(Localization.lang("Edit"), IconTheme.JabRefIcons.EDIT, KeyBinding.EDIT_ENTRY),
DOWNLOAD_FILE(Localization.lang("Download file"), IconTheme.JabRefIcons.DOWNLOAD_FILE),
REDOWNLOAD_FILE(Localization.lang("Redownload file"), IconTheme.JabRefIcons.DOWNLOAD_FILE),
RENAME_FILE_TO_PATTERN(Localization.lang("Rename file to defined pattern"), IconTheme.JabRefIcons.AUTO_RENAME),
RENAME_FILE_TO_NAME(Localization.lang("Rename file to a given name"), IconTheme.JabRefIcons.RENAME, KeyBinding.REPLACE_STRING),
MOVE_FILE_TO_FOLDER(Localization.lang("Move file to file directory"), IconTheme.JabRefIcons.MOVE_TO_FOLDER),
Expand Down
40 changes: 0 additions & 40 deletions src/main/java/org/jabref/gui/externalfiles/FileDownloadTask.java

This file was deleted.

175 changes: 32 additions & 143 deletions src/main/java/org/jabref/gui/fieldeditors/LinkedFileViewModel.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package org.jabref.gui.fieldeditors;

import java.io.IOException;
import java.net.MalformedURLException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
Expand All @@ -11,11 +10,6 @@
import java.util.function.BiPredicate;
import java.util.function.Supplier;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.SSLSocketFactory;

import javafx.beans.Observable;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.ObjectBinding;
Expand All @@ -32,20 +26,16 @@
import org.jabref.gui.AbstractViewModel;
import org.jabref.gui.DialogService;
import org.jabref.gui.desktop.JabRefDesktop;
import org.jabref.gui.externalfiles.FileDownloadTask;
import org.jabref.gui.externalfiletype.ExternalFileType;
import org.jabref.gui.externalfiletype.ExternalFileTypes;
import org.jabref.gui.externalfiletype.StandardExternalFileType;
import org.jabref.gui.icon.IconTheme;
import org.jabref.gui.icon.JabRefIcon;
import org.jabref.gui.linkedfile.DownloadLinkedFileAction;
import org.jabref.gui.linkedfile.LinkedFileEditDialogView;
import org.jabref.gui.mergeentries.MultiMergeEntriesView;
import org.jabref.gui.util.BackgroundTask;
import org.jabref.gui.util.ControlHelper;
import org.jabref.gui.util.TaskExecutor;
import org.jabref.logic.externalfiles.LinkedFileHandler;
import org.jabref.logic.importer.FetcherClientException;
import org.jabref.logic.importer.FetcherServerException;
import org.jabref.logic.importer.Importer;
import org.jabref.logic.importer.ParserResult;
import org.jabref.logic.importer.fileformat.PdfContentImporter;
Expand All @@ -54,8 +44,6 @@
import org.jabref.logic.importer.fileformat.PdfVerbatimBibTextImporter;
import org.jabref.logic.importer.fileformat.PdfXmpImporter;
import org.jabref.logic.l10n.Localization;
import org.jabref.logic.net.URLDownload;
import org.jabref.logic.util.io.FileNameUniqueness;
import org.jabref.logic.util.io.FileUtil;
import org.jabref.model.database.BibDatabaseContext;
import org.jabref.model.entry.BibEntry;
Expand Down Expand Up @@ -421,146 +409,47 @@ public void edit() {
this.linkedFile.setLink(file.getLink());
this.linkedFile.setDescription(file.getDescription());
this.linkedFile.setFileType(file.getFileType());
this.linkedFile.setSourceURL(file.getSourceUrl());
});
}

public void download() {
if (!linkedFile.isOnlineLink()) {
throw new UnsupportedOperationException("In order to download the file it has to be an online link");
}
try {
Optional<Path> targetDirectory = databaseContext.getFirstExistingFileDir(preferencesService.getFilePreferences());
if (targetDirectory.isEmpty()) {
dialogService.showErrorDialogAndWait(Localization.lang("Download file"), Localization.lang("File directory is not set or does not exist!"));
return;
}

URLDownload urlDownload = new URLDownload(linkedFile.getLink());
if (!checkSSLHandshake(urlDownload)) {
return;
}

BackgroundTask<Path> downloadTask = prepareDownloadTask(targetDirectory.get(), urlDownload);
downloadTask.onSuccess(destination -> {
boolean isDuplicate;
try {
isDuplicate = FileNameUniqueness.isDuplicatedFile(targetDirectory.get(), destination.getFileName(), dialogService);
} catch (IOException e) {
LOGGER.error("FileNameUniqueness.isDuplicatedFile failed", e);
return;
}

if (!isDuplicate) {
// we need to call LinkedFileViewModel#fromFile, because we need to make the path relative to the configured directories
LinkedFile newLinkedFile = LinkedFilesEditorViewModel.fromFile(
destination,
databaseContext.getFileDirectories(preferencesService.getFilePreferences()),
preferencesService.getFilePreferences());
entry.replaceDownloadedFile(linkedFile.getLink(), newLinkedFile);

// Notify in bar when the file type is HTML.
if (newLinkedFile.getFileType().equals(StandardExternalFileType.URL.getName())) {
dialogService.notify(Localization.lang("Downloaded website as an HTML file."));
LOGGER.debug("Downloaded website {} as an HTML file at {}", linkedFile.getLink(), destination);
}
}
});
downloadProgress.bind(downloadTask.workDonePercentageProperty());
downloadTask.titleProperty().set(Localization.lang("Downloading"));
downloadTask.messageProperty().set(
Localization.lang("Fulltext for") + ": " + entry.getCitationKey().orElse(Localization.lang("New entry")));
downloadTask.showToUser(true);
downloadTask.onFailure(ex -> {
LOGGER.error("Error downloading from URL: " + urlDownload, ex);
String fetcherExceptionMessage = ex.getMessage();
int statusCode;
if (ex instanceof FetcherClientException clientException) {
statusCode = clientException.getStatusCode();
if (statusCode == 401) {
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));
} else if (statusCode == 403) {
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));
} else if (statusCode == 404) {
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));
}
} else if (ex instanceof FetcherServerException serverException) {
statusCode = serverException.getStatusCode();
dialogService.showInformationDialogAndWait(Localization.lang("Failed to download from URL"),
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()));
} else {
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()));
}
});
taskExecutor.execute(downloadTask);
} catch (MalformedURLException exception) {
dialogService.showErrorDialogAndWait(Localization.lang("Invalid URL"), exception);
}
public void redownload() {
LOGGER.info("Redownloading file from " + linkedFile.getSourceUrl());
if (linkedFile.getSourceUrl().isEmpty() || !LinkedFile.isOnlineLink(linkedFile.getSourceUrl())) {
throw new UnsupportedOperationException("In order to download the file, the source url has to be an online link");
}

public boolean checkSSLHandshake(URLDownload urlDownload) {
try {
urlDownload.canBeReached();
} catch (kong.unirest.UnirestException ex) {
if (ex.getCause() instanceof SSLHandshakeException) {
if (dialogService.showConfirmationDialogAndWait(Localization.lang("Download file"),
Localization.lang("Unable to find valid certification path to requested target(%0), download anyway?",
urlDownload.getSource().toString()))) {
return true;
} else {
dialogService.notify(Localization.lang("Download operation canceled."));
return false;
}
} else {
LOGGER.error("Error while checking if the file can be downloaded", ex);
dialogService.notify(Localization.lang("Error downloading"));
return false;
}
}
return true;
}
String fileName = Path.of(linkedFile.getLink()).getFileName().toString();

public BackgroundTask<Path> prepareDownloadTask(Path targetDirectory, URLDownload urlDownload) {
SSLSocketFactory defaultSSLSocketFactory = HttpsURLConnection.getDefaultSSLSocketFactory();
HostnameVerifier defaultHostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier();
return BackgroundTask
.wrap(() -> {
Optional<ExternalFileType> suggestedType = inferFileType(urlDownload);
ExternalFileType externalFileType = suggestedType.orElse(StandardExternalFileType.PDF);

String suggestedName = linkedFileHandler.getSuggestedFileName(externalFileType.getExtension());
String fulltextDir = FileUtil.createDirNameFromPattern(databaseContext.getDatabase(), entry, preferencesService.getFilePreferences().getFileDirectoryPattern());
suggestedName = FileNameUniqueness.getNonOverWritingFileName(targetDirectory.resolve(fulltextDir), suggestedName);
return targetDirectory.resolve(fulltextDir).resolve(suggestedName);
})
.then(destination -> new FileDownloadTask(urlDownload.getSource(), destination))
.onFailure(ex -> LOGGER.error("Error in download", ex))
.onFinished(() -> URLDownload.setSSLVerification(defaultSSLSocketFactory, defaultHostnameVerifier));
}

private Optional<ExternalFileType> inferFileType(URLDownload urlDownload) {
Optional<ExternalFileType> suggestedType = inferFileTypeFromMimeType(urlDownload);

// If we did not find a file type from the MIME type, try based on extension:
if (suggestedType.isEmpty()) {
suggestedType = inferFileTypeFromURL(urlDownload.getSource().toExternalForm());
}
return suggestedType;
DownloadLinkedFileAction downloadLinkedFileAction = new DownloadLinkedFileAction(
databaseContext,
entry,
linkedFile,
linkedFile.getSourceUrl(),
dialogService,
preferencesService.getFilePreferences(),
taskExecutor,
fileName);
downloadProgress.bind(downloadLinkedFileAction.downloadProgress());
downloadLinkedFileAction.execute();
}

private Optional<ExternalFileType> inferFileTypeFromMimeType(URLDownload urlDownload) {
String mimeType = urlDownload.getMimeType();

if (mimeType != null) {
LOGGER.debug("MIME Type suggested: " + mimeType);
return ExternalFileTypes.getExternalFileTypeByMimeType(mimeType, preferencesService.getFilePreferences());
} else {
return Optional.empty();
public void download() {
LOGGER.info("Downloading file from " + linkedFile.getSourceUrl());
if (!linkedFile.isOnlineLink()) {
throw new UnsupportedOperationException("In order to download the file it has to be an online link");
}
}

private Optional<ExternalFileType> inferFileTypeFromURL(String url) {
return URLUtil.getSuffix(url, preferencesService.getFilePreferences())
.flatMap(extension -> ExternalFileTypes.getExternalFileTypeByExt(extension, preferencesService.getFilePreferences()));
DownloadLinkedFileAction downloadLinkedFileAction = new DownloadLinkedFileAction(
databaseContext,
entry,
linkedFile,
linkedFile.getLink(),
dialogService,
preferencesService.getFilePreferences(),
taskExecutor);
downloadProgress.bind(downloadLinkedFileAction.downloadProgress());
downloadLinkedFileAction.execute();
}

public LinkedFile getFile() {
Expand Down
Loading