/* * [Replicator.java] * * Summary: Replicator, retrieves and unpacks zip archives for the Replicator. * * Copyright: (c) 2003-2017 Roedy Green, Canadian Mind Products, http://mindprod.com * * Licence: This software may be copied and used freely for any purpose but military. * http://mindprod.com/contact/nonmil.html * * Requires: JDK 1.8+ * * Created with: JetBrains IntelliJ IDEA IDE http://www.jetbrains.com/idea/ * * Version History: * 7.2 2006-02-11 add RETIREMENT property to delay deleting old zips. * 7.3 2006-03-05 reformat with IntelliJ and add Javadoc * 7.4 2007-05-30 * 7.5 2007-07-16 IntelliJ inspector. * 7.6 2007-07-17 refactor to use use enums, for:each, pass IntelliJ Inspector. * 7.7 2007-07-20 compress manifests * 7.8 2007-07-23 debugging logs, extra validations. * 7.9 2007-07-23 new way to do zip retires to avoid out-of-order bug. * 8.0 2007-07-28 fix log so works on virgin machines. * 8.1 2007-07-29 Remove Windows centric code. * 8.2 2007-07-30 properties file specified on the command line. * 8.3 2007-08-04 Use new version of FileTransfer with timeouts, more efficient CPU use, etc. * 8.4 2007-08-18 merged clump, move fileLength to FD for verify. * sender and receiver to make it work properly on Ubuntu. More * efficient unpacking of zips, bypassing work it has done * previously. * 8.5 2007-09-02 better stats, can hit verify during download, more spacious layout. * 8.6 2007-09-13 reduced frequency of zip repacking * 8.7 2007-09-18 Fix zip order when deletions * 8.8 2007-09-20 flip to JDK 1.6 * 8.9 2007-09-22 now handles files that change length without changing date. * 9.0 2007-09-23 retirement time now specified in minutes rather than hours. * 9.1 2007-10-28 fix bug to ensure gui state persisted. * 9.2 2007-12-24 display range of times for retired. * 9.3 2008-01-07 emaciate and delete files in the same replicatorSender run. * 9.4 2008-01-28 calculate percentage of deadwood more conservatively * 9.5 2008-02-08 better error message if can't create dirs, tool tip help. * 9.6 2008-02-22 display computed lag and retirement timestamps * 9.7 2008-03-20 only unpack new files, not the entire zip. * 9.8 2008-03-28 fine tune generated JNLP * 9.9 2008-04-09 compact zips under tighter rules immediately after LAG days. * 10.0 2008-08-10 extend timeouts to be more patient with network congestion. * 10.1 2009-01-23 fix harmless bug in saving the internal state * 10.2 2009-04-03 tidy up code to check presence of necessary files to make it more WORA. * 10.3 2009-05-03 localise log output file with Localise and PrintWriterPortable * 10.4 2009-08-08 add RECOVER, HIGHEST_ACCEPTABLE_EXIT_CODE, MAX_UPLOAD_TRIES config parms. * 10.5 2011-04-22 fully automate StartOver.bat, so no additional copies are needed. * 10.6 2011-07-30 add support for Java 1.7 * 10.7 2011-08-05 add packing statistics * 10.8 2011-09-08 sort packing statistics, avoiding voids. * 10.9 2011-09-14 base stats on clumping, and stats on newest repackaged. * 11.0 2011-12-22 update the JNLP * 11.1 2012-03-15 serialise arrays instead of ArrayLists. Correct docs on how LAG works. * 11.2 2012-05-12 more consistent use of empty timestamp markers. Should make more compact uploads. * 11.3 2012-05-13 further more consistent use of empty markers. * 11.4 2014-04-06 bypass Oracle dropping support for JNLP properties. * 11.5 2014-08-19 modify SortedArrayList to account for JDK 1.8.0_20s fobbing Collections.sort on ArrayList.sort. * 11.6 2015-03-10 sign the jar. */ package com.mindprod.replicator; import com.mindprod.common18.Build; import com.mindprod.common18.CMPAboutJBox; import com.mindprod.common18.FontFactory; import com.mindprod.common18.JEButton; import com.mindprod.common18.Laf; import com.mindprod.common18.VersionCheck; import com.mindprod.replicatorcommon.Config; import com.mindprod.replicatorcommon.IO; import com.mindprod.replicatorcommon.ReplicatorCommon; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JEditorPane; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPasswordField; import javax.swing.JProgressBar; import javax.swing.JTextField; import javax.swing.SwingUtilities; import javax.swing.WindowConstants; import java.awt.Color; import java.awt.Container; import java.awt.Font; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.util.Properties; import java.util.concurrent.TimeUnit; import static com.mindprod.replicator.ConfigForReceiver.*; import static java.lang.System.*; /** * Replicator, retrieves and unpacks zip archives for the Replicator. * * @author Roedy Green, Canadian Mind Products * @version 11.6 2015-03-10 sign the jar. * @since 2003-09-15 */ @SuppressWarnings( { "FieldCanBeLocal" } ) public final class Replicator extends JFrame implements PropertyChangeListener { private static final int FIRST_COPYRIGHT_YEAR = 2003; /** * undisplayed copyright notice */ @SuppressWarnings( { "UnusedDeclaration" } ) private static final String EMBEDDED_COPYRIGHT = "Copyright: (c) 2003-2017 Roedy Green, Canadian Mind Products, http://mindprod.com"; /** * title/version/build */ private static final String EXTENDED_TITLE; @SuppressWarnings( { "UnusedDeclaration" } ) private static final String RELEASE_DATE = ReplicatorCommon.RELEASE_DATE; /** * name of this application. */ private static final String TITLE_STRING = "T h e R e p l i c a t o r"; @SuppressWarnings( { "UnusedDeclaration" } ) private static final String VERSION_STRING = ReplicatorCommon.VERSION_STRING; private static final Color FOREGROUND_FOR_LABEL = new Color( 0x1e90ff ); /** * for titles */ private static final Color FOREGROUND_FOR_TITLE = new Color( 0xdc143c ); private static final Font FONT_FOR_DOING = FontFactory.build( "Dialog", Font.PLAIN, 13 ); /** * font used for keyable or text fields. */ private static final Font FONT_FOR_EDITABLE_FIELDS = FontFactory.build( "Dialog", Font.PLAIN, 13 ); /** * font used for labels */ private static final Font FONT_FOR_LABELS = FontFactory.build( "Dialog", Font.PLAIN, 12 ); /** * font for titles */ private static final Font FONT_FOR_TITLE = FontFactory.build( "Dialog", Font.BOLD, 14 ); private static final Insets BOTTOM_LEFT_INSET = new Insets( 2, 9, 10, 3 ); private static final Insets BOTTOM_RIGHT_INSET = new Insets( 2, 3, 10, 9 ); private static final Insets BOTTOM_SPAN_INSET = new Insets( 9, 9, 10, 9 ); private static final Insets LEFT_INSET = new Insets( 10, 9, 10, 3 ); private static final Insets MIDLEFT_INSET = new Insets( 2, 9, 2, 3 ); private static final Insets RIGHT_INSET = new Insets( 10, 3, 10, 9 ); private static final Insets TOP_LEFT_INSET = new Insets( 10, 9, 2, 3 ); private static final Insets TOP_RIGHT_INSET = new Insets( 10, 3, 2, 9 ); /** * use to communicate state of verifyBox checkbox */ static boolean verify = false; /** * background task that downloads zips */ private static FetchZips backgroundTask; /** * The singleton ConfigForReceiver frame */ private static Replicator theFrame; static { // can't define until component pieces are defined. EXTENDED_TITLE = TITLE_STRING + " " + VERSION_STRING + " build " + Build .BUILD_NUMBER; } static { assert Config.VERSION.equals( VERSION_STRING ) : "mismatched versions"; // JNLP ensures we have Java 1.6 } private JButton baseDirChange; private JButton cancel; private JButton go; private JButton zipDirChange; private JCheckBox keepZips; private JCheckBox unpackZips; private JCheckBox verifyBox; private JEditorPane baseDirHelp; private JEditorPane keepZipsHelp; private JEditorPane lanZipUrlHelp; private JEditorPane passwordHelp; private JEditorPane unpackZipsHelp; private JEditorPane userIdHelp; private JEditorPane zipDirHelp; private JFileChooser fc; private JLabel baseDirLabel; private JLabel lanZipUrlLabel; private JLabel passwordLabel; private JLabel project; private JLabel title; private JLabel userIdLabel; private JLabel via; private JLabel zipDirLabel; private JPasswordField password; private JProgressBar progressBar; private JTextField baseDir; private JTextField doing; private JTextField lanZipUrl; private JTextField userId; private JTextField zipDir; /** * Constructor */ @SuppressWarnings( { "WeakerAccess" } ) public Replicator() { super(); } /** * Terminated successfully */ static void cancelled() { theFrame.go.setEnabled( false ); theFrame.cancel.setEnabled( false ); theFrame.verifyBox.setEnabled( false ); StatsForReceiver.displayStats(); doing( "cancelled" ); String message = "Cancelled.\n" + StatsForReceiver.getStats(); JOptionPane.showMessageDialog( theFrame, message, "Cancelled", JOptionPane.WARNING_MESSAGE ); fin(); } /** * quit program after a fatal error. Delays 10 seconds to let user view console log. */ static void die() { // debug, show how we got here. new Throwable().printStackTrace( err ); Log.close(); Toolkit.getDefaultToolkit().beep(); err.println( "You have 20 seconds to click [copy] on the console to save any error messages." ); err.println(); // give 20 seconds for user to look at Console. try { // Sleep at least n milliseconds. // 1 millisecond = 1/1000 of a second. Thread.sleep( TimeUnit.SECONDS.toMillis( 20 ) ); } catch ( InterruptedException e ) { // awakened prematurely } Toolkit.getDefaultToolkit().beep(); System.exit( 1 ); } /** * Keep user informed of what we are doing. * * @param whatsHappening what we are doing. */ static void doing( String whatsHappening ) { theFrame.doing.setText( whatsHappening ); out.println( whatsHappening ); Log.println( whatsHappening ); } /** * Terminated successfully */ static void done() { theFrame.go.setEnabled( false ); theFrame.cancel.setEnabled( false ); theFrame.verifyBox.setEnabled( false ); StatsForReceiver.displayStats(); doing( "done" ); String message = "COMPLETED NORMALLY.\n" + StatsForReceiver.getStats(); JOptionPane.showMessageDialog( theFrame, message, "Completed", JOptionPane.INFORMATION_MESSAGE ); fin(); } /** * Just had a fatal error. Abort with a dialog box finale. * * @param errorMessage What went wrong */ static void fatal( String errorMessage ) { if ( !viaCode.isOfflineAllowed() ) { errorMessage += "\nCheck your Internet connection."; } errorMessage += "\nReboot and try again later in about 30 minutes." + "\nThere may have been a glitch uploading the files to the website." + "\nIf the problem persists, ask for help from the Replicator administrator or " + "roedyg@mindprod.com." + "\nYou can recover by deleting all files in your base directory and zip staging " + "directory" + "\nto start afresh."; err.println( "F A T A L E R R O R" ); err.println( errorMessage ); theFrame.doing.setText( errorMessage ); JOptionPane.showMessageDialog( theFrame, errorMessage, "Fatal Error", JOptionPane.ERROR_MESSAGE ); die(); } /** * get properties. possible sources JNLP file, -D command line, kludge.properties, registry preferences. */ private static void fetchProperties() { final InputStream fis = Replicator.class.getResourceAsStream( "/JNLP-INF/kludge.properties" ); if ( fis != null ) { Properties jnlpProperties = new Properties(); try { out.println( "using temporary kludge properties..." ); // use kludge of properties file as resource to get properties jnlpProperties.load( fis ); // jnlpProperties.list( System.out ); PersistConfigForReceiver.getJNLPConfig( jnlpProperties ); } catch ( IOException e ) { err.println( "problem reading kludge.properties file embedded in the replicator.jar file." ); die(); } } else { // get properties from JNLP file, or -D command line. out.println( "using JNLP properties..." ); PersistConfigForReceiver.getJNLPConfig( System.getProperties() ); } PersistConfigForReceiver.getConfigFromRegistry(); // can't open log until staging dir nailed down. // Will be decided on first use. if ( PREFS_EXIST ) { Log.open( RECEIVER_ZIP_STAGING_DIR + File .separatorChar + RECEIVER_LOG ); } out.println(); out.println( EXTENDED_TITLE ); Log.println( "R E C E I V E R L O G" ); Log.println( EXTENDED_TITLE ); out.println( "Project " + PROJECT_NAME ); Log.println( "Project " + PROJECT_NAME ); out.println( "See " + RECEIVER_LOG + " for a detailed session log." ); Log.println( "current time: " + Config.TIMESTAMP_MILLISECOND_FORMAT .format( System.currentTimeMillis() ) ); } /** * normal exit */ private static void fin() { Log.close(); System.exit( 0 ); } /** * mark the verify box as disabled */ static void freezeVerifyBox() { SwingUtilities.invokeLater( new Runnable() { public void run() { Replicator.theFrame.verifyBox.setEnabled( false ); } } ); } /** * Missing file, that should have been previously downloaded. * * @param missingFile file that is missing on disk, that should have been downloaded. */ static void missing( String missingFile ) { theFrame.go.setEnabled( false ); theFrame.cancel.setEnabled( false ); theFrame.verifyBox.setEnabled( false ); StatsForReceiver.displayStats(); final String missingMessage = "Missing file: " + missingFile + ". It will be retrieved automatically on the next run."; doing( missingMessage ); String message = missingMessage + "\n" + StatsForReceiver.getStats(); JOptionPane.showMessageDialog( theFrame, message, "Missing File", JOptionPane.ERROR_MESSAGE ); die(); } /** * Out of date file, that should have been previously downloaded. * * @param outOfDateFile file that should have been refreshed by downloading but was not. */ static void outDated( String outOfDateFile ) { theFrame.go.setEnabled( false ); theFrame.cancel.setEnabled( false ); theFrame.verifyBox.setEnabled( false ); StatsForReceiver.displayStats(); final String outdatedMessage = "Outdated file: " + outOfDateFile + ". It will be retrieved automatically on the next run."; doing( outdatedMessage ); String message = outdatedMessage + "\n" + StatsForReceiver.getStats(); JOptionPane.showMessageDialog( theFrame, message, "Outdated File", JOptionPane.ERROR_MESSAGE ); die(); } /** * Out of date file, that should have been previously downloaded. * * @param wrongSizeFile file that should have been refreshed by downloading but was not. */ static void wrongSize( String wrongSizeFile ) { theFrame.go.setEnabled( false ); theFrame.cancel.setEnabled( false ); theFrame.verifyBox.setEnabled( false ); StatsForReceiver.displayStats(); final String outdatedMessage = wrongSizeFile + " is the wrong size. It will be retrieved automatically on the next run."; doing( outdatedMessage ); String message = outdatedMessage + "\n" + StatsForReceiver.getStats(); JOptionPane.showMessageDialog( theFrame, message, "Wrong Size File", JOptionPane.ERROR_MESSAGE ); die(); } /** * check if all the fields entered are valid. If not, issues error dialog and returns false. * * @return true if all fields are valid. */ @SuppressWarnings( { "BooleanMethodNameMustStartWithQuestion" } ) private boolean areFieldsValid() { if ( viaCode.isAuthenticationConfigurable() ) { if ( userId.getText().trim().length() == 0 ) { JOptionPane.showMessageDialog( this, "You must fill in the user id.", "Missing userId", JOptionPane.ERROR_MESSAGE ); userId.requestFocus(); return false; } if ( new String( password.getPassword() ).trim().length() < 4 ) { JOptionPane.showMessageDialog( this, "You must fill in the complete password.", "Missing password", JOptionPane.ERROR_MESSAGE ); password.requestFocus(); return false; } } // tidy up the base dir name String baseDirName = baseDir.getText().trim().replace( '/', File.separatorChar ); while ( baseDirName.endsWith( File.separator ) ) { baseDirName = baseDirName.substring( 0, baseDirName.length() - 1 ); } baseDir.setText( baseDirName ); // make sure did not use a root as base dir. for ( File root : File.listRoots() ) { // root will have form A:\ in windows or / in Linux if ( root.getPath() .equalsIgnoreCase( baseDirName + File.separatorChar ) ) { JOptionPane.showMessageDialog( this, "Base directory can't be a root directory. It must be a dedicated subdirectory.", "missing subdirectory.", JOptionPane.ERROR_MESSAGE ); baseDir.requestFocus(); return false; } } if ( baseDirName.length() < 4 ) { JOptionPane.showMessageDialog( this, "You must fill in X:" + File.separatorChar + "xxx for the base directory.", "Missing base directory", JOptionPane.ERROR_MESSAGE ); baseDir.requestFocus(); return false; } try { // create dir if does not exist IO.ensureDirectoryExists( baseDirName ); } catch ( IOException e ) { JOptionPane.showMessageDialog( this, "Could not create base directory " + ConfigForReceiver.RECEIVER_BASE_DIR, "Invalid base directory", JOptionPane.ERROR_MESSAGE ); baseDir.requestFocus(); return false; } // tidy up the zip dir name String zipDirName = zipDir.getText().trim().replace( '/', File.separatorChar ); while ( zipDirName.endsWith( File.separator ) ) { zipDirName = zipDirName.substring( 0, zipDirName.length() - 1 ); } zipDir.setText( zipDirName ); for ( File root : File.listRoots() ) { // root will have form A:\ in windows or / in Linux if ( root.getPath() .equalsIgnoreCase( zipDirName + File.separatorChar ) ) { JOptionPane.showMessageDialog( this, "Zip staging directory can't be a root directory. It must be a dedicated subdirectory.", "missing subdirectory.", JOptionPane.ERROR_MESSAGE ); zipDir.requestFocus(); return false; } } if ( zipDirName.length() < 4 ) { JOptionPane.showMessageDialog( this, "You must fill in X:" + File.separatorChar + "xxx for the zip staging directory.", "Missing zip staging directory", JOptionPane.ERROR_MESSAGE ); zipDir.requestFocus(); return false; } if ( zipDirName.equalsIgnoreCase( baseDirName ) ) { JOptionPane.showMessageDialog( this, "Base directory and zip staging directory can't be the same.", "duplicate directories.", JOptionPane.ERROR_MESSAGE ); zipDir.requestFocus(); return false; } try { // create dir if does not exist IO.ensureDirectoryExists( zipDirName ); } catch ( IOException e ) { JOptionPane.showMessageDialog( this, "Could not create zip staging directory " + ConfigForReceiver .RECEIVER_ZIP_STAGING_DIR, "Invalid zip staging directory", JOptionPane.ERROR_MESSAGE ); zipDir.requestFocus(); return false; } if ( viaCode.isLANZipURLConfigurable() ) { final String tidyLanZipUrl = lanZipUrl.getText().trim().toLowerCase(); if ( tidyLanZipUrl.length() < 10 || ( !tidyLanZipUrl.startsWith( "file://" ) ) ) { JOptionPane.showMessageDialog( this, "You must fill in lan URL starting with file://.", "Missing URL", JOptionPane.ERROR_MESSAGE ); lanZipUrl.requestFocus(); return false; } } return true; } /** * Prompt user to key in a directory * * @param oldDir Current value for that directory. null or "" if none. * * @return Directory the user selected, or null if he refused to select one. */ private String askUserForADir( String oldDir ) { try { if ( oldDir != null && oldDir.length() != 0 ) { fc.setSelectedFile( new File( oldDir ) ); } if ( fc.showOpenDialog( this ) == JFileChooser.APPROVE_OPTION ) { // don't use EIO.getCanOrAbsPath return fc.getSelectedFile().getCanonicalPath(); } else { return null; } } catch ( IOException e ) { return null; } } /** * build a menu with Look & Feel and About across the top */ private void buildMenu() { // turn on anti-alias System.setProperty( "swing.aatext", "true" ); final JMenuBar menubar = new JMenuBar(); setJMenuBar( menubar ); final JMenu lafMenu = Laf.buildLookAndFeelMenu(); if ( lafMenu != null ) { menubar.add( lafMenu ); } final JMenu menuHelp = new JMenu( "Help" ); menubar.add( menuHelp ); final JMenuItem aboutItem = new JMenuItem( "About" ); menuHelp.add( aboutItem ); aboutItem.addActionListener( new ActionListener() { public void actionPerformed( ActionEvent e ) { // open about frame new CMPAboutJBox( TITLE_STRING, VERSION_STRING, "Download or update compressed files from a website.", "", "freeware", RELEASE_DATE, FIRST_COPYRIGHT_YEAR, "Roedy Green", "REPLICATOR", "1.8" ); } } ); } /** * Collect values of fields from the GUI into the Receiver config variables. */ private void gather() { // PROJECT_NAME, UNIQUE_PROJECT_NAME, WEBSITE_ZIP_URL, AUTHENTICATION // cannot change // we first get JNLP, then overide with prefs, then override with GUI USERID = userId.getText().trim(); PASSWORD = new String( password.getPassword() ) .trim(); RECEIVER_BASE_DIR = baseDir.getText().trim(); RECEIVER_ZIP_STAGING_DIR = zipDir.getText().trim(); LAN_ZIP_URL = lanZipUrl.getText().trim(); KEEP_ZIPS = keepZips.isSelected(); UNPACK_ZIPS = unpackZips.isSelected(); RECEIVER_ZIP_URL = viaCode.getReceiverZipURL(); } /** * initialise the frame. This is a Java Web Start app not an applet */ void init() { Container contentPane = this.getContentPane(); if ( !VersionCheck.isJavaVersionOK( 1, 7, 0, contentPane ) ) { // effectively abort return; } project = new JLabel( PROJECT_NAME ); project.setFont( FONT_FOR_TITLE ); project.setForeground( Color.RED ); title = new JLabel( EXTENDED_TITLE ); title.setFont( FONT_FOR_TITLE ); title.setForeground( FOREGROUND_FOR_TITLE ); via = new JLabel( viaCode.getDescription() ); via.setFont( FONT_FOR_TITLE ); via.setForeground( FOREGROUND_FOR_LABEL ); userIdLabel = new JLabel( "User Id" ); userIdLabel.setForeground( FOREGROUND_FOR_LABEL ); userIdLabel.setFont( FONT_FOR_LABELS ); userId = new JTextField( USERID ); userId.setFont( FONT_FOR_EDITABLE_FIELDS ); userId.setEditable( true ); userIdHelp = new JEditorPane(); userIdHelp.setContentType( "text/html" ); userIdHelp.setEditable( false ); userIdHelp.setText( "Your login id to access files on the website." ); passwordLabel = new JLabel( "password" ); passwordLabel.setForeground( FOREGROUND_FOR_LABEL ); passwordLabel.setFont( FONT_FOR_LABELS ); password = new JPasswordField( PASSWORD ); password.setFont( FONT_FOR_EDITABLE_FIELDS ); password.setEditable( true ); passwordHelp = new JEditorPane(); passwordHelp.setContentType( "text/html" ); passwordHelp.setEditable( false ); passwordHelp.setText( "Your password to access files on the website. " + "If you have trouble typing blind, try pasting it in." ); baseDirLabel = new JLabel( "Base directory" ); baseDirLabel.setForeground( FOREGROUND_FOR_LABEL ); baseDirLabel.setFont( FONT_FOR_LABELS ); baseDir = new JTextField( RECEIVER_BASE_DIR ); baseDir.setFont( FONT_FOR_EDITABLE_FIELDS ); baseDir.setToolTipText( "Do not edit directly. Click Change below." ); baseDir.setEditable( false );// use Change button instead baseDirHelp = new JEditorPane(); baseDirHelp.setContentType( "text/html" ); baseDirHelp.setEditable( false ); baseDirHelp.setText( "e.g. " + SUGGESTED_RECEIVER_BASE_DIR + "
Dedicated Base Directory where the incoming files " + "will be placed after they are unpacked. " + "Must not be used by any other program, including other " + "Replicator projects. Do not " + "specify a temporary directory." ); baseDirChange = new JEButton( "Change" ); baseDirChange.setToolTipText( "Change the base directory" ); fc = new JFileChooser(); fc.setFileSelectionMode( JFileChooser.DIRECTORIES_ONLY ); baseDirChange.addActionListener( new ActionListener() { /** * Invoked when an action occurs. */ public void actionPerformed( ActionEvent e ) { String newDir = askUserForADir( baseDir.getText() ); if ( newDir != null ) { baseDir.setText( newDir ); } } } ); zipDirLabel = new JLabel( "Zip Staging Directory" ); zipDirLabel.setForeground( FOREGROUND_FOR_LABEL ); zipDirLabel.setFont( FONT_FOR_LABELS ); zipDir = new JTextField( RECEIVER_ZIP_STAGING_DIR ); zipDir.setFont( FONT_FOR_EDITABLE_FIELDS ); zipDir.setToolTipText( "Do not edit directly. Click Change below." ); zipDir.setEditable( false );// use change button zipDirHelp = new JEditorPane(); zipDirHelp.setContentType( "text/html" ); zipDirHelp.setEditable( false ); zipDirHelp.setText( "e.g. " + SUGGESTED_RECEIVER_ZIP_STAGING_DIR + "
" + "Dedicated permanent Staging Directory " + "for incoming archive zip files. " + "Must not be used by any other program, including other " + "Replicator projects. " + "Do not specify a temporary directory." ); zipDirChange = new JEButton( "Change" ); zipDirChange.setToolTipText( "Change the zip staging directory" ); zipDirChange.addActionListener( new ActionListener() { /** * Invoked when user clicks change zip dir. */ public void actionPerformed( ActionEvent e ) { String newDir = askUserForADir( zipDir.getText() ); if ( newDir != null ) { zipDir.setText( newDir ); } } } ); lanZipUrlLabel = new JLabel( "LAN URL" ); lanZipUrlLabel.setForeground( FOREGROUND_FOR_LABEL ); lanZipUrlLabel.setFont( FONT_FOR_LABELS ); lanZipUrl = new JTextField( LAN_ZIP_URL ); lanZipUrl.setFont( FONT_FOR_EDITABLE_FIELDS ); lanZipUrl.setEditable( true ); lanZipUrlHelp = new JEditorPane(); lanZipUrlHelp.setContentType( "text/html" ); lanZipUrlHelp.setEditable( false ); lanZipUrlHelp.setText( "" + SUGGESTED_LAN_ZIP_URL + "
From where on the LAN (or locally on disk) to fetch the " + "archive zip files that were previously downloaded via the replicator relay. " + "The URL should not have a trailing /. " + "For a local drive or remote drive with local drive letter, " + "you might code: " + "file:///X:/rep-lanstaging. " + "For shared drive you might code: " + "file://bigserver/Cdisk/rep-lanstaging." ); keepZips = new JCheckBox( "Keep Zips?", KEEP_ZIPS ); keepZips.setForeground( FOREGROUND_FOR_LABEL ); keepZipsHelp = new JEditorPane(); keepZipsHelp.setContentType( "text/html" ); keepZipsHelp.setEditable( false ); keepZipsHelp.setText( "Do you want to keep zip files after they have been unpacked so they " + "can be passed along to other clients without Internet access?" ); unpackZips = new JCheckBox( "Unpack Zips?", UNPACK_ZIPS ); unpackZips.setForeground( FOREGROUND_FOR_LABEL ); unpackZipsHelp = new JEditorPane(); unpackZipsHelp.setContentType( "text/html" ); unpackZipsHelp.setEditable( false ); unpackZipsHelp.setText( "Do you want to unpack the archive zip files when arrive? " + "You might leave this unchecked if you were not interested in the files " + "yourself. You are just collecting the archive zips to pass them " + "along to others without Internet access." ); go = new JEButton( "Go" ); go.setToolTipText( "Start the download/update." ); go.setEnabled( true ); go.addActionListener( new ActionListener() { /** * Invoked when User clicks Go */ public void actionPerformed( ActionEvent event ) { if ( areFieldsValid() ) { gather(); // preparing to download zips theFrame.go.setEnabled( false ); theFrame.cancel.setEnabled( true ); theFrame.verifyBox.setEnabled( true ); // now have good Staging and base dir. PREFS_EXIST = true; PersistConfigForReceiver.saveConfigInRegistry(); // run FetchZips/unpacker in parallel so that doing method will // repaint. // start background thread. backgroundTask = new FetchZips(); backgroundTask.addPropertyChangeListener( Replicator.this ); backgroundTask.execute(); } } } ); cancel = new JEButton( "Cancel" ); cancel.setToolTipText( "Cancel/abort the download/update" ); cancel.setEnabled( true ); cancel.addActionListener( new ActionListener() { /** * Invoked when an user hits Cancel. */ public void actionPerformed( ActionEvent e ) { if ( backgroundTask == null ) { // Quietly quit fin(); } else { backgroundTask.cancel( false/* do not interrupt */ ); } } } ); // start unchecked verifyBox = new JCheckBox( "Verify" ); verifyBox.setForeground( FOREGROUND_FOR_LABEL ); verifyBox.addItemListener( new ItemListener() { public void itemStateChanged( ItemEvent e ) { Replicator.verify = Replicator.this.verifyBox.isSelected(); } } ); /* must be 0..100 to work with SwingWorker.setProgress */ progressBar = new JProgressBar( 0, 100 ); progressBar.setValue( 0 ); // no wording on progress bar progressBar.setStringPainted( false ); doing = new JTextField(); doing.setFont( FONT_FOR_DOING ); doing.setEditable( false ); // add components layoutComponents(); validate(); } // end addNotify /** * layout the components */ private void layoutComponents() { Container contentPane = this.getContentPane(); contentPane.setLayout( new GridBagLayout() ); // x y w h wtx wty anchor fill T L B R padx pady int y = 0; contentPane.add( project, new GridBagConstraints( 0, y, 1, 1, 0.0, 0.0, GridBagConstraints.EAST, GridBagConstraints.NONE, TOP_LEFT_INSET, 0, 0 ) ); contentPane.add( title, new GridBagConstraints( 1, y, 2, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, TOP_RIGHT_INSET, 0, 0 ) ); y++; contentPane.add( via, new GridBagConstraints( 1, y, 3, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, BOTTOM_RIGHT_INSET, 0, 0 ) ); y++; if ( viaCode.isAuthenticationConfigurable() ) { contentPane.add( userIdLabel, new GridBagConstraints( 0, y, 1, 1, 1.0, 0.0, GridBagConstraints.NORTHEAST, GridBagConstraints.NONE, TOP_LEFT_INSET, 0, 0 ) ); contentPane.add( userId, new GridBagConstraints( 0, y + 1, 1, 1, 2.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, BOTTOM_LEFT_INSET, 0, 0 ) ); contentPane.add( userIdHelp, new GridBagConstraints( 1, y, 3, 2, 10.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.BOTH, RIGHT_INSET, 40, 0 ) ); y += 2; contentPane.add( passwordLabel, new GridBagConstraints( 0, y, 1, 1, 1.0, 0.0, GridBagConstraints.NORTHEAST, GridBagConstraints.NONE, TOP_LEFT_INSET, 0, 0 ) ); contentPane.add( password, new GridBagConstraints( 0, y + 1, 1, 1, 2.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, BOTTOM_LEFT_INSET, 0, 0 ) ); contentPane.add( passwordHelp, new GridBagConstraints( 1, y, 3, 2, 10.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.BOTH, RIGHT_INSET, 40, 0 ) ); y += 2; } contentPane.add( baseDirLabel, new GridBagConstraints( 0, y, 1, 1, 1.0, 0.0, GridBagConstraints.NORTHEAST, GridBagConstraints.NONE, TOP_LEFT_INSET, 0, 0 ) ); contentPane.add( baseDir, new GridBagConstraints( 0, y + 1, 1, 1, 2.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, MIDLEFT_INSET, 0, 0 ) ); contentPane.add( baseDirChange, new GridBagConstraints( 0, y + 2, 1, 1, 0.0, 0.0, GridBagConstraints.NORTHEAST, GridBagConstraints.NONE, BOTTOM_LEFT_INSET, 0, 0 ) ); contentPane.add( baseDirHelp, new GridBagConstraints( 1, y, 3, 3, 10.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.HORIZONTAL, RIGHT_INSET, 40, 0 ) ); y += 3; contentPane.add( zipDirLabel, new GridBagConstraints( 0, y, 1, 1, 1.0, 0.0, GridBagConstraints.NORTHEAST, GridBagConstraints.NONE, TOP_LEFT_INSET, 0, 0 ) ); contentPane.add( zipDir, new GridBagConstraints( 0, y + 1, 1, 1, 2.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, MIDLEFT_INSET, 0, 0 ) ); contentPane.add( zipDirChange, new GridBagConstraints( 0, y + 2, 1, 1, 0.0, 0.0, GridBagConstraints.NORTHEAST, GridBagConstraints.NONE, BOTTOM_LEFT_INSET, 0, 0 ) ); contentPane.add( zipDirHelp, new GridBagConstraints( 1, y, 3, 3, 10.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.HORIZONTAL, RIGHT_INSET, 40, 0 ) ); y += 3; if ( viaCode.isLANZipURLConfigurable() ) { contentPane.add( lanZipUrlLabel, new GridBagConstraints( 0, y, 1, 1, 1.0, 0.0, GridBagConstraints.NORTHEAST, GridBagConstraints.NONE, TOP_LEFT_INSET, 0, 0 ) ); contentPane.add( lanZipUrl, new GridBagConstraints( 0, y + 1, 1, 1, 2.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, BOTTOM_LEFT_INSET, 0, 0 ) ); contentPane.add( lanZipUrlHelp, new GridBagConstraints( 1, y, 3, 2, 10.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.HORIZONTAL, RIGHT_INSET, 40, 0 ) ); y += 2; } if ( viaCode.isKeepZipsConfigurable() ) { contentPane.add( keepZips, new GridBagConstraints( 0, y, 1, 1, 0.0, 0.0, GridBagConstraints.NORTHEAST, GridBagConstraints.NONE, TOP_LEFT_INSET, 0, 0 ) ); contentPane.add( keepZipsHelp, new GridBagConstraints( 1, y, 3, 1, 0.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.BOTH, RIGHT_INSET, 40, 0 ) ); y++; } if ( viaCode.isUnpackZipsConfigurable() ) { contentPane.add( unpackZips, new GridBagConstraints( 0, y, 1, 1, 0.0, 0.0, GridBagConstraints.NORTHEAST, GridBagConstraints.NONE, TOP_LEFT_INSET, 0, 0 ) ); contentPane.add( unpackZipsHelp, new GridBagConstraints( 1, y, 3, 1, 0.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.BOTH, RIGHT_INSET, 40, 0 ) ); y++; } contentPane.add( go, new GridBagConstraints( 0, y, 1, 1, 0.0, 0.0, GridBagConstraints.NORTHEAST, GridBagConstraints.NONE, LEFT_INSET, 0, 0 ) ); contentPane.add( verifyBox, new GridBagConstraints( 1, y, 1, 1, 0.0, 0.0, GridBagConstraints.EAST, GridBagConstraints.NONE, RIGHT_INSET, 0, 0 ) ); contentPane.add( cancel, new GridBagConstraints( 2, y, 1, 1, 0.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, RIGHT_INSET, 0, 0 ) ); contentPane.add( progressBar, new GridBagConstraints( 3, y, 1, 1, 0.0, 0.0, GridBagConstraints.EAST, GridBagConstraints.HORIZONTAL, RIGHT_INSET, 0, 0 ) ); y++; contentPane.add( doing, new GridBagConstraints( 0, y, 4, 1, 0.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.BOTH, BOTTOM_SPAN_INSET, 0, 0 ) ); } // Layout all packed in a JPanel inside a JScrollPane // // 0 ----------1-----2------3----- // project title ----------0 // verifyBox --via ----- 1 // *userid help -- 2 // userid --- 3 // *passwd help---- 4 // password ------- 5 // *base help ----- 6 // basedir --------- 7 // change -------- 8 // *zip help ------9 // zipdir ------- 10 // change --------- 11 // *url help ------- 12 // url ------------ 13 // ---------------- 14 // keep help -------15 // unpack help ---- 16 // go cancel ------ 17 // doing --------- 18 /** * Main class for client. This a Java Web Start, not a JApplet. * * @param args not used. */ public static void main( String[] args ) { // out.println( "debug version 6" ); // try // { // Thread.sleep( 2 * 1000 ); // } // catch ( InterruptedException e ) // { // } if ( !VersionCheck.isJavaVersionOK( 1, 7, 0 ) ) { err.println( "The replicator requires Java 1.8.0+" ); return; } fetchProperties(); // size is variable and is computed based on viaCode. Effectively theFrame.setSize SwingUtilities.invokeLater( new Runnable() { /** * do all swing work on the swing thread. */ public void run() { theFrame = new Replicator(); theFrame.buildMenu(); // also initial L&F theFrame.init(); theFrame.setResizable( true ); theFrame.setUndecorated( false ); viaCode.setFrameSize( theFrame ); theFrame.setDefaultCloseOperation( WindowConstants.EXIT_ON_CLOSE ); theFrame.setVisible( true ); // when user clicks Go, it will trigger FetchZips.doInBackground to download the zips and unpack them } } ); } /** * Invoked when task's progress property changes. */ public void propertyChange( PropertyChangeEvent evt ) { // works even if propertyName is null. if ( "progress".equals( evt.getPropertyName() ) ) { progressBar.setValue( ( Integer ) evt.getNewValue() ); } } } // end Replicator