package com.wilutions.itol; import java.awt.Toolkit; import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.Transferable; import java.awt.image.RenderedImage; import java.io.File; import java.io.FileOutputStream; import java.nio.file.Files; import java.nio.file.StandardCopyOption; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.logging.Level; import java.util.logging.Logger; import javax.imageio.ImageIO; import com.wilutions.itol.db.Attachment; import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; public class Attachments implements Iterable<Attachment> { private final static Logger log = Logger.getLogger("AttachmentHelper"); private final static SimpleDateFormat DATEFORMAT = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss"); ObservableList<Attachment> attachments; public Attachments(ObservableList<Attachment> attachments) { this.attachments = attachments; debugInitListener(); } public CompletableFuture<List<Attachment>> addAttachmentsFromClipboard() { CompletableFuture<List<Attachment>> attachments = new CompletableFuture<List<Attachment>>(); try { java.awt.Image image = getImageFromClipboard(); if (image != null) { try { Attachment attachment = makeAttachmentFromImage(image); attachments.complete(Arrays.asList(attachment)); } catch (Exception e) { log.log(Level.WARNING, "Failed to paste data from clipboard.", e); attachments.completeExceptionally(e); } } List<File> clipFiles = getFilesFromClipboard(); if (clipFiles != null) { attachments = dropFiles(clipFiles); } if (image == null & clipFiles == null) { attachments.complete(new ArrayList<Attachment>(0)); } } catch (Exception ex) { attachments.completeExceptionally(ex); } return attachments; } private Attachment makeAttachmentFromImage(java.awt.Image image) throws Exception { if (log.isLoggable(Level.FINE)) log.fine("dropImage(" + image); // Save clipboard image to temp file String iso = DATEFORMAT.format(new Date()); String fileName = makeUniqueAttachmentFileName(attachments, "image-" + iso + ".png"); File file = new File(Globals.getTempDir(), fileName); writeImageToFile(image, file); Attachment attachment = addFileAsAttachment(file); if (log.isLoggable(Level.FINE)) log.fine(")dropImage=" + attachment); return attachment; } public CompletableFuture<List<Attachment>> dropFiles(List<File> files) { if (log.isLoggable(Level.FINE)) log.fine("dropFiles(" + files); CompletableFuture<List<Attachment>> fcopies = CompletableFuture.supplyAsync(() -> { ArrayList<Attachment> copies = new ArrayList<Attachment>(); for (File src : files) { try { String fileName = makeUniqueAttachmentFileName(attachments, src.getName()); File target = new File(Globals.getTempDir(), fileName); if (log.isLoggable(Level.FINE)) log.fine("copy source=" + src + ", target=" + target); Files.copy(src.toPath(), target.toPath(), StandardCopyOption.REPLACE_EXISTING); Attachment attachment = addFileAsAttachment(target); copies.add(attachment); } catch (Exception e) { throw new IllegalStateException(e); } } return copies; }); if (log.isLoggable(Level.FINE)) log.fine(")dropFiles"); return fcopies; } private Attachment addFileAsAttachment(File file) throws Exception { if (log.isLoggable(Level.FINE)) log.fine("addFileAsAttachment(" + file); Attachment att = MailAttachmentHelper.createFromFile(file); attachments.add(att); if (log.isLoggable(Level.INFO)) log.info("add attachment=" + att); if (log.isLoggable(Level.FINE)) log.fine(")addFileAsAttachment"); return att; } public static boolean isImageFile(String fileName) { boolean ret = false; fileName = fileName.toLowerCase(); String ext = fileName.substring(fileName.lastIndexOf('.')+1); switch (ext) { case "png": case "jpg": case "gif": ret = true; break; default: ret = false; } return ret; } private void writeImageToFile(java.awt.Image image, File file) throws Exception { if (log.isLoggable(Level.FINE)) log.fine("writeImageToFile(image=" + image + ", file=" + file); FileOutputStream out = new FileOutputStream(file); try { ImageIO.write((RenderedImage) image, "png", out); } finally { out.close(); } if (log.isLoggable(Level.FINE)) log.fine(")writeImageToFile"); } /** * Creates a unique file name for an attachment. * @param fileName * @return */ private String makeUniqueAttachmentFileName(Collection<Attachment> attachments, String fileName) { int retry = 0; while (findAttachment(attachments, fileName).isPresent()) { String name = fileName; String ext = ""; int d = fileName.lastIndexOf('.'); if (d >= 0) { name = fileName.substring(0, d); ext = fileName.substring(d); } // remove already inserted retry counter in file name if (name.endsWith(")")) { int p = name.lastIndexOf("("); if (p >= 0) { int e = name.length()-1; String sretry = name.substring(p+1, e); try { Integer.parseInt(sretry); name = name.substring(0, p).trim(); } catch (Exception ignored) {} } } fileName = name + " (" + (++retry) + ")" + ext; } return fileName; } /** * Find attachment by name. * @param fileName * @return */ private Optional<Attachment> findAttachment(Collection<Attachment> attachments, String fileName) { String fileNameLC = fileName.toLowerCase(); Optional<Attachment> ret = attachments.stream().filter( (att) -> att.getFileName().toLowerCase().equals(fileNameLC) ).findFirst(); return ret; } private java.awt.Image getImageFromClipboard() throws Exception { if (log.isLoggable(Level.FINE)) log.fine("getImageFromClipboard(("); java.awt.Image ret = null; Transferable transferable = Toolkit.getDefaultToolkit().getSystemClipboard().getContents(null); if (transferable != null && transferable.isDataFlavorSupported(DataFlavor.imageFlavor)) { ret = (java.awt.Image) transferable.getTransferData(DataFlavor.imageFlavor); } if (log.isLoggable(Level.FINE)) log.fine(")getImageFromClipboard=" + ret); return ret; } @SuppressWarnings("unchecked") private List<File> getFilesFromClipboard() throws Exception { if (log.isLoggable(Level.FINE)) log.fine("getFilesFromClipboard("); List<File> ret = null; Transferable transferable = Toolkit.getDefaultToolkit().getSystemClipboard().getContents(null); if (transferable != null && transferable.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) { ret = (List<File>) transferable.getTransferData(DataFlavor.javaFileListFlavor); } if (log.isLoggable(Level.FINE)) log.fine(")getFilesFromClipboard=" + ret); return ret; } public ObservableList<Attachment> getObservableList() { return attachments; } public void add(Attachment att) { getObservableList().add(att); } public boolean isEmpty() { return getObservableList().isEmpty(); } @Override public Iterator<Attachment> iterator() { return getObservableList().iterator(); } public void addListener(ListChangeListener<Attachment> listChangeListener) { getObservableList().addListener(listChangeListener); } private void debugInitListener() { getObservableList().addListener(new ListChangeListener<Attachment>() { public void onChanged(javafx.collections.ListChangeListener.Change<? extends Attachment> att) { log.fine("Attachment changed: " + att); } }); } }