2009-01-14 17:29:30 +00:00
#region Disclaimer / License
2011-05-09 19:52:28 +00:00
// Copyright (C) 2011, Kenneth Skovhede
2009-01-14 17:29:30 +00:00
// http://www.hexad.dk, opensource@hexad.dk
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
//
#endregion
using System ;
using System.Collections.Generic ;
using System.Text ;
using System.IO ;
namespace Duplicati.Library.Main.RSync
{
/// <summary>
/// This class wraps the process of creating a diff for an entire folder
/// </summary>
public class RSyncDir : IDisposable
{
2010-12-15 19:08:16 +00:00
/// <summary>
/// The time between each progress event
/// </summary>
private static readonly TimeSpan PROGRESS_TIMESPAN = TimeSpan . FromSeconds ( 1 );
2011-01-12 19:05:33 +00:00
/// <summary>
/// The margin that a file is allowed to grow during signature generation,
/// without incurring a performance penalty
/// </summary>
private const double FILESIZE_GROW_MARGIN_MULTIPLIER = 1.01 ;
2010-05-12 08:41:54 +00:00
/// <summary>
/// The possible filetypes in an archive
/// </summary>
2009-03-29 21:59:00 +00:00
public enum PatchFileType
{
2010-05-12 08:41:54 +00:00
/// <summary>
/// Indicates that the entry represents a deleted folder
/// </summary>
2009-03-29 21:59:00 +00:00
DeletedFolder ,
2010-05-12 08:41:54 +00:00
/// <summary>
/// Indicates that the entry represents an added folder
/// </summary>
2009-03-29 21:59:00 +00:00
AddedFolder ,
2010-05-12 08:41:54 +00:00
/// <summary>
/// Indicates that the entry represents a deleted file
/// </summary>
2009-03-29 21:59:00 +00:00
DeletedFile ,
2010-05-12 08:41:54 +00:00
/// <summary>
/// Indicates that the entry represents an added file
/// </summary>
AddedFile ,
/// <summary>
/// Indicates that the entry represents an updated file
/// </summary>
UpdatedFile ,
/// <summary>
/// Indicates that the entry represents an incomplete file
/// </summary>
IncompleteFile ,
/// <summary>
/// Indicates that the entry represents a control file
/// </summary>
ControlFile ,
/// <summary>
/// Indicates that the entry is a file that is either added or updated.
/// This state is only possible in Duplicati 1.0 archives.
/// </summary>
2012-08-07 20:12:12 +00:00
AddedOrUpdatedFile ,
/// <summary>
/// Indicates that the file is a symbolic link
/// </summary>
Symlink
2009-03-29 21:59:00 +00:00
};
2010-11-17 19:45:25 +00:00
#region Helper classes
2010-05-12 08:41:54 +00:00
/// <summary>
/// An internal helper class that aids in supporting three different prefixes for signatures
/// </summary>
private class ArchiveWrapper
{
/// <summary>
/// The archive to wrap
/// </summary>
2012-08-07 17:42:09 +00:00
private CompressionWrapper m_archive ;
2010-05-12 08:41:54 +00:00
/// <summary>
/// The prefix to append to request
/// </summary>
private string m_prefix ;
2010-08-31 19:50:52 +00:00
/// <summary>
/// A value indicating if the archive dates are in UTC format
/// </summary>
private bool m_isDateInUtc ;
2010-10-23 08:58:24 +00:00
/// <summary>
/// The time the archive was created
/// </summary>
private DateTime m_createTime ;
2010-05-12 08:41:54 +00:00
/// <summary>
/// Constructs a new ArchiveWrapper with a given prefix
/// </summary>
/// <param name="arch">The archive to wrap</param>
/// <param name="prefix">The prefix to use</param>
2010-10-23 08:58:24 +00:00
/// <param name="backupTime">The time the backup was created</param>
2012-08-07 17:42:09 +00:00
public ArchiveWrapper ( CompressionWrapper arch , DateTime createTime , string prefix )
2010-05-12 08:41:54 +00:00
{
m_archive = arch ;
m_prefix = prefix ;
2010-08-31 19:50:52 +00:00
m_isDateInUtc = arch . FileExists ( UTC_TIME_MARKER );
2010-10-23 08:58:24 +00:00
m_createTime = createTime . ToUniversalTime ();
2010-05-12 08:41:54 +00:00
}
/// <summary>
/// Gets a stream representing the content of the file
/// </summary>
/// <param name="relpath">The path to the file, excluding the prefix</param>
/// <returns>An open stream for reading</returns>
internal Stream OpenRead ( string relpath )
{
return m_archive . OpenRead ( System . IO . Path . Combine ( m_prefix , relpath ));
}
/// <summary>
2010-10-23 08:58:24 +00:00
/// Gets the time the archive was created
2010-05-12 08:41:54 +00:00
/// </summary>
2010-10-23 08:58:24 +00:00
internal DateTime CreateTime { get { return m_createTime ; } }
2010-05-12 08:41:54 +00:00
}
/// <summary>
/// An internal helper class that allows a file to span multiple volumes
/// </summary>
private class PartialFileEntry : IDisposable
{
2011-02-15 19:12:32 +00:00
public readonly string fullname ;
2010-05-12 08:41:54 +00:00
public readonly string relativeName ;
2010-09-16 16:12:55 +00:00
private System . IO . Stream m_fs ;
2010-05-12 08:41:54 +00:00
private System . IO . Stream m_signatureStream ;
2010-11-20 13:57:12 +00:00
private System . IO . Stream m_originalSignatureStream ;
2010-05-12 08:41:54 +00:00
private string m_signaturePath ;
2010-08-31 19:50:52 +00:00
private DateTime m_lastWrite ;
2010-05-12 08:41:54 +00:00
2011-02-15 19:12:32 +00:00
public PartialFileEntry ( System . IO . Stream fs , string relname , string fullname , long position , System . IO . Stream signatureFile , string signaturePath , DateTime lastWrite )
2010-05-12 08:41:54 +00:00
{
2010-09-16 16:12:55 +00:00
m_fs = fs ;
2010-05-12 08:41:54 +00:00
m_fs . Position = position ;
2011-02-15 19:12:32 +00:00
this . fullname = fullname ;
2010-05-12 08:41:54 +00:00
this . relativeName = relname ;
this . m_signatureStream = signatureFile ;
this . m_signaturePath = signaturePath ;
2010-08-31 19:50:52 +00:00
this . m_lastWrite = lastWrite ;
2010-05-12 08:41:54 +00:00
}
2010-09-16 16:12:55 +00:00
public System . IO . Stream Stream { get { return m_fs ; } }
2010-08-31 19:50:52 +00:00
public DateTime LastWriteTime { get { return m_lastWrite ; } }
2010-05-12 08:41:54 +00:00
2010-11-20 13:57:12 +00:00
public System . IO . Stream OriginalSignatureStream { get { return m_originalSignatureStream ; } set { m_originalSignatureStream = value ; } }
2012-08-07 17:42:09 +00:00
public bool DumpSignature ( CompressionWrapper signatureArchive )
2010-05-12 08:41:54 +00:00
{
2010-11-20 13:57:12 +00:00
bool success = true ;
2010-05-12 08:41:54 +00:00
//Add signature AFTER content.
//If content is present, it is restoreable, if signature is missing, file will be backed up on next run
//If signature is present, but not content, the entire differential sequence will be unable to recover the file
2010-11-20 13:57:12 +00:00
if ( m_fs is SharpRSync . ChecksumGeneratingStream )
m_fs . Flush ();
2010-05-12 08:41:54 +00:00
using ( m_signatureStream )
2010-11-20 13:57:12 +00:00
{
if ( m_originalSignatureStream != null )
{
//Rewind both streams
m_originalSignatureStream . Position = 0 ;
m_signatureStream . Position = 0 ;
2010-11-21 11:08:15 +00:00
success = Utility . Utility . CompareStreams ( m_originalSignatureStream , m_signatureStream , true );
2010-11-20 13:57:12 +00:00
//Rewind signature
m_signatureStream . Position = 0 ;
}
using ( System . IO . Stream s3 = signatureArchive . CreateFile ( this . m_signaturePath , m_lastWrite ))
2010-11-21 11:08:15 +00:00
Utility . Utility . CopyStream ( m_signatureStream , s3 , true );
2010-11-20 13:57:12 +00:00
}
2010-05-12 08:41:54 +00:00
m_signatureStream = null ;
2010-11-20 13:57:12 +00:00
return success ;
2010-05-12 08:41:54 +00:00
}
/// <summary>
2010-08-31 19:50:52 +00:00
/// Gets a value representing a rough estimate of how many bytes this partial entry will use when written to the datastream.
2010-05-12 08:41:54 +00:00
/// </summary>
public long ExtraSize { get { return relativeName . Length + 100 ; } }
#region IDisposable Members
public void Dispose ()
{
if ( m_fs != null )
{
m_fs . Dispose ();
m_fs = null ;
}
if ( m_signatureStream != null )
{
m_signatureStream . Dispose ();
m_signatureStream = null ;
}
2010-11-20 13:57:12 +00:00
if ( m_originalSignatureStream != null )
{
m_originalSignatureStream . Dispose ();
m_originalSignatureStream = null ;
}
2010-05-12 08:41:54 +00:00
}
#endregion
}
/// <summary>
/// Class that represents a partial record in the INCOMPLETE_FILE or COMPLETED_FILE
/// </summary>
public class PartialEntryRecord
{
2011-10-28 05:43:15 +00:00
public readonly string Filename ;
public readonly long StartOffset ;
public readonly long Length ;
public readonly long TotalFileSize ;
public readonly string PlatformConvertedFilename ;
2010-05-12 08:41:54 +00:00
public PartialEntryRecord ( string [] items )
{
if ( items . Length != 4 )
throw new Exception ( Strings . RSyncDir . InvalidPartialRecordError );
this . Filename = items [ 0 ];
this . StartOffset = long . Parse ( items [ 1 ]);
this . Length = long . Parse ( items [ 2 ]);
this . TotalFileSize = long . Parse ( items [ 3 ]);
2011-10-28 05:43:15 +00:00
this . PlatformConvertedFilename = ConvertedFilename ( this . Filename );
2010-05-12 08:41:54 +00:00
}
public PartialEntryRecord ( string filename , long start , long size , long totalSize )
{
this . Filename = filename ;
this . StartOffset = start ;
this . Length = size ;
this . TotalFileSize = totalSize ;
2011-10-28 05:43:15 +00:00
this . PlatformConvertedFilename = ConvertedFilename ( this . Filename );
}
/// <summary>
/// This function attempts to discover what path separator
/// was used to generate the filename, and then convert the
/// filename to use the current OS separator.
///
/// This fixes an issue where the backup is made on
/// one OS and attempted restored on another.
///
/// This is really a sub-optimal workaround for
/// the fact that the filename should have been recorded
/// as a platform independent filename.
///
/// Changing this requires increasing the manifest
/// version number to ensure backwards compatibility,
/// and then changing the recording function
/// </summary>
/// <param name="filename">The filename to convert</param>
/// <returns>The filename adapted to the local OS filename convetions</returns>
private static string ConvertedFilename ( string filename )
{
//TODO: Once recorded correctly, this can just be:
//return FromplatformIndependantFilename(filename);
//We know that the filename is prefixed with either "snapshot" or "delta",
// so we just look for the first seperator (assumes there is only / and \),
int ix_linux = filename . IndexOf ( '/' );
int ix_win = filename . IndexOf ( '\\' );
char sep_char ;
if ( ix_linux >= 0 && ix_win >= 0 )
sep_char = filename [ Math . Min ( ix_linux , ix_win )];
else if ( ix_linux >= 0 )
sep_char = filename [ ix_linux ];
else if ( ix_win >= 0 )
sep_char = filename [ ix_win ];
else
//No sep char means no need to convert
return filename ;
if ( sep_char != System . IO . Path . DirectorySeparatorChar )
filename = filename . Replace ( sep_char , System . IO . Path . DirectorySeparatorChar );
return filename ;
2010-05-12 08:41:54 +00:00
}
public string [] Serialize ()
{
return new string [] {
this . Filename ,
this . StartOffset . ToString (),
this . Length . ToString (),
this . TotalFileSize . ToString ()
};
}
}
2010-11-17 19:45:25 +00:00
/// <summary>
/// Class that represents a USN record
/// </summary>
public class USNRecord
{
/// <summary>
/// The USN value list
/// </summary>
private Dictionary < string , KeyValuePair < long , long >> m_values ;
/// <summary>
/// Constructs a new USNRecord
/// </summary>
public USNRecord ()
{
2010-11-21 11:08:15 +00:00
m_values = new Dictionary < string , KeyValuePair < long , long >>( Utility . Utility . ClientFilenameStringComparer );
2010-11-17 19:45:25 +00:00
}
/// <summary>
/// Reads an existing UNSRecord from an xml file
/// </summary>
/// <param name="stream">The stream with xml data</param>
public USNRecord ( System . IO . Stream stream )
: this ( CreateDoc ( stream ))
{
}
/// <summary>
/// Reads an existing USNRecord from an xml document
/// </summary>
/// <param name="doc">The XmlDocument with USN data</param>
public USNRecord ( System . Xml . XmlDocument doc )
: this ()
{
System . Xml . XmlNode root = doc [ "usnroot" ];
foreach ( System . Xml . XmlNode n in root . SelectNodes ( "usnrecord" ))
if ( n . Attributes [ "root" ] != null && n . Attributes [ "journalid" ] != null && n . Attributes [ "usn" ] != null )
m_values . Add ( n . Attributes [ "root" ]. Value , new KeyValuePair < long , long >( long . Parse ( n . Attributes [ "journalid" ]. Value ), long . Parse ( n . Attributes [ "usn" ]. Value )));
}
/// <summary>
/// Internal helper that creates an XmlDocument from a stream
/// </summary>
/// <param name="stream">The stream data to read</param>
/// <returns>An XmlDocument</returns>
private static System . Xml . XmlDocument CreateDoc ( System . IO . Stream stream )
{
System . Xml . XmlDocument doc = new System . Xml . XmlDocument ();
doc . Load ( stream );
return doc ;
}
/// <summary>
/// Saves the USN data to an XmlDocument
/// </summary>
/// <returns>An XmlDocument with the USN data</returns>
public System . Xml . XmlDocument Save ()
{
System . Xml . XmlDocument doc = new System . Xml . XmlDocument ();
System . Xml . XmlNode root = doc . AppendChild ( doc . CreateElement ( "usnroot" ));
foreach ( KeyValuePair < string , KeyValuePair < long , long >> kv in m_values )
{
System . Xml . XmlNode n = root . AppendChild ( doc . CreateElement ( "usnrecord" ));
n . Attributes . Append ( doc . CreateAttribute ( "root" )). Value = kv . Key ;
n . Attributes . Append ( doc . CreateAttribute ( "journalid" )). Value = kv . Value . Key . ToString ();
n . Attributes . Append ( doc . CreateAttribute ( "usn" )). Value = kv . Value . Value . ToString ();
}
return doc ;
}
/// <summary>
/// Saves the USN data to a stream as xml
/// </summary>
/// <param name="stream">The stream to write the xml data to</param>
public void Save ( System . IO . Stream stream )
{
Save (). Save ( stream );
}
/// <summary>
/// Gets the USN values recorded
/// </summary>
public Dictionary < string , KeyValuePair < long , long >> Values
{
get { return m_values ; }
}
/// <summary>
/// Gets a value indicating if the root folder was found in this USN entry
/// </summary>
/// <param name="rootFolder">The root folder to look for</param>
/// <returns>True if found, false otherwise</returns>
public bool ContainsKey ( string rootFolder )
{
return m_values . ContainsKey ( rootFolder );
}
/// <summary>
/// Gets the JournalId and USN number for the root folder
/// </summary>
/// <param name="index">The root folder to look for</param>
/// <returns>The JournalId and USN number</returns>
public KeyValuePair < long , long > this [ string index ]
{
get { return m_values [ index ]; }
}
/// <summary>
/// Adds a new USN record
/// </summary>
/// <param name="rootfolder">The folder this entry represents</param>
/// <param name="journalId">The volume journalId</param>
/// <param name="usn">The volume USN</param>
public void Add ( string rootfolder , long journalId , long usn )
{
m_values . Add ( rootfolder , new KeyValuePair < long , long >( journalId , usn ));
}
}
#endregion
2010-05-12 08:41:54 +00:00
2010-11-20 13:57:12 +00:00
#region Filenames
2012-07-29 20:07:10 +00:00
internal const string COMBINED_SIGNATURE_ROOT = "signature" ;
internal const string CONTENT_SIGNATURE_ROOT = "content_signature" ;
internal const string DELTA_SIGNATURE_ROOT = "delta_signature" ;
2010-05-12 08:41:54 +00:00
2012-07-29 20:07:10 +00:00
internal const string CONTENT_ROOT = "snapshot" ;
internal const string DELTA_ROOT = "diff" ;
//private const string DACL_ROOT = "dacl";
internal const string CONTROL_ROOT = "controlfiles" ;
internal const string SYMLINK_ROOT = "symlinks" ;
2009-01-14 17:29:30 +00:00
2009-02-09 06:47:00 +00:00
internal static readonly string DELETED_FILES = "deleted_files.txt" ;
internal static readonly string DELETED_FOLDERS = "deleted_folders.txt" ;
2009-01-14 17:29:30 +00:00
2009-02-09 06:47:00 +00:00
internal static readonly string ADDED_FOLDERS = "added_folders.txt" ;
2010-08-31 19:50:52 +00:00
internal static readonly string ADDED_FOLDERS_TIMESTAMPS = "added_folders_timestamps.txt" ;
2010-05-12 08:41:54 +00:00
internal static readonly string INCOMPLETE_FILE = "incomplete_file.txt" ;
internal static readonly string COMPLETED_FILE = "completed_file.txt" ;
2010-08-31 19:50:52 +00:00
internal static readonly string UTC_TIME_MARKER = "utc-times" ;
internal static readonly string UPDATED_FOLDERS = "updated_folders.txt" ;
internal static readonly string UPDATED_FOLDERS_TIMESTAMPS = "updated_folders_timestamps.txt" ;
2010-10-23 08:58:24 +00:00
internal static readonly string UNMODIFIED_FILES = "unmodified-files.txt" ;
2010-11-17 19:45:25 +00:00
internal static readonly string USN_VALUES = "usn-values.xml" ;
2010-11-20 13:57:12 +00:00
#endregion
2009-02-02 22:11:28 +00:00
2009-03-11 20:56:01 +00:00
public delegate void ProgressEventDelegate ( int progress , string filename );
public event ProgressEventDelegate ProgressEvent ;
2010-11-17 19:45:25 +00:00
#region Private instance variables
2009-01-14 17:29:30 +00:00
/// <summary>
2010-05-12 08:41:54 +00:00
/// This is the folders being backed up
2009-01-14 17:29:30 +00:00
/// </summary>
2010-05-12 08:41:54 +00:00
public string [] m_sourcefolder ;
2009-01-14 17:29:30 +00:00
/// <summary>
/// This is a list of existing file signatures.
/// Key is path to the file.
/// value is path to the signature file.
/// </summary>
2010-05-12 08:41:54 +00:00
private Dictionary < string , ArchiveWrapper > m_oldSignatures ;
2009-01-14 17:29:30 +00:00
/// <summary>
/// This is a list of existing folders.
/// </summary>
2010-08-31 19:50:52 +00:00
private Dictionary < string , DateTime > m_oldFolders ;
2012-07-29 20:07:10 +00:00
/// <summary>
/// This is a list of old symlinks
/// </summary>
private Dictionary < string , string > m_oldSymlinks ;
2009-01-14 17:29:30 +00:00
/// <summary>
/// This is the list of added files
/// </summary>
private Dictionary < string , string > m_newfiles ;
/// <summary>
/// This is the list of modified files
/// </summary>
private Dictionary < string , string > m_modifiedFiles ;
/// <summary>
/// This is the list of deleted files
/// </summary>
private List < string > m_deletedfiles ;
/// <summary>
2010-10-23 08:58:24 +00:00
/// This is the list of examined files that were unchanged
/// </summary>
private List < string > m_checkedUnchangedFiles ;
/// <summary>
2009-01-14 17:29:30 +00:00
/// This is the list of added folders
/// </summary>
2010-08-31 19:50:52 +00:00
private List < KeyValuePair < string , DateTime >> m_newfolders ;
/// <summary>
/// This is the list of updated folders
/// </summary>
private List < KeyValuePair < string , DateTime >> m_updatedfolders ;
2009-01-14 17:29:30 +00:00
/// <summary>
/// This is the list of deleted folders
/// </summary>
private List < string > m_deletedfolders ;
2010-10-23 08:58:24 +00:00
/// <summary>
/// This is a dictionary with the last time a file was examined (and not changed) with the current signature
/// </summary>
private Dictionary < string , DateTime > m_lastVerificationTime ;
2009-01-17 15:27:51 +00:00
/// <summary>
/// The total number of files found
/// </summary>
private long m_totalfiles ;
/// <summary>
/// The number of files examined
/// </summary>
private long m_examinedfiles ;
/// <summary>
2010-11-04 21:37:32 +00:00
/// The number of files opened for matching
/// </summary>
private long m_filesopened ;
/// <summary>
2009-01-17 15:27:51 +00:00
/// The combined size of the examined files
/// </summary>
private long m_examinedfilesize ;
/// <summary>
/// The number of files that are found to be modified
/// </summary>
private long m_diffedfiles ;
/// <summary>
/// The combined size of all the modified files
/// </summary>
private long m_diffedfilessize ;
/// <summary>
/// The combines size of all delta files generated
/// </summary>
private long m_diffsize ;
/// <summary>
/// The number of files added
/// </summary>
private long m_addedfiles ;
/// <summary>
/// The combined size of all added files
/// </summary>
private long m_addedfilessize ;
2012-05-14 18:48:21 +00:00
/// <summary>
/// Metadata collector, assigned to dummy instance to avoid null checks
/// </summary>
private Backupmetadata m_metadata = new Backupmetadata ();
2009-01-17 15:27:51 +00:00
2009-02-12 21:52:58 +00:00
/// <summary>
/// The filter applied to restore or backup
/// </summary>
2010-11-21 11:08:15 +00:00
private Utility . FilenameFilter m_filter ;
2009-02-12 21:52:58 +00:00
2009-01-20 17:17:46 +00:00
/// <summary>
/// Statistics reporting
/// </summary>
private CommunicationStatistics m_stat ;
/// <summary>
/// Flag indicating if the final values are written to the signature file
/// </summary>
private bool m_finalized = false ;
2009-02-14 17:30:52 +00:00
/// <summary>
/// A flag indicating if this entry is the first multipass
/// </summary>
private bool m_isfirstmultipass = false ;
2009-02-26 18:33:52 +00:00
/// <summary>
/// A variable that controls if the filetime check is disabled
/// </summary>
private bool m_disableFiletimeCheck = false ;
2009-03-26 19:39:50 +00:00
/// <summary>
/// A variable that controls the maximum size of a file
/// </summary>
private long m_maxFileSize = long . MaxValue ;
2009-01-14 17:29:30 +00:00
/// <summary>
/// This is a list of unprocessed files, used in multipass runs
/// </summary>
2009-03-30 20:17:54 +00:00
private PathCollector m_unproccesed ;
2009-01-14 17:29:30 +00:00
2009-03-26 19:39:50 +00:00
/// <summary>
/// A list of patch files for removal
/// </summary>
2012-08-07 17:42:09 +00:00
private List < CompressionWrapper > m_patches ;
2009-03-26 19:39:50 +00:00
2010-05-12 08:41:54 +00:00
/// <summary>
/// A leftover file that is partially written, used when creating backups
/// </summary>
private PartialFileEntry m_lastPartialFile = null ;
/// <summary>
/// A list of partial delta files, used when restoring
/// </summary>
2010-11-21 11:08:15 +00:00
private Dictionary < string , Utility . TempFile > m_partialDeltas ;
2010-05-12 08:41:54 +00:00
2010-08-31 19:50:52 +00:00
/// <summary>
/// A list of timestamps to be assigned to the folders during restore finalization
/// </summary>
private Dictionary < string , DateTime > m_folderTimestamps ;
2010-05-12 08:41:54 +00:00
/// <summary>
/// A list of folders that should be deleted after the current restore operation has completed
/// </summary>
private List < string > m_folders_to_delete ;
2010-08-12 18:32:15 +00:00
/// <summary>
/// The snapshot control that guards this backup
/// </summary>
private Snapshots . ISnapshotService m_snapshot ;
2010-11-20 13:57:12 +00:00
/// <summary>
/// The open file strategy to use if not using VSS
/// </summary>
private Options . OpenFileStrategy m_openfilepolicy ;
2010-11-17 19:45:25 +00:00
/// <summary>
/// The USN values recorded in this run
/// </summary>
private USNRecord m_currentUSN = null ;
/// <summary>
/// The set of USN values from the last complete run
/// </summary>
private USNRecord m_lastUSN = null ;
2010-12-15 19:08:16 +00:00
/// <summary>
/// A variable that indicates if the file list is sorted, if this variable is false,
/// files are picked at random from the file list
/// </summary>
private bool m_sortedfilelist ;
2010-11-17 19:45:25 +00:00
#endregion
2010-05-12 08:41:54 +00:00
/// <summary>
/// Initializes a RSyncDir instance, binds it to the source folder, and reads in the supplied patches
/// </summary>
/// <param name="sourcefolder">The folders to create a backup from</param>
/// <param name="stat">The status report object</param>
/// <param name="filter">An optional filter that controls what files to include</param>
2010-10-23 08:58:24 +00:00
/// <param name="patches">A list of signature archives to read, MUST be sorted in the creation order, oldest first</param>
2012-08-07 17:42:09 +00:00
public RSyncDir ( string [] sourcefolder , CommunicationStatistics stat , Backupmetadata metadata , Utility . FilenameFilter filter , List < KeyValuePair < ManifestEntry , CompressionWrapper >> patches )
2012-05-14 18:48:21 +00:00
: this ( sourcefolder , stat , metadata , filter )
2009-02-10 23:29:01 +00:00
{
2010-05-12 08:41:54 +00:00
string [] prefixes = new string [] {
2010-11-21 11:08:15 +00:00
Utility . Utility . AppendDirSeparator ( COMBINED_SIGNATURE_ROOT ),
Utility . Utility . AppendDirSeparator ( CONTENT_SIGNATURE_ROOT ),
Utility . Utility . AppendDirSeparator ( DELTA_SIGNATURE_ROOT )
2010-05-12 08:41:54 +00:00
};
2009-02-10 23:29:01 +00:00
2012-08-07 17:42:09 +00:00
m_patches = new List < CompressionWrapper >();
foreach ( KeyValuePair < ManifestEntry , CompressionWrapper > patch in patches )
2010-10-23 08:58:24 +00:00
m_patches . Add ( patch . Value );
2009-03-26 19:39:50 +00:00
2012-08-07 17:42:09 +00:00
foreach ( KeyValuePair < ManifestEntry , CompressionWrapper > patch in patches )
2009-02-10 23:29:01 +00:00
{
2012-08-07 17:42:09 +00:00
CompressionWrapper z = patch . Value ;
2010-10-23 08:58:24 +00:00
2009-02-10 23:29:01 +00:00
if ( z . FileExists ( DELETED_FILES ))
2012-08-07 17:42:09 +00:00
foreach ( string s in z . ReadPathLines ( DELETED_FILES ))
2010-10-23 08:58:24 +00:00
{
m_oldSignatures . Remove ( s );
m_lastVerificationTime . Remove ( s );
2012-07-29 20:07:10 +00:00
m_oldSymlinks . Remove ( s );
2010-10-23 08:58:24 +00:00
}
2009-02-10 23:29:01 +00:00
2010-10-23 08:58:24 +00:00
foreach ( string prefix in prefixes )
{
ArchiveWrapper aw = new ArchiveWrapper ( z , patch . Key . Time . ToUniversalTime (), prefix );
2012-08-07 17:42:09 +00:00
foreach ( string f in z . ListFiles ( prefix ))
2010-10-23 08:58:24 +00:00
{
string name = f . Substring ( prefix . Length );
m_oldSignatures [ name ] = aw ;
m_lastVerificationTime . Remove ( name );
}
}
2012-07-29 20:07:10 +00:00
string symlinkprefix = Utility . Utility . AppendDirSeparator ( SYMLINK_ROOT );
2012-08-07 17:42:09 +00:00
foreach ( string s in z . ListFiles ( symlinkprefix ))
m_oldSymlinks [ s . Substring ( symlinkprefix . Length )] = z . ReadPathString ( s );
2012-07-29 20:07:10 +00:00
2010-10-23 08:58:24 +00:00
if ( z . FileExists ( UNMODIFIED_FILES ))
2012-08-07 17:42:09 +00:00
foreach ( string s in z . ReadPathLines ( UNMODIFIED_FILES ))
2010-10-23 08:58:24 +00:00
m_lastVerificationTime [ s ] = patch . Key . Time . ToUniversalTime ();
2009-02-10 23:29:01 +00:00
if ( z . FileExists ( DELETED_FOLDERS ))
2012-08-07 17:42:09 +00:00
foreach ( string s in z . ReadPathLines ( DELETED_FOLDERS ))
2010-10-23 08:58:24 +00:00
m_oldFolders . Remove ( s );
2009-02-10 23:29:01 +00:00
if ( z . FileExists ( ADDED_FOLDERS ))
2010-08-31 19:50:52 +00:00
{
DateTime t = z . GetLastWriteTime ( ADDED_FOLDERS ). ToUniversalTime ();
2012-08-07 17:42:09 +00:00
string [] filenames = z . ReadPathLines ( ADDED_FOLDERS );
2010-08-31 19:50:52 +00:00
if ( z . FileExists ( ADDED_FOLDERS_TIMESTAMPS ))
{
string [] timestamps = z . ReadAllLines ( ADDED_FOLDERS_TIMESTAMPS );
long l ;
for ( int i = 0 ; i < Math . Min ( filenames . Length , timestamps . Length ); i ++)
if ( long . TryParse ( timestamps [ i ], out l ))
2012-05-14 20:49:41 +00:00
m_oldFolders [ filenames [ i ]] = Library . Utility . Utility . EPOCH . AddSeconds ( l );
2010-08-31 19:50:52 +00:00
else
m_oldFolders [ filenames [ i ]] = t ;
}
else
{
foreach ( string s in filenames )
m_oldFolders [ s ] = t ;
}
}
if ( z . FileExists ( UPDATED_FOLDERS ) && z . FileExists ( UPDATED_FOLDERS_TIMESTAMPS ))
{
2012-08-07 17:42:09 +00:00
string [] filenames = z . ReadPathLines ( UPDATED_FOLDERS );
2010-08-31 19:50:52 +00:00
string [] timestamps = z . ReadAllLines ( UPDATED_FOLDERS_TIMESTAMPS );
long l ;
for ( int i = 0 ; i < Math . Min ( filenames . Length , timestamps . Length ); i ++)
if ( long . TryParse ( timestamps [ i ], out l ))
2012-05-14 20:49:41 +00:00
m_oldFolders [ filenames [ i ]] = Utility . Utility . EPOCH . AddSeconds ( l );
2010-08-31 19:50:52 +00:00
}
2010-11-17 19:45:25 +00:00
//The latest file is the most valid
if ( z . FileExists ( USN_VALUES ))
using ( System . IO . Stream s = z . OpenRead ( USN_VALUES ))
m_lastUSN = new USNRecord ( s );
2009-02-10 23:29:01 +00:00
}
}
2010-05-12 08:41:54 +00:00
/// <summary>
/// Initializes a RSyncDir instance, and binds it to the source folder
/// </summary>
/// <param name="sourcefolder">The folders to create a backup from</param>
/// <param name="stat">The status report object</param>
/// <param name="filter">An optional filter that controls what files to include</param>
2012-05-14 18:48:21 +00:00
public RSyncDir ( string [] sourcefolder , CommunicationStatistics stat , Backupmetadata metadata , Utility . FilenameFilter filter )
2009-01-14 17:29:30 +00:00
{
2009-02-12 21:52:58 +00:00
m_filter = filter ;
2012-05-14 18:48:21 +00:00
m_metadata = metadata == null ? new Backupmetadata () : metadata ;
2011-04-10 15:01:17 +00:00
m_oldSignatures = new Dictionary < string , ArchiveWrapper >( Utility . Utility . ClientFilenameStringComparer );
m_oldFolders = new Dictionary < string , DateTime >( Utility . Utility . ClientFilenameStringComparer );
2012-07-29 20:07:10 +00:00
m_oldSymlinks = new Dictionary < string , string >( Utility . Utility . ClientFilenameStringComparer );
2011-04-10 15:01:17 +00:00
m_lastVerificationTime = new Dictionary < string , DateTime >( Utility . Utility . ClientFilenameStringComparer );
2010-10-23 08:58:24 +00:00
2010-05-12 08:41:54 +00:00
for ( int i = 0 ; i < sourcefolder . Length ; i ++)
{
if (! System . IO . Path . IsPathRooted ( sourcefolder [ i ]))
sourcefolder [ i ] = System . IO . Path . GetFullPath ( sourcefolder [ i ]);
2010-11-21 11:08:15 +00:00
sourcefolder [ i ] = Utility . Utility . AppendDirSeparator ( sourcefolder [ i ]);
2010-05-12 08:41:54 +00:00
}
2009-01-20 17:17:46 +00:00
m_stat = stat ;
2010-05-12 08:41:54 +00:00
m_sourcefolder = sourcefolder ;
2009-02-12 21:52:58 +00:00
if ( m_filter == null )
2010-11-21 11:08:15 +00:00
m_filter = new Duplicati . Library . Utility . FilenameFilter ( new List < KeyValuePair < bool , string >>());
2009-01-14 17:29:30 +00:00
}
2010-05-12 08:41:54 +00:00
/// <summary>
/// Creates a new patch content/signature pair.
/// Does not create multiple volumes, not used by Duplicati
/// </summary>
/// <param name="signatures">An archive where signatures can be put into</param>
/// <param name="content">An archive where content can be put into</param>
2012-08-07 17:42:09 +00:00
public void CreatePatch ( CompressionWrapper signatures , CompressionWrapper content )
2009-01-14 17:29:30 +00:00
{
2009-02-12 21:52:58 +00:00
InitiateMultiPassDiff ( false );
2009-02-10 23:29:01 +00:00
MakeMultiPassDiff ( signatures , content , long . MaxValue );
2010-05-12 08:41:54 +00:00
FinalizeMultiPass ( signatures , content , long . MaxValue );
2009-01-14 17:29:30 +00:00
}
2010-05-12 08:41:54 +00:00
/// <summary>
/// Initiates creating a content/signature pair.
/// </summary>
/// <param name="full">True if the set is a full backup, false if it is incremental</param>
2009-02-12 21:52:58 +00:00
public void InitiateMultiPassDiff ( bool full )
2010-08-12 18:32:15 +00:00
{
2010-11-17 19:45:25 +00:00
InitiateMultiPassDiff ( full , new Options ( new Dictionary < string , string >()));
2010-08-12 18:32:15 +00:00
}
/// <summary>
/// Initiates creating a content/signature pair.
/// </summary>
/// <param name="full">True if the set is a full backup, false if it is incremental</param>
2010-11-17 19:45:25 +00:00
/// <param name="options">Any setup options to use</param>
public void InitiateMultiPassDiff ( bool full , Options options )
2009-01-14 17:29:30 +00:00
{
if ( full )
{
2010-08-31 19:50:52 +00:00
m_oldFolders = new Dictionary < string , DateTime >();
2010-05-12 08:41:54 +00:00
m_oldSignatures = new Dictionary < string , ArchiveWrapper >();
2012-07-29 20:07:10 +00:00
m_oldSymlinks = new Dictionary < string , string >();
2009-01-14 17:29:30 +00:00
}
m_newfiles = new Dictionary < string , string >();
m_modifiedFiles = new Dictionary < string , string >();
m_deletedfiles = new List < string >();
2010-08-31 19:50:52 +00:00
m_newfolders = new List < KeyValuePair < string , DateTime >>();
m_updatedfolders = new List < KeyValuePair < string , DateTime >>();
2009-01-14 17:29:30 +00:00
m_deletedfolders = new List < string >();
2010-10-23 08:58:24 +00:00
m_checkedUnchangedFiles = new List < string >();
2010-05-12 08:41:54 +00:00
m_lastPartialFile = null ;
2010-11-20 13:57:12 +00:00
m_openfilepolicy = Options . OpenFileStrategy . Ignore ;
2009-01-14 17:29:30 +00:00
2010-08-12 18:32:15 +00:00
try
{
2010-11-17 19:45:25 +00:00
if ( options . SnapShotStrategy != Options . OptimizationStrategy . Off )
m_snapshot = Duplicati . Library . Snapshots . SnapshotUtility . CreateSnapshot ( m_sourcefolder , options . RawOptions );
2010-08-12 18:32:15 +00:00
}
catch ( Exception ex )
{
2010-11-17 19:45:25 +00:00
if ( options . SnapShotStrategy == Options . OptimizationStrategy . Required )
2010-08-12 18:32:15 +00:00
throw ;
2010-11-17 19:45:25 +00:00
else if ( options . SnapShotStrategy == Options . OptimizationStrategy . On )
2010-08-12 18:32:15 +00:00
{
if ( m_stat != null )
2010-11-17 19:45:25 +00:00
m_stat . LogWarning ( string . Format ( Strings . RSyncDir . SnapshotFailedError , ex . ToString ()), ex );
2010-08-12 18:32:15 +00:00
}
}
//Failsafe, just use a plain implementation
if ( m_snapshot == null )
2010-11-17 19:45:25 +00:00
{
2012-07-29 20:07:10 +00:00
m_snapshot = Utility . Utility . IsClientLinux ?
( Library . Snapshots . ISnapshotService ) new Duplicati . Library . Snapshots . NoSnapshotLinux ( m_sourcefolder , options . RawOptions )
:
( Library . Snapshots . ISnapshotService ) new Duplicati . Library . Snapshots . NoSnapshotWindows ( m_sourcefolder , options . RawOptions );
2010-11-20 13:57:12 +00:00
m_openfilepolicy = options . OpenFilePolicy ;
2010-11-17 19:45:25 +00:00
}
Dictionary < string , Snapshots . USNHelper > usnHelpers = null ;
List < string > unchanged = new List < string >();
2012-07-29 20:07:10 +00:00
m_unproccesed = new PathCollector ( m_snapshot , options . SymlinkPolicy , options . FileAttributeFilter , m_filter , m_stat );
2010-08-12 18:32:15 +00:00
2010-11-17 19:45:25 +00:00
try
{
if ( options . UsnStrategy != Options . OptimizationStrategy . Off )
{
2010-11-21 11:08:15 +00:00
if ( Utility . Utility . IsClientLinux && options . UsnStrategy != Options . OptimizationStrategy . Auto )
2010-11-17 19:45:25 +00:00
throw new Exception ( Strings . RSyncDir . UsnNotSupportedOnLinuxError );
2010-08-12 18:32:15 +00:00
2010-12-28 12:01:53 +00:00
/*
2010-11-17 19:45:25 +00:00
if (options.DisableUSNDiffCheck)
m_lastUSN = null;
2010-12-28 12:01:53 +00:00
*/
2010-11-17 19:45:25 +00:00
2010-11-21 11:08:15 +00:00
usnHelpers = new Dictionary < string , Duplicati . Library . Snapshots . USNHelper >( Utility . Utility . ClientFilenameStringComparer );
2010-11-17 19:45:25 +00:00
foreach ( string s in m_sourcefolder )
{
string rootFolder = System . IO . Path . GetPathRoot ( s );
if (! usnHelpers . ContainsKey ( rootFolder ))
try { usnHelpers [ rootFolder ] = new Duplicati . Library . Snapshots . USNHelper ( rootFolder ); }
catch ( Exception ex )
{
2010-11-26 16:52:53 +00:00
if ( options . UsnStrategy == Options . OptimizationStrategy . Required )
2010-11-17 19:45:25 +00:00
throw ;
2010-11-26 16:52:53 +00:00
else if ( options . UsnStrategy == Options . OptimizationStrategy . On )
2010-11-17 19:45:25 +00:00
{
if ( m_stat != null )
m_stat . LogWarning ( string . Format ( Strings . RSyncDir . UsnFailedError , ex . ToString ()), ex );
}
}
if ( usnHelpers . ContainsKey ( rootFolder ))
{
2010-12-28 12:01:53 +00:00
//This code is broken, see issue 332:
//http://code.google.com/p/duplicati/issues/detail?id=332
/* if (m_lastUSN != null && m_lastUSN.ContainsKey(rootFolder))
2010-11-17 19:45:25 +00:00
{
if (m_lastUSN[rootFolder].Key != usnHelpers[rootFolder].JournalID)
{
if (m_stat != null)
m_stat.LogWarning(string.Format(Strings.RSyncDir.UsnJournalIdChangedWarning, rootFolder, m_lastUSN[rootFolder].Key, usnHelpers[rootFolder].JournalID), null);
//Just take it all again
usnHelpers[rootFolder].EnumerateFilesAndFolders(s, m_filter, m_unproccesed.Callback);
}
else if (m_lastUSN[rootFolder].Value > usnHelpers[rootFolder].USN)
{
if (m_stat != null)
m_stat.LogWarning(string.Format(Strings.RSyncDir.UsnNumberingFaultWarning, rootFolder, m_lastUSN[rootFolder].Value, usnHelpers[rootFolder].USN), null);
//Just take it all again
usnHelpers[rootFolder].EnumerateFilesAndFolders(s, m_filter, m_unproccesed.Callback);
}
else //All good we rely on USN numbers to find a list of changed files
{
2010-12-28 12:01:53 +00:00
//Find all changed files and folders
2010-11-21 11:08:15 +00:00
Dictionary<string, string> tmp = new Dictionary<string, string>(Utility.Utility.ClientFilenameStringComparer);
2010-11-17 19:45:25 +00:00
foreach (string sx in usnHelpers[rootFolder].GetChangedFileSystemEntries(s, m_lastUSN[rootFolder].Value))
{
tmp.Add(sx, null);
2010-12-28 12:01:53 +00:00
if (!sx.EndsWith(System.IO.Path.DirectorySeparatorChar.ToString()))
m_unproccesed.Files.Add(sx);
2010-11-17 19:45:25 +00:00
}
//Remove the existing unchanged ones
foreach (string x in usnHelpers[rootFolder].GetFileSystemEntries(s))
if (!tmp.ContainsKey(x))
unchanged.Add(x);
}
}
2010-12-28 12:01:53 +00:00
else */
2010-11-17 19:45:25 +00:00
{
2012-07-29 20:07:10 +00:00
usnHelpers [ rootFolder ]. EnumerateFilesAndFolders ( s , m_unproccesed . Callback );
2010-11-17 19:45:25 +00:00
}
}
}
if ( usnHelpers . Count > 0 )
{
m_currentUSN = new USNRecord ();
foreach ( KeyValuePair < string , Snapshots . USNHelper > kx in usnHelpers )
m_currentUSN . Add ( kx . Key , kx . Value . JournalID , kx . Value . USN );
}
}
}
catch ( Exception ex )
{
2010-11-26 16:52:53 +00:00
if ( options . UsnStrategy == Options . OptimizationStrategy . Required )
2010-11-17 19:45:25 +00:00
throw ;
2010-11-26 16:52:53 +00:00
else if ( options . UsnStrategy == Options . OptimizationStrategy . On )
2010-11-17 19:45:25 +00:00
{
if ( m_stat != null )
m_stat . LogWarning ( string . Format ( Strings . RSyncDir . UsnFailedError , ex . ToString ()), ex );
}
//If we get here, something went really wrong with USN, so we disable it
m_currentUSN = null ;
2012-07-29 20:07:10 +00:00
m_unproccesed = new PathCollector ( m_snapshot , options . SymlinkPolicy , options . FileAttributeFilter , m_filter , m_stat );
2010-11-17 19:45:25 +00:00
unchanged = null ;
}
finally
{
if ( usnHelpers != null )
foreach ( Snapshots . USNHelper h in usnHelpers . Values )
try { h . Dispose (); }
catch ( Exception ex )
{
if ( m_stat != null )
m_stat . LogWarning ( string . Format ( Strings . RSyncDir . UsnDisposeFailedWarning , ex . ToString ()), ex );
}
}
if ( m_currentUSN == null )
{
2012-07-29 20:07:10 +00:00
m_snapshot . EnumerateFilesAndFolders ( m_unproccesed . Callback );
2010-11-17 19:45:25 +00:00
}
else
{
//Skip all items that we know are unchanged
foreach ( string x in unchanged )
{
string relpath = GetRelativeName ( x );
m_oldSignatures . Remove ( relpath );
m_oldFolders . Remove ( relpath );
2012-07-29 20:07:10 +00:00
m_oldSymlinks . Remove ( relpath );
2010-11-17 19:45:25 +00:00
}
//If some folders did not support USN, add their files now
foreach ( string s in m_sourcefolder )
if (! m_currentUSN . ContainsKey ( System . IO . Path . GetPathRoot ( s )))
2012-07-29 20:07:10 +00:00
m_snapshot . EnumerateFilesAndFolders ( s , m_unproccesed . Callback );
2010-11-17 19:45:25 +00:00
}
2009-01-14 17:29:30 +00:00
2009-03-30 20:17:54 +00:00
m_totalfiles = m_unproccesed . Files . Count ;
2009-02-14 17:30:52 +00:00
m_isfirstmultipass = true ;
2010-09-17 18:17:12 +00:00
2010-11-17 19:45:25 +00:00
if ( options . ExcludeEmptyFolders )
2010-09-17 18:17:12 +00:00
{
//We remove the folders that have no files.
//It would be more optimal to exclude them from the list before this point,
// but that would require rewriting of the snapshots
//We can't rely on the order of the folders, so we sort them to get the shortest folder names first
2010-11-21 11:08:15 +00:00
m_unproccesed . Folders . Sort ( Utility . Utility . ClientFilenameStringComparer );
2010-09-17 18:17:12 +00:00
//We can't rely on the order of the files either, but sorting them allows us to use a O=log(n) search rather than O=n
2010-11-21 11:08:15 +00:00
m_unproccesed . Files . Sort ( Utility . Utility . ClientFilenameStringComparer );
2010-09-17 18:17:12 +00:00
for ( int i = 0 ; i < m_unproccesed . Folders . Count ; i ++)
{
string folder = m_unproccesed . Folders [ i ];
2010-11-21 11:08:15 +00:00
int ix = m_unproccesed . Files . BinarySearch ( folder , Utility . Utility . ClientFilenameStringComparer );
2010-09-17 18:17:12 +00:00
if ( ix >= 0 )
continue ; //Should not happen, means that a file has the same name as a folder
//Get the next index larger than the foldername
ix = ~ ix ;
if ( ix >= m_unproccesed . Files . Count )
{
//No files matched
m_unproccesed . Folders . RemoveAt ( i );
i --;
}
else
{
//If the element does not start with the foldername, no files from the folder are included
2010-11-21 11:08:15 +00:00
if (! m_unproccesed . Files [ ix ]. StartsWith ( folder , Utility . Utility . ClientFilenameStringComparision ))
2010-09-17 18:17:12 +00:00
{
//Speedup, remove all subfolders as well without performing binary searches
while ( i < m_unproccesed . Folders . Count && m_unproccesed . Folders [ i ]. StartsWith ( folder ))
m_unproccesed . Folders . RemoveAt ( i );
//We have removed at least one, so adjust the loop counter
i --;
}
}
}
}
2012-05-14 18:48:21 +00:00
m_metadata . SourceFolderCount = m_unproccesed . Folders . Count ;
2009-02-10 23:29:01 +00:00
//Build folder diffs
2010-05-12 08:41:54 +00:00
foreach ( string s in m_unproccesed . Folders )
2009-01-14 17:29:30 +00:00
{
2010-12-27 16:33:26 +00:00
try
2009-04-01 08:30:14 +00:00
{
2010-12-27 16:33:26 +00:00
string relpath = GetRelativeName ( s );
if ( relpath . Trim (). Length != 0 )
{
2012-07-29 20:07:10 +00:00
DateTime lastWrite = m_snapshot . GetLastWriteTime ( s ). ToUniversalTime ();
2010-08-31 19:50:52 +00:00
2010-12-27 16:33:26 +00:00
//Cut off as we only have seconds stored
lastWrite = new DateTime ( lastWrite . Year , lastWrite . Month , lastWrite . Day , lastWrite . Hour , lastWrite . Minute , lastWrite . Second , DateTimeKind . Utc );
2010-08-31 19:50:52 +00:00
2010-12-27 16:33:26 +00:00
if (! m_oldFolders . ContainsKey ( relpath ))
m_newfolders . Add ( new KeyValuePair < string , DateTime >( relpath , lastWrite ));
else
{
if ( m_oldFolders [ relpath ] != lastWrite )
m_updatedfolders . Add ( new KeyValuePair < string , DateTime >( relpath , lastWrite ));
m_oldFolders . Remove ( relpath );
}
2010-08-31 19:50:52 +00:00
}
2009-04-01 08:30:14 +00:00
}
2010-12-27 16:33:26 +00:00
catch ( Exception ex )
{
m_unproccesed . Errors . Add ( s );
m_stat . LogError ( string . Format ( Strings . RSyncDir . FolderModificationTimeReadError , s , ex . Message ), ex );
}
2009-01-14 17:29:30 +00:00
}
2009-03-30 20:17:54 +00:00
m_unproccesed . Folders . Clear ();
2009-04-01 08:30:14 +00:00
foreach ( string s in m_oldFolders . Keys )
if (! m_unproccesed . IsAffectedByError ( s ))
2009-04-16 19:52:03 +00:00
m_deletedfolders . Add ( s );
2010-12-15 19:08:16 +00:00
2012-07-29 20:07:10 +00:00
//Build symlink diffs
if ( m_oldSymlinks . Count > 0 )
{
for ( int i = m_unproccesed . Symlinks . Count - 1 ; i >= 0 ; i --)
{
string s = m_unproccesed . Symlinks [ i ]. Key ;
try
{
string relpath = GetRelativeName ( s );
if ( relpath . Trim (). Length != 0 )
{
string oldLink ;
if ( m_oldSymlinks . TryGetValue ( relpath , out oldLink ))
{
m_oldSymlinks . Remove ( relpath );
if ( string . Equals ( oldLink , m_unproccesed . Symlinks [ i ]. Value , Utility . Utility . ClientFilenameStringComparision ))
{
m_unproccesed . Symlinks . RemoveAt ( i );
}
}
}
}
catch ( Exception ex )
{
m_unproccesed . Errors . Add ( s );
m_stat . LogError ( string . Format ( Strings . RSyncDir . SymlinkReadError , s , ex . Message ), ex );
}
}
}
m_deletedfiles . AddRange ( m_oldSymlinks . Keys );
m_oldSymlinks . Clear ();
2010-12-15 19:08:16 +00:00
m_sortedfilelist = options . SortedFilelist ;
if ( m_sortedfilelist )
m_unproccesed . Files . Sort ( Utility . Utility . ClientFilenameStringComparer );
2009-01-14 17:29:30 +00:00
}
2010-05-12 08:41:54 +00:00
/// <summary>
/// Gets the source folder that contains the given path
/// </summary>
/// <param name="path">The path to find the source folder for</param>
/// <returns>The source folder path</returns>
private string GetSourceFolder ( string path )
{
foreach ( string s in m_sourcefolder )
2010-11-21 11:08:15 +00:00
if ( path . StartsWith ( s , Utility . Utility . IsFSCaseSensitive ? StringComparison . CurrentCulture : StringComparison . CurrentCultureIgnoreCase ))
2010-05-12 08:41:54 +00:00
return s ;
throw new Exception ( string . Format ( Strings . RSyncDir . InternalPathMappingError , path , string . Join ( System . IO . Path . PathSeparator . ToString (), m_sourcefolder )));
}
/// <summary>
/// Returns the relative name for a file.
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
private string GetRelativeName ( string path )
{
if ( m_sourcefolder . Length == 1 )
return path . Substring ( m_sourcefolder [ 0 ]. Length );
2010-10-29 20:54:12 +00:00
for ( int i = 0 ; i < m_sourcefolder . Length ; i ++)
2010-11-21 11:08:15 +00:00
if ( path . StartsWith ( m_sourcefolder [ i ], Utility . Utility . IsFSCaseSensitive ? StringComparison . CurrentCulture : StringComparison . CurrentCultureIgnoreCase ))
2010-10-29 20:54:12 +00:00
if ( path . Length == m_sourcefolder [ i ]. Length )
2010-11-21 11:08:15 +00:00
return Utility . Utility . AppendDirSeparator ( i . ToString ()); //This is a folder, and must be suffix with a slash
2010-10-29 20:54:12 +00:00
else
return System . IO . Path . Combine ( i . ToString (), path . Substring ( m_sourcefolder [ i ]. Length )); //This will use whatever suffix the path already has
2010-05-12 08:41:54 +00:00
throw new Exception ( string . Format ( Strings . RSyncDir . InternalPathMappingError , path , string . Join ( System . IO . Path . PathSeparator . ToString (), m_sourcefolder )));
}
2010-10-23 13:10:54 +00:00
private static string GetRelativeName ( string [] sourcefolders , string path )
{
if ( sourcefolders . Length == 1 )
{
2010-11-21 11:08:15 +00:00
if (! path . StartsWith ( Utility . Utility . AppendDirSeparator ( sourcefolders [ 0 ]), Utility . Utility . ClientFilenameStringComparision ))
2010-10-23 13:10:54 +00:00
throw new Exception ( string . Format ( Strings . RSyncDir . InternalPathMappingError , path , sourcefolders [ 0 ]));
2010-11-21 11:08:15 +00:00
return path . Substring ( Utility . Utility . AppendDirSeparator ( sourcefolders [ 0 ]). Length );
2010-10-23 13:10:54 +00:00
}
for ( int i = 0 ; i < sourcefolders . Length ; i ++)
2010-11-21 11:08:15 +00:00
if ( path . StartsWith ( Utility . Utility . AppendDirSeparator ( sourcefolders [ i ]), Utility . Utility . ClientFilenameStringComparision ))
return System . IO . Path . Combine ( i . ToString (), path . Substring ( Utility . Utility . AppendDirSeparator ( sourcefolders [ i ]). Length ));
2010-10-23 13:10:54 +00:00
throw new Exception ( string . Format ( Strings . RSyncDir . InternalPathMappingError , path , string . Join ( System . IO . Path . PathSeparator . ToString (), sourcefolders )));
}
2010-05-12 08:41:54 +00:00
/// <summary>
/// Gets the system path, given a relative filename
/// </summary>
/// <param name="relpath">The relative filename</param>
/// <returns>The full system path to the file</returns>
private string GetFullPathFromRelname ( string relpath )
{
2010-10-29 20:54:12 +00:00
return GetFullPathFromRelname ( m_sourcefolder , relpath );
2010-05-12 08:41:54 +00:00
}
/// <summary>
/// Maps a list of relative names to their full path names
/// </summary>
/// <param name="sourcefolders">The list of target folders supplied</param>
/// <param name="relpath">A list of relative filenames</param>
/// <returns>A list of absolute filenames</returns>
private static List < string > GetFullPathFromRelname ( string [] sourcefolders , List < string > relpath )
{
for ( int i = 0 ; i < relpath . Count ; i ++)
relpath [ i ] = GetFullPathFromRelname ( sourcefolders , relpath [ i ]);
return relpath ;
}
/// <summary>
/// Maps a relative name to its target folder
/// </summary>
/// <param name="sourcefolders">The list of target folders supplied</param>
/// <param name="relpath">The relative path to map</param>
/// <returns>A full path, based on the relative path</returns>
private static string GetFullPathFromRelname ( string [] sourcefolders , string relpath )
{
2012-10-08 18:48:36 +00:00
try
{
if ( sourcefolders . Length == 1 )
return System . IO . Path . Combine ( sourcefolders [ 0 ], relpath );
else
{
int ix = relpath . IndexOf ( System . IO . Path . DirectorySeparatorChar );
//In some versions, Duplicati incorrectly writes the folder name without a trailing slash
int pos = ix < 0 ? int . Parse ( relpath ) : int . Parse ( relpath . Substring ( 0 , ix ));
return System . IO . Path . Combine ( sourcefolders [ Math . Min ( sourcefolders . Length - 1 , pos )], relpath . Substring ( ix + 1 ));
}
}
catch ( Exception ex )
2010-05-12 08:41:54 +00:00
{
2012-10-08 18:48:36 +00:00
throw new ArgumentException ( string . Format ( Strings . RSyncDir . InvalidRelFilenameError , relpath , string . Join ( Environment . NewLine , sourcefolders ), ex . Message ) , ex );
2010-05-12 08:41:54 +00:00
}
}
/// <summary>
/// Ends the sequence of creating a content/signature pair.
/// Writes the list of deleted files to the archives.
/// </summary>
/// <param name="signaturefile">The signature archive file</param>
/// <param name="contentfile">The content archive file</param>
/// <param name="volumesize">The max volume size</param>
/// <returns>True if the volume is completed, false otherwise</returns>
2012-08-07 17:42:09 +00:00
public bool FinalizeMultiPass ( CompressionWrapper signaturefile , CompressionWrapper contentfile , long volumesize )
2009-01-20 17:17:46 +00:00
{
if (! m_finalized )
{
2009-03-30 20:17:54 +00:00
if ( m_unproccesed . Files . Count == 0 )
2009-03-17 16:37:44 +00:00
{
2010-05-12 08:41:54 +00:00
long stringsize = 0 ;
foreach ( string s in m_oldSignatures . Keys )
{
string sourcefolder = "<unknown>" ;
2009-07-26 14:13:02 +00:00
try
{
2010-05-12 08:41:54 +00:00
string fullpath = GetFullPathFromRelname ( s );
sourcefolder = GetSourceFolder ( fullpath );
if (! m_unproccesed . IsAffectedByError ( fullpath ))
{
2009-07-26 14:13:02 +00:00
m_deletedfiles . Add ( s );
2012-07-29 20:07:10 +00:00
stringsize += System . Text . Encoding . UTF8 . GetByteCount ( s + "\r\n" );
2010-05-12 08:41:54 +00:00
}
2009-07-26 14:13:02 +00:00
}
catch ( Exception ex )
{
if ( m_stat != null )
2010-09-11 12:43:55 +00:00
m_stat . LogError ( string . Format ( Strings . RSyncDir . DeletedFilenameError , s , sourcefolder ), ex );
2010-05-12 08:41:54 +00:00
Logging . Log . WriteMessage ( string . Format ( Strings . RSyncDir . DeletedFilenameError , s , sourcefolder ), Duplicati . Library . Logging . LogMessageType . Error , ex );
2009-07-26 14:13:02 +00:00
m_unproccesed . FilesWithError . Add ( s );
}
2010-05-12 08:41:54 +00:00
}
2009-02-10 23:29:01 +00:00
2012-07-29 20:07:10 +00:00
m_oldSignatures . Clear ();
2009-03-17 16:37:44 +00:00
if ( m_deletedfiles . Count > 0 )
{
2010-05-12 08:41:54 +00:00
//The +100 is a safety margin
stringsize += System . Text . Encoding . UTF8 . GetByteCount ( DELETED_FILES ) + 100 ;
if ( contentfile . Size + contentfile . FlushBufferSize + stringsize > volumesize )
return false ; //The followup cannot fit in the volume, so we make a full new volume
2012-08-07 17:42:09 +00:00
signaturefile . WritePathLines ( DELETED_FILES , m_deletedfiles . ToArray ());
contentfile . WritePathLines ( DELETED_FILES , m_deletedfiles . ToArray ());
2012-07-29 20:07:10 +00:00
m_deletedfiles . Clear ();
2009-03-17 16:37:44 +00:00
}
2010-11-17 19:45:25 +00:00
//We only write the USN if all files were processed
if ( m_currentUSN != null )
2012-08-07 17:42:09 +00:00
using ( System . IO . Stream s = signaturefile . CreateFile ( USN_VALUES , DateTime . Now ))
2010-11-17 19:45:25 +00:00
m_currentUSN . Save ( s );
//Only write this if all files were processed
if ( m_checkedUnchangedFiles . Count > 0 )
2012-08-07 17:42:09 +00:00
signaturefile . WritePathLines ( UNMODIFIED_FILES , m_checkedUnchangedFiles . ToArray ());
2012-07-29 20:07:10 +00:00
if ( m_unproccesed . Symlinks . Count > 0 )
{
foreach ( KeyValuePair < string , string > kvp in m_unproccesed . Symlinks )
{
string source = Path . Combine ( SYMLINK_ROOT , GetRelativeName ( kvp . Key ));
2012-08-07 17:42:09 +00:00
contentfile . WritePathString ( source , kvp . Value );
signaturefile . WritePathString ( source , kvp . Value );
2012-07-29 20:07:10 +00:00
}
m_unproccesed . Symlinks . Clear ();
}
2010-11-17 19:45:25 +00:00
}
2010-10-23 08:58:24 +00:00
2009-01-20 17:17:46 +00:00
m_finalized = true ;
}
2010-05-12 08:41:54 +00:00
return m_finalized ;
2009-01-20 17:17:46 +00:00
}
2010-05-12 08:41:54 +00:00
/// <summary>
/// Creates a signature/content pair.
/// Returns true when all files are processed.
/// Returns false if there are still files to process.
/// This method will only return false if the volumesize or remainingSize is exceeded.
/// </summary>
/// <param name="signaturefile">The signaure archive file</param>
/// <param name="contentfile">The content archive file</param>
/// <param name="volumesize">The max size of this volume</param>
/// <param name="remainingSize">The max remaining size of arhive space</param>
/// <returns>False if there are still files to process, true if all files are processed</returns>
2012-08-07 17:42:09 +00:00
public bool MakeMultiPassDiff ( CompressionWrapper signaturefile , CompressionWrapper contentfile , long volumesize )
2009-01-14 17:29:30 +00:00
{
if ( m_unproccesed == null )
2009-06-17 22:47:51 +00:00
throw new Exception ( Strings . RSyncDir . MultipassUsageError );
2009-01-14 17:29:30 +00:00
Random r = new Random ();
long totalSize = 0 ;
2010-08-31 19:50:52 +00:00
//Insert the marker file
2012-08-07 17:42:09 +00:00
contentfile . CreateFile ( UTC_TIME_MARKER , DateTime . Now ). Dispose ();
signaturefile . CreateFile ( UTC_TIME_MARKER , DateTime . Now ). Dispose ();
2010-08-31 19:50:52 +00:00
2012-08-23 07:15:15 +00:00
//In the first volume we insert some lists with data on updated and deleted folders
2009-02-14 17:30:52 +00:00
if ( m_isfirstmultipass )
{
//We write these files to the very first volume
if ( m_deletedfolders . Count > 0 )
{
2012-08-07 17:42:09 +00:00
string [] folders = m_deletedfolders . ToArray ();
signaturefile . WritePathLines ( DELETED_FOLDERS , folders );
contentfile . WritePathLines ( DELETED_FOLDERS , folders );
2009-02-14 17:30:52 +00:00
}
if ( m_newfolders . Count > 0 )
{
2010-08-31 19:50:52 +00:00
string [] folders = new string [ m_newfolders . Count ];
string [] timestamps = new string [ m_newfolders . Count ];
for ( int i = 0 ; i < m_newfolders . Count ; i ++)
{
folders [ i ] = m_newfolders [ i ]. Key ;
2012-05-14 20:49:41 +00:00
timestamps [ i ] = (( long )(( m_newfolders [ i ]. Value - Utility . Utility . EPOCH ). TotalSeconds )). ToString ();
2010-08-31 19:50:52 +00:00
}
2012-08-07 17:42:09 +00:00
signaturefile . WritePathLines ( ADDED_FOLDERS , folders );
2010-08-31 19:50:52 +00:00
signaturefile . WriteAllLines ( ADDED_FOLDERS_TIMESTAMPS , timestamps );
2012-08-07 17:42:09 +00:00
contentfile . WritePathLines ( ADDED_FOLDERS , folders );
2010-08-31 19:50:52 +00:00
contentfile . WriteAllLines ( ADDED_FOLDERS_TIMESTAMPS , timestamps );
}
if ( m_updatedfolders . Count > 0 )
{
string [] folders = new string [ m_updatedfolders . Count ];
string [] timestamps = new string [ m_updatedfolders . Count ];
for ( int i = 0 ; i < m_updatedfolders . Count ; i ++)
{
folders [ i ] = m_updatedfolders [ i ]. Key ;
2012-05-14 20:49:41 +00:00
timestamps [ i ] = (( long )(( m_updatedfolders [ i ]. Value - Utility . Utility . EPOCH ). TotalSeconds )). ToString ();
2010-08-31 19:50:52 +00:00
}
2012-08-07 17:42:09 +00:00
signaturefile . WritePathLines ( UPDATED_FOLDERS , folders );
2010-08-31 19:50:52 +00:00
signaturefile . WriteAllLines ( UPDATED_FOLDERS_TIMESTAMPS , timestamps );
2012-08-07 17:42:09 +00:00
contentfile . WritePathLines ( UPDATED_FOLDERS , folders );
2010-08-31 19:50:52 +00:00
contentfile . WriteAllLines ( UPDATED_FOLDERS_TIMESTAMPS , timestamps );
2009-02-14 17:30:52 +00:00
}
m_isfirstmultipass = false ;
}
2011-02-15 19:12:32 +00:00
//Last update was a looong time ago
DateTime nextProgressEvent = DateTime . Now . AddYears (- 1 );
2012-08-23 07:15:15 +00:00
//If the previous volume had a file that was too large to fit in the volume,
// we resume that file here
2010-05-12 08:41:54 +00:00
if ( m_lastPartialFile != null )
2011-02-15 19:12:32 +00:00
{
if ( ProgressEvent != null )
{
int pg = 100 - (( int )(( m_unproccesed . Files . Count / ( double ) m_totalfiles ) * 100 ));
nextProgressEvent = DateTime . Now + PROGRESS_TIMESPAN ;
ProgressEvent ( pg , m_lastPartialFile . fullname );
}
2010-05-12 08:41:54 +00:00
m_lastPartialFile = WritePossiblePartial ( m_lastPartialFile , contentfile , signaturefile , volumesize );
2011-02-15 19:12:32 +00:00
}
2010-05-12 08:41:54 +00:00
2012-08-23 07:15:15 +00:00
//This is the main file processing loop, that takes a file at a time
2012-07-29 20:07:10 +00:00
while ( m_unproccesed . Files . Count > 0 )
2009-01-14 17:29:30 +00:00
{
2012-08-23 07:15:15 +00:00
//If we have a file that is not complete, we do not start a new file
2012-08-22 09:49:11 +00:00
if ( m_lastPartialFile != null )
return false ;
2012-08-23 07:15:15 +00:00
//If we have filled a volume, stop now
2012-07-29 20:07:10 +00:00
if ( totalSize >= volumesize )
break ;
2010-12-15 19:08:16 +00:00
int next = m_sortedfilelist ? 0 : r . Next ( 0 , m_unproccesed . Files . Count );
2009-03-30 20:17:54 +00:00
string s = m_unproccesed . Files [ next ];
m_unproccesed . Files . RemoveAt ( next );
2009-01-14 17:29:30 +00:00
2012-05-14 18:48:21 +00:00
try
{
long size = m_snapshot . GetFileSize ( s );
if ( size <= MaxFileSize && size > 0 )
{
m_metadata . SourceFileSize += size ;
m_metadata . SourceFileCount ++;
}
}
catch { }
2010-12-15 19:08:16 +00:00
if ( ProgressEvent != null && DateTime . Now > nextProgressEvent )
2009-03-11 20:56:01 +00:00
{
2010-12-15 19:08:16 +00:00
int pg = 100 - (( int )(( m_unproccesed . Files . Count / ( double ) m_totalfiles ) * 100 ));
2011-02-15 19:12:32 +00:00
nextProgressEvent = DateTime . Now + PROGRESS_TIMESPAN ;
2010-12-15 19:08:16 +00:00
ProgressEvent ( pg , s );
2009-03-11 20:56:01 +00:00
}
2009-02-10 23:29:01 +00:00
try
{
2009-02-26 18:33:52 +00:00
if (! m_disableFiletimeCheck )
{
2009-04-05 21:11:47 +00:00
//TODO: Make this check faster somehow
2010-05-12 08:41:54 +00:00
string relpath = GetRelativeName ( s );
2009-02-26 18:33:52 +00:00
if ( m_oldSignatures . ContainsKey ( relpath ))
{
2011-06-16 16:18:05 +00:00
try
{
//Reports show that the file time can be missing :(
DateTime lastFileWrite = m_snapshot . GetLastWriteTime ( s ). ToUniversalTime ();
//Cut off as we only preserve precision in seconds after compression
lastFileWrite = new DateTime ( lastFileWrite . Year , lastFileWrite . Month , lastFileWrite . Day , lastFileWrite . Hour , lastFileWrite . Minute , lastFileWrite . Second , DateTimeKind . Utc );
2010-08-31 19:50:52 +00:00
2011-06-16 16:18:05 +00:00
DateTime lastCheck ;
if (! m_lastVerificationTime . TryGetValue ( relpath , out lastCheck ))
lastCheck = m_oldSignatures [ relpath ]. CreateTime ;
2010-10-23 08:58:24 +00:00
2011-06-16 16:18:05 +00:00
//Compare with the modification time of the last known check time
if ( lastFileWrite <= lastCheck )
{
m_oldSignatures . Remove ( relpath );
m_examinedfiles ++;
continue ;
}
}
catch ( Exception ex )
2009-02-26 18:33:52 +00:00
{
2011-06-16 16:18:05 +00:00
Logging . Log . WriteMessage ( string . Format ( Strings . RSyncDir . InvalidTimeStampError , s , ex . Message ), Duplicati . Library . Logging . LogMessageType . Warning , ex );
2009-02-26 18:33:52 +00:00
}
}
}
2009-03-30 20:17:54 +00:00
if ( m_unproccesed . Errors . Count > 0 && m_unproccesed . IsAffectedByError ( s ))
m_unproccesed . FilesWithError . Add ( s );
else
2009-02-14 17:30:52 +00:00
{
2010-09-16 16:12:55 +00:00
System . IO . Stream fs = null ;
try
2009-03-26 19:39:50 +00:00
{
2010-11-20 13:57:12 +00:00
bool isLockedStream = false ;
2010-11-04 21:37:32 +00:00
m_filesopened ++;
2010-10-29 20:54:12 +00:00
//We cannot have a "using" directive here because the fs may need to survive multiple rounds
2010-11-20 13:57:12 +00:00
try { fs = m_snapshot . OpenRead ( s ); }
catch
{
if ( m_snapshot is Snapshots . NoSnapshot && m_openfilepolicy != Options . OpenFileStrategy . Ignore )
{
try { fs = (( Snapshots . NoSnapshot ) m_snapshot ). OpenLockedRead ( s ); }
catch { }
//Rethrow original error
if ( fs == null )
throw ;
isLockedStream = true ;
}
else
throw ;
}
2011-06-16 16:18:05 +00:00
2012-05-14 20:49:41 +00:00
DateTime lastWrite = Utility . Utility . EPOCH ;
2011-06-16 16:18:05 +00:00
try
{
//Record the change time after we opened (and thus locked) the file
lastWrite = m_snapshot . GetLastWriteTime ( s ). ToUniversalTime ();
//Cut off as we only preserve precision in seconds
lastWrite = new DateTime ( lastWrite . Year , lastWrite . Month , lastWrite . Day , lastWrite . Hour , lastWrite . Minute , lastWrite . Second , DateTimeKind . Utc );
}
catch ( Exception ex )
{
Logging . Log . WriteMessage ( string . Format ( Strings . RSyncDir . InvalidTimeStampError , s , ex . Message ), Duplicati . Library . Logging . LogMessageType . Warning , ex );
}
2010-10-23 08:58:24 +00:00
2010-08-31 19:50:52 +00:00
2009-03-30 20:17:54 +00:00
if ( fs . Length > m_maxFileSize )
2010-10-29 20:54:12 +00:00
{
2009-03-30 20:17:54 +00:00
m_unproccesed . FilesTooLarge . Add ( s );
2010-10-29 20:54:12 +00:00
}
2009-03-30 20:17:54 +00:00
else
{
//If the file is > 10mb, update the display to show the file being processed
if ( ProgressEvent != null && fs . Length > 1024 * 1024 * 10 )
2010-12-15 19:08:16 +00:00
{
int pg = 100 - (( int )(( m_unproccesed . Files . Count / ( double ) m_totalfiles ) * 100 ));
2011-02-15 19:12:32 +00:00
nextProgressEvent = DateTime . Now + PROGRESS_TIMESPAN ;
2010-12-15 19:08:16 +00:00
ProgressEvent ( pg , s );
}
2009-03-26 19:39:50 +00:00
2009-03-30 20:17:54 +00:00
System . IO . Stream signature = ProccessDiff ( fs , s , signaturefile );
2010-10-23 08:58:24 +00:00
if ( signature == null )
{
//If we had to check the file, it's timestamp was modified, so we record that the file is still unchanged
// so we can avoid checking the next time
if (! m_disableFiletimeCheck )
m_checkedUnchangedFiles . Add ( GetRelativeName ( s ));
2010-10-29 20:54:12 +00:00
2010-10-23 08:58:24 +00:00
//TODO: If the file timestamp was changed AFTER the backup started, we will record it in this and the next backup.
// This can be avoided, but only happens if the file was not modified, so it will happen rarely
}
else
2010-10-29 20:54:12 +00:00
{
2010-11-20 13:57:12 +00:00
System . IO . Stream originalSignature = null ;
//If the stream was locked, we hijack it here to ensure that the signature recorded
// matches the file data being read
if ( isLockedStream )
{
if ( m_openfilepolicy == Options . OpenFileStrategy . Copy )
{
using ( MemoryStream newSig = new MemoryStream ())
{
fs . Position = 0 ;
using ( SharpRSync . ChecksumGeneratingStream ts = new SharpRSync . ChecksumGeneratingStream ( newSig , fs ))
{
2011-02-06 12:50:59 +00:00
newSig . Capacity = Math . Max ( ts . BytesGeneratedForSignature (( long )( fs . Length * FILESIZE_GROW_MARGIN_MULTIPLIER )), newSig . Capacity );
2010-11-21 11:08:15 +00:00
fs = new Utility . TempFileStream ();
Utility . Utility . CopyStream ( ts , fs , false );
2010-11-20 13:57:12 +00:00
}
fs . Position = 0 ;
signature . Position = 0 ;
newSig . Position = 0 ;
2010-11-21 11:08:15 +00:00
if (! Utility . Utility . CompareStreams ( signature , newSig , true ))
2010-11-20 13:57:12 +00:00
throw new Exception ( string . Format ( Strings . RSyncDir . FileChangedWhileReadError , s ));
signature . Position = 0 ;
}
}
else
{
//Keep a copy of the original signature for change detection
originalSignature = signature ;
//Set up for a new round
signature = new System . IO . MemoryStream ();
fs . Position = 0 ;
2011-01-12 19:05:33 +00:00
long filelen = fs . Length ;
2010-11-20 13:57:12 +00:00
fs = new SharpRSync . ChecksumGeneratingStream ( signature , fs );
2011-02-06 12:50:59 +00:00
(( MemoryStream ) signature ). Capacity = Math . Max ((( SharpRSync . ChecksumGeneratingStream ) fs ). BytesGeneratedForSignature ( filelen ), (( MemoryStream ) signature ). Capacity );
2010-11-20 13:57:12 +00:00
}
}
2010-08-31 19:50:52 +00:00
totalSize = AddFileToCompression ( fs , s , signature , contentfile , signaturefile , volumesize , lastWrite );
2010-10-29 20:54:12 +00:00
//If this turned into a partial full entry, we must keep the file open.
//The file will be closed when m_lastPartialFile is disposed
2010-11-20 13:57:12 +00:00
if ( m_lastPartialFile != null )
{
m_lastPartialFile . OriginalSignatureStream = originalSignature ;
if ( m_lastPartialFile . Stream == fs )
fs = null ;
}
2010-10-29 20:54:12 +00:00
}
2009-03-30 20:17:54 +00:00
}
2009-03-26 19:39:50 +00:00
}
2010-10-29 20:54:12 +00:00
finally
2010-09-16 16:12:55 +00:00
{
try
{
if ( fs != null )
fs . Dispose ();
}
catch { }
}
2009-02-14 17:30:52 +00:00
}
2009-02-10 23:29:01 +00:00
}
catch ( Exception ex )
{
if ( m_stat != null )
2010-09-11 12:43:55 +00:00
m_stat . LogError ( string . Format ( Strings . RSyncDir . FileProcessError , s , ex . Message ), ex );
2009-06-17 22:47:51 +00:00
Logging . Log . WriteMessage ( string . Format ( Strings . RSyncDir . FileProcessError , s , ex . Message ), Duplicati . Library . Logging . LogMessageType . Error , ex );
2009-03-30 20:17:54 +00:00
m_unproccesed . FilesWithError . Add ( s );
2009-01-15 10:46:57 +00:00
}
2009-01-14 17:29:30 +00:00
}
2012-08-23 07:15:15 +00:00
//Return true if there are no more data, false otherwise
2012-08-23 07:07:52 +00:00
if ( m_unproccesed . Files . Count == 0 && m_lastPartialFile == null )
2010-05-12 08:41:54 +00:00
return FinalizeMultiPass ( signaturefile , contentfile , volumesize );
else
return false ;
2009-01-14 17:29:30 +00:00
}
2010-05-12 08:41:54 +00:00
/// <summary>
/// Generates a signature stream for the given file and compares it with an existing signature stream, if one exists.
/// </summary>
/// <param name="fs">The file to examine, the filestream should be at position 0</param>
/// <param name="s">The full name of the file</param>
/// <param name="signaturefile">The signature archive file</param>
/// <returns>The signature stream if the file is new or modified, null if the file has not been modified</returns>
2012-08-07 17:42:09 +00:00
private System . IO . Stream ProccessDiff ( System . IO . Stream fs , string s , CompressionWrapper signaturefile )
2009-01-14 17:29:30 +00:00
{
2010-05-12 08:41:54 +00:00
string relpath = GetRelativeName ( s );
2009-01-14 17:29:30 +00:00
2009-02-14 17:30:52 +00:00
m_examinedfilesize += fs . Length ;
m_examinedfiles ++;
2011-01-12 19:05:33 +00:00
System . IO . MemoryStream ms = new MemoryStream ();
SharpRSync . ChecksumFileWriter ws = new Duplicati . Library . SharpRSync . ChecksumFileWriter ( ms );
2011-02-06 12:50:59 +00:00
2011-01-12 19:05:33 +00:00
//Expand the memorystream to contain all bytes, which avoids re-allocation
2011-02-06 12:50:59 +00:00
ms . Capacity = Math . Max ( ws . BytesGeneratedForSignature (( long )( fs . Length * FILESIZE_GROW_MARGIN_MULTIPLIER )), ms . Capacity );
2011-01-12 19:05:33 +00:00
ws . AddStream ( fs );
2009-02-14 17:30:52 +00:00
ms . Position = 0 ;
if (! m_oldSignatures . ContainsKey ( relpath ))
{
m_newfiles . Add ( s , null );
return ms ;
}
else
2009-01-14 17:29:30 +00:00
{
2009-02-14 17:30:52 +00:00
bool equals ;
2010-05-12 08:41:54 +00:00
//File exists in archive, check if signature has changed
using ( System . IO . Stream s2 = m_oldSignatures [ relpath ]. OpenRead ( relpath ))
2010-11-21 11:08:15 +00:00
equals = Utility . Utility . CompareStreams ( s2 , ms , false );
2009-01-14 17:29:30 +00:00
2009-02-14 17:30:52 +00:00
ms . Position = 0 ;
if (! equals )
2009-01-14 17:29:30 +00:00
{
2010-05-12 08:41:54 +00:00
//Yes, changed, return the signature stream
2009-02-14 17:30:52 +00:00
m_modifiedFiles . Add ( s , null );
return ms ;
2009-01-14 17:29:30 +00:00
}
else
{
2010-05-12 08:41:54 +00:00
//No changes, discard the signature stream
2009-02-14 17:30:52 +00:00
ms . Close ();
ms . Dispose ();
m_oldSignatures . Remove ( relpath );
return null ;
2009-01-14 17:29:30 +00:00
}
}
}
2010-05-12 08:41:54 +00:00
/// <summary>
/// Appends a file to the content and signature archives.
/// If the file existed, a delta file is added, otherwise the entire file is added.
/// </summary>
/// <param name="fs">The file to add</param>
/// <param name="s">The full name of the file</param>
/// <param name="signature">The signature archive file</param>
/// <param name="contentfile">The content archive file</param>
/// <param name="signaturefile">The signature stream to add</param>
/// <param name="volumesize">The max size of the volume</param>
2010-08-31 19:50:52 +00:00
/// <param name="lastWrite">The time the source file was last written</param>
2010-05-12 08:41:54 +00:00
/// <returns>The current size of the content archive</returns>
2012-08-07 17:42:09 +00:00
private long AddFileToCompression ( System . IO . Stream fs , string s , System . IO . Stream signature , CompressionWrapper contentfile , CompressionWrapper signaturefile , long volumesize , DateTime lastWrite )
2009-01-14 17:29:30 +00:00
{
2009-01-25 19:33:36 +00:00
fs . Position = 0 ;
2010-05-12 08:41:54 +00:00
string relpath = GetRelativeName ( s );
2009-01-25 19:33:36 +00:00
2009-01-14 17:29:30 +00:00
if ( m_modifiedFiles . ContainsKey ( s ))
{
2010-05-12 08:41:54 +00:00
//Existing file, write the delta file
2009-02-10 23:29:01 +00:00
string target = System . IO . Path . Combine ( DELTA_ROOT , relpath );
2010-05-12 08:41:54 +00:00
string signaturepath = System . IO . Path . Combine ( DELTA_SIGNATURE_ROOT , relpath );
2009-01-14 17:29:30 +00:00
2010-05-12 08:41:54 +00:00
using ( System . IO . Stream sigfs = m_oldSignatures [ relpath ]. OpenRead ( relpath ))
2009-01-17 15:27:51 +00:00
{
2010-05-12 08:41:54 +00:00
long lbefore = contentfile . Size ;
2010-11-21 11:08:15 +00:00
Utility . TempFileStream deltaTemp = null ;
2010-05-12 08:41:54 +00:00
try
{
2010-11-21 11:08:15 +00:00
deltaTemp = new Duplicati . Library . Utility . TempFileStream ();
2010-11-20 13:57:12 +00:00
SharpRSync . Interface . GenerateDelta ( sigfs , fs , deltaTemp );
deltaTemp . Position = 0 ;
2010-05-12 08:41:54 +00:00
2011-02-15 19:12:32 +00:00
m_lastPartialFile = WritePossiblePartial ( new PartialFileEntry ( deltaTemp , target , s , 0 , signature , signaturepath , lastWrite ), contentfile , signaturefile , volumesize );
2010-05-12 08:41:54 +00:00
}
catch
2009-02-14 17:30:52 +00:00
{
2010-05-12 08:41:54 +00:00
if ( deltaTemp != null )
deltaTemp . Dispose ();
throw ;
2009-02-14 17:30:52 +00:00
}
2009-02-10 23:29:01 +00:00
2010-05-12 08:41:54 +00:00
m_diffsize += contentfile . Size - lbefore ;
2009-01-20 17:17:46 +00:00
m_diffedfilessize += fs . Length ;
2009-01-17 15:27:51 +00:00
m_diffedfiles ++;
}
2009-01-14 17:29:30 +00:00
m_modifiedFiles . Remove ( s );
2009-02-10 23:29:01 +00:00
m_oldSignatures . Remove ( relpath );
2009-01-14 17:29:30 +00:00
}
else
{
2010-05-12 08:41:54 +00:00
//New file, write as content
string signaturepath = System . IO . Path . Combine ( CONTENT_SIGNATURE_ROOT , relpath );
2009-02-10 23:29:01 +00:00
string target = System . IO . Path . Combine ( CONTENT_ROOT , relpath );
2009-01-14 17:29:30 +00:00
2010-09-16 16:12:55 +00:00
long size = fs . Length ;
2011-02-15 19:12:32 +00:00
m_lastPartialFile = WritePossiblePartial ( new PartialFileEntry ( fs , target , s , 0 , signature , signaturepath , lastWrite ), contentfile , signaturefile , volumesize );
2009-02-10 23:29:01 +00:00
2009-01-20 17:17:46 +00:00
m_addedfiles ++;
2010-09-16 16:12:55 +00:00
m_addedfilessize += size ;
2009-01-14 17:29:30 +00:00
m_newfiles . Remove ( s );
}
2009-02-10 23:29:01 +00:00
return contentfile . Size ;
2009-01-14 17:29:30 +00:00
}
2010-05-12 08:41:54 +00:00
/// <summary>
/// Appends a file to the content and signature archives, watching the content archive file size.
/// Returns the partial file entry if the volume size was exceeded.
/// Returns null if the file was written entirely.
/// </summary>
/// <param name="entry">The entry that describes the partial file</param>
/// <param name="contentfile">The content archive file</param>
/// <param name="signaturefile">The signature archive file</param>
/// <param name="volumesize">The max allowed volumesize</param>
/// <returns>The partial file entry if the volume size was exceeded. Returns null if the file was written entirely.</returns>
2012-08-07 17:42:09 +00:00
private PartialFileEntry WritePossiblePartial ( PartialFileEntry entry , CompressionWrapper contentfile , CompressionWrapper signaturefile , long volumesize )
2010-05-12 08:41:54 +00:00
{
long startPos = entry . Stream . Position ;
//Protect against writing this file if there is not enough space to hold the INCOMPLETE_FILE
if ( startPos == 0 && contentfile . Size + contentfile . FlushBufferSize + ( entry . ExtraSize * 2 ) > volumesize )
return entry ;
2009-01-14 17:29:30 +00:00
2010-05-12 08:41:54 +00:00
PartialFileEntry pe = WritePossiblePartialInternal ( entry , contentfile , volumesize );
if ( pe != null )
{
//The record is (still) partial
string [] tmplines = new PartialEntryRecord ( entry . relativeName , startPos , entry . Stream . Position - startPos , entry . Stream . Length ). Serialize ();
contentfile . WriteAllLines ( INCOMPLETE_FILE , tmplines );
signaturefile . WriteAllLines ( INCOMPLETE_FILE , tmplines );
2010-10-29 20:54:12 +00:00
2010-05-12 08:41:54 +00:00
//If we are debugging, this can be nice to have
Logging . Log . WriteMessage ( string . Format ( Strings . RSyncDir . PartialFileAddedLogMessage , entry . relativeName , startPos ), Duplicati . Library . Logging . LogMessageType . Information );
}
else
{
//If the file was partial before, mark the file as completed
if ( startPos != 0 )
2010-05-17 15:35:58 +00:00
{
string [] tmplines = new PartialEntryRecord ( entry . relativeName , startPos , entry . Stream . Position - startPos , entry . Stream . Length ). Serialize ();
contentfile . WriteAllLines ( COMPLETED_FILE , tmplines );
signaturefile . WriteAllLines ( COMPLETED_FILE , tmplines );
}
2010-05-12 08:41:54 +00:00
//Add signature AFTER content is completed.
//If content is present, it is restoreable, if signature is missing, file will be backed up on next run
//If signature is present, but not content, the entire differential sequence will be unable to recover the file
2010-11-20 13:57:12 +00:00
if (! entry . DumpSignature ( signaturefile ))
{
if ( m_stat != null )
2012-06-26 14:19:45 +00:00
m_stat . LogWarning ( string . Format ( Strings . RSyncDir . FileChangedWhileReadWarning , entry . fullname ), null );
2010-11-20 13:57:12 +00:00
}
2010-05-12 08:41:54 +00:00
entry . Dispose ();
}
return pe ;
}
/// <summary>
/// Appends a file to the content archive, watching the content archive file size.
/// Does not record anything in either content or signature volumes
/// Returns the partial file entry if the volume size was exceeded.
/// Returns null if the file was written entirely.
/// </summary>
/// <param name="entry">The entry that describes the partial file</param>
/// <param name="contentfile">The content archive file</param>
/// <param name="volumesize">The max allowed volumesize</param>
/// <returns>The partial file entry if the volume size was exceeded. Returns null if the file was written entirely.</returns>
2012-08-07 17:42:09 +00:00
private PartialFileEntry WritePossiblePartialInternal ( PartialFileEntry entry , CompressionWrapper contentfile , long volumesize )
2009-01-14 17:29:30 +00:00
{
2010-05-12 08:41:54 +00:00
//append chuncks of 1kb, checking on the total size after each write
byte [] tmp = new byte [ 1024 ];
2010-08-31 19:50:52 +00:00
using ( System . IO . Stream s3 = contentfile . CreateFile ( entry . relativeName , entry . LastWriteTime ))
2010-05-12 08:41:54 +00:00
{
int a ;
while (( a = entry . Stream . Read ( tmp , 0 , tmp . Length )) != 0 )
{
s3 . Write ( tmp , 0 , a );
if ( contentfile . Size + contentfile . FlushBufferSize + entry . ExtraSize + tmp . Length > volumesize )
return entry ;
}
}
2009-01-14 17:29:30 +00:00
2010-05-12 08:41:54 +00:00
return null ;
}
public bool HasChanges { get { return m_newfiles . Count > 0 || m_modifiedFiles . Count > 0 ; } }
/// <summary>
/// Restores all files to the destination, given a set of content files.
/// This function is not used by Duplicati.
/// </summary>
/// <param name="destination">The destination to restore to</param>
/// <param name="patches">The list of patches (content files) to process</param>
2012-08-07 17:42:09 +00:00
public void Restore ( string [] destination , List < CompressionWrapper > patches )
2009-01-14 17:29:30 +00:00
{
2009-02-10 23:29:01 +00:00
if ( patches != null )
2010-05-12 08:41:54 +00:00
{
2012-08-07 17:42:09 +00:00
foreach ( CompressionWrapper s in patches )
2009-01-14 17:29:30 +00:00
Patch ( destination , s );
2010-05-12 08:41:54 +00:00
FinalizeRestore ();
}
}
/// <summary>
/// A function that makes sure that empty leftover folders are deleted,
/// and that there are no leftover partial files from the patches.
/// Call this function after calling the Patch function with all patches.
/// </summary>
public void FinalizeRestore ()
{
2012-07-29 20:07:10 +00:00
Snapshots . ISystemIO SystemIO = Utility . Utility . IsClientLinux ? ( Snapshots . ISystemIO ) new Snapshots . SystemIOLinux () : ( Snapshots . ISystemIO ) new Snapshots . SystemIOWindows ();
2010-05-12 08:41:54 +00:00
if ( m_folders_to_delete != null )
{
foreach ( string s in m_folders_to_delete )
{
2012-07-29 20:07:10 +00:00
if ( SystemIO . DirectoryExists ( s ))
2010-05-12 08:41:54 +00:00
try
{
//TODO: Perhaps read ahead in patches to prevent creation
2012-07-29 20:07:10 +00:00
SystemIO . DirectoryDelete ( s , false );
2010-09-11 11:12:46 +00:00
if ( m_stat as RestoreStatistics != null )
{
( m_stat as RestoreStatistics ). FoldersDeleted ++;
( m_stat as RestoreStatistics ). FoldersRestored --;
}
2010-05-12 08:41:54 +00:00
}
catch ( Exception ex )
{
if ( m_stat != null )
2010-09-11 12:43:55 +00:00
m_stat . LogError ( string . Format ( Strings . RSyncDir . DeleteFolderError , s , ex . Message ), ex );
2010-05-12 08:41:54 +00:00
Logging . Log . WriteMessage ( string . Format ( Strings . RSyncDir . DeleteFolderError , s , ex . Message ), Duplicati . Library . Logging . LogMessageType . Warning , ex );
}
else
Logging . Log . WriteMessage ( string . Format ( Strings . RSyncDir . FolderToDeleteMissingError , s ), Duplicati . Library . Logging . LogMessageType . Warning );
}
m_folders_to_delete = null ;
}
if ( m_partialDeltas != null )
{
2010-11-21 11:08:15 +00:00
foreach ( KeyValuePair < string , Utility . TempFile > tf in m_partialDeltas )
2010-05-12 08:41:54 +00:00
{
Logging . Log . WriteMessage ( string . Format ( Strings . RSyncDir . PartialFileIncompleteWarning , tf . Key ), Duplicati . Library . Logging . LogMessageType . Warning );
try
{
tf . Value . Protected = false ;
tf . Value . Dispose ();
}
catch ( Exception ex )
{
string logmessage = string . Format ( Strings . RSyncDir . PartialLeftoverDeleteError , tf . Key , ex . Message );
Logging . Log . WriteMessage ( logmessage , Duplicati . Library . Logging . LogMessageType . Error , ex );
if ( m_stat != null )
2010-09-11 12:43:55 +00:00
m_stat . LogError ( logmessage , ex );
2010-05-12 08:41:54 +00:00
}
}
m_partialDeltas = null ;
}
2010-08-31 19:50:52 +00:00
if ( m_folderTimestamps != null )
foreach ( KeyValuePair < string , DateTime > t in m_folderTimestamps )
2012-07-29 20:07:10 +00:00
if ( SystemIO . DirectoryExists ( t . Key ))
try { SystemIO . DirectorySetLastWriteTimeUtc ( t . Key , t . Value ); }
2010-08-31 19:50:52 +00:00
catch ( Exception ex )
{
2010-09-11 12:43:55 +00:00
m_stat . LogWarning ( string . Format ( Strings . RSyncDir . FailedToSetFolderWriteTime , t . Key , ex . Message ), ex );
2010-08-31 19:50:52 +00:00
}
2010-08-31 20:00:31 +00:00
m_folderTimestamps = null ;
2010-05-12 08:41:54 +00:00
}
/// <summary>
/// Internal helper class to filter files during a partial restore
/// </summary>
private class FilterHelper
{
/// <summary>
/// A copy of the filter to use
/// </summary>
2010-11-21 11:08:15 +00:00
private Utility . FilenameFilter m_filter ;
2010-05-12 08:41:54 +00:00
/// <summary>
/// The list of folders to restore to
/// </summary>
private string [] m_folders ;
/// <summary>
/// The parent folder
/// </summary>
private RSyncDir m_parent ;
/// <summary>
/// Constructs a new filter helper
/// </summary>
/// <param name="folders">The restore folder list</param>
/// <param name="filter">The filter to apply to the files</param>
2010-11-21 11:08:15 +00:00
public FilterHelper ( RSyncDir parent , string [] folders , Utility . FilenameFilter filter )
2010-05-12 08:41:54 +00:00
{
m_parent = parent ;
m_folders = folders ;
m_filter = filter ;
}
/// <summary>
/// A filter predicate used to filter unwanted elements from the list
/// </summary>
/// <param name="element">The relative string to examine</param>
/// <returns>True if the element is accepted, false if it is filtered</returns>
private bool FilterPredicate ( string element )
2012-05-17 08:07:41 +00:00
{
return m_filter . ShouldInclude ( Utility . Utility . DirectorySeparatorString , Utility . Utility . DirectorySeparatorString + element );
}
/// <summary>
/// A filter predicate used to filter unwanted folder elements from the list
/// </summary>
/// <param name="element">The relative string to examine</param>
/// <returns>True if the folder element is accepted, false if it is filtered</returns>
private bool FilterFoldersPredicate ( string element )
2010-05-12 08:41:54 +00:00
{
2010-11-21 11:08:15 +00:00
return m_filter . ShouldInclude ( Utility . Utility . DirectorySeparatorString , Utility . Utility . DirectorySeparatorString + Utility . Utility . AppendDirSeparator ( element ));
2010-05-12 08:41:54 +00:00
}
/// <summary>
/// A conversion delegate used to transform relative names into full names
/// </summary>
/// <param name="input">The relative filename</param>
/// <returns>The full path</returns>
private string GetFullpathFunc ( string input )
{
return RSyncDir . GetFullPathFromRelname ( m_folders , input );
}
/// <summary>
/// Filters a list by hooking into the enumeration system, rather than copying the list
/// </summary>
/// <param name="input">The list of relative filenames to filter</param>
2012-05-17 08:07:41 +00:00
/// <param name="isFolderList">True if all elements should be interpreted as folders, false otherwise</param>
2010-05-12 08:41:54 +00:00
/// <returns>A filtered list</returns>
2012-05-17 08:07:41 +00:00
public IEnumerable < string > Filterlist ( IEnumerable < string > input , bool isFolderList )
2010-05-12 08:41:54 +00:00
{
2012-05-17 08:07:41 +00:00
return new Utility . PlugableEnumerable < string >( isFolderList ? new Predicate < string >( FilterFoldersPredicate ) : new Predicate < string >( FilterPredicate ), new Utility . Func < string , string >( GetFullpathFunc ), input );
2010-05-12 08:41:54 +00:00
}
2009-01-14 17:29:30 +00:00
}
2010-05-12 08:41:54 +00:00
/// <summary>
/// Applies a patch (content file) to the destination
/// </summary>
/// <param name="destination">The destination that contains the previous version of the data</param>
/// <param name="patch">The content file that the destination is patched with</param>
2012-08-07 17:42:09 +00:00
public void Patch ( string [] destination , CompressionWrapper patch )
2009-01-14 17:29:30 +00:00
{
2012-07-29 20:07:10 +00:00
Snapshots . ISystemIO SystemIO = Utility . Utility . IsClientLinux ? ( Snapshots . ISystemIO ) new Snapshots . SystemIOLinux () : ( Snapshots . ISystemIO ) new Snapshots . SystemIOWindows ();
2010-05-12 08:41:54 +00:00
if ( m_partialDeltas == null )
2010-11-21 11:08:15 +00:00
m_partialDeltas = new Dictionary < string , Duplicati . Library . Utility . TempFile >();
2010-05-12 08:41:54 +00:00
2010-08-31 19:50:52 +00:00
if ( m_folderTimestamps == null )
m_folderTimestamps = new Dictionary < string , DateTime >();
2010-05-12 08:41:54 +00:00
for ( int i = 0 ; i < destination . Length ; i ++)
2010-11-21 11:08:15 +00:00
destination [ i ] = Utility . Utility . AppendDirSeparator ( destination [ i ]);
2009-02-10 23:29:01 +00:00
2010-08-31 19:50:52 +00:00
bool isUtc = patch . FileExists ( UTC_TIME_MARKER );
2010-05-12 08:41:54 +00:00
//Set up the filter system to avoid dealing with filtered items
FilterHelper fh = new FilterHelper ( this , destination , m_filter );
//Delete all files that were removed
2009-02-10 23:29:01 +00:00
if ( patch . FileExists ( DELETED_FILES ))
2012-08-07 17:42:09 +00:00
foreach ( string s in fh . Filterlist ( patch . ReadPathLines ( DELETED_FILES ), false ))
2009-01-14 17:29:30 +00:00
{
2012-07-29 20:07:10 +00:00
if ( SystemIO . FileExists ( s ))
2009-01-14 17:29:30 +00:00
{
try
{
2009-02-10 23:29:01 +00:00
//TODO: Perhaps read ahead in patches to prevent creation
2012-07-29 20:07:10 +00:00
long size = SystemIO . FileLength ( s );
2010-09-11 11:12:46 +00:00
2012-07-29 20:07:10 +00:00
SystemIO . FileDelete ( s );
2010-09-11 11:12:46 +00:00
if ( m_stat as RestoreStatistics != null )
{
( m_stat as RestoreStatistics ). FilesRestored --;
( m_stat as RestoreStatistics ). SizeOfRestoredFiles -= size ;
( m_stat as RestoreStatistics ). FilesDeleted ++;
}
2009-01-14 17:29:30 +00:00
}
catch ( Exception ex )
{
2009-01-20 17:17:46 +00:00
if ( m_stat != null )
2010-09-11 12:43:55 +00:00
m_stat . LogError ( string . Format ( Strings . RSyncDir . DeleteFileError , s , ex . Message ), ex );
2009-06-17 22:47:51 +00:00
Logging . Log . WriteMessage ( string . Format ( Strings . RSyncDir . DeleteFileError , s , ex . Message ), Duplicati . Library . Logging . LogMessageType . Warning , ex );
2009-01-14 17:29:30 +00:00
}
}
else
{
2009-06-17 22:47:51 +00:00
Logging . Log . WriteMessage ( string . Format ( Strings . RSyncDir . FileToDeleteMissingError , s ), Duplicati . Library . Logging . LogMessageType . Warning );
2009-01-14 17:29:30 +00:00
}
}
2010-05-12 08:41:54 +00:00
//Delete all folders that were removed
2009-02-10 23:29:01 +00:00
if ( patch . FileExists ( DELETED_FOLDERS ))
2009-01-14 17:29:30 +00:00
{
2010-05-12 08:41:54 +00:00
if ( m_folders_to_delete == null )
m_folders_to_delete = new List < string >();
2012-08-07 17:42:09 +00:00
List < string > deletedfolders = new List < string >( fh . Filterlist ( patch . ReadPathLines ( DELETED_FOLDERS ), true ));
2009-01-14 17:29:30 +00:00
//Make sure subfolders are deleted first
2009-02-12 21:52:58 +00:00
deletedfolders . Sort ();
deletedfolders . Reverse ();
2009-01-14 17:29:30 +00:00
2010-05-12 08:41:54 +00:00
//Append to the list of folders to remove.
//The folders are removed when the patch sequence is finalized,
//because the deleted file list is not present until
//the last content file has been applied.
m_folders_to_delete . AddRange ( deletedfolders );
2009-01-14 17:29:30 +00:00
}
2010-05-12 08:41:54 +00:00
//Add folders. This mainly applies to empty folders,
//as non-empty folders will also be created when files are restored
2009-02-10 23:29:01 +00:00
if ( patch . FileExists ( ADDED_FOLDERS ))
2009-01-14 17:29:30 +00:00
{
2012-08-07 17:42:09 +00:00
List < string > addedfolders = new List < string >( fh . Filterlist ( patch . ReadPathLines ( ADDED_FOLDERS ), true ));
2009-02-10 23:29:01 +00:00
//Make sure topfolders are created first
2009-02-12 21:52:58 +00:00
addedfolders . Sort ();
2009-01-14 17:29:30 +00:00
2009-02-10 23:29:01 +00:00
foreach ( string s in addedfolders )
2009-02-02 22:11:28 +00:00
{
2012-07-29 20:07:10 +00:00
if (! SystemIO . DirectoryExists ( s ))
2009-02-02 22:11:28 +00:00
try
{
2012-07-29 20:07:10 +00:00
SystemIO . DirectoryCreate ( s );
2010-09-11 11:12:46 +00:00
if ( m_stat as RestoreStatistics != null )
( m_stat as RestoreStatistics ). FoldersRestored ++;
2009-02-02 22:11:28 +00:00
}
catch ( Exception ex )
{
if ( m_stat != null )
2010-09-11 12:43:55 +00:00
m_stat . LogError ( string . Format ( Strings . RSyncDir . CreateFolderError , s , ex . Message ), ex );
2009-06-17 22:47:51 +00:00
Logging . Log . WriteMessage ( string . Format ( Strings . RSyncDir . CreateFolderError , s , ex . Message ), Duplicati . Library . Logging . LogMessageType . Warning , ex );
2009-02-02 22:11:28 +00:00
}
}
}
2009-01-14 17:29:30 +00:00
2010-08-31 19:50:52 +00:00
if ( patch . FileExists ( ADDED_FOLDERS_TIMESTAMPS ))
{
//These times are always utc
2012-08-07 17:42:09 +00:00
string [] folders = patch . ReadPathLines ( ADDED_FOLDERS );
2010-08-31 19:50:52 +00:00
string [] timestamps = patch . ReadAllLines ( ADDED_FOLDERS_TIMESTAMPS );
2012-08-07 17:42:09 +00:00
long l ;
2010-08-31 19:50:52 +00:00
for ( int i = 0 ; i < folders . Length ; i ++)
2012-08-07 17:42:09 +00:00
if ( long . TryParse ( timestamps [ i ], out l ))
m_folderTimestamps [ RSyncDir . GetFullPathFromRelname ( destination , folders [ i ])] = Utility . Utility . EPOCH . AddSeconds ( l );
2010-08-31 19:50:52 +00:00
}
if ( patch . FileExists ( UPDATED_FOLDERS ) && patch . FileExists ( UPDATED_FOLDERS_TIMESTAMPS ))
{
//These times are always utc
2012-08-07 17:42:09 +00:00
string [] folders = patch . ReadPathLines ( UPDATED_FOLDERS );
2010-08-31 19:50:52 +00:00
string [] timestamps = patch . ReadAllLines ( UPDATED_FOLDERS_TIMESTAMPS );
long l ;
for ( int i = 0 ; i < folders . Length ; i ++)
if ( long . TryParse ( timestamps [ i ], out l ))
2012-05-14 20:49:41 +00:00
m_folderTimestamps [ RSyncDir . GetFullPathFromRelname ( destination , folders [ i ])] = Utility . Utility . EPOCH . AddSeconds ( l );
2010-08-31 19:50:52 +00:00
}
2010-05-12 08:41:54 +00:00
PartialEntryRecord pe = null ;
if ( patch . FileExists ( INCOMPLETE_FILE ))
pe = new PartialEntryRecord ( patch . ReadAllLines ( INCOMPLETE_FILE ));
PartialEntryRecord fe = null ;
if ( patch . FileExists ( COMPLETED_FILE ))
fe = new PartialEntryRecord ( patch . ReadAllLines ( COMPLETED_FILE ));
2010-09-26 20:43:16 +00:00
int lastPg = - 1 ;
2010-11-21 11:08:15 +00:00
string contentprefix = Utility . Utility . AppendDirSeparator ( CONTENT_ROOT );
2010-09-26 20:43:16 +00:00
List < string > contentfiles = m_filter . FilterList ( contentprefix , patch . ListFiles ( contentprefix ));
2010-11-21 11:08:15 +00:00
string deltaprefix = Utility . Utility . AppendDirSeparator ( DELTA_ROOT );
2010-09-26 20:43:16 +00:00
List < string > deltafiles = m_filter . FilterList ( deltaprefix , patch . ListFiles ( deltaprefix ));
2009-01-14 17:29:30 +00:00
2012-07-29 20:07:10 +00:00
string symlinkprefix = Utility . Utility . AppendDirSeparator ( SYMLINK_ROOT );
List < string > symlinks = m_filter . FilterList ( symlinkprefix , patch . ListFiles ( symlinkprefix ));
2010-09-26 20:43:16 +00:00
long totalfiles = deltafiles . Count + contentfiles . Count ;
long fileindex = 0 ;
//Restore new files
foreach ( string s in contentfiles )
2009-01-14 17:29:30 +00:00
{
2010-09-26 20:43:16 +00:00
string target = GetFullPathFromRelname ( destination , s . Substring ( contentprefix . Length ));
2009-01-20 17:17:46 +00:00
try
{
2012-07-29 20:07:10 +00:00
if (! SystemIO . DirectoryExists ( SystemIO . PathGetDirectoryName ( target )))
2009-02-10 23:29:01 +00:00
{
2009-06-17 22:47:51 +00:00
Logging . Log . WriteMessage ( string . Format ( Strings . RSyncDir . RestoreFolderMissingError , target ), Duplicati . Library . Logging . LogMessageType . Warning );
2012-07-29 20:07:10 +00:00
SystemIO . DirectoryCreate ( SystemIO . PathGetDirectoryName ( target ));
2009-02-10 23:29:01 +00:00
}
2010-09-26 20:43:16 +00:00
//Update each 0.5%
int pg = ( int )(( fileindex / ( double ) totalfiles ) * 200 );
if ( pg != lastPg )
{
ProgressEvent ( pg / 2 , target );
lastPg = pg ;
}
2009-02-10 23:29:01 +00:00
using ( System . IO . Stream s1 = patch . OpenRead ( s ))
2010-05-12 08:41:54 +00:00
{
PartialEntryRecord pex = null ;
2010-11-21 11:08:15 +00:00
Utility . TempFile partialFile = null ;
2010-11-17 19:45:25 +00:00
2011-10-28 05:43:15 +00:00
if ( pe != null && string . Equals ( pe . PlatformConvertedFilename , s ))
2010-05-12 08:41:54 +00:00
pex = pe ; //The file is incomplete
2011-10-28 05:43:15 +00:00
else if ( fe != null && string . Equals ( fe . PlatformConvertedFilename , s ))
2010-05-12 08:41:54 +00:00
pex = fe ; //The file has the final segment
2010-11-17 19:45:25 +00:00
2011-10-28 05:43:15 +00:00
if ( pex != null && string . Equals ( pex . PlatformConvertedFilename , s ))
2010-11-17 19:45:25 +00:00
{
//Ensure that the partial file list is in the correct state
if ( pex . StartOffset == 0 && m_partialDeltas . ContainsKey ( s ))
2010-05-12 08:41:54 +00:00
throw new Exception ( string . Format ( Strings . RSyncDir . InvalidPartialFileEntry , s ));
2010-11-17 19:45:25 +00:00
else if ( pex . StartOffset != 0 && ! m_partialDeltas . ContainsKey ( s ))
throw new Exception ( string . Format ( Strings . RSyncDir . InvalidPartialFileEntry , s ));
else if ( pex . StartOffset == 0 ) //First entry, so create a temp file
2010-11-21 11:08:15 +00:00
m_partialDeltas . Add ( s , new Duplicati . Library . Utility . TempFile ());
2010-11-17 19:45:25 +00:00
2010-05-12 08:41:54 +00:00
partialFile = m_partialDeltas [ s ];
}
2010-11-17 19:45:25 +00:00
else if ( m_partialDeltas . ContainsKey ( s ))
throw new Exception ( string . Format ( Strings . RSyncDir . FileShouldBePartialError , s ));
2010-05-12 08:41:54 +00:00
long startOffset = pex == null ? 0 : pex . StartOffset ;
2012-07-29 20:07:10 +00:00
using ( System . IO . Stream s2 = SystemIO . FileOpenWrite ( partialFile == null ? target : ( string ) partialFile ))
2010-05-12 08:41:54 +00:00
{
2010-11-17 19:45:25 +00:00
if ( s2 . Length != startOffset )
throw new Exception ( string . Format ( Strings . RSyncDir . InvalidPartialFileEntry , s ));
2010-05-12 08:41:54 +00:00
s2 . Position = startOffset ;
if ( startOffset == 0 )
s2 . SetLength ( 0 );
2010-11-21 11:08:15 +00:00
Utility . Utility . CopyStream ( s1 , s2 );
2010-05-12 08:41:54 +00:00
}
if ( pex != null && pex == fe )
{
2012-07-29 20:07:10 +00:00
if ( SystemIO . FileExists ( target ))
SystemIO . FileDelete ( target );
SystemIO . FileMove ( partialFile , target );
2010-05-12 08:41:54 +00:00
partialFile . Dispose ();
m_partialDeltas . Remove ( s );
}
2010-09-11 11:12:46 +00:00
if ( m_stat is RestoreStatistics && ( partialFile == null || pex == fe ))
{
( m_stat as RestoreStatistics ). FilesRestored ++;
2012-07-29 20:07:10 +00:00
( m_stat as RestoreStatistics ). SizeOfRestoredFiles += SystemIO . FileLength ( target );
2010-09-11 11:12:46 +00:00
}
2010-05-12 08:41:54 +00:00
}
2010-08-31 19:50:52 +00:00
if ( File . Exists ( target ))
{
DateTime t = patch . GetLastWriteTime ( s );
if (! isUtc )
t = t . ToUniversalTime ();
2012-07-29 20:07:10 +00:00
try { SystemIO . FileSetLastWriteTimeUtc ( target , t ); }
2010-08-31 19:50:52 +00:00
catch ( Exception ex )
{
2010-09-11 11:12:46 +00:00
if ( m_stat != null )
2010-09-11 12:43:55 +00:00
m_stat . LogWarning ( string . Format ( Strings . RSyncDir . FailedToSetFileWriteTime , target , ex . Message ), ex );
2010-08-31 19:50:52 +00:00
}
}
2009-01-20 17:17:46 +00:00
}
catch ( Exception ex )
{
2009-02-10 23:29:01 +00:00
if ( m_stat != null )
2010-09-11 12:43:55 +00:00
m_stat . LogError ( string . Format ( Strings . RSyncDir . RestoreFileError , s , ex . Message ), ex );
2009-06-17 22:47:51 +00:00
Logging . Log . WriteMessage ( string . Format ( Strings . RSyncDir . RestoreFileError , s , ex . Message ), Duplicati . Library . Logging . LogMessageType . Error , ex );
2009-01-20 17:17:46 +00:00
}
2010-09-26 20:43:16 +00:00
fileindex ++;
2009-01-14 17:29:30 +00:00
}
2010-05-12 08:41:54 +00:00
//Patch modfied files
2010-09-26 20:43:16 +00:00
foreach ( string s in deltafiles )
2009-01-14 17:29:30 +00:00
{
2010-09-26 20:43:16 +00:00
string target = GetFullPathFromRelname ( destination , s . Substring ( deltaprefix . Length ));
2009-01-14 17:29:30 +00:00
try
{
2010-09-26 20:43:16 +00:00
//Update each 0.5%
int pg = ( int )(( fileindex / ( double ) totalfiles ) * 200 );
if ( pg != lastPg )
{
ProgressEvent ( pg / 2 , target );
lastPg = pg ;
}
2012-07-29 20:07:10 +00:00
if (! SystemIO . DirectoryExists ( SystemIO . PathGetDirectoryName ( target )))
2009-02-10 23:29:01 +00:00
{
2009-06-17 22:47:51 +00:00
Logging . Log . WriteMessage ( string . Format ( Strings . RSyncDir . RestoreFolderDeltaError , target ), Duplicati . Library . Logging . LogMessageType . Warning );
2012-07-29 20:07:10 +00:00
SystemIO . DirectoryCreate ( SystemIO . PathGetDirectoryName ( target ));
2009-02-10 23:29:01 +00:00
}
2010-05-12 08:41:54 +00:00
PartialEntryRecord pex = null ;
2011-10-28 05:43:15 +00:00
if ( pe != null && string . Equals ( pe . PlatformConvertedFilename , s ))
2010-05-12 08:41:54 +00:00
pex = pe ; //The file is incomplete
2011-10-28 05:43:15 +00:00
else if ( fe != null && string . Equals ( fe . PlatformConvertedFilename , s ))
2010-05-12 08:41:54 +00:00
pex = fe ; //The file has the final segment
2010-11-21 11:08:15 +00:00
Utility . TempFile tempDelta = null ;
2010-05-12 08:41:54 +00:00
2011-10-28 05:43:15 +00:00
if ( pex != null && string . Equals ( pex . PlatformConvertedFilename , s ))
2010-05-12 08:41:54 +00:00
{
//Ensure that the partial file list is in the correct state
if ( pex . StartOffset == 0 && m_partialDeltas . ContainsKey ( s ))
throw new Exception ( string . Format ( Strings . RSyncDir . InvalidPartialFileEntry , s ));
else if ( pex . StartOffset != 0 && ! m_partialDeltas . ContainsKey ( s ))
throw new Exception ( string . Format ( Strings . RSyncDir . InvalidPartialFileEntry , s ));
else if ( pex . StartOffset == 0 ) //First entry, so create a temp file
2010-11-21 11:08:15 +00:00
m_partialDeltas . Add ( s , new Duplicati . Library . Utility . TempFile ());
2010-05-12 08:41:54 +00:00
//Dump the content in the temp file at the specified offset
2012-07-29 20:07:10 +00:00
using ( System . IO . Stream st = SystemIO . FileOpenWrite ( m_partialDeltas [ s ]))
2010-05-12 08:41:54 +00:00
{
if ( st . Length != pex . StartOffset )
throw new Exception ( string . Format ( Strings . RSyncDir . InvalidPartialFileEntry , s ));
st . Position = pex . StartOffset ;
using ( System . IO . Stream s2 = patch . OpenRead ( s ))
2010-11-21 11:08:15 +00:00
Utility . Utility . CopyStream ( s2 , st );
2010-05-12 08:41:54 +00:00
}
2010-08-12 18:37:15 +00:00
//We can't process it until it is received completely
2010-05-12 08:41:54 +00:00
if ( pex != fe )
continue ;
tempDelta = m_partialDeltas [ s ];
m_partialDeltas . Remove ( s );
}
else if ( m_partialDeltas . ContainsKey ( s ))
throw new Exception ( string . Format ( Strings . RSyncDir . FileShouldBePartialError , s ));
2010-11-21 11:08:15 +00:00
using ( Utility . TempFile tempfile = new Utility . TempFile ())
2010-05-12 08:41:54 +00:00
using ( tempDelta ) //May be null, but the using directive does not care
2009-02-10 23:29:01 +00:00
{
2010-05-12 08:41:54 +00:00
//Use either the patch directly, or the partial temp file
2012-07-29 20:07:10 +00:00
System . IO . Stream deltaStream = tempDelta == null ? patch . OpenRead ( s ) : SystemIO . FileOpenRead ( tempDelta );
2010-05-12 08:41:54 +00:00
using ( System . IO . Stream s2 = deltaStream )
2012-07-29 20:07:10 +00:00
using ( System . IO . Stream s1 = SystemIO . FileOpenRead ( target ))
using ( System . IO . Stream s3 = SystemIO . FileCreate ( tempfile ))
2009-02-10 23:29:01 +00:00
SharpRSync . Interface . PatchFile ( s1 , s2 , s3 );
2010-09-11 11:12:46 +00:00
if ( m_stat as RestoreStatistics != null )
{
2012-07-29 20:07:10 +00:00
( m_stat as RestoreStatistics ). SizeOfRestoredFiles -= SystemIO . FileLength ( target );
( m_stat as RestoreStatistics ). SizeOfRestoredFiles += SystemIO . FileLength ( tempfile );
2010-09-11 11:12:46 +00:00
( m_stat as RestoreStatistics ). FilesPatched ++;
}
2012-07-29 20:07:10 +00:00
SystemIO . FileDelete ( target );
2011-03-10 18:26:38 +00:00
2012-07-29 20:07:10 +00:00
try { SystemIO . FileMove ( tempfile , target ); }
2011-03-10 18:26:38 +00:00
catch
{
//The OS sometimes reports the file as existing even after a delete
// this seems to be related to MS Security Essentials?
System . Threading . Thread . Sleep ( 500 );
2012-07-29 20:07:10 +00:00
SystemIO . FileMove ( tempfile , target );
2011-03-10 18:26:38 +00:00
}
2009-02-10 23:29:01 +00:00
}
2010-08-31 19:50:52 +00:00
if ( File . Exists ( target ))
{
DateTime t = patch . GetLastWriteTime ( s );
if (! isUtc )
t = t . ToUniversalTime ();
2012-07-29 20:07:10 +00:00
try { SystemIO . FileSetLastWriteTimeUtc ( target , t ); }
2010-08-31 19:50:52 +00:00
catch ( Exception ex )
{
2010-09-11 11:12:46 +00:00
if ( m_stat != null )
2010-09-11 12:43:55 +00:00
m_stat . LogWarning ( string . Format ( Strings . RSyncDir . FailedToSetFileWriteTime , target , ex . Message ), ex );
2010-08-31 19:50:52 +00:00
}
}
2009-01-14 17:29:30 +00:00
}
catch ( Exception ex )
{
2009-02-10 23:29:01 +00:00
if ( m_stat != null )
2010-09-11 12:43:55 +00:00
m_stat . LogError ( string . Format ( Strings . RSyncDir . RestoreFileError , s , ex . Message ), ex );
2009-06-17 22:47:51 +00:00
Logging . Log . WriteMessage ( string . Format ( Strings . RSyncDir . RestoreFileError , s , ex . Message ), Duplicati . Library . Logging . LogMessageType . Error , ex );
2009-02-10 23:29:01 +00:00
2012-07-29 20:07:10 +00:00
try { SystemIO . FileDelete ( target ); }
2009-02-10 23:29:01 +00:00
catch { }
2009-01-14 17:29:30 +00:00
}
2010-09-26 20:43:16 +00:00
fileindex ++;
2009-01-14 17:29:30 +00:00
}
2012-07-29 20:07:10 +00:00
//Re-create symlinks (no progress report here, should be really fast)
foreach ( string s in symlinks )
{
string target = GetFullPathFromRelname ( destination , s . Substring ( symlinkprefix . Length ));
string symlinktarget = "" ;
try
{
2012-08-07 17:42:09 +00:00
symlinktarget = patch . ReadPathString ( s );
2012-07-29 20:07:10 +00:00
bool isDir = symlinktarget [ symlinktarget . Length - 1 ] == Path . DirectorySeparatorChar ;
if ( isDir )
symlinktarget = symlinktarget . Substring ( 0 , symlinktarget . Length - 1 );
try
{
//In case another symlink is present, we "update" it
if ( SystemIO . FileExists ( target ) && ( SystemIO . GetFileAttributes ( target ) & FileAttributes . ReparsePoint ) != 0 )
SystemIO . FileDelete ( target );
}
catch ( Exception ex )
{
Logging . Log . WriteMessage ( string . Format ( Strings . RSyncDir . RestoreFileError , s , ex . Message ), Duplicati . Library . Logging . LogMessageType . Error , ex );
}
SystemIO . CreateSymlink ( target , symlinktarget , isDir );
}
catch ( Exception ex )
{
if ( m_stat != null )
m_stat . LogError ( string . Format ( Strings . RSyncDir . RestoreFileError , s , ex . Message ), ex );
Logging . Log . WriteMessage ( string . Format ( Strings . RSyncDir . RestoreFileError , s , ex . Message ), Duplicati . Library . Logging . LogMessageType . Error , ex );
try { SystemIO . FileDelete ( target ); }
catch { }
try
{
if (! string . IsNullOrEmpty ( symlinktarget ))
using ( System . IO . StreamWriter sw = new System . IO . StreamWriter ( SystemIO . FileOpenWrite ( target )))
sw . Write ( symlinktarget );
}
catch
{
}
}
}
2009-01-14 17:29:30 +00:00
}
2011-10-30 14:31:46 +00:00
public bool AnyChangesFound
{
get
{
long count = 0 ;
if ( m_deletedfiles != null )
count += m_deletedfiles . Count ;
if ( m_deletedfolders != null )
count += m_deletedfolders . Count ;
count += m_diffedfiles ;
count += m_addedfiles ;
count += m_diffedfilessize ;
count += m_addedfilessize ;
if ( m_newfolders != null )
count += m_newfolders . Count ;
return count != 0 ;
}
}
2009-01-14 17:29:30 +00:00
#region IDisposable Members
public void Dispose ()
{
2012-03-20 20:22:43 +00:00
if ( m_lastPartialFile != null )
{
try { m_lastPartialFile . Dispose (); }
catch { }
m_lastPartialFile = null ;
}
2009-03-26 19:39:50 +00:00
if ( m_patches != null )
{
2012-08-07 17:42:09 +00:00
foreach ( CompressionWrapper arc in m_patches )
2009-03-26 19:39:50 +00:00
try { arc . Dispose (); }
catch { }
m_patches = null ;
}
2009-03-30 20:17:54 +00:00
2010-05-12 08:41:54 +00:00
if ( m_partialDeltas != null )
{
2010-11-21 11:08:15 +00:00
foreach ( Utility . TempFile tf in m_partialDeltas . Values )
2010-05-12 08:41:54 +00:00
try
{
if ( tf != null )
tf . Dispose ();
}
catch { }
m_partialDeltas = null ;
}
2009-03-30 20:17:54 +00:00
if ( m_stat is BackupStatistics )
{
BackupStatistics bs = m_stat as BackupStatistics ;
2010-09-01 21:04:18 +00:00
if ( m_deletedfiles != null )
bs . DeletedFiles = m_deletedfiles . Count ;
if ( m_deletedfolders != null )
bs . DeletedFolders = m_deletedfolders . Count ;
2010-09-09 17:18:33 +00:00
bs . ModifiedFiles = m_diffedfiles ;
bs . AddedFiles = m_addedfiles ;
bs . ExaminedFiles = m_examinedfiles ;
2010-11-04 21:37:32 +00:00
bs . OpenedFiles = m_filesopened ;
2010-09-09 17:18:33 +00:00
bs . SizeOfModifiedFiles = m_diffedfilessize ;
bs . SizeOfAddedFiles = m_addedfilessize ;
bs . SizeOfExaminedFiles = m_examinedfilesize ;
2010-08-12 18:32:15 +00:00
if ( m_unproccesed != null && m_unproccesed . Files != null )
bs . UnprocessedFiles = m_unproccesed . Files . Count ;
2010-09-01 21:04:18 +00:00
if ( m_newfolders != null )
bs . AddedFolders = m_newfolders . Count ;
2009-03-30 20:17:54 +00:00
}
2010-08-12 18:32:15 +00:00
if ( m_snapshot != null )
2009-01-14 17:29:30 +00:00
{
2010-08-12 18:32:15 +00:00
m_snapshot . Dispose ();
m_snapshot = null ;
2009-01-14 17:29:30 +00:00
}
2010-08-12 18:32:15 +00:00
}
2009-02-02 22:11:28 +00:00
2010-08-12 18:32:15 +00:00
#endregion
2010-05-12 08:41:54 +00:00
/// <summary>
/// Gets or sets a value that indicates if the file modification time should be used as an indicator for file modification
/// </summary>
2009-02-26 18:33:52 +00:00
public bool DisableFiletimeCheck
{
get { return m_disableFiletimeCheck ; }
set { m_disableFiletimeCheck = value ; }
}
2009-03-21 12:46:54 +00:00
2010-05-12 08:41:54 +00:00
/// <summary>
/// Gets or sets the largest file allowed
/// </summary>
2009-03-26 19:39:50 +00:00
public long MaxFileSize
{
get { return m_maxFileSize ; }
set { m_maxFileSize = value ; }
}
2010-05-12 08:41:54 +00:00
/// <summary>
/// Gets a list of files and folders that are not yet processed
/// </summary>
/// <returns></returns>
2009-03-21 12:46:54 +00:00
public List < string > UnmatchedFiles ()
{
List < string > lst = new List < string >();
lst . AddRange ( m_oldFolders . Keys );
lst . AddRange ( m_oldSignatures . Keys );
2012-07-29 20:07:10 +00:00
lst . AddRange ( m_oldSymlinks . Keys );
2009-03-21 12:46:54 +00:00
return lst ;
}
2009-03-29 21:59:00 +00:00
2010-05-12 08:41:54 +00:00
/// <summary>
/// Extracts the files found in a signature volume
/// </summary>
/// <param name="patch">The signature volume to read</param>
/// <returns>A list of file or folder names and their types</returns>
2012-08-07 17:42:09 +00:00
public List < KeyValuePair < PatchFileType , string >> ListPatchFiles ( CompressionWrapper patch )
2009-04-07 22:02:51 +00:00
{
2012-08-07 17:42:09 +00:00
List < CompressionWrapper > patches = new List < CompressionWrapper >();
2009-04-07 22:02:51 +00:00
patches . Add ( patch );
return ListPatchFiles ( patches );
}
2010-05-12 08:41:54 +00:00
/// <summary>
/// Extracts the files found in a signature volume
/// </summary>
/// <param name="patchs">The signature volumes to read</param>
/// <returns>A list of file or folder names and their types</returns>
2012-08-07 17:42:09 +00:00
public List < KeyValuePair < PatchFileType , string >> ListPatchFiles ( List < CompressionWrapper > patches )
2009-03-29 21:59:00 +00:00
{
List < KeyValuePair < PatchFileType , string >> files = new List < KeyValuePair < PatchFileType , string >>();
2010-05-12 08:41:54 +00:00
KeyValuePair < PatchFileType , string >[] signatures = new KeyValuePair < PatchFileType , string >[] {
2010-11-21 11:08:15 +00:00
new KeyValuePair < PatchFileType , string >( PatchFileType . AddedOrUpdatedFile , Utility . Utility . AppendDirSeparator ( COMBINED_SIGNATURE_ROOT )),
new KeyValuePair < PatchFileType , string >( PatchFileType . AddedFile , Utility . Utility . AppendDirSeparator ( CONTENT_SIGNATURE_ROOT )),
new KeyValuePair < PatchFileType , string >( PatchFileType . UpdatedFile , Utility . Utility . AppendDirSeparator ( DELTA_SIGNATURE_ROOT )),
2010-05-12 08:41:54 +00:00
};
2010-11-21 11:08:15 +00:00
string content_prefix = Utility . Utility . AppendDirSeparator ( CONTENT_ROOT );
string delta_prefix = Utility . Utility . AppendDirSeparator ( DELTA_ROOT );
string control_prefix = Utility . Utility . AppendDirSeparator ( CONTROL_ROOT );
2012-08-07 20:12:12 +00:00
string symlink_prefix = Utility . Utility . AppendDirSeparator ( SYMLINK_ROOT );
2010-05-17 15:35:58 +00:00
Dictionary < string , bool > partials = new Dictionary < string , bool >();
2009-03-29 21:59:00 +00:00
2012-08-07 17:42:09 +00:00
foreach ( CompressionWrapper arch in patches )
2009-03-29 21:59:00 +00:00
{
if ( arch . FileExists ( DELETED_FILES ))
2012-08-07 17:42:09 +00:00
foreach ( string s in arch . ReadPathLines ( DELETED_FILES ))
2009-03-30 20:17:54 +00:00
files . Add ( new KeyValuePair < PatchFileType , string >( PatchFileType . DeletedFile , s ));
2009-03-29 21:59:00 +00:00
2010-05-12 08:41:54 +00:00
foreach ( KeyValuePair < PatchFileType , string > sigentry in signatures )
2012-08-07 17:42:09 +00:00
foreach ( string f in arch . ListFiles ( sigentry . Value ))
2010-05-12 08:41:54 +00:00
{
if ( partials . ContainsKey ( f ))
partials . Remove ( f );
files . Add ( new KeyValuePair < PatchFileType , string >( sigentry . Key , f . Substring ( sigentry . Value . Length )));
}
2009-03-29 21:59:00 +00:00
2012-08-07 17:42:09 +00:00
foreach ( string f in arch . ListFiles ( control_prefix ))
2009-03-29 21:59:00 +00:00
files . Add ( new KeyValuePair < PatchFileType , string >( PatchFileType . ControlFile , f . Substring ( control_prefix . Length )));
2012-08-07 20:12:12 +00:00
foreach ( string f in arch . ListFiles ( symlink_prefix ))
2012-08-07 20:14:20 +00:00
files . Add ( new KeyValuePair < PatchFileType , string >( PatchFileType . Symlink , f . Substring ( symlink_prefix . Length )));
2012-08-07 20:12:12 +00:00
2009-03-29 21:59:00 +00:00
if ( arch . FileExists ( DELETED_FOLDERS ))
2012-08-07 17:42:09 +00:00
foreach ( string s in arch . ReadPathLines ( DELETED_FOLDERS ))
2009-03-29 21:59:00 +00:00
files . Add ( new KeyValuePair < PatchFileType , string >( PatchFileType . DeletedFolder , s ));
if ( arch . FileExists ( ADDED_FOLDERS ))
2012-08-07 17:42:09 +00:00
foreach ( string s in arch . ReadPathLines ( ADDED_FOLDERS ))
2009-03-29 21:59:00 +00:00
files . Add ( new KeyValuePair < PatchFileType , string >( PatchFileType . AddedFolder , s ));
2010-05-12 08:41:54 +00:00
if ( arch . FileExists ( INCOMPLETE_FILE ))
{
2010-05-17 15:35:58 +00:00
PartialEntryRecord pre = new PartialEntryRecord ( arch . ReadAllLines ( INCOMPLETE_FILE ));
2012-08-07 17:42:09 +00:00
string filename = CompressionWrapper . ToSystemPath ( pre . Filename );
2010-05-17 15:35:58 +00:00
if ( filename . StartsWith ( content_prefix ))
{
if (! partials . ContainsKey ( filename . Substring ( content_prefix . Length )))
partials . Add ( filename . Substring ( content_prefix . Length ), false );
}
else if ( filename . StartsWith ( delta_prefix ))
{
if (! partials . ContainsKey ( filename . Substring ( delta_prefix . Length )))
partials . Add ( filename . Substring ( delta_prefix . Length ), false );
}
}
if ( arch . FileExists ( COMPLETED_FILE ))
{
PartialEntryRecord pre = new PartialEntryRecord ( arch . ReadAllLines ( COMPLETED_FILE ));
2012-08-07 17:42:09 +00:00
string filename = CompressionWrapper . ToSystemPath ( pre . Filename );
2010-05-12 08:41:54 +00:00
if ( filename . StartsWith ( content_prefix ))
2010-05-17 15:35:58 +00:00
partials [ filename . Substring ( content_prefix . Length )]= true ;
2010-05-12 08:41:54 +00:00
else if ( filename . StartsWith ( delta_prefix ))
2010-05-17 15:35:58 +00:00
partials [ filename . Substring ( delta_prefix . Length )] = true ;
2010-05-12 08:41:54 +00:00
}
2009-03-29 21:59:00 +00:00
}
2010-05-17 15:35:58 +00:00
foreach ( KeyValuePair < string , bool > s in partials )
{
//Index of last found file that matches
int lastIx = - 1 ;
for ( int i = 0 ; i < files . Count ; i ++)
{
KeyValuePair < PatchFileType , string > px = files [ i ];
if (( px . Key == PatchFileType . AddedFile || px . Key == PatchFileType . AddedOrUpdatedFile || px . Key == PatchFileType . UpdatedFile ) && px . Value == s . Key )
{
//We have a new file, if one is already found, remove it
if ( lastIx != - 1 )
{
files . RemoveAt ( lastIx );
i --;
}
lastIx = i ;
}
}
//The file is incomplete, remove that only file entry, and insert the incomplete file entry
if (! s . Value )
{
2010-05-22 08:00:48 +00:00
if ( lastIx != - 1 )
files . RemoveAt ( lastIx );
2010-05-17 15:35:58 +00:00
files . Add ( new KeyValuePair < PatchFileType , string >( PatchFileType . IncompleteFile , s . Key ));
}
//If the file is completed, there is now only one entry left
}
2010-05-12 08:41:54 +00:00
2009-03-29 21:59:00 +00:00
return files ;
}
2009-03-30 20:17:54 +00:00
/// <summary>
/// An internal helper class to collect filenames from the enumeration callback
/// </summary>
private class PathCollector
{
2012-07-29 20:07:10 +00:00
private readonly Options . SymlinkStrategy m_symlinkHandling ;
private readonly FileAttributes m_attributeMask ;
private readonly Utility . FilenameFilter m_filter ;
private readonly Snapshots . ISnapshotService m_snapshot ;
private readonly CommunicationStatistics m_stat ;
2009-03-30 20:17:54 +00:00
private List < string > m_files = new List < string >();
private List < string > m_folders = new List < string >();
private List < string > m_errors = new List < string >();
private List < string > m_filesWithError = new List < string >();
private List < string > m_filesTooLarge = new List < string >();
2012-07-29 20:07:10 +00:00
private List < KeyValuePair < string , string >> m_symlinks = new List < KeyValuePair < string , string >>();
public PathCollector ( Snapshots . ISnapshotService snapshot , Options . SymlinkStrategy symlinkHandling , FileAttributes attributeMask , Utility . FilenameFilter filter , CommunicationStatistics stat )
{
m_symlinkHandling = symlinkHandling ;
m_filter = filter ;
m_attributeMask = attributeMask ;
m_snapshot = snapshot ;
m_stat = stat ;
}
2009-03-30 20:17:54 +00:00
2012-07-29 20:07:10 +00:00
public bool Callback ( string rootpath , string path , FileAttributes attributes )
2009-03-30 20:17:54 +00:00
{
2012-07-29 20:07:10 +00:00
if (( attributes & Utility . Utility . ATTRIBUTE_ERROR ) != 0 )
{
m_errors . Add ( path );
return false ;
}
if (( attributes & m_attributeMask ) != 0 )
return false ;
if (! m_filter . ShouldInclude ( rootpath , path ))
return false ;
if (( attributes & FileAttributes . ReparsePoint ) != 0 )
{
if ( m_symlinkHandling == Options . SymlinkStrategy . Ignore )
return false ;
else if ( m_symlinkHandling == Options . SymlinkStrategy . Store )
{
try
{
//We treat symlinks as files, even if they point to folders
if ( path [ path . Length - 1 ] == Path . DirectorySeparatorChar )
path = path . Substring ( 0 , path . Length - 1 );
string s = m_snapshot . GetSymlinkTarget ( path );
if ( s != null )
{
if (( attributes & FileAttributes . Directory ) != 0 )
s = Utility . Utility . AppendDirSeparator ( s );
m_symlinks . Add ( new KeyValuePair < string , string >( path , s ));
}
}
catch ( Exception ex )
{
Logging . Log . WriteMessage ( string . Format ( "Failed to obtain symlink target for {0}, message: {1}" , path , ex . Message ), Duplicati . Library . Logging . LogMessageType . Warning , ex );
if ( m_stat != null )
m_stat . LogWarning ( string . Format ( "Failed to obtain symlink target for {0}, message: {1}" , path , ex . Message ), ex );
m_errors . Add ( path );
}
return false ;
}
}
if (( attributes & FileAttributes . Directory ) != 0 )
2009-03-30 20:17:54 +00:00
m_folders . Add ( path );
2012-07-29 20:07:10 +00:00
else
2009-03-30 20:17:54 +00:00
m_files . Add ( path );
2012-07-29 20:07:10 +00:00
return true ;
2009-03-30 20:17:54 +00:00
}
public List < string > Files { get { return m_files ; } }
public List < string > Folders { get { return m_folders ; } }
public List < string > Errors { get { return m_errors ; } }
public List < string > FilesWithError { get { return m_filesWithError ; } }
public List < string > FilesTooLarge { get { return m_filesTooLarge ; } }
2012-07-29 20:07:10 +00:00
public List < KeyValuePair < string , string >> Symlinks { get { return m_symlinks ; } }
2009-03-30 20:17:54 +00:00
public bool IsAffectedByError ( string path )
{
foreach ( string s in m_errors )
if ( path . StartsWith ( s ))
return true ;
2009-04-23 19:34:47 +00:00
return m_filesWithError . Contains ( path );
2009-03-30 20:17:54 +00:00
}
}
2010-10-23 13:10:54 +00:00
/// <summary>
/// Helper method to search a signature file for existence of a specified file
/// </summary>
/// <param name="mfi">The manifest that the signature file derives from</param>
/// <param name="fileToFind">The files to find</param>
/// <param name="signature">The signature file</param>
2012-08-07 17:42:09 +00:00
internal static void ContainsFile ( Manifestfile mfi , string [] filesToFind , CompressionWrapper signature )
2010-10-23 13:10:54 +00:00
{
string [] prefixes = new string [] {
2010-11-21 11:08:15 +00:00
Utility . Utility . AppendDirSeparator ( COMBINED_SIGNATURE_ROOT ),
Utility . Utility . AppendDirSeparator ( CONTENT_SIGNATURE_ROOT ),
Utility . Utility . AppendDirSeparator ( DELTA_SIGNATURE_ROOT )
2010-10-23 13:10:54 +00:00
};
foreach ( string prefix in prefixes )
2012-08-07 17:42:09 +00:00
foreach ( string f in signature . ListFiles ( prefix ))
2010-10-23 13:10:54 +00:00
{
string fname = f . Substring ( prefix . Length );
for ( int i = 0 ; i < filesToFind . Length ; i ++)
{
if ( string . IsNullOrEmpty ( filesToFind [ i ]))
continue ;
string fileToFind = filesToFind [ i ];
string name = System . IO . Path . IsPathRooted ( fileToFind ) ? GetRelativeName ( mfi . SourceDirs , fileToFind ) : fileToFind ;
2010-11-21 11:08:15 +00:00
if ( fname . Equals ( name , Utility . Utility . ClientFilenameStringComparision ))
2010-10-23 13:10:54 +00:00
filesToFind [ i ] = null ;
}
}
}
2009-01-14 17:29:30 +00:00
}
}