/* * [MiniFD.java] * * Summary: File Descriptor, describes one file. Not for directories!. * * 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: * 10.2 2009-04-03 tidy up code to check presence of necessary files to make it more WORA. * Case sensitive. * Stored internally relative to a base. This is the mini-descriptor used in the client. It is used * only to * describe the last file in a zip, not to describe every file. FD extends MiniFD for use in sender. */ package com.mindprod.replicatorcommon; import com.mindprod.common18.Misc; import com.mindprod.replicator.ConfigForReceiver; import java.io.File; import java.io.IOException; import java.io.ObjectInputStream; import java.io.Serializable; import java.util.Comparator; import java.util.regex.Pattern; /** * File Descriptor, describes one file. Not for directories!. * * @author Roedy Green, Canadian Mind Products * @version 10.2 2009-04-03 tidy up code to check presence of necessary files to make it more WORA. * Case sensitive. * Stored internally relative to a base. This is the mini-descriptor used in the client. It is used only to * describe the last file in a zip, not to describe every file. FD extends MiniFD for use in sender. * @since 2003-08-21 */ public class MiniFD implements Serializable { /** * Layout version number. */ public static final long serialVersionUID = 105L; /** * Comparator to sort by clumping timestamp and filename. */ public static final Comparator BY_CLUMPING_AND_FILENAME = new ByClumpingAndFilename(); /** * Regex to split full filename into fragments separated by /. Separator char between elements of the directory name * e.g. / or \ in a regex == is ok. We are comparing chars, not Strings. */ private static final Pattern SLASH_SPLITTER = Pattern.compile( File.separatorChar == '\\' ? "\\\\" : String.valueOf( File.separatorChar ) ); /** * Regex to split a filename into words separated by space. Do not "correct" to \\s+ */ private static final Pattern SPACE_SPLITTER = Pattern.compile( " " ); /** * each node of the directory name, interned. */ private final String[] dirNodes; /** * true if this file has been been deleted */ protected boolean isDeleted; /** * Timestamp milliseconds since 1970 of the master copy of this file. Magic value marks Deletions. */ protected long timestamp = Config.NULL_TIMESTAMP; /** * Each word of directory name interned. Words are separated by a single space. Trailing space removed. */ private String[] fileWords; /** * file extension after the dot interned */ private String extension; /** * Usually the same as the timestamp, but for files that appear out of the blue with dates in the past, is the * cutoff timestamp for the last round of distributions, at so that it will clump with the newly created files. * Otherwise we would imagine this file was already done in some past zip. Sometimes we adjust the time by a * millisecond or two just to ensure all files have unique timestamps. Otherwise we would have trouble picking up * where we left off in the middle of a group of files with identical timestamps. */ private long clumping = Config.NULL_TIMESTAMP; /** * file size in bytes, note long not int. Java Zip entry length is restricted to int. */ private long fileLength; /** * copy constructor to demote an FD to a MiniFD * * @param m MiniFD or FD to copy. */ public MiniFD( MiniFD m ) { dirNodes = m.dirNodes; fileWords = m.fileWords; extension = m.extension; fileLength = m.fileLength; clumping = m.clumping; timestamp = m.timestamp; isDeleted = m.isDeleted; } /** * constructor * * @param dir directory, relative to base, without leading or trailing \ * @param filename including extension. */ public MiniFD( String dir, String filename ) { // split into fragments separated by / if ( dir.length() == 0 ) { dirNodes = new String[ 0 ]; } else { dirNodes = SLASH_SPLITTER.split( dir ); for ( int i = 0; i < dirNodes.length; i++ ) { // We intern for faster comparing. // We can detect equal strings faster. dirNodes[ i ] = dirNodes[ i ].intern(); } // end for } // Find extension extension = ""; int whereDot = filename.lastIndexOf( '.' ); if ( whereDot > 0 ) { extension = filename.substring( whereDot + 1 ); // chop extension off filename = filename.substring( 0, whereDot ); } // We intern for faster comparing. // We can detect equal strings faster. extension = extension.intern(); if ( dir.length() != dir.trim().length() || filename.length() != filename.trim().length() ) { throw new IllegalArgumentException( new StringBuilder( 200 ).append( "File: \"" ) .append( dir ) .append( File .separatorChar ) .append( filename ) .append( "." ) .append( extension ) .append( "\" should not have leading or trailing spaces." ).toString() ); } // if filename contains two spaces in a row, will we have an empty fileword. fileWords = SPACE_SPLITTER.split( filename ); for ( int i = 0; i < fileWords.length; i++ ) { // We intern for faster comparing. // We can detect equal strings faster. fileWords[ i ] = fileWords[ i ].intern(); } } /** * Case sensitive compare of just dir, filename and extension * * @param o1 first file to compare * @param o2 second file to compare * * @return +1 if a>b, 0 if a=b, -1 if ab, 0 if a=b, -1 if a 0 ) { sb.append( '.' ); sb.append( extension ); } return sb.toString(); } // end getRelativeFilename /** * get timestamp date file was last updated. * * @return timestamp millis since 1970 */ public long getTimestamp() { return this.timestamp; } /** * Set timestamp of last update of the file. * * @param timestamp millis since 1970 */ public void setTimestamp( long timestamp ) { this.timestamp = timestamp; } /** * has this file been deleted? * * @return true if this file has been deleted. */ public boolean isDeleted() { return isDeleted; } /** * mark this file as having been deleted */ public void markAsDeleted() { isDeleted = true; } /** * Convert to String for display * * @return Filename as a String */ public String toString() { return getFilename( FilenameContext.INSIDE_ZIP ); } /** * sort by clumping. *

* Defines an alternate sort order for MiniFD. * * @version 1.0 2009-05-22 - initial release * @since 2009-05-22 */ static class ByClumpingAndFilename implements Comparator, Serializable { /** * Layout version number. */ static final long serialVersionUID = 2L; /** * sort by clumping. * Defines an alternate sort order for MiniFD. * Compare two MiniFD Objects. * Compares clumping then path. * Informally, returns (a-b), or +ve if a is more positive than b. * * @param a first MiniFD to compare * @param b second MiniFD to compare * * @return +ve if a>b, 0 if a==b, -ve if a<b */ public final int compare( MiniFD a, MiniFD b ) { // Long.compare not available except in 1.7. final int diff = Misc.signum( a.clumping - b.clumping ); if ( diff != 0 ) { return diff; } // on tie, compare by dirnodes return Config.CASE_SENSITIVE ? comparePathCaseSensitive( a, b ) : comparePathIgnoreCase( a, b ); } } }