package lighthouse.subwindows; import com.google.common.collect.*; import com.google.protobuf.*; import de.jensd.fx.fontawesome.*; import javafx.application.Platform; import javafx.fxml.*; import javafx.scene.*; import javafx.scene.control.*; import javafx.scene.input.*; import javafx.scene.layout.*; import javafx.scene.paint.*; import javafx.stage.*; import lighthouse.*; import lighthouse.protocol.*; import lighthouse.utils.*; import lighthouse.wallet.*; import org.slf4j.*; import javax.annotation.*; import java.io.*; import java.nio.file.*; import static lighthouse.utils.I18nUtil.*; // TODO: The folder explainer here is actually dead code. public class ExportWindow { private static final Logger log = LoggerFactory.getLogger(ExportWindow.class); @FXML StackPane dragArea; @FXML Label moneyIcon; @FXML Button saveButton; public Main.OverlayUI<InnerWindow> overlayUI; private Project project; @Nullable private PledgingWallet.PledgeSupplier pledge; public void initialize() { // TODO: Make a composite icon that looks more pledgey. AwesomeDude.setIcon(moneyIcon, AwesomeIcon.MONEY, "70.0"); moneyIcon.setTextFill(Color.valueOf("#cccccc")); } public static void openForPledge(Project project, PledgingWallet.PledgeSupplier pledge) { log.info("Open ExportWindow for a pledge for {}", project.getTitle()); ExportWindow window = Main.instance.<ExportWindow>overlayUI("subwindows/export.fxml", tr("Export pledge")).controller; window.project = project; window.pledge = pledge; } public static void openForProject(Project project) { log.info("Open ExportWindow for saving project '{}'", project.getTitle()); ExportWindow window = Main.instance.<ExportWindow>overlayUI("subwindows/export.fxml", tr("Export project")).controller; window.project = project; } public static DataFormat PLEDGE_MIME_TYPE = new DataFormat(LHUtils.PLEDGE_MIME_TYPE); public static DataFormat PROJECT_MIME_TYPE = new DataFormat(LHUtils.PROJECT_MIME_TYPE); public static class DragData { public Path tempDragDirectory, tempDragFile; public void done() { log.info("Drag done!"); // Tell the JVM to clean up at shutdown time. If we delete too early the OS notifies the drag is done but // the file manager is still trying to grab the file. There doesn't seem to be any way to provide a named // file for dragging without it already being on the filesystem. Runtime.getRuntime().addShutdownHook(new Thread(() -> { try { log.info("Deleting temp file+dir {}", tempDragFile); Files.delete(tempDragFile); Files.delete(tempDragDirectory); } catch (IOException e) { log.error("Could not delete temp dir after drag", e); } }, "Temp file deleter thread")); } } private DragData dragData; @FXML public void dragDetected(MouseEvent event) { // Create a temp directory so the file can have a nice name, and then we will delete it a few seconds // after the drag operation has finished. final String fileName = getFileName(); boolean savingPledge = pledge != null; Message data = savingPledge ? pledge.getData() : project.getProto(); final DataFormat mimeType = savingPledge ? PLEDGE_MIME_TYPE : PROJECT_MIME_TYPE; dragData = startDrag(fileName, data, mimeType, dragArea); } public static DragData startDrag(String fileName, Message data, DataFormat mimeType, Node dragArea) { try { DragData dd = new DragData(); dd.tempDragDirectory = Files.createTempDirectory(Main.APP_NAME); dd.tempDragFile = dd.tempDragDirectory.resolve(fileName); try (OutputStream outputStream = Files.newOutputStream(dd.tempDragFile)) { data.writeTo(outputStream); } Dragboard dragboard = dragArea.startDragAndDrop(TransferMode.COPY); ClipboardContent content = new ClipboardContent(); content.putFilesByPath(ImmutableList.of(dd.tempDragFile.toString())); content.put(mimeType, data.toByteArray()); dragboard.setContent(content); return dd; } catch (IOException e) { throw new RuntimeException(e); } } private String getFileName() { if (pledge != null) { // TRANS: %1$s = pledger's name, %2$s = project name, %3$s = file extension final String FILE_NAME = tr("Pledge by %1$s for %2$s%3$s"); return String.format(FILE_NAME, Main.demoName == null ? System.getProperty("user.name") : Main.demoName, project.getTitle(), LighthouseBackend.PLEDGE_FILE_EXTENSION); } else { return project.getSuggestedFileName(); } } @FXML public void dragDone(DragEvent event) { dragData.done(); if (pledge != null) pledge.commit(true); else if (!maybeShowServerGuidance()) overlayUI.done(); } public boolean maybeShowServerGuidance() { if (project.getPaymentURL() != null) { String host = project.getPaymentURL().getHost(); ServerList.Entry entry = ServerList.hostnameToServer.get(host); if (entry != null) { // If we know about this server, i.e. because the user accepted our default, then show the // submit email address to them as guidance. ProjectSubmitInstructionsWindow.open(entry.getSubmitAddress(), entry.getSubmitType()); return true; } } return false; } @FXML public void saveClicked() { // runLater: shitty hack around RT-37821 (consider upgrading to 8u40 when available and/or applying fix locally) // otherwise pressing enter can cause a crash here when we open a new window with a default button Platform.runLater(() -> { boolean savingPledge = pledge != null; log.info("Save clicked for {}", savingPledge ? "pledge" : "project"); FileChooser chooser = new FileChooser(); chooser.setTitle(savingPledge ? tr("Save pledge to a file") : tr("Save project to a file")); chooser.setInitialFileName(getFileName()); GuiUtils.platformFiddleChooser(chooser); File file = chooser.showSaveDialog(Main.instance.mainStage); if (file == null) { log.info(" ... but user cancelled"); return; } Message data = savingPledge ? pledge.getData() : project.getProto(); log.info("Saving {} data to {}", pledge != null ? "pledge" : "project", file); try (OutputStream outputStream = new FileOutputStream(file)) { data.writeTo(outputStream); if (savingPledge) { pledge.commit(true); } else if (maybeShowServerGuidance()) { return; } overlayUI.done(); } catch (IOException e) { GuiUtils.informationalAlert(tr("Failed to save file"), e.getLocalizedMessage()); } }); } }