/*
* [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