Files
duplicati/Duplicati/Library/Main/ForestHash/Database/LocalDatabase.cs
T
kenneth@hexad.dk a0521da54d Initial version of the new block-based storage format.
Still needs a work over in terms of error handling, progress reporting, warning output, refactoring etc.
Also misses the volume reuse/reclaim algorithms.
But it passes the unit tests now, with and without a local database.

git-svn-id: https://duplicati.googlecode.com/svn/sandboxes/Kenneth/ForestHash@1526 59da171f-624f-0410-aa54-27559c288bec
2013-02-12 21:43:14 +00:00

342 lines
15 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Duplicati.Library.Main.ForestHash.Database
{
public class Localdatabase : IDisposable
{
protected readonly System.Data.IDbConnection m_connection;
protected readonly long m_operationid = -1;
protected object m_lock = new object();
private readonly System.Data.IDbCommand m_updateremotevolumeCommand;
private readonly System.Data.IDbCommand m_selectremotevolumesCommand;
private readonly System.Data.IDbCommand m_selectremotevolumeCommand;
private readonly System.Data.IDbCommand m_removeremotevolumeCommand;
private readonly System.Data.IDbCommand m_insertlogCommand;
private readonly System.Data.IDbCommand m_insertremotelogCommand;
public const long FOLDER_BLOCKSET_ID = -100;
public const long SYMLINK_BLOCKSET_ID = -200;
public DateTime OperationTimestamp { get; private set; }
/// <summary>
/// Creates a new database instance and starts a new operation
/// </summary>
/// <param name="path">The path to the database</param>
/// <param name="operation">The name of the operation</param>
public Localdatabase(string path, string operation)
{
this.OperationTimestamp = DateTime.UtcNow;
m_connection = (System.Data.IDbConnection)Activator.CreateInstance(Duplicati.Library.Utility.SQLiteLoader.SQLiteConnectionType);
if (!System.IO.Directory.Exists(System.IO.Path.GetDirectoryName(path)))
System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(path));
Utility.DatabaseUpgrader.UpgradeDatabase(m_connection, path, typeof(Localdatabase));
using (var cmd = m_connection.CreateCommand())
{
cmd.CommandText = @"INSERT INTO ""Operation"" (""Description"", ""Timestamp"") VALUES (?, ?)";
cmd.AddParameter(operation);
cmd.AddParameter(OperationTimestamp);
cmd.ExecuteNonQuery();
cmd.CommandText = "SELECT last_insert_rowid()";
m_operationid = Convert.ToInt64(cmd.ExecuteScalar());
}
m_updateremotevolumeCommand = m_connection.CreateCommand();
m_selectremotevolumesCommand = m_connection.CreateCommand();
m_selectremotevolumeCommand = m_connection.CreateCommand();
m_insertlogCommand = m_connection.CreateCommand();
m_insertremotelogCommand = m_connection.CreateCommand();
m_removeremotevolumeCommand = m_connection.CreateCommand();
m_insertlogCommand.CommandText = @"INSERT INTO ""LogData"" (""OperationID"", ""Timestamp"", ""Type"", ""Message"", ""Exception"") VALUES (?, ?, ?, ?, ?)";
m_insertlogCommand.AddParameter(m_operationid);
m_insertlogCommand.AddParameters(4);
m_insertremotelogCommand.CommandText = @"INSERT INTO ""RemoteOperation"" (""OperationID"", ""Timestamp"", ""Operation"", ""Path"", ""Data"") VALUES (?, ?, ?, ?, ?)";
m_insertremotelogCommand.AddParameter(m_operationid);
m_insertremotelogCommand.AddParameters(4);
m_updateremotevolumeCommand.CommandText = @"UPDATE ""Remotevolume"" SET ""OperationID"" = ?, ""State"" = ?, ""Hash"" = ?, ""Size"" = ? WHERE ""Name"" = ?";
m_updateremotevolumeCommand.AddParameter(m_operationid);
m_updateremotevolumeCommand.AddParameters(4);
m_selectremotevolumesCommand.CommandText = @"SELECT ""Name"", ""Type"", ""Size"", ""Hash"", ""State"" FROM ""Remotevolume""";
m_selectremotevolumeCommand.CommandText = @"SELECT ""Type"", ""Size"", ""Hash"", ""State"" FROM ""Remotevolume"" WHERE ""Name"" = ?";
m_selectremotevolumeCommand.AddParameter();
m_removeremotevolumeCommand.CommandText = @"DELETE FROM ""Remotevolume"" WHERE ""Name"" = ?";
m_removeremotevolumeCommand.AddParameter();
}
public void UpdateRemoteVolume(string name, RemoteVolumeState state, long size, string hash)
{
lock (m_lock)
{
m_updateremotevolumeCommand.SetParameterValue(1, state.ToString());
m_updateremotevolumeCommand.SetParameterValue(2, hash);
m_updateremotevolumeCommand.SetParameterValue(3, size);
m_updateremotevolumeCommand.SetParameterValue(4, name);
m_updateremotevolumeCommand.ExecuteNonQuery();
}
}
public bool GetRemoteVolume(string file, out string hash, out long size, out RemoteVolumeType type, out RemoteVolumeState state)
{
m_selectremotevolumeCommand.SetParameterValue(0, file);
using (var rd = m_selectremotevolumeCommand.ExecuteReader())
if (rd.Read())
{
hash = (rd.GetValue(2) == null || rd.GetValue(2) == DBNull.Value) ? null : rd.GetValue(3).ToString();
size = (rd.GetValue(1) == null || rd.GetValue(1) == DBNull.Value) ? -1 : Convert.ToInt64(rd.GetValue(2));
type = (RemoteVolumeType)Enum.Parse(typeof(RemoteVolumeType), rd.GetValue(0).ToString());
state = (RemoteVolumeState)Enum.Parse(typeof(RemoteVolumeState), rd.GetValue(3).ToString());
return true;
}
hash = null;
size = -1;
type = (RemoteVolumeType)(-1);
state = (RemoteVolumeState)(-1);
return false;
}
public IList<RemoteVolumeEntry> GetRemoteVolumes()
{
var res = new List<RemoteVolumeEntry>();
using (var rd = m_selectremotevolumesCommand.ExecuteReader())
{
while (rd.Read())
{
res.Add(new RemoteVolumeEntry(
rd.GetValue(0).ToString(),
(rd.GetValue(3) == null || rd.GetValue(3) == DBNull.Value) ? null : rd.GetValue(3).ToString(),
(rd.GetValue(2) == null || rd.GetValue(2) == DBNull.Value) ? -1 : Convert.ToInt64(rd.GetValue(2)),
(RemoteVolumeType)Enum.Parse(typeof(RemoteVolumeType), rd.GetValue(1).ToString()),
(RemoteVolumeState)Enum.Parse(typeof(RemoteVolumeState), rd.GetValue(4).ToString())
)
);
}
}
return res;
}
/// <summary>
/// Log an operation performed on the remote backend
/// </summary>
/// <param name="operation">The operation performed</param>
/// <param name="path">The path involved</param>
/// <param name="data">Any data relating to the operation</param>
public void LogRemoteOperation(string operation, string path, string data)
{
lock (m_lock)
{
m_insertremotelogCommand.SetParameterValue(1, DateTime.UtcNow);
m_insertremotelogCommand.SetParameterValue(2, operation);
m_insertremotelogCommand.SetParameterValue(3, path);
m_insertremotelogCommand.SetParameterValue(4, data);
m_insertremotelogCommand.ExecuteNonQuery();
}
}
/// <summary>
/// Log a debug message
/// </summary>
/// <param name="type">The message type</param>
/// <param name="message">The message</param>
/// <param name="exception">An optional exception</param>
public void LogMessage(string type, string message, Exception exception)
{
lock (m_lock)
{
m_insertlogCommand.SetParameterValue(1, DateTime.UtcNow);
m_insertlogCommand.SetParameterValue(2, type);
m_insertlogCommand.SetParameterValue(3, message);
m_insertlogCommand.SetParameterValue(4, exception == null ? null : exception.ToString());
m_insertlogCommand.ExecuteNonQuery();
}
}
public void RemoveRemoteVolume(string name)
{
lock (m_lock)
{
using (var tr = m_connection.BeginTransaction())
{
var deletecmd = m_connection.CreateCommand();
deletecmd.Transaction = tr;
var updatedfiles = deletecmd.ExecuteNonQuery(@"UPDATE ""Fileset"" SET ""BlocksetID"" = -1 WHERE ""BlocksetID"" IN (SELECT DISTINCT ""BlocksetID"" FROM ""BlocksetEntry"" WHERE ""BlockID"" IN (SELECT ""ID"" FROM ""Block"" WHERE ""File"" = ?))", name);
var updatedmetadata = deletecmd.ExecuteNonQuery(@"UPDATE ""Metadataset"" SET ""BlocksetID"" = -1 WHERE ""BlocksetID"" IN (SELECT DISTINCT ""BlocksetID"" FROM ""BlocksetEntry"" WHERE ""BlockID"" IN (SELECT ""ID"" FROM ""Block"" WHERE ""File"" = ?))", name);
var deletedblocksets = deletecmd.ExecuteNonQuery(@"DELETE FROM ""Blockset"" WHERE ""ID"" IN (SELECT DISTINCT ""BlocksetID"" FROM ""BlocksetEntry"" WHERE ""BlockID"" IN (SELECT ""ID"" FROM ""Block"" WHERE ""File"" = ?))", name);
var deletedblocksetentries = deletecmd.ExecuteNonQuery(@"DELETE FROM ""BlocksetEntry"" WHERE ""BlocksetID"" IN (SELECT DISTINCT ""BlocksetID"" FROM ""BlocksetEntry"" WHERE ""ID"" IN (SELECT ""ID"" FROM ""Block"" WHERE ""File"" = ?))", name);
var deletedblocks = deletecmd.ExecuteNonQuery(@"DELETE FROM ""Block"" WHERE ""File"" = ?", name);
((System.Data.IDataParameter)m_removeremotevolumeCommand.Parameters[0]).Value = name;
m_removeremotevolumeCommand.Transaction = tr;
m_removeremotevolumeCommand.ExecuteNonQuery();
tr.Commit();
}
}
}
public long GetFilesetID(DateTime restoretime)
{
if (restoretime.Kind == DateTimeKind.Unspecified)
throw new Exception("Invalid DateTime given, must be either local or UTC");
using (var cmd = m_connection.CreateCommand())
{
cmd.CommandText = @"SELECT ""ID"" FROM ""Operation"" WHERE (strftime(""%s"",?) - strftime(""%s"", ""Timestamp"")) >= 0 AND ""Description"" = ""Backup"" ORDER BY ""Timestamp"" DESC";
cmd.AddParameter(restoretime.ToUniversalTime());
object r = cmd.ExecuteScalar();
if (r == null)
{
cmd.Parameters.Clear();
cmd.CommandText = @"SELECT ""ID"" FROM ""Operation"" WHERE ""Description"" = ""Backup"" ORDER BY ""Timestamp"" DESC ";
r = cmd.ExecuteScalar();
if (r == null)
throw new Exception("No backup at the specified date");
}
return Convert.ToInt64(r);
}
}
private class LocalFileEntryEnumerable : IEnumerable<ILocalFileEntry>
{
private class LocalFileEntryEnumerator : IEnumerator<ILocalFileEntry>
{
private class LocalFileEntry : ILocalFileEntry
{
private System.Data.IDataReader m_reader;
public LocalFileEntry(System.Data.IDataReader reader)
{
m_reader = reader;
}
public string Path
{
get
{
var c = m_reader.GetValue(0);
if (c == null || c == DBNull.Value)
return null;
return c.ToString();
}
}
public long Length
{
get
{
var c = m_reader.GetValue(1);
if (c == null || c == DBNull.Value)
return -1;
return Convert.ToInt64(c);
}
}
public string Hash
{
get
{
var c = m_reader.GetValue(2);
if (c == null || c == DBNull.Value)
return null;
return c.ToString();
}
}
public string Metahash
{
get
{
var c = m_reader.GetValue(3);
if (c == null || c == DBNull.Value)
return null;
return c.ToString();
}
}
}
private System.Data.IDbConnection m_connection;
private System.Data.IDbCommand m_command;
private System.Data.IDataReader m_reader;
private long m_filesetid;
private LocalFileEntry m_current;
public LocalFileEntryEnumerator(System.Data.IDbConnection connection, long filesetid)
{
m_connection = connection;
m_filesetid = filesetid;
this.Reset();
}
public ILocalFileEntry Current { get { return m_current; } }
object System.Collections.IEnumerator.Current { get { return this.Current; } }
public void Dispose()
{
if (m_reader != null)
try { m_reader.Dispose(); }
finally { m_reader = null; }
if (m_command != null)
try { m_command.Dispose(); }
finally { m_command = null; }
}
public bool MoveNext()
{
return m_reader.Read();
}
public void Reset()
{
this.Dispose();
m_command = m_connection.CreateCommand();
m_command.CommandText = @"SELECT ""A"".""Path"", ""B"".""Length"", ""B"".""FullHash"", ""D"".""FullHash"" FROM ""Fileset"" A, ""Blockset"" B, ""Metadataset"" C, ""Blockset"" D WHERE ""A"".""BlocksetID"" = ""B"".""ID"" AND ""A"".""MetadataID"" = ""C"".""ID"" AND ""C"".""BlocksetID"" = ""D"".""ID"" AND ""A"".""OperationID"" = ? ";
m_command.AddParameter(m_filesetid);
m_reader = m_command.ExecuteReader();
m_current = new LocalFileEntry(m_reader);
}
}
private System.Data.IDbConnection m_connection;
private long m_filesetid;
public LocalFileEntryEnumerable(System.Data.IDbConnection connection, long filesetid)
{
m_connection = connection;
m_filesetid = filesetid;
}
public IEnumerator<ILocalFileEntry> GetEnumerator() { return new LocalFileEntryEnumerator(m_connection, m_filesetid); }
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return this.GetEnumerator(); }
}
public IEnumerable<ILocalFileEntry> GetFiles(long filesetid)
{
return new LocalFileEntryEnumerable(m_connection, filesetid);
}
public virtual void Dispose()
{
}
}
}