package greenfoot.export;
import bluej.Boot;
import bluej.Config;
import bluej.pkgmgr.Project;
import bluej.utility.Utility;
import greenfoot.event.PublishEvent;
import greenfoot.event.PublishListener;
import greenfoot.export.mygame.MyGameClient;
import greenfoot.export.mygame.ExportInfo;
import greenfoot.guifx.export.ExportDialog;
import greenfoot.guifx.export.ProxyAuthDialog;
import greenfoot.util.GreenfootUtil;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.net.UnknownHostException;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import javafx.application.Platform;
import javafx.embed.swing.SwingFXUtils;
import javafx.geometry.Dimension2D;
import javax.imageio.ImageIO;
import threadchecker.OnThread;
import threadchecker.Tag;
| Class Exporter manages the various possible export functions, such as writing
| jar files or publishing to the scenario web server.
|
| The exporter is a singleton
|
| @author Michael Kolling
|
public class Exporter
implements PublishListener{
| An enum for the different export functions
|
public enum ExportFunction
{
PUBLISH, PROJECT, APP;
| Returns the export function which corresponds to the passed name.
| In case the name doesn't match a function, returns
| ExportFunction.PUBLISH as a default function.
|
| @param name The function name
| @return The corresponding function to the name passed,
| otherwise return ExportFunction.PUBLISH
|
public static ExportFunction getFunction(String name)
{
try
{
return ExportFunction.valueOf(name);
}
catch (IllegalArgumentException ex)
{
return ExportFunction.PUBLISH;
}
}
}
private static final String GREENFOOT_CORE_JAR = getGreenfootCoreJar();
private static final String GALLERY_SHARED_JARS = "sharedjars/";
private static String getGreenfootCoreJar()
{
String coreJar = "Greenfoot-core-";
int lastDot = Boot.GREENFOOT_API_VERSION.lastIndexOf('.');
coreJar += Boot.GREENFOOT_API_VERSION.substring(0, lastDot) + ".jar";
return coreJar;
}
private static Exporter instance;
public static synchronized Exporter getInstance()
{
if (instance == null) {
instance = new Exporter();
}
return instance;
}
private File tmpJarFile;
private File tmpImgFile;
private File tmpZipFile;
private MyGameClient webPublisher;
private Project project;
private ExportInfo scenarioInfo;
private String worldName;
private double worldWidth;
private double worldHeight;
private ExportDialog dialog;
| Creates a new instance of Exporter.
|
private Exporter()
{
}
| Publish/Export this scenario based on the passed function.
|
| @param project The current project.
| @param dialog A share/export dialog reference to show progress/messages to user.
| @param scenarioSaver The listener that will enable us to save the scenario when exporting.
| @param scenarioInfo The scenario info needed for different export functions.
| @param function The share function type which will be perform.
| @param worldName The world's name.
| @param worldWidth The world's width.
| @param worldHeight The world's height.
|
@OnThread(Tag.Worker)
public void doExport(Project project, ExportDialog dialog, ScenarioSaver scenarioSaver,
ExportInfo scenarioInfo, ExportFunction function, String worldName,
double worldWidth, double worldHeight)
{
this.project = project;
this.dialog = dialog;
this.scenarioInfo = scenarioInfo;
this.worldName = worldName;
this.worldWidth = worldWidth;
this.worldHeight = worldHeight;
if (function.equals(ExportFunction.PUBLISH))
{
publishToWebServer();
}
if (function.equals(ExportFunction.APP))
{
makeApplication();
}
if (function.equals(ExportFunction.PROJECT))
{
makeProject();
}
}
| Publish this scenario to the web server.
|
@OnThread(Tag.Worker)
private void publishToWebServer()
{
dialog.setProgress(true, Config.getString("export.progress.bundling"));
try {
tmpJarFile = File.createTempFile("greenfoot", ".jar", null);
tmpJarFile.deleteOnExit();
}
catch (IOException e) {
e.printStackTrace();
return;
}
File exportDir = tmpJarFile.getParentFile();
String jarName = tmpJarFile.getName();
String hostAddress = Config.getPropString("greenfoot.gameserver.address", "http://www.greenfoot.org/");
if (! hostAddress.endsWith("/")) {
hostAddress += "/";
}
boolean lockScenario = scenarioInfo.isLocked();
JarCreator jarCreator = new JarCreator(project, exportDir, jarName, worldName,
lockScenario, true);
jarCreator.includeSource(false);
jarCreator.addToClassPath(hostAddress + GALLERY_SHARED_JARS + GREENFOOT_CORE_JAR);
Set<File> thirdPartyLibs = GreenfootUtil.get3rdPartyLibs();
for (File lib : thirdPartyLibs) {
jarCreator.addToClassPath(hostAddress + GALLERY_SHARED_JARS + lib.getName());
}
jarCreator.putManifestEntry("title", scenarioInfo.getTitle());
jarCreator.putManifestEntry("short-description", scenarioInfo.getShortDescription());
jarCreator.putManifestEntry("description", scenarioInfo.getLongDescription());
jarCreator.putManifestEntry("url", scenarioInfo.getUrl());
jarCreator.putManifestEntry("greenfoot-version", Boot.GREENFOOT_VERSION);
jarCreator.putManifestEntry("java-version", System.getProperty("java.version"));
jarCreator.putManifestEntry("java-vm-name", System.getProperty("java.vm.name"));
jarCreator.putManifestEntry("java-vm-version", System.getProperty("java.vm.version"));
jarCreator.putManifestEntry("java-vm-vendor", System.getProperty("java.vm.vendor"));
jarCreator.putManifestEntry("os-name", System.getProperty("os.name"));
jarCreator.putManifestEntry("os-version", System.getProperty("os.version"));
jarCreator.putManifestEntry("os-arch", System.getProperty("os.arch"));
jarCreator.putManifestEntry("java-home", System.getProperty("java.home"));
Dimension2D size = getSize(!lockScenario);
jarCreator.putManifestEntry("width", "" + size.getWidth());
jarCreator.putManifestEntry("height", "" + size.getHeight());
jarCreator.create();
if (scenarioInfo.isIncludeSource())
{
try
{
tmpZipFile = File.createTempFile("greenfootSource", ".zip", null);
tmpZipFile.deleteOnExit();
}
catch (IOException e)
{
e.printStackTrace();
return;
}
String zipName = tmpZipFile.getName();
JarCreator zipCreator = new JarCreator(project, exportDir, zipName);
zipCreator.create();
}
if (!scenarioInfo.isKeepSavedScreenshot())
{
String formatName = "png";
try {
tmpImgFile = File.createTempFile("greenfoot", "." + formatName, null);
BufferedImage bufferedImage = SwingFXUtils.fromFXImage(scenarioInfo.getImage(), null);
ImageIO.write(bufferedImage, formatName, tmpImgFile);
tmpImgFile.deleteOnExit();
}
catch (IOException e) {
e.printStackTrace();
return;
}
}
String login = scenarioInfo.getUserName();
String password = scenarioInfo.getPassword();
String scenarioName = scenarioInfo.getTitle();
if (scenarioName != null && scenarioName.length() < 1)
{
scenarioName = "NO_NAME";
}
if (webPublisher == null) {
webPublisher = new MyGameClient(this);
}
dialog.setProgress(true, Config.getString("export.progress.publishing"));
try
{
ExportInfo exportedInfo = scenarioInfo;
int uploadSize = (int) tmpJarFile.length();
if (tmpImgFile != null)
{
uploadSize += (int) tmpImgFile.length();
}
if (tmpZipFile != null)
{
uploadSize += (int) tmpZipFile.length();
}
setUploadSize(uploadSize);
webPublisher.submit(hostAddress, login, password, tmpJarFile.getAbsolutePath(),
tmpZipFile, tmpImgFile, (int)size.getWidth(), (int)size.getHeight(), exportedInfo);
}
catch (UnknownHostException e)
{
dialog.setProgress(false,
Config.getString("export.publish.unknownHost") + " (" + e.getMessage() + ")");
}
catch (IOException e)
{
dialog.setProgress(false, Config.getString("export.publish.fail") + " " + e.getMessage());
}
}
private static File[] getJarsInPlusLib(Project project)
{
File plusLibsDir = new File(project.getProjectDir(), Project.projectLibDirName);
return plusLibsDir.listFiles((dir, name) -> name.toLowerCase().endsWith(".jar"));
}
| Create an application (jar-file)
|
@OnThread(Tag.Worker)
private void makeApplication()
{
dialog.setProgress(true, Config.getString("export.progress.writingJar"));
File exportFile = new File(scenarioInfo.getExportFileName());
File exportDir = exportFile.getParentFile();
String jarName = exportFile.getName();
boolean lockScenario = scenarioInfo.isLocked();
boolean hideControls = scenarioInfo.isHideControls();
JarCreator jarCreator = new JarCreator(project, exportDir, jarName, worldName,
lockScenario, hideControls, false, false);
jarCreator.includeSource(false);
File greenfootLibDir = Config.getGreenfootLibDir();
File greenfootDir = new File(greenfootLibDir, "standalone");
jarCreator.addFile(greenfootDir);
Set<File> thirdPartyLibs = GreenfootUtil.get3rdPartyLibs();
for (File lib : thirdPartyLibs) {
jarCreator.addJarToJar(lib);
}
File[] jarFiles = getJarsInPlusLib(project);
if (jarFiles != null) {
for (File file : jarFiles) {
jarCreator.addJarToJar(file);
}
}
try {
File license = new File(Utility.getGreenfootDir(), "GREENFOOT_LICENSES.txt");
if (license.exists()) {
jarCreator.addFile(license);
}
}
catch (IOException e) {
}
jarCreator.create();
dialog.setProgress(false, Config.getString("export.progress.complete"));
}
| Create an standalone project (gfar-file)
|
@OnThread(Tag.Worker)
private void makeProject()
{
dialog.setProgress(true, Config.getString("export.progress.writingGfar"));
File exportFile = new File(scenarioInfo.getExportFileName());
File exportDir = exportFile.getParentFile();
String gfarName = exportFile.getName();
JarCreator gfarCrator = new JarCreator(project, exportDir, gfarName);
gfarCrator.create();
dialog.setProgress(false, Config.getString("export.progress.complete"));
}
| Get the size needed to display the application and control panel.
| @return The width and the height wrapped in Dimension2D object.
|
private Dimension2D getSize(boolean includeControls)
{
final int EMPTY_BORDER_SIZE = 5;
Dimension2D controlsBorder = new Dimension2D((EMPTY_BORDER_SIZE) * 2, (EMPTY_BORDER_SIZE) * 2);
double controlsWidth = controlsBorder.getWidth() + (includeControls ? 560 : 410);
double controlHeight = controlsBorder.getHeight() + 48;
Dimension2D worldBorder = new Dimension2D((EMPTY_BORDER_SIZE + 1) * 2, (EMPTY_BORDER_SIZE + 1) * 2);
double width = Math.max(worldWidth + worldBorder.getWidth() + 2, controlsWidth);
double height = controlHeight + worldHeight + worldBorder.getHeight() + 2;
return new Dimension2D(width, height);
}
| Something went wrong when publishing.
|
@Override
public void errorRecieved(final PublishEvent event)
{
deleteTmpFiles();
Platform.runLater(() -> dialog.publishFinished(false, Config.getString("export.publish.fail") + " " + event.getMessage()));
}
| Publish succeeded.
|
@Override
public void uploadComplete(PublishEvent event)
{
deleteTmpFiles();
Platform.runLater(() -> dialog.publishFinished(true, Config.getString("export.publish.complete")));
}
private void deleteTmpFiles()
{
if (tmpJarFile != null) {
tmpJarFile.delete();
tmpJarFile = null;
}
if (tmpImgFile != null) {
tmpImgFile.delete();
tmpImgFile = null;
}
if (tmpZipFile != null) {
tmpZipFile.delete();
tmpZipFile = null;
}
}
| We now know the total upload size.
|
public void setUploadSize(final int size)
{
Platform.runLater(() -> dialog.setUploadSize(size));
}
| Upload progress made.
|
@Override
public void progressMade(final PublishEvent event)
{
Platform.runLater(() -> dialog.progressMade(event.getBytes()));
}
@Override
public String[] needProxyAuth()
{
CompletableFuture<String[]> detailsFuture = new CompletableFuture<>();
Platform.runLater(() ->
{
Optional<ProxyAuthDialog.ProxyAuthInfo> infoOptional =
new ProxyAuthDialog(dialog.asWindow()).showAndWait();
if (infoOptional.isPresent())
{
ProxyAuthDialog.ProxyAuthInfo info = infoOptional.get();
detailsFuture.complete(new String[] {info.getUsername(), info.getPassword()
});
}
else
{
detailsFuture.complete(null);
}
});
try
{
return detailsFuture.get();
}
catch (InterruptedException e)
{
return null;
}
catch (ExecutionException e)
{
throw new RuntimeException(e.getCause());
}
}
}
top,
use,
map,
class Exporter
. getFunction
. getGreenfootCoreJar
. getInstance
. Exporter
. doExport
. publishToWebServer
. getJarsInPlusLib
. makeApplication
. makeProject
. getSize
. errorRecieved
. uploadComplete
. deleteTmpFiles
. setUploadSize
. progressMade
. needProxyAuth
566 neLoCode
+ 30 LoComm