/*
* [ReplicatorSender.java]
*
* Summary: Prepares for zips for sending, and arranges to upload them to the website.
*
* 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
* use import static on ConfigForSender to make code terser
* qualifyInSenderPersist to encapsulate the File.separator logic.
* 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.a
*/
package com.mindprod.replicatorsender;
import com.mindprod.common18.Build;
import com.mindprod.fastcat.FastCat;
import com.mindprod.replicatorcommon.Config;
import com.mindprod.replicatorcommon.IO;
import com.mindprod.replicatorcommon.ReplicatorCommon;
import com.mindprod.sorted.SortedArrayList;
import com.mindprod.untouch.Untouch;
import java.awt.Toolkit;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import static com.mindprod.replicatorsender.ConfigForSender.*;
import static java.lang.System.*;
/**
* Prepares for zips for sending, and arranges to upload them to the website.
*
* Creates record of what we have previously distributed and deleted in E:\com\mindprod\replicatorsender\sender.ser.
* We don't give this to the clients.
*
* It works by maintaining the newest file in each zip in replicator\zipmanifest.ser which we do distribute to the
* clients.
*
* When we distribute, we create an in-ram list of all files we plan to distribute, remove those have already
* distributed and are already up to date. We prepare a parallel list of files to be deleted. Then we break the entries
* up into clumps assigning them to zip file we are about to create.
*
* To retire old zips, we simply mark its files as undistributed. Then they get added to newly created zips. They are
* added from the disk original, not from the old zip.
*
* @author Roedy Green, Canadian Mind Products
* @version 11.6 2015-03-10 sign the jar.
* @since 2003-09-15
*/
public final class ReplicatorSender
{
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";
/**
* when version released.
*/
@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 S e n d e r";
/**
* embedded version string.
*/
@SuppressWarnings( { "UnusedDeclaration" } )
private static final String VERSION_STRING = ReplicatorCommon.VERSION_STRING;
/**
* all zip descriptors, including unused, emaciated, but not deleted zips, kept in order by zip number. We never
* sort this an any other other.
*/
static ArrayList allZips;
/**
* Combined list of filesToDistribute and filesToDelete. Not persisted to sender.ser. Persisted in filemanifest.ser
* sent to receiver for verify. Recreated from filesToDelete and filesToDistribute.
*/
static SortedArrayList combinedFilesAndDeletions;
/**
* Files with timestamps prior to this are too late to fit in their natural zips. Timestamp just after last file we
* distributed previously.
*/
static long cutOffTimestamp;
/**
* Estimates of how many files fall in the category of trees wanted, unwanted, dirs wanted, unwanted and files
* wanted, unwanted. Enables us to make more accurate estimates on the next run. 0=wanted tree 1=unwanted tree
* 2=wanted dirs 3=unwanted dirs 4=wanted files 5=unwanted files
*/
static int[] estimates = new int[ 6 ];
/**
* file descriptors of all files marked deleted in the current active distribution, Including ones in zips
* previously distributed. kept in order by filename.
*/
static SortedArrayList filesToDelete;
/**
* file descriptors of all files in the current active distribution, Including ones in zips previously distributed.
* kept in order by clumping. All these SortedArrayLists reference some subset of the same objects, so the RAM needs
* are not as bad as they look.
*/
static SortedArrayList filesToDistribute;
/**
* timestamp of last file to be repacked, prior to new files and newly changed files,
* Long.MIN_VALUE if no repackings.
* repacked deletions count as files.
*/
static long newestRepackagedTimestamp;
/**
* Next unused zip number. starts at 1. The first file is z1.zip
*/
static int nextZipNumber;
/**
* file descriptors of all the files were marked deleted ina nay zip on the previous run. Kept in order by filename.
*/
static SortedArrayList oldFilesDeleted;
/**
* file descriptors of all the files we distributed the previous run. kept in order by clumping.
*/
static SortedArrayList oldFilesDistributed;
/*
* Even though we maintain counts for many other collections of files, there is no corresponding SortedArrayList.
* The counts in StatsForSender are generated by arithmetic on the sizes of the basic sets.
*/
static
{
final FastCat sb = new FastCat( 4 );
sb.append( "Programming error: Mismatched versions: " );
sb.append( Config.VERSION );
sb.append( " vs " );
sb.append( VERSION_STRING );
assert Config.VERSION.equals( VERSION_STRING ) : sb.toString();
}
/**
* Calculate cutoff timestamp. Files with timestamps prior to this are too late to fit in their natural zips.
* Such files
* appear if they are old files copied into the distribution, or back dated.
*
* @return the cutoff timestamp.
*/
static long calcCutOffTimestamp()
{
long cutoff = Config.CUTOFF_TIMESTAMP;
final int size = allZips.size();
if ( size != 0 )
{
final MaxiZD lastZip = allZips.get( size - 1 );
// one second since last file of previous distribution.
cutoff = lastZip.getHighestClumpingInZip() + 1000;
}
return cutoff;
}
/**
* Get estimate of number of files in a given category so we can use better ArrayList initialisation size.
*
* @param index 0=trees wanted 1=trees withheld 2=dirs wanted 3=dirs withheld 4=files wanted 5=files withheld
*
* @return estimate of how many files will be in that category. Includes a 10% growth factor.
*/
static int getEstimate( int index )
{
return estimates[ index ];
}
/**
* Set estimate of number of files in a given category so we can use better ArrayList initialisations next
* elapsedTime.
*
* @param index 0=trees wanted 1=trees withheld 2=dirs wanted 3=dirs withheld 4=files wanted 5=files
* withheld
* @param numberOfFiles Number of files that fell in that category this elapsedTime.
*/
static void setEstimate( int index, int numberOfFiles )
{
// bump estimate up for next elapsedTime by 10% of this elapsedTime's actual.
numberOfFiles = numberOfFiles * 110 / 100;
if ( numberOfFiles < 16 )
{
numberOfFiles = 16;
}
estimates[ index ] = numberOfFiles;
}
/**
* Warning on send side
*
* @param warnMessage what went wrong
*/
static void warn( String warnMessage )
{
err.println( "W A R N I N G" );
Toolkit.getDefaultToolkit().beep();
err.println( warnMessage );
}
/**
* Main method to create archive files.
*
* @param args arg[0] is fully qualified absolute or relative name of properties file e.g.
* xxxreplicator.properties.
*/
public static void main( String[] args )
{
try
{
out.println();
out.println( TITLE_STRING
+ " "
+ VERSION_STRING
+ " build "
+ Build
.BUILD_NUMBER
);
// get config info from sender's xxxreplicator.properties file
getConfigFromPropertiesFile( args[ 0 ] );
// display configuration.
out.println( dumpConfig() );
// roll call sanity check all crucial files present.
DistributeAuxAndJNLP.auxRollCall();
// make sure directories we need exist
IO.ensureDirectoryExists( SENDER_BASE_DIR );
IO.ensureDirectoryExists( SENDER_ZIP_STAGING_DIR );
if ( UNTOUCH )
{
out.println( "untouching " + ConfigForSender
.SENDER_BASE_DIR + " ..." );
new Untouch( true
/* quiet */, false
/* wantLogs */ ).untouchAllFilesInTree( new File(
SENDER_BASE_DIR ) );
}
// prepare files for distribution other than zip manifest and zips
DistributeAuxAndJNLP.distribute();
// restore previous system state
PersistSenderState.restoreStateFromSenderSer();
// gather list of files to distribute, all files existing now
Gather.process();
// find files deleted since last distribution. Do deadwood accounting.
Deletions.process();
// we don't need oldFilesDeleted any more since complete list of files
// already deleted plus those to delete are now
// safely in filesToDelete
ReplicatorSender.oldFilesDeleted = null;
// detect changes since last distribution. Compute list of files to distribute both old and new mixed.
Changes.process();
// we don't need oldFilesDistributed list any more since complete list of files
// already distributed plus those to distribute are now
// safely in filesToDistribute
ReplicatorSender.oldFilesDistributed = null;
// retire zips with excess deadwood.
Retire.process();
// clump files, repack files previously distributed in retired zips, and pack new files and newly changed
// files into zips
Clump.process();
// now is a good Time for gc when we have pruned down to the minimum.
System.gc();
// prepare zips, this is where we compose the nnnn.zip files we
// distribute
ProcessZips.processZipsPass1();
// Computer stats for display.
StatsForSender.computeZipsStats();
// display stats on how many files processed
out.println( StatsForSender.dump() );
// save state so we can pick up where we left off
PersistSenderState.saveStateToSenderSer();
// also in human-readable form to figure out problems.
PersistSenderState.saveStateToSenderLog();
// upload files have prepared so far to the website, will spawn netload
// to do it. Upload zips for pass 1
Upload.uploadZips();
// no need to save state. Upload does not modify it.
// delete emaciated files.
ProcessZips.processZipsPass2();
// prepare a manifest of just the active zips and files for the client.
WriteManifests.prepareZipManifestSer();
// save list of files for client to verify.
WriteManifests.prepareFileManifestSer();
// save modified state so we can pick up where we left off
PersistSenderState.saveStateToSenderSer();
// also in human-readable form to figure out problems.
PersistSenderState.saveStateToSenderLog();
// upload the zip and file manifests separately after zips safely uploaded.
// zipmanifest.ser will naturally upload after the z999.zip files, but
// having two passes makes sure it will.
// Also does to final delete stage for emaciated zips
Upload.uploadManifests();
// no need to resave state. Upload does not modify it.
out.println( "Replicator sender done" );
out.println();
}
catch ( IOException e )
{
e.printStackTrace( err );
err.println();
}
}
}