/******************************************************************************* * This file is part of logisim-evolution. * * logisim-evolution is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * logisim-evolution is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with logisim-evolution. If not, see <http://www.gnu.org/licenses/>. * * Original code by Carl Burch (http://www.cburch.com), 2011. * Subsequent modifications by : * + Haute École Spécialisée Bernoise * http://www.bfh.ch * + Haute École du paysage, d'ingénierie et d'architecture de Genève * http://hepia.hesge.ch/ * + Haute École d'Ingénierie et de Gestion du Canton de Vaud * http://www.heig-vd.ch/ * The project is currently maintained by : * + REDS Institute - HEIG-VD * Yverdon-les-Bains, Switzerland * http://reds.heig-vd.ch *******************************************************************************/ package com.cburch.logisim.gui.start; import java.io.BufferedInputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URISyntaxException; import java.net.URL; import java.net.URLConnection; import java.security.CodeSource; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import javax.swing.JOptionPane; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.cburch.logisim.LogisimVersion; import com.cburch.logisim.Main; import com.cburch.logisim.file.LoadFailedException; import com.cburch.logisim.file.Loader; import com.cburch.logisim.gui.main.Print; import com.cburch.logisim.gui.menu.LogisimMenuBar; import com.cburch.logisim.gui.menu.WindowManagers; import com.cburch.logisim.prefs.AppPreferences; import com.cburch.logisim.proj.Project; import com.cburch.logisim.proj.ProjectActions; import com.cburch.logisim.util.ArgonXML; import com.cburch.logisim.util.LocaleManager; import com.cburch.logisim.util.MacCompatibility; import com.cburch.logisim.util.StringUtil; public class Startup { static void doOpen(File file) { if (startupTemp != null) { startupTemp.doOpenFile(file); } } static void doPrint(File file) { if (startupTemp != null) { startupTemp.doPrintFile(file); } } public static Startup parseArgs(String[] args) { // see whether we'll be using any graphics boolean isTty = false; boolean isClearPreferences = false; for (int i = 0; i < args.length; i++) { if (args[i].equals("-tty")) { isTty = true; } else if (args[i].equals("-clearprefs") || args[i].equals("-clearprops")) { isClearPreferences = true; } } if (!isTty) { // we're using the GUI: Set up the Look&Feel to match the platform System.setProperty( "com.apple.mrj.application.apple.menu.about.name", "Logisim-evolution"); System.setProperty("apple.laf.useScreenMenuBar", "true"); LocaleManager.setReplaceAccents(false); // Initialize graphics acceleration if appropriate AppPreferences.handleGraphicsAcceleration(); } Startup ret = new Startup(isTty); startupTemp = ret; if (!isTty) { registerHandler(); } if (isClearPreferences) { AppPreferences.clear(); } // parse arguments for (int i = 0; i < args.length; i++) { String arg = args[i]; if (arg.equals("-tty")) { if (i + 1 < args.length) { i++; String[] fmts = args[i].split(","); if (fmts.length == 0) { logger.error("{}", Strings.get("ttyFormatError")); } for (int j = 0; j < fmts.length; j++) { String fmt = fmts[j].trim(); if (fmt.equals("table")) { ret.ttyFormat |= TtyInterface.FORMAT_TABLE; } else if (fmt.equals("speed")) { ret.ttyFormat |= TtyInterface.FORMAT_SPEED; } else if (fmt.equals("tty")) { ret.ttyFormat |= TtyInterface.FORMAT_TTY; } else if (fmt.equals("halt")) { ret.ttyFormat |= TtyInterface.FORMAT_HALT; } else if (fmt.equals("stats")) { ret.ttyFormat |= TtyInterface.FORMAT_STATISTICS; } else { logger.error("{}", Strings.get("ttyFormatError")); } } } else { logger.error("{}", Strings.get("ttyFormatError")); return null; } } else if (arg.equals("-sub")) { if (i + 2 < args.length) { File a = new File(args[i + 1]); File b = new File(args[i + 2]); if (ret.substitutions.containsKey(a)) { logger.error("{}", Strings.get("argDuplicateSubstitutionError")); return null; } else { ret.substitutions.put(a, b); i += 2; } } else { logger.error("{}", Strings.get("argTwoSubstitutionError")); return null; } } else if (arg.equals("-load")) { if (i + 1 < args.length) { i++; if (ret.loadFile != null) { logger.error("{}", Strings.get("loadMultipleError")); } File f = new File(args[i]); ret.loadFile = f; } else { logger.error("{}", Strings.get("loadNeedsFileError")); return null; } } else if (arg.equals("-empty")) { if (ret.templFile != null || ret.templEmpty || ret.templPlain) { logger.error("{}", Strings.get("argOneTemplateError")); return null; } ret.templEmpty = true; } else if (arg.equals("-plain")) { if (ret.templFile != null || ret.templEmpty || ret.templPlain) { logger.error("{}", Strings.get("argOneTemplateError")); return null; } ret.templPlain = true; } else if (arg.equals("-version")) { System.out.println(Main.VERSION_NAME); // OK return null; } else if (arg.equals("-gates")) { i++; if (i >= args.length) { printUsage(); } String a = args[i]; if (a.equals("shaped")) { AppPreferences.GATE_SHAPE.set(AppPreferences.SHAPE_SHAPED); } else if (a.equals("rectangular")) { AppPreferences.GATE_SHAPE .set(AppPreferences.SHAPE_RECTANGULAR); } else { logger.error("{}", Strings.get("argGatesOptionError")); System.exit(-1); } } else if (arg.equals("-locale")) { i++; if (i >= args.length) { printUsage(); } setLocale(args[i]); } else if (arg.equals("-accents")) { i++; if (i >= args.length) { printUsage(); } String a = args[i]; if (a.equals("yes")) { AppPreferences.ACCENTS_REPLACE.setBoolean(false); } else if (a.equals("no")) { AppPreferences.ACCENTS_REPLACE.setBoolean(true); } else { logger.error("{}", Strings.get("argAccentsOptionError")); System.exit(-1); } } else if (arg.equals("-template")) { if (ret.templFile != null || ret.templEmpty || ret.templPlain) { logger.error("{}", Strings.get("argOneTemplateError")); return null; } i++; if (i >= args.length) { printUsage(); } ret.templFile = new File(args[i]); if (!ret.templFile.exists()) { logger.error("{}", StringUtil.format( Strings.get("templateMissingError"), args[i])); } else if (!ret.templFile.canRead()) { logger.error("{}", StringUtil.format( Strings.get("templateCannotReadError"), args[i])); } } else if (arg.equals("-nosplash")) { ret.showSplash = false; } else if (arg.equals("-test")) { i++; if (i >= args.length) printUsage(); ret.circuitToTest = args[i]; i++; if (i >= args.length) printUsage(); ret.testVector = args[i]; ret.showSplash = false; ret.exitAfterStartup = true; } else if (arg.equals("-clearprefs")) { // already handled above } else if (arg.equals("-analyze")) { Main.ANALYZE = true; } else if (arg.equals("-noupdates")) { Main.UPDATE = false; } else if (arg.equals("-questa")) { i++; if (i >= args.length) { printUsage(); } String a = args[i]; if (a.equals("yes")) { AppPreferences.QUESTA_VALIDATION.setBoolean(true); } else if (a.equals("no")) { AppPreferences.QUESTA_VALIDATION.setBoolean(false); } else { logger.error("{}", Strings.get("argQuestaOptionError")); System.exit(-1); } } else if (arg.charAt(0) == '-') { printUsage(); return null; } else { ret.filesToOpen.add(new File(arg)); } } if (ret.exitAfterStartup && ret.filesToOpen.isEmpty()) { printUsage(); } if (ret.isTty && ret.filesToOpen.isEmpty()) { logger.error("{}", Strings.get("ttyNeedsFileError")); return null; } if (ret.loadFile != null && !ret.isTty) { logger.error("{}", Strings.get("loadNeedsTtyError")); return null; } return ret; } private static void printUsage() { System.err.println(StringUtil.format(Strings.get("argUsage"), Startup.class.getName())); // OK System.err.println(); // OK System.err.println(Strings.get("argOptionHeader")); // OK System.err.println(" " + Strings.get("argAccentsOption")); // OK System.err.println(" " + Strings.get("argClearOption")); // OK System.err.println(" " + Strings.get("argEmptyOption")); // OK System.err.println(" " + Strings.get("argTestOption")); // OK System.err.println(" " + Strings.get("argGatesOption")); // OK System.err.println(" " + Strings.get("argHelpOption")); // OK System.err.println(" " + Strings.get("argLoadOption")); // OK System.err.println(" " + Strings.get("argLocaleOption")); // OK System.err.println(" " + Strings.get("argNoSplashOption")); // OK System.err.println(" " + Strings.get("argPlainOption")); // OK System.err.println(" " + Strings.get("argSubOption")); // OK System.err.println(" " + Strings.get("argTemplateOption")); // OK System.err.println(" " + Strings.get("argTtyOption")); // OK System.err.println(" " + Strings.get("argQuestaOption")); // OK System.err.println(" " + Strings.get("argVersionOption")); // OK System.exit(-1); } private static void registerHandler() { try { Class<?> needed1 = Class.forName("com.apple.eawt.Application"); if (needed1 == null) { return; } Class<?> needed2 = Class .forName("com.apple.eawt.ApplicationAdapter"); if (needed2 == null) { return; } MacOsAdapter.register(); MacOsAdapter.addListeners(true); } catch (ClassNotFoundException e) { return; } catch (Exception t) { try { MacOsAdapter.addListeners(false); } catch (Exception t2) { } } } private static void setLocale(String lang) { Locale[] opts = Strings.getLocaleOptions(); for (int i = 0; i < opts.length; i++) { if (lang.equals(opts[i].toString())) { LocaleManager.setLocale(opts[i]); return; } } logger.warn("{}", Strings.get("invalidLocaleError")); logger.warn("{}", Strings.get("invalidLocaleOptionsHeader")); for (int i = 0; i < opts.length; i++) { logger.warn(" {}", opts[i].toString()); } System.exit(-1); } final static Logger logger = LoggerFactory.getLogger(Startup.class); private static Startup startupTemp = null; // based on command line boolean isTty; private File templFile = null; private boolean templEmpty = false; private boolean templPlain = false; private ArrayList<File> filesToOpen = new ArrayList<File>(); private String testVector = null; private String circuitToTest = null; private boolean exitAfterStartup = false; private boolean showSplash; private File loadFile; private HashMap<File, File> substitutions = new HashMap<File, File>(); private int ttyFormat = 0; // from other sources private boolean initialized = false; private SplashScreen monitor = null; private ArrayList<File> filesToPrint = new ArrayList<File>(); private Startup(boolean isTty) { this.isTty = isTty; this.showSplash = !isTty; } /** * Auto-update Logisim-evolution if a new version is available * * Original idea taken from Jupar: * http://masterex.github.io/archive/2011/12/25/jupar.html by Periklis * Master_ex Ntanasis <pntanasis@gmail.com> * * @return true if the code has been updated, and therefore the execution * has to be stopped, false otherwise */ public boolean autoUpdate() { if (!Main.UPDATE || !networkConnectionAvailable()) { // Auto-update disabled from command line, or network connection not // available return (false); } // Get the remote XML file containing the current version URL xmlURL; try { xmlURL = new URL(Main.UPDATE_URL); } catch (MalformedURLException e) { logger.error("The URL of the XML file for the auto-updater is malformed.\nPlease report this error to the software maintainer\n-- AUTO-UPDATE ABORTED --"); return (false); } URLConnection conn; try { conn = xmlURL.openConnection(); } catch (IOException e) { logger.error("Although an Internet connection should be available, the system couldn't connect to the URL requested by the auto-updater\nIf the error persist, please contact the software maintainer\n-- AUTO-UPDATE ABORTED --"); return (false); } InputStream in; try { in = conn.getInputStream(); } catch (IOException e) { logger.error("Although an Internet connection should be available, the system couldn't retrieve the data requested by the auto-updater.\nIf the error persist, please contact the software maintainer\n-- AUTO-UPDATE ABORTED --"); return (false); } ArgonXML logisimData = new ArgonXML(in, "logisim-evolution"); // Get the appropriate remote version number LogisimVersion remoteVersion = LogisimVersion.parse(Main.VERSION .hasTracker() ? logisimData.child("tracked_version").content() : logisimData.child("untracked_version").content()); // If the remote version is newer, perform the update if (remoteVersion.compareTo(Main.VERSION) > 0) { int answer = JOptionPane.showConfirmDialog(null, "A new Logisim-evolution version (" + remoteVersion + ") is available!\nWould you like to update?", "Update", JOptionPane.YES_NO_OPTION, JOptionPane.INFORMATION_MESSAGE); if (answer == 1) { // User refused to update -- we just hope he gets sufficiently // annoyed by the message that he finally updates! return (false); } // Obtain the base directory of the jar archive CodeSource codeSource = Startup.class.getProtectionDomain() .getCodeSource(); File jarFile = null; try { jarFile = new File(codeSource.getLocation().toURI().getPath()); } catch (URISyntaxException e) { logger.error("Error in the syntax of the URI for the path of the executed Logisim-evolution JAR file!"); e.printStackTrace(); JOptionPane .showMessageDialog( null, "An error occurred while updating to the new Logisim-evolution version.\nPlease check the console for log information.", "Update failed", JOptionPane.ERROR_MESSAGE); return (false); } // Get the appropriate remote filename to download String remoteJar = Main.VERSION.hasTracker() ? logisimData.child( "tracked_file").content() : logisimData.child( "untracked_file").content(); boolean updateOk = downloadInstallUpdatedVersion(remoteJar, jarFile.getAbsolutePath()); if (updateOk) { JOptionPane .showMessageDialog( null, "The new Logisim-evolution version (" + remoteVersion + ") has been correctly installed.\nPlease restart Logisim-evolution for the changes to take effect.", "Update succeeded", JOptionPane.INFORMATION_MESSAGE); return (true); } else { JOptionPane .showMessageDialog( null, "An error occurred while updating to the new Logisim-evolution version.\nPlease check the console for log information.", "Update failed", JOptionPane.ERROR_MESSAGE); return (false); } } return (false); } private void doOpenFile(File file) { if (initialized) { ProjectActions.doOpen(null, null, file); } else { filesToOpen.add(file); } } private void doPrintFile(File file) { if (initialized) { Project toPrint = ProjectActions.doOpen(null, null, file); Print.doPrint(toPrint); toPrint.getFrame().dispose(); } else { filesToPrint.add(file); } } /** * Download a new version of Logisim, according to the instructions received * from autoUpdate(), and install it at the specified location * * Original idea taken from: * http://baptiste-wicht.developpez.com/tutoriels/java/update/ by Baptiste * Wicht * * @param filePath * remote file URL * @param destination * local destination for the updated Jar file * @return true if the new version has been downloaded and installed, false * otherwise * @throws IOException */ private boolean downloadInstallUpdatedVersion(String filePath, String destination) { URL fileURL; try { fileURL = new URL(filePath); } catch (MalformedURLException e) { logger.error("The URL of the requested update file is malformed.\nPlease report this error to the software maintainer.\n-- AUTO-UPDATE ABORTED --"); return (false); } URLConnection conn; try { conn = fileURL.openConnection(); } catch (IOException e) { logger.error("Although an Internet connection should be available, the system couldn't connect to the URL of the updated file requested by the auto-updater.\nIf the error persist, please contact the software maintainer\n-- AUTO-UPDATE ABORTED --"); return (false); } // Get remote file size int length = conn.getContentLength(); if (length == -1) { logger.error("Cannot retrieve the file containing the updated version.\nIf the error persist, please contact the software maintainer\n-- AUTO-UPDATE ABORTED --"); return (false); } // Get remote file stream InputStream is; try { is = new BufferedInputStream(conn.getInputStream()); } catch (IOException e) { logger.error("Cannot get remote file stream.\nIf the error persist, please contact the software maintainer\n-- AUTO-UPDATE ABORTED --"); return (false); } // Local file buffer byte[] data = new byte[length]; // Helper variables for marking the current position in the downloaded // file int currentBit = 0; int deplacement = 0; // Download remote content try { while (deplacement < length) { currentBit = is.read(data, deplacement, data.length - deplacement); if (currentBit == -1) { // Reached EOF break; } deplacement += currentBit; } } catch (IOException e) { logger.error("An error occured while retrieving remote file (remote peer hung up).\nIf the error persist, please contact the software maintainer\n-- AUTO-UPDATE ABORTED --"); return (false); } // Close remote stream try { is.close(); } catch (IOException e) { logger.error("Error encountered while closing the remote stream!"); e.printStackTrace(); } // If not all the bytes have been retrieved, abort update if (deplacement != length) { logger.error("An error occured while retrieving remote file (local size != remote size), download corrupted.\nIf the error persist, please contact the software maintainer\n-- AUTO-UPDATE ABORTED --"); return (false); } // Open stream for local Jar and write data FileOutputStream destinationFile; try { destinationFile = new FileOutputStream(destination); } catch (FileNotFoundException e) { logger.error("An error occured while opening the local Jar file.\n-- AUTO-UPDATE ABORTED --"); return (false); } try { destinationFile.write(data); destinationFile.flush(); } catch (IOException e) { logger.error("An error occured while writing to the local Jar file.\n-- AUTO-UPDATE ABORTED --\nThe local file might be corrupted. If this is the case, please download a new copy of Logisim."); } finally { try { destinationFile.close(); } catch (IOException e) { logger.error("Error encountered while closing the local destination file!\nThe local file might be corrupted. If this is the case, please download a new copy of Logisim."); return (false); } } return (true); } List<File> getFilesToOpen() { return filesToOpen; } File getLoadFile() { return loadFile; } Map<File, File> getSubstitutions() { return Collections.unmodifiableMap(substitutions); } int getTtyFormat() { return ttyFormat; } private void loadTemplate(Loader loader, File templFile, boolean templEmpty) { if (showSplash) { monitor.setProgress(SplashScreen.TEMPLATE_OPEN); } if (templFile != null) { AppPreferences.setTemplateFile(templFile); AppPreferences.setTemplateType(AppPreferences.TEMPLATE_CUSTOM); } else if (templEmpty) { AppPreferences.setTemplateType(AppPreferences.TEMPLATE_EMPTY); } else if (templPlain) { AppPreferences.setTemplateType(AppPreferences.TEMPLATE_PLAIN); } } /** * Check if network connection is available. * * This function tries to connect to google in order to test the * availability of a network connection. This step is needed before * attempting to perform an auto-update. It assumes that google is * accessible -- usually this is the case, and it should also provide a * quick reply to the connection attempt, reducing the lag. * * @return true if the connection is available, false otherwise */ private boolean networkConnectionAvailable() { try { URL url = new URL("http://www.google.com"); URLConnection uC = url.openConnection(); uC.connect(); return (true); } catch (MalformedURLException e) { logger.error("The URL used to check the connectivity is malformed -- no Google?"); e.printStackTrace(); } catch (IOException e) { // If we get here, the connection somehow failed return (false); } return (false); } public void run() { if (isTty) { try { TtyInterface.run(this); return; } catch (Exception t) { t.printStackTrace(); System.exit(-1); return; } } // kick off the progress monitor // (The values used for progress values are based on a single run where // I loaded a large file.) if (showSplash) { try { monitor = new SplashScreen(); monitor.setVisible(true); } catch (Exception t) { monitor = null; showSplash = false; } } // pre-load the two basic component libraries, just so that the time // taken is shown separately in the progress bar. if (showSplash) { monitor.setProgress(SplashScreen.LIBRARIES); } Loader templLoader = new Loader(monitor); int count = templLoader.getBuiltin().getLibrary("Base").getTools() .size() + templLoader.getBuiltin().getLibrary("Gates").getTools() .size(); if (count < 0) { // this will never happen, but the optimizer doesn't know that... logger.error("FATAL ERROR - no components"); // OK System.exit(-1); } // load in template loadTemplate(templLoader, templFile, templEmpty); // now that the splash screen is almost gone, we do some last-minute // interface initialization if (showSplash) { monitor.setProgress(SplashScreen.GUI_INIT); } WindowManagers.initialize(); if (MacCompatibility.isSwingUsingScreenMenuBar()) { MacCompatibility .setFramelessJMenuBar(new LogisimMenuBar(null, null)); } else { new LogisimMenuBar(null, null); // most of the time occupied here will be in loading menus, which // will occur eventually anyway; we might as well do it when the // monitor says we are } // if user has double-clicked a file to open, we'll // use that as the file to open now. initialized = true; // load file if (filesToOpen.isEmpty()) { Project proj = ProjectActions.doNew(monitor); proj.setStartupScreen(true); if (showSplash) { monitor.close(); } } else { int numOpened = 0; boolean first = true; for (File fileToOpen : filesToOpen) { try { if (testVector != null) { Project proj = ProjectActions.doOpenNoWindow(monitor, fileToOpen); proj.doTestVector(testVector, circuitToTest); } else { ProjectActions.doOpen(monitor, fileToOpen, substitutions); } numOpened++; } catch (LoadFailedException ex) { logger.error("{} : {}", fileToOpen.getName(), ex.getMessage()); } if (first) { first = false; if (showSplash) { monitor.close(); } monitor = null; } } if (numOpened == 0) System.exit(-1); } for (File fileToPrint : filesToPrint) { doPrintFile(fileToPrint); } if (exitAfterStartup) { System.exit(0); } } }