Skip to content

Download covers when preview panel is opened#15572

Merged
Siedlerchr merged 24 commits into
JabRef:mainfrom
DD2480-Group-14:to-jabref
May 14, 2026
Merged

Download covers when preview panel is opened#15572
Siedlerchr merged 24 commits into
JabRef:mainfrom
DD2480-Group-14:to-jabref

Conversation

@melkerolle

Copy link
Copy Markdown
Contributor

Related issues and pull requests

Closes #14848
Follow up to #15250

PR Description

This PR makes it so the book cover is automatically downloaded when the preview panel is opened. To make this possible, BookCoverFetcher was modified to create .not-available files when it could not find a remote cover. Repeated attempts to download the cover should not be made if a previous attempt failed within 24 hours. A new test file, BookCoverFetcherTest.java was created to test this functionality. Additionaly, PreviewViewer was modified to download the book cover when opened and refresh when that is done.

This PR also solves a bug where the fetcher downloads an empty image when no real cover is found, which displays as a white image in the preview panel.

HELP NEEDED

As per the request in #15250 I made the tests use real downloads. However, I can not simply use the FetcherTest annotation since the annotation is in jabref/jablib and BookCoverFetcherTest is in jabref/jabgui. I see two possible solutions:

  1. Make it possible to use FetcherTest annotations outside of jabref/jablib. I do not know how to solve this in an elegant way.
  2. Move BookCoverFether (and BookCoverFetcherTest) to jabref/jablib. I think this would be the better solution since as of now BookCoverFetcher seems to be the only "fetcher" outside of jabref/jablib. I could make this change but do not want to do it before I get a green light from a maintainer.

Steps to test

Test download and refresh

Disable download book covers in preferences. Create a new entry with the ISBN filled in. Enable download book covers in preferences. Open the preview panel for the created entry. The book cover should download and refresh the panel content.

Test download cooldown

Go to the book covers directory and create the file isbn-9780141036144.not-available. Make a new entry with ISBN 9780141036144. The book cover should not be downloaded (see logs). Change your time to be more than 24 hours in the future. Open the entry again. The book cover should now be downloaded and the panel refreshed. The .not-available file should be deleted.

Screenshot

image

Checklist

  • I own the copyright of the code submitted and I license it under the MIT license
  • I manually tested my changes in running JabRef (always required)
  • I added JUnit tests for changes (if applicable)
  • [/] I added screenshots in the PR description (if change is visible to the user)
  • I added a screenshot in the PR description showing a library with a single entry with me as author and as title the issue number
  • I described the change in CHANGELOG.md in a way that can be understood by the average user (if change is visible to the user)
  • I checked the user documentation for up to dateness and submitted a pull request to our user documentation repository

@github-actions github-actions Bot added the good first issue An issue intended for project-newcomers. Varies in difficulty. label Apr 17, 2026
@github-actions github-actions Bot added status: no-bot-comments status: changes-required Pull requests that are not yet complete and removed status: no-bot-comments labels Apr 17, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Your pull request conflicts with the target branch.

Please merge with your code. For a step-by-step guide to resolve merge conflicts, see https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/addressing-merge-conflicts/resolving-a-merge-conflict-using-the-command-line.

@github-actions github-actions Bot added status: no-bot-comments and removed status: changes-required Pull requests that are not yet complete labels Apr 24, 2026
@melkerolle melkerolle marked this pull request as ready for review May 2, 2026 10:25
@qodo-free-for-open-source-projects

Copy link
Copy Markdown
Contributor

Review Summary by Qodo

Automatic book cover download with cooldown and failure tracking

✨ Enhancement 🧪 Tests

Grey Divider

Walkthroughs

Description
• Automatically download book covers when preview panel opens
• Implement 24-hour cooldown for failed cover download attempts
• Create .not-available marker files for unavailable covers
• Add comprehensive test suite for cover fetcher functionality
Diagram
flowchart LR
  A["Preview Panel Opens"] -->|checks preference| B["Download Covers Enabled?"]
  B -->|yes| C["BookCoverFetcher.downloadCoversForEntry"]
  C -->|check existing image| D["Image Found?"]
  D -->|yes| E["Use Existing Cover"]
  D -->|no| F["Check .not-available File"]
  F -->|exists & recent| G["Skip Download<br/>24h Cooldown Active"]
  F -->|missing or old| H["Download Cover"]
  H -->|success| I["Delete .not-available<br/>Refresh Preview"]
  H -->|failure| J["Create/Update .not-available<br/>File with Timestamp"]
Loading

Grey Divider

File Changes

1. jabgui/src/main/java/org/jabref/gui/importer/BookCoverFetcher.java ✨ Enhancement +82/-5

Add cooldown and failure tracking for cover downloads

• Added 24-hour cooldown mechanism using .not-available marker files
• Implemented timeSincePreviousAttempt() to check file modification timestamps
• Added flagAsNotAvailable() to create/update marker files on download failure
• Added deleteNotAvailableFileIfExists() to clean up markers after successful downloads
• Enhanced error handling to distinguish between client/server errors and other exceptions
• Modified image fallback URL suffix to include ?default=false parameter
• Added imports for time/duration handling and exception types

jabgui/src/main/java/org/jabref/gui/importer/BookCoverFetcher.java


2. jabgui/src/main/java/org/jabref/gui/preview/PreviewViewer.java ✨ Enhancement +16/-1

Trigger background cover download on preview open

• Modified update() method to trigger cover download after preview generation
• Added downloadCoverAndRefresh() method to download covers in background task
• Integrated preference check for shouldDownloadCovers() setting
• Refresh preview text after successful cover download if entry is still current

jabgui/src/main/java/org/jabref/gui/preview/PreviewViewer.java


3. jabgui/src/test/java/org/jabref/gui/importer/BookCoverFetcherTest.java 🧪 Tests +176/-0

Add comprehensive BookCoverFetcher test suite

• Created comprehensive test suite with 7 test cases covering core functionality
• Tests cover: not-available file creation, empty directory handling, existing cover retrieval
• Tests verify 24-hour cooldown behavior with modification time checks
• Tests confirm deletion of marker files after successful downloads
• Uses mocked Directories and ExternalApplicationsPreferences with temporary directories
• Tests use real ISBNs (9780141036144 for valid, 1234567881 for invalid covers)

jabgui/src/test/java/org/jabref/gui/importer/BookCoverFetcherTest.java


View more (1)
4. CHANGELOG.md 📝 Documentation +1/-0

Document automatic cover download feature

• Added entry documenting automatic book cover download feature
• References issue #14848 in the Added section

CHANGELOG.md


Grey Divider

Qodo Logo

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented May 2, 2026

Copy link
Copy Markdown
Contributor

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (2) 📎 Requirement gaps (0)

Grey Divider


Action required

1. Optional.get() after isEmpty() ✓ Resolved 📘 Rule violation ⚙ Maintainability
Description
New code uses Optional.isEmpty() checks followed by Optional.get(), which is discouraged and can
lead to less readable and more error-prone control flow. The compliance rule requires using
idiomatic Optional APIs (map/filter/ifPresentOrElse/orElseThrow) instead.
Code

jabgui/src/main/java/org/jabref/gui/importer/BookCoverFetcher.java[R64-66]

+            Optional<Duration> timeSincePreviousAttempt = timeSincePreviousAttempt(name, directory);
+            if (!timeSincePreviousAttempt.isEmpty() && timeSincePreviousAttempt.get().toHours() < IMAGE_DOWNLOAD_COOLDOWN_HOURS) {
+                LOGGER.info("Skipped download attempt for {}, attempted less than 24 hours ago", name);
Evidence
PR Compliance ID 24 forbids presence checks (isEmpty/isPresent) followed by get() in
new/modified code; the added cooldown logic does exactly that when inspecting
timeSincePreviousAttempt.

jabgui/src/main/java/org/jabref/gui/importer/BookCoverFetcher.java[64-66]
Best Practice: Learned patterns

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`BookCoverFetcher` uses `Optional.isEmpty()` + `Optional.get()` in new code paths, which violates the project's Optional style guidelines.
## Issue Context
The cooldown logic should use `Optional` idioms such as `filter`, `map`, `ifPresentOrElse`, or `orElseThrow` instead of presence checks + `get()`.
## Fix Focus Areas
- jabgui/src/main/java/org/jabref/gui/importer/BookCoverFetcher.java[64-66]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Tests perform real HTTP 📘 Rule violation ☼ Reliability
Description
The newly added tests call downloadCoversForEntry(...), which performs real HTTP downloads
(URLDownload), making tests slow and potentially flaky in CI/offline environments. Compliance
requires tests to remain deterministic and fast unless clearly justified and controlled.
Code

jabgui/src/test/java/org/jabref/gui/importer/BookCoverFetcherTest.java[R76-81]

+    void createNotAvailableFileAfterFailedDownload() throws Exception {
+        bookCoverFetcher.downloadCoversForEntry(badEntry);
+
+        assertTrue(Files.notExists(badCoverPath));
+        assertTrue(Files.exists(badNotAvailablePath));
+    }
Evidence
PR Compliance ID 17 requires tests to be deterministic and fast; the new test directly triggers
cover downloading without mocking network access, and the implementation performs network requests
via URLDownload.

AGENTS.md
jabgui/src/test/java/org/jabref/gui/importer/BookCoverFetcherTest.java[76-81]
jabgui/src/main/java/org/jabref/gui/importer/BookCoverFetcher.java[103-109]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`BookCoverFetcherTest` triggers real HTTP requests via `BookCoverFetcher.downloadCoversForEntry(...)`, which can make CI runs flaky/slow and fail in restricted network environments.
## Issue Context
`BookCoverFetcher` uses `new URLDownload(url)` to fetch content over the network. Tests should avoid real network by injecting a downloader abstraction or by mocking/stubbing the network layer.
## Fix Focus Areas
- jabgui/src/test/java/org/jabref/gui/importer/BookCoverFetcherTest.java[76-81]
- jabgui/src/main/java/org/jabref/gui/importer/BookCoverFetcher.java[103-109]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

3. Cover cooldown logic in GUI 📘 Rule violation ⚙ Maintainability
Description
The PR adds substantial non-UI logic (download retry cooldown and .not-available state tracking)
inside the GUI module (org.jabref.gui). This increases coupling and violates the layering rule
that GUI should delegate business logic to the logic layer.
Code

jabgui/src/main/java/org/jabref/gui/importer/BookCoverFetcher.java[R74-91]

+    private Optional<Duration> timeSincePreviousAttempt(String name, Path directory) {
+        Optional<Path> notAvailablePathOptional = resolveNameWithType(directory, name, NOT_AVAILABLE_FILE_TYPE);
+        if (notAvailablePathOptional.isEmpty()) {
+            LOGGER.warn("Could not find not available file path for: {}", name);
+            return Optional.empty();
+        }
+        Path notAvailablePath = notAvailablePathOptional.get();
+        if (Files.exists(notAvailablePath)) {
+            try {
+                FileTime lastModifiedTimeStamp = Files.getLastModifiedTime(notAvailablePath);
+                Duration timeSinceLastModification = Duration.between(lastModifiedTimeStamp.toInstant(), Instant.now());
+                return Optional.of(timeSinceLastModification);
+            } catch (IOException e) {
+                LOGGER.warn("Could not read last modified time", e);
+            }
+        }
+        return Optional.empty();
+    }
Evidence
PR Compliance ID 12 requires the GUI layer to delegate complex non-UI behavior to
org.jabref.logic; the added method timeSincePreviousAttempt(...) implements filesystem-based
state tracking and cooldown logic directly in the GUI module.

AGENTS.md
jabgui/src/main/java/org/jabref/gui/importer/BookCoverFetcher.java[74-91]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
New cover download cooldown/state logic is implemented in the GUI module, rather than being delegated to the logic layer.
## Issue Context
`BookCoverFetcher` performs filesystem state tracking (`.not-available`) and cooldown calculation. Per layering guidelines, this kind of behavior should live in `org.jabref.logic` (e.g., as a service) with the GUI calling into it.
## Fix Focus Areas
- jabgui/src/main/java/org/jabref/gui/importer/BookCoverFetcher.java[74-91]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


4. 5xx marked not available 🐞 Bug ☼ Reliability
Description
BookCoverFetcher flags a cover as “.not-available” for FetcherServerException (HTTP 5xx), so
transient server outages can suppress retries for 24 hours via the cooldown check. This can
incorrectly delay cover downloads long after the server has recovered.
Code

jabgui/src/main/java/org/jabref/gui/importer/BookCoverFetcher.java[R128-132]

+        } catch (FetcherClientException | FetcherServerException e) {
+            LOGGER.info("Remote book cover does not exist or server returned an error for URL: {}", url);
+            LOGGER.info("Flagging book cover as not available");
+            flagAsNotAvailable(name, directory);
      } catch (FetcherException e) {
-            LOGGER.error("Error while downloading cover image file", e);
Evidence
URLDownload throws FetcherServerException for HTTP status >= 500; BookCoverFetcher catches it
together with 4xx and writes a “not available” marker. Later, downloadCoverForISBN consults that
marker and skips retries for 24 hours.

jabgui/src/main/java/org/jabref/gui/importer/BookCoverFetcher.java[61-71]
jabgui/src/main/java/org/jabref/gui/importer/BookCoverFetcher.java[125-134]
jablib/src/main/java/org/jabref/logic/net/URLDownload.java[379-387]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`downloadCoverImage` treats HTTP 4xx (client) and 5xx (server) failures identically by creating/updating the `.not-available` marker. That marker then triggers a 24h cooldown, so temporary server failures can prevent retries for a day.
### Issue Context
`URLDownload.openConnection` throws `FetcherClientException` for 4xx and `FetcherServerException` for 5xx.
### Fix Focus Areas
- jabgui/src/main/java/org/jabref/gui/importer/BookCoverFetcher.java[61-71]
- jabgui/src/main/java/org/jabref/gui/importer/BookCoverFetcher.java[125-134]
### Suggested implementation direction
- Split the catch:
- On `FetcherClientException`: keep current behavior (create/update `.not-available`).
- On `FetcherServerException`: log and return without creating `.not-available` (or use a separate shorter-lived marker).
- (Optional) If you only want to mark “not available” on specific 4xx (e.g., 404), inspect `e.getHttpResponse().map(SimpleHttpResponse::statusCode)` via `FetcherException.getHttpResponse()` and gate marker creation accordingly.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

@melkerolle

Copy link
Copy Markdown
Contributor Author

Setting this as ready for review in hopes that I get the help needed

Comment thread jabgui/src/main/java/org/jabref/gui/importer/BookCoverFetcher.java
Comment on lines +76 to +81
void createNotAvailableFileAfterFailedDownload() throws Exception {
bookCoverFetcher.downloadCoversForEntry(badEntry);

assertTrue(Files.notExists(badCoverPath));
assertTrue(Files.exists(badNotAvailablePath));
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Action required

2. Tests perform real http 📘 Rule violation ☼ Reliability

The newly added tests call downloadCoversForEntry(...), which performs real HTTP downloads
(URLDownload), making tests slow and potentially flaky in CI/offline environments. Compliance
requires tests to remain deterministic and fast unless clearly justified and controlled.
Agent Prompt
## Issue description
`BookCoverFetcherTest` triggers real HTTP requests via `BookCoverFetcher.downloadCoversForEntry(...)`, which can make CI runs flaky/slow and fail in restricted network environments.

## Issue Context
`BookCoverFetcher` uses `new URLDownload(url)` to fetch content over the network. Tests should avoid real network by injecting a downloader abstraction or by mocking/stubbing the network layer.

## Fix Focus Areas
- jabgui/src/test/java/org/jabref/gui/importer/BookCoverFetcherTest.java[76-81]
- jabgui/src/main/java/org/jabref/gui/importer/BookCoverFetcher.java[103-109]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@github-actions github-actions Bot added status: changes-required Pull requests that are not yet complete and removed status: no-bot-comments labels May 12, 2026
@Siedlerchr

Copy link
Copy Markdown
Member

I don't have permisisons to push to your branch, can you please resolve the merge conflicts?
Or enable https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/allowing-changes-to-a-pull-request-branch-created-from-a-fork

Siedlerchr
Siedlerchr previously approved these changes May 14, 2026
@Siedlerchr Siedlerchr added status: ready-for-review Pull Requests that are ready to be reviewed by the maintainers and removed status: changes-required Pull requests that are not yet complete labels May 14, 2026
@Siedlerchr

Copy link
Copy Markdown
Member

Regarding your questions:

Move BookCoverFether (and BookCoverFetcherTest) to jabref/jablib. I think this would be the better solution since as of now BookCoverFetcher seems to be the only "fetcher" outside of jabref/jablib. I could make this change but do not want to do it before I get a green light from a maintainer.

Yep, I think this would be a valid approach, but I would suggest you can do this in a follow-up.

@github-actions github-actions Bot added status: changes-required Pull requests that are not yet complete and removed status: ready-for-review Pull Requests that are ready to be reviewed by the maintainers labels May 14, 2026
@github-actions github-actions Bot added status: no-bot-comments and removed status: changes-required Pull requests that are not yet complete labels May 14, 2026
@Siedlerchr Siedlerchr added this pull request to the merge queue May 14, 2026
@github-actions github-actions Bot added the status: to-be-merged PRs which are accepted and should go into the merge-queue. label May 14, 2026
Merged via the queue into JabRef:main with commit 3c0bea5 May 14, 2026
54 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

good first issue An issue intended for project-newcomers. Varies in difficulty. status: no-bot-comments status: to-be-merged PRs which are accepted and should go into the merge-queue.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Download book cover when entry editor is showing the book cover

3 participants