Files
duplicati/Duplicati/Library/Main/ForestHash/Operation/CleanupHandler.cs
T
2013-03-31 19:33:34 +02:00

209 lines
7.1 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Duplicati.Library.Main.ForestHash.Database;
using Duplicati.Library.Main.ForestHash.Volumes;
namespace Duplicati.Library.Main.ForestHash.Operation
{
internal class CleanupHandler : IDisposable
{
private string m_backendurl;
private FhOptions m_options;
private CommunicationStatistics m_stat;
public CleanupHandler(string backend, FhOptions options, CommunicationStatistics stat)
{
m_backendurl = backend;
m_options = options;
m_stat = stat;
}
public void Run()
{
if (!System.IO.File.Exists(m_options.Fhdbpath))
throw new Exception(string.Format("Database file does not exist: {0}", m_options.Fhdbpath));
using(var db = new LocalCleanupDatabase(m_options.Fhdbpath))
using(var tr = db.BeginTransaction())
using(var backend = new FhBackend(m_backendurl, m_options, db, m_stat))
{
ForestHash.VerifyParameters(db, m_options);
var tp = ForestHash.RemoteListAnalysis(backend, m_options, db);
var extra = tp.Item1.ToList();
var missing = tp.Item2.ToList();
var buffer = new byte[m_options.Fhblocksize];
var blockhasher = System.Security.Cryptography.HashAlgorithm.Create(m_options.FhBlockHashAlgorithm);
if (blockhasher == null)
throw new Exception(string.Format(Strings.Foresthash.InvalidHashAlgorithm, m_options.FhBlockHashAlgorithm));
if (!blockhasher.CanReuseTransform)
throw new Exception(string.Format(Strings.Foresthash.InvalidCryptoSystem, m_options.FhBlockHashAlgorithm));
if (extra.Count > 0 || missing.Count > 0)
{
foreach(var n in extra)
if (m_options.Force && !m_options.FhDryrun)
backend.Delete(n.Name);
else
m_stat.LogMessage("[Dryrun] would delete file {0}", n.Name);
foreach(var n in missing)
{
if (n.Type == RemoteVolumeType.Files)
{
var operationId = db.GetOperationIdFromRemotename(n.Name);
var w = new FilesetVolumeWriter(m_options, DateTime.UtcNow);
w.SetRemoteFilename(n.Name);
using(var b = new LocalBackupDatabase(db.Connection, m_options))
b.WriteFileset(w, operationId);
w.Close();
if (m_options.FhDryrun)
m_stat.LogMessage("[Dryrun] would re-upload fileset {0}", n.Name);
else
{
db.UpdateRemoteVolume(w.RemoteFilename, RemoteVolumeState.Uploading, -1, null, tr);
backend.Put(w);
}
}
else if (n.Type == RemoteVolumeType.Shadow)
{
var blockvolumename = GetBlockVolumeName(n.Name);
var w = new ShadowVolumeWriter(m_options);
w.SetRemoteFilename(n.Name);
w.StartVolume(blockvolumename);
string blockvolumehash;
long blockvolumesize;
RemoteVolumeType blockvolumetype;
RemoteVolumeState blockvolumestate;
db.GetRemoteVolume(blockvolumename, out blockvolumehash, out blockvolumesize, out blockvolumetype, out blockvolumestate);
var volumeid = db.GetRemoteVolumeID(blockvolumename);
foreach(var b in db.GetBlocks(volumeid))
w.AddBlock(b.Hash, b.Size);
w.FinishVolume(blockvolumehash, blockvolumesize);
w.Close();
if (m_options.FhDryrun)
m_stat.LogMessage("[Dryrun] would re-upload shadow file {0}", n.Name);
else
{
db.UpdateRemoteVolume(w.RemoteFilename, RemoteVolumeState.Uploading, -1, null, tr);
backend.Put(w);
}
}
else if (n.Type == RemoteVolumeType.Blocks)
{
var w = new BlockVolumeWriter(m_options);
w.SetRemoteFilename(n.Name);
var volumeid = db.GetRemoteVolumeID(n.Name);
foreach(var block in db.GetSourceFilesWithBlocks(volumeid, m_options.Fhblocksize))
{
var hash = block.Hash;
var size = (int)block.Size;
var recovered = false;
foreach(var source in block.Sources)
{
var file = source.File;
var offset = source.Offset;
try
{
using(var f = System.IO.File.OpenRead(file))
{
f.Position = offset;
if (size == Utility.Utility.ForceStreamRead(f, buffer, size))
{
var newhash = Convert.ToBase64String(blockhasher.ComputeHash(buffer, 0, size));
if (newhash == hash)
{
w.AddBlock(hash, buffer, size, Duplicati.Library.Interface.CompressionHint.Default);
recovered = true;
break;
}
}
}
}
catch (Exception ex)
{
m_stat.LogError(string.Format("Failed to access file: {0}", file), ex);
}
}
if (!recovered)
{
//TODO: Ineffecient to download the remote files repeatedly
foreach(var vol in db.GetBlockFromRemote(hash, size))
{
try
{
var p = VolumeBase.ParseFilename(vol.Name);
using(var file = backend.Get(vol.Name, vol.Size, vol.Hash))
using(var f = new BlockVolumeReader(p.CompressionModule, file, m_options))
if (f.ReadBlock(hash, buffer) == size && Convert.ToBase64String(blockhasher.ComputeHash(buffer)) == hash)
{
w.AddBlock(hash, buffer, size, Duplicati.Library.Interface.CompressionHint.Default);
recovered = true;
break;
}
}
catch (Exception ex)
{
m_stat.LogError(string.Format("Failed to access remote file: {0}", vol.Name), ex);
}
}
}
if (!recovered)
{
m_stat.LogMessage("Repair cannot acquire block with hash {0} and size {1}, which is required by the following filesets: ", hash, size);
foreach(var f in db.GetFilesetsUsingBlock(hash, size))
m_stat.LogMessage(f.Name);
m_stat.LogMessage("This may be fixed by deleting the filesets and running cleanup again");
throw new Exception(string.Format("Block {0} is required for recreating the file \"{1}\". Repair not possible!!!", hash, n.Name));
}
}
w.Close();
if (m_options.FhDryrun)
m_stat.LogMessage("[Dryrun] would upload new block file {0}", n.Name);
else
{
db.UpdateRemoteVolume(w.RemoteFilename, RemoteVolumeState.Uploading, -1, null, tr);
backend.Put(w);
}
}
}
}
if (!m_options.FhDryrun)
tr.Commit();
else
tr.Rollback();
}
}
public void Dispose()
{
}
string GetBlockVolumeName(string name)
{
var e = VolumeBase.ParseFilename(name);
return VolumeBase.GenerateFilename(RemoteVolumeType.Blocks, e.Prefix, e.Guid, e.Time, e.CompressionModule, e.EncryptionModule);
}
}
}