2013-03-27 16:06:45 +01:00
// Copyright (C) 2013, The Duplicati Team
// http://www.duplicati.com, opensource@duplicati.com
//
// 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
using System ;
using System.Linq ;
using System.Collections.Generic ;
2013-05-08 20:17:07 +02:00
using Duplicati.Library.Main.Database ;
using Duplicati.Library.Main.Volumes ;
2013-03-27 16:06:45 +01:00
using System.Text ;
2013-05-08 20:17:07 +02:00
namespace Duplicati.Library.Main.Operation
2013-03-27 16:06:45 +01:00
{
2013-05-25 16:40:15 +02:00
internal class CompactHandler
2013-03-27 16:06:45 +01:00
{
protected string m_backendurl ;
2013-05-06 09:58:19 +02:00
protected Options m_options ;
2013-05-25 16:40:15 +02:00
protected CompactResults m_result ;
2013-03-27 16:06:45 +01:00
2013-05-25 16:40:15 +02:00
public CompactHandler ( string backend , Options options , CompactResults result )
2013-03-27 16:06:45 +01:00
{
m_backendurl = backend ;
m_options = options ;
2013-05-25 16:40:15 +02:00
m_result = result ;
2013-03-27 16:06:45 +01:00
}
2013-05-25 16:40:15 +02:00
public virtual void Run ()
2013-06-09 16:26:08 +02:00
{
if (! System . IO . File . Exists ( m_options . Dbpath ))
throw new Exception ( string . Format ( "Database file does not exist: {0}" , m_options . Dbpath ));
2013-03-27 16:06:45 +01:00
2013-06-09 16:26:08 +02:00
using ( var db = new LocalDeleteDatabase ( m_options . Dbpath , true ))
using ( var tr = db . BeginTransaction ())
{
2013-05-25 16:40:15 +02:00
m_result . SetDatabase ( db );
2013-06-09 16:26:08 +02:00
Utility . VerifyParameters ( db , m_options );
2013-03-31 19:28:46 +02:00
2013-07-01 14:40:45 +02:00
var changed = DoCompact ( db , false , tr );
if ( changed && m_options . UploadVerificationFile )
FilelistProcessor . UploadVerificationFile ( m_backendurl , m_options , m_result . BackendWriter , db , null );
2013-06-09 16:26:08 +02:00
if (! m_options . Dryrun )
{
using ( new Logging . Timer ( "CommitCompact" ))
tr . Commit ();
2013-08-23 22:15:07 +02:00
if ( changed )
db . Vacuum ();
2013-06-09 16:26:08 +02:00
}
2013-03-27 16:06:45 +01:00
else
tr . Rollback ();
2013-08-23 22:15:07 +02:00
}
2013-03-27 16:06:45 +01:00
}
2013-07-01 14:40:45 +02:00
internal bool DoCompact ( LocalDeleteDatabase db , bool hasVerifiedBackend , System . Data . IDbTransaction transaction )
2013-03-27 16:06:45 +01:00
{
2013-05-21 21:16:01 +02:00
var report = db . GetCompactReport ( m_options . VolumeSize , m_options . Threshold , m_options . SmallFileSize , m_options . SmallFileMaxCount , transaction );
2013-05-25 16:40:15 +02:00
report . ReportCompactData ( m_result );
2013-03-27 16:06:45 +01:00
2013-04-07 09:10:04 +02:00
if ( report . ShouldReclaim || report . ShouldCompact )
2013-03-27 16:06:45 +01:00
{
2013-05-25 16:40:15 +02:00
using ( var backend = new BackendManager ( m_backendurl , m_options , m_result . BackendWriter , db ))
2013-03-27 16:06:45 +01:00
{
2013-05-08 19:57:13 +02:00
if (! hasVerifiedBackend && ! m_options . NoBackendverification )
2013-05-25 16:40:15 +02:00
FilelistProcessor . VerifyRemoteList ( backend , m_options , db , m_result . BackendWriter );
2013-03-27 16:06:45 +01:00
BlockVolumeWriter newvol = new BlockVolumeWriter ( m_options );
newvol . VolumeID = db . RegisterRemoteVolume ( newvol . RemoteFilename , RemoteVolumeType . Blocks , RemoteVolumeState . Temporary , transaction );
2013-04-29 23:50:38 +02:00
IndexVolumeWriter newvolindex = null ;
2013-07-01 10:33:07 +02:00
if ( m_options . IndexfilePolicy != Options . IndexFileStrategy . None )
2013-03-27 16:06:45 +01:00
{
2013-04-29 23:50:38 +02:00
newvolindex = new IndexVolumeWriter ( m_options );
2013-08-27 10:23:21 +02:00
newvolindex . VolumeID = db . RegisterRemoteVolume ( newvolindex . RemoteFilename , RemoteVolumeType . Index , RemoteVolumeState . Temporary , transaction );
db . AddIndexBlockLink ( newvolindex . VolumeID , newvol . VolumeID , transaction );
2013-04-29 23:50:38 +02:00
newvolindex . StartVolume ( newvol . RemoteFilename );
2013-03-27 16:06:45 +01:00
}
long blocksInVolume = 0 ;
long discardedBlocks = 0 ;
long discardedSize = 0 ;
2013-05-08 19:57:13 +02:00
byte [] buffer = new byte [ m_options . Blocksize ];
2013-07-01 11:58:33 +02:00
var remoteList = db . GetRemoteVolumes (). ToArray ();
2013-03-27 16:06:45 +01:00
//These are for bookkeeping
var uploadedVolumes = new List < KeyValuePair < string , long >>();
var deletedVolumes = new List < KeyValuePair < string , long >>();
2013-04-08 22:24:54 +02:00
var downloadedVolumes = new List < KeyValuePair < string , long >>();
2013-03-27 16:06:45 +01:00
2013-04-08 22:24:54 +02:00
//We start by deleting unused volumes to save space before uploading new stuff
var fullyDeleteable = ( from v in remoteList
where report . DeleteableVolumes . Contains ( v . Name )
select ( IRemoteVolume ) v ). ToList ();
deletedVolumes . AddRange ( DoDelete ( db , backend , fullyDeleteable , transaction ));
// This list is used to pick up unused volumes,
// so they can be deleted once the upload of the
// required fragments is complete
var deleteableVolumes = new List < IRemoteVolume >();
2013-04-07 09:10:04 +02:00
if ( report . ShouldCompact )
2013-03-27 16:06:45 +01:00
{
2013-04-08 22:24:54 +02:00
var volumesToDownload = ( from v in remoteList
where report . CompactableVolumes . Contains ( v . Name )
select ( IRemoteVolume ) v ). ToList ();
2013-04-07 09:03:54 +02:00
using ( var q = db . CreateBlockQueryHelper ( m_options , transaction ))
2013-03-27 16:06:45 +01:00
{
2013-04-16 22:36:19 +02:00
foreach ( var entry in new AsyncDownloader ( volumesToDownload , backend ))
2013-05-20 15:02:36 +02:00
using ( var tmpfile = entry . TempFile )
2013-03-27 16:06:45 +01:00
{
2013-05-20 15:02:36 +02:00
downloadedVolumes . Add ( new KeyValuePair < string , long >( entry . Name , entry . Size ));
var inst = VolumeBase . ParseFilename ( entry . Name );
2013-04-16 22:36:19 +02:00
using ( var f = new BlockVolumeReader ( inst . CompressionModule , tmpfile , m_options ))
2013-03-27 16:06:45 +01:00
{
2013-04-07 09:03:54 +02:00
foreach ( var e in f . Blocks )
2013-03-27 16:06:45 +01:00
{
2013-04-07 09:03:54 +02:00
if ( q . UseBlock ( e . Key , e . Value ))
2013-03-27 16:06:45 +01:00
{
2013-04-07 09:03:54 +02:00
//TODO: How do we get the compression hint? Reverse query for filename in db?
var s = f . ReadBlock ( e . Key , buffer );
if ( s != e . Value )
throw new Exception ( "Size mismatch problem, {0} vs {1}" );
2013-08-30 22:46:34 +02:00
newvol . AddBlock ( e . Key , buffer , 0 , s , Duplicati . Library . Interface . CompressionHint . Compressible );
2013-04-29 23:50:38 +02:00
if ( newvolindex != null )
newvolindex . AddBlock ( e . Key , e . Value );
2013-04-07 09:03:54 +02:00
db . MoveBlockToNewVolume ( e . Key , e . Value , newvol . VolumeID , transaction );
blocksInVolume ++;
2013-03-27 16:06:45 +01:00
2013-04-07 09:03:54 +02:00
if ( newvol . Filesize > m_options . VolumeSize )
2013-03-27 16:06:45 +01:00
{
2013-04-07 09:03:54 +02:00
uploadedVolumes . Add ( new KeyValuePair < string , long >( newvol . RemoteFilename , new System . IO . FileInfo ( newvol . LocalFilename ). Length ));
2013-04-29 23:50:38 +02:00
if ( newvolindex != null )
uploadedVolumes . Add ( new KeyValuePair < string , long >( newvolindex . RemoteFilename , new System . IO . FileInfo ( newvolindex . LocalFilename ). Length ));
2013-04-07 09:03:54 +02:00
2013-05-25 16:56:53 +02:00
if (! m_options . Dryrun )
2013-04-29 23:50:38 +02:00
backend . Put ( newvol , newvolindex );
2013-03-27 16:06:45 +01:00
else
2013-05-25 17:32:39 +02:00
m_result . AddDryrunMessage ( string . Format ( "Would upload generated blockset of size {0}" , Library . Utility . Utility . FormatSizeString ( new System . IO . FileInfo ( newvol . LocalFilename ). Length )));
2013-04-07 09:03:54 +02:00
newvol = new BlockVolumeWriter ( m_options );
newvol . VolumeID = db . RegisterRemoteVolume ( newvol . RemoteFilename , RemoteVolumeType . Blocks , RemoteVolumeState . Temporary , transaction );
2013-07-01 10:33:07 +02:00
if ( m_options . IndexfilePolicy != Options . IndexFileStrategy . None )
2013-04-07 09:03:54 +02:00
{
2013-04-29 23:50:38 +02:00
newvolindex = new IndexVolumeWriter ( m_options );
2013-08-27 10:23:21 +02:00
newvolindex . VolumeID = db . RegisterRemoteVolume ( newvolindex . RemoteFilename , RemoteVolumeType . Index , RemoteVolumeState . Temporary , transaction );
db . AddIndexBlockLink ( newvolindex . VolumeID , newvol . VolumeID , transaction );
2013-04-29 23:50:38 +02:00
newvolindex . StartVolume ( newvol . RemoteFilename );
2013-04-07 09:03:54 +02:00
}
blocksInVolume = 0 ;
//After we upload this volume, we can delete all previous encountered volumes
2013-04-08 22:24:54 +02:00
deletedVolumes . AddRange ( DoDelete ( db , backend , deleteableVolumes , transaction ));
2013-03-27 16:06:45 +01:00
}
}
2013-04-07 09:03:54 +02:00
else
{
discardedBlocks ++;
discardedSize += e . Value ;
}
2013-03-27 16:06:45 +01:00
}
}
2013-04-07 09:03:54 +02:00
2013-05-20 15:02:36 +02:00
deleteableVolumes . Add ( entry );
2013-04-07 09:03:54 +02:00
}
if ( blocksInVolume > 0 )
{
uploadedVolumes . Add ( new KeyValuePair < string , long >( newvol . RemoteFilename , new System . IO . FileInfo ( newvol . LocalFilename ). Length ));
2013-04-29 23:50:38 +02:00
if ( newvolindex != null )
uploadedVolumes . Add ( new KeyValuePair < string , long >( newvolindex . RemoteFilename , new System . IO . FileInfo ( newvolindex . LocalFilename ). Length ));
2013-05-25 16:56:53 +02:00
if (! m_options . Dryrun )
2013-04-29 23:50:38 +02:00
backend . Put ( newvol , newvolindex );
2013-04-07 09:03:54 +02:00
else
2013-05-25 17:32:39 +02:00
m_result . AddDryrunMessage ( string . Format ( "Would upload generated blockset of size {0}" , Library . Utility . Utility . FormatSizeString ( new System . IO . FileInfo ( newvol . LocalFilename ). Length )));
2013-03-27 16:06:45 +01:00
}
else
2013-04-07 09:03:54 +02:00
{
db . RemoveRemoteVolume ( newvol . RemoteFilename , transaction );
2013-04-29 23:50:38 +02:00
if ( newvolindex != null )
2013-04-07 09:03:54 +02:00
{
2013-04-29 23:50:38 +02:00
db . RemoveRemoteVolume ( newvolindex . RemoteFilename , transaction );
newvolindex . FinishVolume ( null , 0 );
2013-04-07 09:03:54 +02:00
}
}
2013-03-27 16:06:45 +01:00
}
2013-04-07 09:03:54 +02:00
}
2013-04-08 22:24:54 +02:00
deletedVolumes . AddRange ( DoDelete ( db , backend , deleteableVolumes , transaction ));
var downloadSize = downloadedVolumes . Aggregate ( 0L , ( a , x ) => a + x . Value );
var deletedSize = deletedVolumes . Aggregate ( 0L , ( a , x ) => a + x . Value );
2013-04-07 09:03:54 +02:00
var uploadSize = uploadedVolumes . Aggregate ( 0L , ( a , x ) => a + x . Value );
2013-05-25 16:40:15 +02:00
m_result . DeletedFileCount = deletedVolumes . Count ;
m_result . DownloadedFileCount = downloadedVolumes . Count ;
m_result . UploadedFileCount = uploadedVolumes . Count ;
m_result . DeletedFileSize = deletedSize ;
m_result . DownloadedFileSize = downloadSize ;
m_result . UploadedFileSize = uploadSize ;
2013-05-25 16:56:53 +02:00
m_result . Dryrun = m_options . Dryrun ;
2013-05-25 16:40:15 +02:00
if ( m_result . Dryrun )
2013-04-08 22:24:54 +02:00
{
2013-08-24 22:31:16 +02:00
if ( downloadedVolumes . Count == 0 )
m_result . AddDryrunMessage ( string . Format ( "Would delete {0} files, which would reduce storage by {1}" , m_result . DeletedFileCount , Library . Utility . Utility . FormatSizeString ( m_result . DeletedFileSize )));
else
m_result . AddDryrunMessage ( string . Format ( "Would download {0} file(s) with a total size of {1}, delete {2} file(s) with a total size of {3}, and compact to {4} file(s) with a size of {5}, which would reduce storage by {6} file(s) and {7}" , m_result . DownloadedFileCount , Library . Utility . Utility . FormatSizeString ( m_result . DownloadedFileSize ), m_result . DeletedFileCount , Library . Utility . Utility . FormatSizeString ( m_result . DeletedFileSize ), m_result . UploadedFileCount , Library . Utility . Utility . FormatSizeString ( m_result . UploadedFileSize ), m_result . DeletedFileCount - m_result . UploadedFileCount , Library . Utility . Utility . FormatSizeString ( m_result . DeletedFileSize - m_result . UploadedFileSize )));
2013-04-08 22:24:54 +02:00
}
2013-04-07 09:03:54 +02:00
else
2013-04-08 22:24:54 +02:00
{
2013-08-24 22:31:16 +02:00
if ( m_result . DownloadedFileCount == 0 )
m_result . AddMessage ( string . Format ( "Deleted {0} files, which reduced storage by {1}" , m_result . DeletedFileCount , Library . Utility . Utility . FormatSizeString ( m_result . DeletedFileSize )));
else
m_result . AddMessage ( string . Format ( "Downloaded {0} file(s) with a total size of {1}, deleted {2} file(s) with a total size of {3}, and compacted to {4} file(s) with a size of {5}, which reduced storage by {6} file(s) and {7}" , m_result . DownloadedFileCount , Library . Utility . Utility . FormatSizeString ( downloadSize ), m_result . DeletedFileCount , Library . Utility . Utility . FormatSizeString ( m_result . DeletedFileSize ), m_result . UploadedFileCount , Library . Utility . Utility . FormatSizeString ( m_result . UploadedFileSize ), m_result . DeletedFileCount - m_result . UploadedFileCount , Library . Utility . Utility . FormatSizeString ( m_result . DeletedFileSize - m_result . UploadedFileSize )));
2013-04-08 22:24:54 +02:00
}
2013-04-07 09:03:54 +02:00
2013-04-09 20:43:27 +02:00
backend . WaitForComplete ( db , transaction );
2013-03-27 16:06:45 +01:00
}
2013-07-01 14:40:45 +02:00
return ( m_result . DeletedFileCount + m_result . UploadedFileCount ) > 0 ;
2013-03-27 16:06:45 +01:00
}
else
{
2013-07-01 14:40:45 +02:00
return false ;
2013-03-27 16:06:45 +01:00
}
}
2013-04-08 22:24:54 +02:00
2013-05-08 20:17:07 +02:00
private IEnumerable < KeyValuePair < string , long >> DoDelete ( LocalDeleteDatabase db , BackendManager backend , List < IRemoteVolume > deleteableVolumes , System . Data . IDbTransaction transaction )
2013-04-08 22:24:54 +02:00
{
foreach ( var f in db . GetDeletableVolumes ( deleteableVolumes , transaction ))
{
2013-05-25 16:56:53 +02:00
if (! m_options . Dryrun )
2013-08-25 07:49:31 +02:00
backend . Delete ( f . Name , f . Size );
2013-04-08 22:24:54 +02:00
else
2013-05-25 17:32:39 +02:00
m_result . AddDryrunMessage ( string . Format ( "Would delete remote file: {0}, size: {1}" , f . Name , Library . Utility . Utility . FormatSizeString ( f . Size )));
2013-04-08 22:24:54 +02:00
yield return new KeyValuePair < string , long >( f . Name , f . Size );
}
deleteableVolumes . Clear ();
}
2013-03-27 16:06:45 +01:00
}
}