package org.jabref.gui.externalfiles; import java.awt.event.ActionEvent; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; import javax.swing.AbstractAction; import javax.swing.Action; import javax.xml.transform.TransformerException; import org.jabref.Globals; import org.jabref.gui.BasePanel; import org.jabref.gui.IconTheme; import org.jabref.gui.entryeditor.EntryEditor; import org.jabref.gui.worker.AbstractWorker; import org.jabref.logic.l10n.Localization; import org.jabref.logic.xmp.XMPUtil; import org.jabref.model.entry.BibEntry; /** * Write XMP action for EntryEditor toolbar. */ public class WriteXMPEntryEditorAction extends AbstractAction { private final BasePanel panel; private final EntryEditor editor; private String message; public WriteXMPEntryEditorAction(BasePanel panel, EntryEditor editor) { this.panel = panel; this.editor = editor; // normally, the next call should be without "Localization.lang". However, the string "Write XMP" is also used in non-menu places and therefore, the translation must be also available at Localization.lang putValue(Action.NAME, Localization.lang("Write XMP")); putValue(Action.SMALL_ICON, IconTheme.JabRefIcon.WRITE_XMP.getIcon()); putValue(Action.SHORT_DESCRIPTION, Localization.lang("Write BibTeXEntry as XMP-metadata to PDF.")); } @Override public void actionPerformed(ActionEvent actionEvent) { setEnabled(false); panel.output(Localization.lang("Writing XMP-metadata...")); panel.frame().setProgressBarIndeterminate(true); panel.frame().setProgressBarVisible(true); BibEntry entry = editor.getEntry(); // Make a list of all PDFs linked from this entry: List<Path> files = entry.getFiles().stream() .filter(file -> file.getFileType().equalsIgnoreCase("pdf")) .map(file -> file.findIn(panel.getBibDatabaseContext(), Globals.prefs.getFileDirectoryPreferences())) .filter(Optional::isPresent) .map(Optional::get) .collect(Collectors.toList()); // We want to offload the actual work to a background thread, so we have a worker // thread: AbstractWorker worker = new WriteXMPWorker(files, entry); // Using Spin, we get a thread that gets synchronously offloaded to a new thread, // blocking the execution of this method: worker.getWorker().run(); // After the worker thread finishes, we are unblocked and ready to print the // status message: panel.output(message); panel.frame().setProgressBarVisible(false); setEnabled(true); } class WriteXMPWorker extends AbstractWorker { private final List<Path> files; private final BibEntry entry; public WriteXMPWorker(List<Path> files, BibEntry entry) { this.files = files; this.entry = entry; } @Override public void run() { if (files.isEmpty()) { message = Localization.lang("No PDF linked") + ".\n"; } else { int written = 0; int error = 0; for (Path file : files) { if (!Files.exists(file)) { if (files.size() == 1) { message = Localization.lang("PDF does not exist"); } error++; } else { try { XMPUtil.writeXMP(file.toFile(), entry, panel.getDatabase(), Globals.prefs.getXMPPreferences()); if (files.size() == 1) { message = Localization.lang("Wrote XMP-metadata"); } written++; } catch (IOException | TransformerException e) { if (files.size() == 1) { message = Localization.lang("Error while writing") + " '" + file.toString() + "'"; } error++; } } } if (files.size() > 1) { StringBuilder sb = new StringBuilder(); sb.append(Localization.lang("Finished writing XMP-metadata. Wrote to %0 file(s).", String.valueOf(written))); if (error > 0) { sb.append(' ').append(Localization.lang("Error writing to %0 file(s).", String.valueOf(error))); } message = sb.toString(); } } } } }