Added option to log operations and expected backend state for debugging purposes.
git-svn-id: https://duplicati.googlecode.com/svn/trunk@1098 59da171f-624f-0410-aa54-27559c288bec
This commit is contained in:
@@ -95,6 +95,7 @@
|
||||
<AutoGen>True</AutoGen>
|
||||
<DesignTime>True</DesignTime>
|
||||
</Compile>
|
||||
<Compile Include="IBackendInteraction.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Strings\CommandLineArgument.resx">
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
#region Disclaimer / License
|
||||
// Copyright (C) 2011, Kenneth Skovhede
|
||||
// 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;
|
||||
|
||||
namespace Duplicati.Library.Interface
|
||||
{
|
||||
/// <summary>
|
||||
/// An interface for logging interactions with the backend
|
||||
/// </summary>
|
||||
public interface IBackendInteraction
|
||||
{
|
||||
/// <summary>
|
||||
/// Signals the start of a operation on the backend, such as cleanup or backup
|
||||
/// </summary>
|
||||
/// <param name='name'>The name of the operation</param>
|
||||
void BeginOperation(string name);
|
||||
|
||||
/// <summary>
|
||||
/// Signals the end of the operation
|
||||
/// </summary>
|
||||
void EndOperation();
|
||||
|
||||
/// <summary>
|
||||
/// Registers a get operation
|
||||
/// </summary>
|
||||
/// <param name='entry'>The entry involved</param>
|
||||
/// <param name='success'>True if the operation succeeeded, false otherwise</param>
|
||||
/// <param name='errorMessage'>An error message if the operation did not succeeed</param>
|
||||
void RegisterGet(IFileEntry entry, bool success, String errorMessage);
|
||||
|
||||
/// <summary>
|
||||
/// Registers a put operation
|
||||
/// </summary>
|
||||
/// <param name='entry'>The entry involved</param>
|
||||
/// <param name='success'>True if the operation succeeeded, false otherwise</param>
|
||||
/// <param name='errorMessage'>An error message if the operation did not succeeed</param>
|
||||
void RegisterPut(IFileEntry entry, bool success, String errorMessage);
|
||||
|
||||
/// <summary>
|
||||
/// Registers a delete operation
|
||||
/// </summary>
|
||||
/// <param name='entry'>The entry involved</param>
|
||||
/// <param name='success'>True if the operation succeeeded, false otherwise</param>
|
||||
/// <param name='errorMessage'>An error message if the operation did not succeeed</param>
|
||||
void RegisterDelete(IFileEntry entry, bool success, String errorMessage);
|
||||
|
||||
/// <summary>
|
||||
/// Registers a create folder operation
|
||||
/// </summary>
|
||||
/// <param name='success'>True if the operation succeeeded, false otherwise</param>
|
||||
/// <param name='errorMessage'>An error message if the operation did not succeeed</param>
|
||||
void RegisterCreateFolder(bool success, String errorMessage);
|
||||
|
||||
/// <summary>
|
||||
/// Registers a list operation
|
||||
/// </summary>
|
||||
/// <param name='files'>The entries returned, if the operation suceeded, null otherwise</param>
|
||||
/// <param name='success'>True if the operation succeeeded, false otherwise</param>
|
||||
/// <param name='errorMessage'>An error message if the operation did not succeeed</param>
|
||||
void RegisterList(List<Duplicati.Library.Interface.IFileEntry> files, bool success, String errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,7 +170,12 @@ namespace Duplicati.Library.Main
|
||||
/// Gets the communication statistics assigned to the wrapper
|
||||
/// </summary>
|
||||
public CommunicationStatistics Statistics { get { return m_statistics; } }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Recorder for remote operations
|
||||
/// </summary>
|
||||
private Duplicati.Library.Interface.IBackendInteraction m_backendInterfaceLogger;
|
||||
|
||||
/// <summary>
|
||||
/// Class to represent hash failures
|
||||
/// </summary>
|
||||
@@ -223,7 +228,13 @@ namespace Duplicati.Library.Main
|
||||
|
||||
if (!string.IsNullOrEmpty(m_options.SignatureCachePath) && !System.IO.Directory.Exists(m_options.SignatureCachePath))
|
||||
System.IO.Directory.CreateDirectory(m_options.SignatureCachePath);
|
||||
|
||||
|
||||
if (!string.IsNullOrEmpty(m_options.Backendlogdatabase))
|
||||
{
|
||||
m_backendInterfaceLogger = new StateVerification.StateDatabase(m_options.Backendlogdatabase, statistics);
|
||||
m_backendInterfaceLogger.BeginOperation(m_options.MainAction.ToString());
|
||||
}
|
||||
|
||||
m_async = m_options.AsynchronousUpload;
|
||||
if (m_async)
|
||||
{
|
||||
@@ -953,16 +964,23 @@ namespace Duplicati.Library.Main
|
||||
{
|
||||
ResetBackend();
|
||||
m_statistics.AddNumberOfRemoteCalls(1);
|
||||
return m_backend.List();
|
||||
List<Duplicati.Library.Interface.IFileEntry> res = m_backend.List();
|
||||
if (m_backendInterfaceLogger != null)
|
||||
m_backendInterfaceLogger.RegisterList(res, true, null);
|
||||
return res;
|
||||
}
|
||||
catch (System.Threading.ThreadAbortException)
|
||||
catch (System.Threading.ThreadAbortException tex)
|
||||
{
|
||||
if (m_backendInterfaceLogger != null)
|
||||
m_backendInterfaceLogger.RegisterList(null, false, tex.ToString());
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
lastEx = ex;
|
||||
m_statistics.LogRetryAttempt(ex.Message, ex);
|
||||
if (m_backendInterfaceLogger != null)
|
||||
m_backendInterfaceLogger.RegisterList(null, false, ex.ToString());
|
||||
DisposeBackend();
|
||||
|
||||
if (ex is Library.Interface.FolderMissingException && m_backendSupportsCreateFolder && m_options.AutocreateFolders)
|
||||
@@ -990,15 +1008,21 @@ namespace Duplicati.Library.Main
|
||||
m_statistics.AddNumberOfRemoteCalls(1);
|
||||
m_backend.Delete(remote.Filename);
|
||||
lastEx = null;
|
||||
if (m_backendInterfaceLogger != null)
|
||||
m_backendInterfaceLogger.RegisterDelete(remote.Fileentry, true, null);
|
||||
}
|
||||
catch (System.Threading.ThreadAbortException)
|
||||
catch (System.Threading.ThreadAbortException tex)
|
||||
{
|
||||
if (m_backendInterfaceLogger != null)
|
||||
m_backendInterfaceLogger.RegisterDelete(remote.Fileentry, false, tex.ToString());
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
lastEx = ex;
|
||||
m_statistics.LogRetryAttempt(ex.Message, ex);
|
||||
if (m_backendInterfaceLogger != null)
|
||||
m_backendInterfaceLogger.RegisterDelete(remote.Fileentry, false, ex.ToString());
|
||||
DisposeBackend();
|
||||
|
||||
retries--;
|
||||
@@ -1206,6 +1230,15 @@ namespace Duplicati.Library.Main
|
||||
|
||||
lastEx = null;
|
||||
tempfile.Protected = true; //Don't delete it
|
||||
|
||||
if (m_backendInterfaceLogger != null)
|
||||
{
|
||||
|
||||
if (remote is ManifestEntry)
|
||||
m_backendInterfaceLogger.RegisterGet(remote.Fileentry, true, System.IO.File.ReadAllText(tempfile));
|
||||
else
|
||||
m_backendInterfaceLogger.RegisterGet(remote.Fileentry, true, null);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -1219,14 +1252,18 @@ namespace Duplicati.Library.Main
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (System.Threading.ThreadAbortException)
|
||||
catch (System.Threading.ThreadAbortException tex)
|
||||
{
|
||||
if (m_backendInterfaceLogger != null)
|
||||
m_backendInterfaceLogger.RegisterGet(remote.Fileentry, false, tex.ToString());
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
lastEx = ex;
|
||||
m_statistics.LogRetryAttempt(ex.Message, ex);
|
||||
if (m_backendInterfaceLogger != null)
|
||||
m_backendInterfaceLogger.RegisterGet(remote.Fileentry, false, ex.ToString());
|
||||
DisposeBackend();
|
||||
|
||||
retries--;
|
||||
@@ -1250,7 +1287,8 @@ namespace Duplicati.Library.Main
|
||||
{
|
||||
string remotename = remote.Filename;
|
||||
m_statusmessage = string.Format(Strings.BackendWrapper.StatusMessageUploading, remotename, Utility.Utility.FormatSizeString(new System.IO.FileInfo(filename).Length));
|
||||
|
||||
Duplicati.Library.Interface.IFileEntry log_fe = new Duplicati.Library.Interface.FileEntry(remote.Filename, remote.Filesize, DateTime.Now, DateTime.Now);
|
||||
|
||||
try
|
||||
{
|
||||
int retries = m_options.NumberOfRetries;
|
||||
@@ -1329,13 +1367,19 @@ namespace Duplicati.Library.Main
|
||||
|
||||
success = true;
|
||||
lastEx = null;
|
||||
if (m_backendInterfaceLogger != null)
|
||||
m_backendInterfaceLogger.RegisterPut(log_fe, true, null);
|
||||
}
|
||||
catch (System.Threading.ThreadAbortException)
|
||||
catch (System.Threading.ThreadAbortException tex)
|
||||
{
|
||||
if (m_backendInterfaceLogger != null)
|
||||
m_backendInterfaceLogger.RegisterPut(log_fe, false, tex.ToString());
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (m_backendInterfaceLogger != null)
|
||||
m_backendInterfaceLogger.RegisterPut(log_fe, false, ex.ToString());
|
||||
DisposeBackend();
|
||||
|
||||
//Even if we can create the folder, we still count it as an error to prevent trouble with backends
|
||||
@@ -1391,8 +1435,18 @@ namespace Duplicati.Library.Main
|
||||
|
||||
private void CreateFolderInternal()
|
||||
{
|
||||
ResetBackend();
|
||||
(m_backend as Library.Interface.IBackend_v2).CreateFolder();
|
||||
try
|
||||
{
|
||||
ResetBackend();
|
||||
(m_backend as Library.Interface.IBackend_v2).CreateFolder();
|
||||
if (m_backendInterfaceLogger != null)
|
||||
m_backendInterfaceLogger.RegisterCreateFolder(true, null);
|
||||
} catch (Exception ex) {
|
||||
if (m_backendInterfaceLogger != null)
|
||||
m_backendInterfaceLogger.RegisterCreateFolder(false, ex.ToString());
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public void CreateFolder()
|
||||
@@ -1624,6 +1678,9 @@ namespace Duplicati.Library.Main
|
||||
ProtectedInvoke("DisposeInternal");
|
||||
if (m_encryption != null)
|
||||
m_encryption.Dispose();
|
||||
|
||||
if (m_backendInterfaceLogger != null)
|
||||
m_backendInterfaceLogger.EndOperation();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -109,6 +109,8 @@
|
||||
<DependentUpon>RSyncDir.resx</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="VerificationFile.cs" />
|
||||
<Compile Include="StateVerification\StateDatabase.cs" />
|
||||
<Compile Include="StateVerification\LogEntryClasses.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Compression\Duplicati.Library.Compression.csproj">
|
||||
@@ -191,4 +193,7 @@
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
<ItemGroup>
|
||||
<Folder Include="StateVerification\" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -335,7 +335,8 @@ namespace Duplicati.Library.Main
|
||||
m_asyncReserved = ASYNC_RESERVED;
|
||||
m_allowUploadProgress = false;
|
||||
}
|
||||
|
||||
|
||||
//Unused, but triggers errors in the encryption setup here
|
||||
Library.Interface.IEncryption encryptionModule = m_options.NoEncryption ? null : DynamicLoader.EncryptionLoader.GetModule(m_options.EncryptionModule, m_options.Passphrase, m_options.RawOptions);
|
||||
|
||||
using (new Logging.Timer("Backup from " + string.Join(";", sources) + " to " + m_backend))
|
||||
|
||||
@@ -203,6 +203,7 @@ namespace Duplicati.Library.Main
|
||||
new CommandLineArgument("allow-sleep", CommandLineArgument.ArgumentType.Boolean, Strings.Options.AllowsleepShort, Strings.Options.AllowsleepShort, "false"),
|
||||
new CommandLineArgument("no-connection-reuse", CommandLineArgument.ArgumentType.Boolean, Strings.Options.NoconnectionreuseShort, Strings.Options.NoconnectionreuseLong, "false"),
|
||||
|
||||
new CommandLineArgument("backend-log-database", CommandLineArgument.ArgumentType.Path, Strings.Options.BackendlogdatabaseShort, Strings.Options.BackendlogdatabaseLong),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -978,6 +979,21 @@ namespace Duplicati.Library.Main
|
||||
/// </summary>
|
||||
public bool NoConnectionReuse { get { return GetBool("no-connection-reuse"); } }
|
||||
|
||||
/// <summary>
|
||||
/// The path to a log database file, or null
|
||||
/// </summary>
|
||||
public string Backendlogdatabase
|
||||
{
|
||||
get
|
||||
{
|
||||
string value;
|
||||
if (!m_options.TryGetValue("backend-log-database", out value))
|
||||
value = null;
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of modules, the key indicates if they are loaded
|
||||
/// </summary>
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
#region Disclaimer / License
|
||||
// Copyright (C) 2011, Kenneth Skovhede
|
||||
// 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.Xml.Serialization;
|
||||
|
||||
namespace Duplicati.Library.Main.StateVerification
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a single invocation of Duplicati
|
||||
/// </summary>
|
||||
[XmlType(TypeName="collection")]
|
||||
[XmlRootAttribute(ElementName="duplicati-state-db")]
|
||||
[Serializable()]
|
||||
public class InvocationCollection
|
||||
{
|
||||
[XmlAttribute("begin")]
|
||||
public DateTime BeginTime;
|
||||
[XmlAttribute("end")]
|
||||
public DateTime EndTime;
|
||||
[XmlAttribute("operation")]
|
||||
public string Operation;
|
||||
|
||||
[XmlElement("events")]
|
||||
[XmlElement("list", typeof(ListLogEntry))]
|
||||
[XmlElement("put", typeof(PutLogEntry))]
|
||||
[XmlElement("get", typeof(GetLogEntry))]
|
||||
[XmlElement("delete", typeof(DeleteLogEntry))]
|
||||
[XmlElement("createfolder", typeof(CreateFolderLogEntry))]
|
||||
public List<LogEntryBase> Entries;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The base for all operations performed on a remote folder
|
||||
/// </summary>
|
||||
[Serializable()]
|
||||
public abstract class LogEntryBase
|
||||
{
|
||||
[XmlAttribute("op")]
|
||||
public BackendOperation Operation;
|
||||
[XmlElement("message")]
|
||||
public string LogMessage;
|
||||
[XmlAttribute("success")]
|
||||
public bool Success;
|
||||
[XmlAttribute("timestamp")]
|
||||
public DateTime Timestamp = DateTime.Now;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A single remote file
|
||||
/// </summary>
|
||||
[XmlType(TypeName="file")]
|
||||
public class FileItem
|
||||
{
|
||||
[XmlAttribute("name")]
|
||||
public string Name;
|
||||
[XmlAttribute("size")]
|
||||
public long Size;
|
||||
[XmlAttribute("modified")]
|
||||
public DateTime Modified;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Representation of the LIST operation
|
||||
/// </summary>
|
||||
[XmlType(TypeName="list")]
|
||||
public class ListLogEntry : LogEntryBase
|
||||
{
|
||||
[XmlElement("contents")]
|
||||
public List<FileItem> Files;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Representation of the file based entries
|
||||
/// </summary>
|
||||
public abstract class FileEntryType : LogEntryBase
|
||||
{
|
||||
[XmlElement("file")]
|
||||
public FileItem File;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Representation of a DELETE operation
|
||||
/// </summary>
|
||||
[XmlType(TypeName="delete")]
|
||||
public class DeleteLogEntry : FileEntryType
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Representation of the GET operation
|
||||
/// </summary>
|
||||
[XmlType(TypeName="get")]
|
||||
public class GetLogEntry : FileEntryType
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Representation of the PUT operation
|
||||
/// </summary>
|
||||
[XmlType(TypeName="put")]
|
||||
public class PutLogEntry : FileEntryType
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Representation of the CREATEFOLDER operation
|
||||
/// </summary>
|
||||
[XmlType(TypeName="createfolder")]
|
||||
public class CreateFolderLogEntry : LogEntryBase
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,334 @@
|
||||
#region Disclaimer / License
|
||||
// Copyright (C) 2011, Kenneth Skovhede
|
||||
// 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.Xml.Serialization;
|
||||
|
||||
namespace Duplicati.Library.Main.StateVerification
|
||||
{
|
||||
/// <summary>
|
||||
/// Possible backend operations
|
||||
/// </summary>
|
||||
public enum BackendOperation
|
||||
{
|
||||
Get,
|
||||
Put,
|
||||
List,
|
||||
Delete,
|
||||
CreateFolder
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maintains local state information about the expected remote directory contents
|
||||
/// </summary>
|
||||
public class StateDatabase : Duplicati.Library.Interface.IBackendInteraction, IEnumerable<LogEntryBase>
|
||||
{
|
||||
private string m_filename;
|
||||
private List<InvocationCollection> m_invocations;
|
||||
private InvocationCollection m_current;
|
||||
private CommunicationStatistics m_stats;
|
||||
|
||||
public StateDatabase(string filename, CommunicationStatistics stats)
|
||||
{
|
||||
m_filename = filename;
|
||||
m_stats = stats;
|
||||
|
||||
if (System.IO.File.Exists(filename))
|
||||
{
|
||||
XmlSerializer s = new XmlSerializer(typeof(List<InvocationCollection>), new XmlRootAttribute("duplicati-state-db"));
|
||||
using (System.IO.FileStream fs = System.IO.File.OpenRead(m_filename))
|
||||
m_invocations = (List<InvocationCollection>)s.Deserialize(fs);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_invocations = new List<InvocationCollection>();
|
||||
}
|
||||
|
||||
m_current = new InvocationCollection();
|
||||
m_current.BeginTime = DateTime.Now;
|
||||
m_current.Entries = new List<LogEntryBase>();
|
||||
}
|
||||
|
||||
public void BeginOperation(string name)
|
||||
{
|
||||
m_current.Operation = name;
|
||||
}
|
||||
|
||||
public void EndOperation()
|
||||
{
|
||||
m_current.EndTime = DateTime.Now;
|
||||
Save();
|
||||
}
|
||||
|
||||
|
||||
private class ItemEnumerator : IEnumerator<LogEntryBase>
|
||||
{
|
||||
private List<InvocationCollection> m_invocations;
|
||||
private InvocationCollection m_current;
|
||||
private int m_i;
|
||||
private int m_j;
|
||||
private bool m_isCurrentAdded;
|
||||
|
||||
public ItemEnumerator(List<InvocationCollection> invocations, InvocationCollection current)
|
||||
{
|
||||
m_invocations = invocations;
|
||||
m_current = current;
|
||||
m_isCurrentAdded = invocations.Count > 0 && invocations[invocations.Count - 1] == current;
|
||||
this.Reset();
|
||||
}
|
||||
|
||||
#region IEnumerator[LogEntryBase] implementation
|
||||
public LogEntryBase Current
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_i >= m_invocations.Count + (m_isCurrentAdded ? 0 : 1))
|
||||
return null;
|
||||
|
||||
InvocationCollection ic = (m_i >= m_invocations.Count) ? m_current : m_invocations[m_i];
|
||||
if (m_j >= ic.Entries.Count)
|
||||
return null;
|
||||
|
||||
return ic.Entries[m_j];
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region IEnumerator implementation
|
||||
public bool MoveNext ()
|
||||
{
|
||||
if (m_i >= m_invocations.Count + (m_isCurrentAdded ? 0 : 1))
|
||||
return false;
|
||||
|
||||
InvocationCollection ic = (m_i >= m_invocations.Count) ? m_current : m_invocations[m_i];
|
||||
m_j++;
|
||||
|
||||
if (m_j >= ic.Entries.Count)
|
||||
{
|
||||
m_i++;
|
||||
m_j = -1;
|
||||
return MoveNext();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Reset ()
|
||||
{
|
||||
m_i = 0;
|
||||
m_j = -1;
|
||||
}
|
||||
|
||||
object System.Collections.IEnumerator.Current
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.Current;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region IDisposable implementation
|
||||
public void Dispose ()
|
||||
{
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
private int FindFileItem(List<FileItem> files, string name)
|
||||
{
|
||||
for(int i = 0; i < files.Count; i++)
|
||||
if (files[i].Name.Equals(name, StringComparison.InvariantCulture))
|
||||
return i;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public List<FileItem> GetAndVerifyCurrentState()
|
||||
{
|
||||
//First we find the most recent LIST operation
|
||||
List<FileItem> last_list = new List<FileItem>();
|
||||
foreach(LogEntryBase leb in this)
|
||||
{
|
||||
if (leb.Success)
|
||||
{
|
||||
if (leb.Operation == BackendOperation.List)
|
||||
{
|
||||
//If there was a list operation before, check if we have the same contents
|
||||
List<FileItem> new_list = ((ListLogEntry)leb).Files;
|
||||
if (last_list != null && last_list.Count != 0)
|
||||
{
|
||||
StringBuilder changeMessages = new StringBuilder();
|
||||
|
||||
foreach(FileItem fi in new_list)
|
||||
{
|
||||
int ix = FindFileItem(last_list, fi.Name);
|
||||
if (ix == -1)
|
||||
changeMessages.AppendLine("A file was added unexpectedly: " + fi.Name);
|
||||
else
|
||||
last_list.RemoveAt(ix);
|
||||
}
|
||||
|
||||
foreach(FileItem fi in last_list)
|
||||
changeMessages.AppendLine("A file was removed unexpectedly: " + fi.Name);
|
||||
|
||||
if (changeMessages.Length > 0 && m_stats != null)
|
||||
{
|
||||
m_stats.LogWarning("Change found in list @" + leb.Timestamp
|
||||
+ Environment.NewLine +
|
||||
changeMessages.ToString(), null);
|
||||
}
|
||||
}
|
||||
|
||||
last_list = new List<FileItem>(new_list);
|
||||
}
|
||||
|
||||
if (last_list != null)
|
||||
{
|
||||
if (leb.Operation == BackendOperation.Put)
|
||||
{
|
||||
//Check for overwrite
|
||||
int ix = FindFileItem(last_list, ((PutLogEntry)leb).File.Name);
|
||||
if (ix != -1)
|
||||
last_list.RemoveAt(ix);
|
||||
last_list.Add(((PutLogEntry)leb).File);
|
||||
|
||||
}
|
||||
else if (leb.Operation == BackendOperation.Delete)
|
||||
{
|
||||
string name = ((DeleteLogEntry)leb).File.Name;
|
||||
int ix = FindFileItem(last_list, name);
|
||||
|
||||
if (ix == -1)
|
||||
{
|
||||
if (m_stats != null)
|
||||
m_stats.LogWarning("Delete @" + leb.Timestamp + " for file not found: " + name, null);
|
||||
}
|
||||
else
|
||||
last_list.RemoveAt(ix);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return last_list;
|
||||
}
|
||||
|
||||
public void Save()
|
||||
{
|
||||
if (m_invocations.Count == 0 || m_invocations[m_invocations.Count -1] != m_current)
|
||||
m_invocations.Add(m_current);
|
||||
|
||||
XmlSerializer s = new XmlSerializer(typeof(List<InvocationCollection>), new XmlRootAttribute("duplicati-state-db"));
|
||||
using (System.IO.FileStream fs = System.IO.File.OpenWrite(m_filename))
|
||||
s.Serialize(fs, m_invocations);
|
||||
}
|
||||
|
||||
public void RegisterGet(Duplicati.Library.Interface.IFileEntry entry, bool success, String errorMessage)
|
||||
{
|
||||
m_current.Entries.Add(new GetLogEntry() {
|
||||
Operation = BackendOperation.Get,
|
||||
Success = success,
|
||||
LogMessage = errorMessage,
|
||||
File = new FileItem() {
|
||||
Name = entry.Name,
|
||||
Size = entry.Size,
|
||||
Modified = entry.LastModification
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void RegisterDelete(Duplicati.Library.Interface.IFileEntry entry, bool success, String errorMessage)
|
||||
{
|
||||
m_current.Entries.Add(new DeleteLogEntry() {
|
||||
Operation = BackendOperation.Delete,
|
||||
Success = success,
|
||||
LogMessage = errorMessage,
|
||||
File = new FileItem() {
|
||||
Name = entry.Name,
|
||||
Size = entry.Size,
|
||||
Modified = entry.LastModification
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void RegisterPut(Duplicati.Library.Interface.IFileEntry entry, bool success, String errorMessage)
|
||||
{
|
||||
m_current.Entries.Add(new PutLogEntry() {
|
||||
Operation = BackendOperation.Put,
|
||||
Success = success,
|
||||
LogMessage = errorMessage,
|
||||
File = new FileItem() {
|
||||
Name = entry.Name,
|
||||
Size = entry.Size,
|
||||
Modified = entry.LastModification
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void RegisterCreateFolder(bool success, String errorMessage)
|
||||
{
|
||||
m_current.Entries.Add(new CreateFolderLogEntry() {
|
||||
Operation = BackendOperation.CreateFolder,
|
||||
Success = success,
|
||||
LogMessage = errorMessage
|
||||
});
|
||||
}
|
||||
|
||||
public void RegisterList(List<Duplicati.Library.Interface.IFileEntry> files, bool success, String errorMessage)
|
||||
{
|
||||
var ent = new ListLogEntry() {
|
||||
Operation = BackendOperation.List,
|
||||
LogMessage = errorMessage,
|
||||
Success = success,
|
||||
Files = files == null ? null : new List<FileItem>()
|
||||
};
|
||||
|
||||
if (files != null)
|
||||
foreach(var f in files)
|
||||
ent.Files.Add(new FileItem() {
|
||||
Name = f.Name,
|
||||
Size = f.Size,
|
||||
Modified = f.LastModification
|
||||
});
|
||||
|
||||
m_current.Entries.Add(ent);
|
||||
|
||||
//As we have added the entry, we can just run the normal verification process
|
||||
GetAndVerifyCurrentState();
|
||||
}
|
||||
|
||||
#region IEnumerable[LogEntryBase] implementation
|
||||
public IEnumerator<LogEntryBase> GetEnumerator ()
|
||||
{
|
||||
return new ItemEnumerator(m_invocations, m_current);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region IEnumerable implementation
|
||||
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator ()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
+19
-1
@@ -1229,5 +1229,23 @@ namespace Duplicati.Library.Main.Strings {
|
||||
return ResourceManager.GetString("VssexcludewritersShort", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Database log of remote operations.
|
||||
/// </summary>
|
||||
internal static string BackendlogdatabaseShort {
|
||||
get {
|
||||
return ResourceManager.GetString("BackendlogdatabaseShort", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Use this option to specify a path to a file that will store information about backend operations. This option can be used to track down errors by comparing the backend state with the expected backend state..
|
||||
/// </summary>
|
||||
internal static string BackendlogdatabaseLong {
|
||||
get {
|
||||
return ResourceManager.GetString("BackendlogdatabaseLong", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -505,6 +505,12 @@
|
||||
<value>If no files have changed, Duplicati will not upload a backup set. If the backup data is used to verify that a backup was executed, this option will make Duplicati upload a backupset even if it is empty</value>
|
||||
</data>
|
||||
<data name="UploadUnchangedBackupsShort" xml:space="preserve">
|
||||
<value>Upload empty backup files </value>
|
||||
<value>Upload empty backup files</value>
|
||||
</data>
|
||||
<data name="BackendlogdatabaseShort" xml:space="preserve">
|
||||
<value>Database log of remote operations</value>
|
||||
</data>
|
||||
<data name="BackendlogdatabaseLong" xml:space="preserve">
|
||||
<value>Use this option to specify a path to a file that will store information about backend operations. This option can be used to track down errors by comparing the backend state with the expected backend state.</value>
|
||||
</data>
|
||||
</root>
|
||||
Reference in New Issue
Block a user