Commit of initial kdbx load perf testing and optimisations
This commit is contained in:
		| @@ -40,8 +40,14 @@ namespace KeePassLib.Collections | ||||
| 		IDeepCloneable<ProtectedBinaryDictionary>, | ||||
| 		IEnumerable<KeyValuePair<string, ProtectedBinary>> | ||||
| 	{ | ||||
| 		/* | ||||
| 		private SortedDictionary<string, ProtectedBinary> m_vBinaries = | ||||
| 			new SortedDictionary<string, ProtectedBinary>(); | ||||
| 		*/ | ||||
|  | ||||
| 		private Dictionary<string, ProtectedBinary> m_vBinaries = | ||||
| 			new Dictionary<string, ProtectedBinary>(); | ||||
|  | ||||
|  | ||||
| 		/// <summary> | ||||
| 		/// Get the number of binaries in this entry. | ||||
|   | ||||
| @@ -40,8 +40,9 @@ namespace KeePassLib.Collections | ||||
| 		IDeepCloneable<ProtectedStringDictionary>, | ||||
| 		IEnumerable<KeyValuePair<string, ProtectedString>> | ||||
| 	{ | ||||
| 		private SortedDictionary<string, ProtectedString> m_vStrings = | ||||
| 			new SortedDictionary<string, ProtectedString>(); | ||||
| 		/*private SortedDictionary<string, ProtectedString> m_vStrings = | ||||
| 			new SortedDictionary<string, ProtectedString>();*/ | ||||
| 		private Dictionary<string, ProtectedString> m_vStrings = new Dictionary<string, ProtectedString>(); | ||||
|  | ||||
| 		/// <summary> | ||||
| 		/// Get the number of strings in this entry. | ||||
|   | ||||
| @@ -101,5 +101,19 @@ namespace KeePassLib.Interfaces | ||||
| 		/// </summary> | ||||
| 		/// <param name="bModified">Update last modification time.</param> | ||||
| 		void Touch(bool bModified); | ||||
|  | ||||
| 		#region Set times lazily | ||||
| 		// Passing xml datetime string to be parsed only on demand | ||||
|  | ||||
| 		void SetLazyLastModificationTime(string xmlDateTime); | ||||
|  | ||||
| 		void SetLazyCreationTime(string xmlDateTime); | ||||
|  | ||||
| 		void SetLazyLastAccessTime(string xmlDateTime); | ||||
|  | ||||
| 		void SetLazyExpiryTime(string xmlDateTime); | ||||
|  | ||||
| 		void SetLazyLocationChanged(string xmlDateTime); | ||||
| 		#endregion | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | ||||
|   <PropertyGroup> | ||||
|     <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> | ||||
| @@ -20,7 +20,7 @@ | ||||
|     <DebugType>full</DebugType> | ||||
|     <Optimize>False</Optimize> | ||||
|     <OutputPath>bin\Debug</OutputPath> | ||||
|     <DefineConstants>DEBUG;</DefineConstants> | ||||
|     <DefineConstants>TRACE;DEBUG;</DefineConstants> | ||||
|     <ErrorReport>prompt</ErrorReport> | ||||
|     <WarningLevel>4</WarningLevel> | ||||
|     <ConsolePause>False</ConsolePause> | ||||
| @@ -113,6 +113,7 @@ | ||||
|     <Compile Include="Security\XorredBuffer.cs" /> | ||||
|     <Compile Include="Security\ProtectedBinary.cs" /> | ||||
|     <Compile Include="Security\ProtectedString.cs" /> | ||||
|     <Compile Include="Serialization\AsynchronousBufferedXmlReader.cs" /> | ||||
|     <Compile Include="Serialization\BinaryReaderEx.cs" /> | ||||
|     <Compile Include="Serialization\FileLock.cs" /> | ||||
|     <Compile Include="Serialization\FileTransactionEx.cs" /> | ||||
|   | ||||
| @@ -40,6 +40,7 @@ namespace KeePassLib | ||||
| 		private PwUuid m_uuid = PwUuid.Zero; | ||||
| 		private PwGroup m_pParentGroup = null; | ||||
| 		private DateTime m_tParentGroupLastMod = PwDefs.DtDefaultNow; | ||||
| 		private string m_tParentGroupLastModLazy; | ||||
|  | ||||
| 		private ProtectedStringDictionary m_listStrings = new ProtectedStringDictionary(); | ||||
| 		private ProtectedBinaryDictionary m_listBinaries = new ProtectedBinaryDictionary(); | ||||
| @@ -56,6 +57,12 @@ namespace KeePassLib | ||||
| 		private DateTime m_tLastMod = PwDefs.DtDefaultNow; | ||||
| 		private DateTime m_tLastAccess = PwDefs.DtDefaultNow; | ||||
| 		private DateTime m_tExpire = PwDefs.DtDefaultNow; | ||||
|  | ||||
| 		private string m_tCreationLazy; | ||||
| 		private string m_tLastModLazy; | ||||
| 		private string m_tLastAccessLazy; | ||||
| 		private string m_tExpireLazy; | ||||
|  | ||||
| 		private bool m_bExpires = false; | ||||
| 		private ulong m_uUsageCount = 0; | ||||
|  | ||||
| @@ -92,8 +99,13 @@ namespace KeePassLib | ||||
| 		/// </summary> | ||||
| 		public DateTime LocationChanged | ||||
| 		{ | ||||
| 			get { return m_tParentGroupLastMod; } | ||||
| 			set { m_tParentGroupLastMod = value; } | ||||
| 			get { return GetLazyTime(ref m_tParentGroupLastModLazy, ref m_tParentGroupLastMod); } | ||||
| 			set { m_tParentGroupLastMod = value; m_tParentGroupLastModLazy = null; } | ||||
| 		} | ||||
|  | ||||
| 		public void SetLazyLocationChanged(string xmlDateTime) | ||||
| 		{ | ||||
| 			m_tParentGroupLastModLazy = xmlDateTime; | ||||
| 		} | ||||
|  | ||||
| 		/// <summary> | ||||
| @@ -195,8 +207,13 @@ namespace KeePassLib | ||||
| 		/// </summary> | ||||
| 		public DateTime CreationTime | ||||
| 		{ | ||||
| 			get { return m_tCreation; } | ||||
| 			set { m_tCreation = value; } | ||||
| 			get { return GetLazyTime(ref m_tCreationLazy, ref m_tCreation); } | ||||
| 			set { m_tCreation = value; m_tCreationLazy = null; } | ||||
| 		} | ||||
|  | ||||
| 		public void SetLazyCreationTime(string xmlDateTime) | ||||
| 		{ | ||||
| 			m_tCreationLazy = xmlDateTime; | ||||
| 		} | ||||
|  | ||||
| 		/// <summary> | ||||
| @@ -204,8 +221,13 @@ namespace KeePassLib | ||||
| 		/// </summary> | ||||
| 		public DateTime LastAccessTime | ||||
| 		{ | ||||
| 			get { return m_tLastAccess; } | ||||
| 			set { m_tLastAccess = value; } | ||||
| 			get { return GetLazyTime(ref m_tLastAccessLazy, ref m_tLastAccess); } | ||||
| 			set { m_tLastAccess = value; m_tLastAccessLazy = null; } | ||||
| 		} | ||||
|  | ||||
| 		public void SetLazyLastAccessTime(string xmlDateTime) | ||||
| 		{ | ||||
| 			m_tLastAccessLazy = xmlDateTime; | ||||
| 		} | ||||
|  | ||||
| 		/// <summary> | ||||
| @@ -213,8 +235,13 @@ namespace KeePassLib | ||||
| 		/// </summary> | ||||
| 		public DateTime LastModificationTime | ||||
| 		{ | ||||
| 			get { return m_tLastMod; } | ||||
| 			set { m_tLastMod = value; } | ||||
| 			get { return GetLazyTime(ref m_tLastModLazy, ref m_tLastMod); } | ||||
| 			set { m_tLastMod = value; m_tLastModLazy = null; } | ||||
| 		} | ||||
|  | ||||
| 		public void SetLazyLastModificationTime(string xmlDateTime) | ||||
| 		{ | ||||
| 			m_tLastModLazy = xmlDateTime; | ||||
| 		} | ||||
|  | ||||
| 		/// <summary> | ||||
| @@ -223,8 +250,13 @@ namespace KeePassLib | ||||
| 		/// </summary> | ||||
| 		public DateTime ExpiryTime | ||||
| 		{ | ||||
| 			get { return m_tExpire; } | ||||
| 			set { m_tExpire = value; } | ||||
| 			get { return GetLazyTime(ref m_tExpireLazy, ref m_tExpire); } | ||||
| 			set { m_tExpire = value; m_tExpireLazy = null; } | ||||
| 		} | ||||
|  | ||||
| 		public void SetLazyExpiryTime(string xmlDateTime) | ||||
| 		{ | ||||
| 			m_tExpireLazy = xmlDateTime; | ||||
| 		} | ||||
|  | ||||
| 		/// <summary> | ||||
| @@ -844,6 +876,16 @@ namespace KeePassLib | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		private DateTime GetLazyTime(ref string lazyTime, ref DateTime dateTime) | ||||
| 		{ | ||||
| 			if (lazyTime != null) | ||||
| 			{ | ||||
| 				dateTime = TimeUtil.DeserializeUtcOrDefault(lazyTime, dateTime); | ||||
| 				lazyTime = null; | ||||
| 			} | ||||
| 			return dateTime;  | ||||
| 		}		 | ||||
| 	} | ||||
|  | ||||
| 	public sealed class PwEntryComparer : IComparer<PwEntry> | ||||
|   | ||||
| @@ -42,6 +42,7 @@ namespace KeePassLib | ||||
| 		private PwObjectList<PwEntry> m_listEntries = new PwObjectList<PwEntry>(); | ||||
| 		private PwGroup m_pParentGroup = null; | ||||
| 		private DateTime m_tParentGroupLastMod = PwDefs.DtDefaultNow; | ||||
| 		private string m_tParentGroupLastModLazy; | ||||
|  | ||||
| 		private PwUuid m_uuid = PwUuid.Zero; | ||||
| 		private string m_strName = string.Empty; | ||||
| @@ -54,6 +55,12 @@ namespace KeePassLib | ||||
| 		private DateTime m_tLastMod = PwDefs.DtDefaultNow; | ||||
| 		private DateTime m_tLastAccess = PwDefs.DtDefaultNow; | ||||
| 		private DateTime m_tExpire = PwDefs.DtDefaultNow; | ||||
|  | ||||
| 		private string m_tCreationLazy; | ||||
| 		private string m_tLastModLazy; | ||||
| 		private string m_tLastAccessLazy; | ||||
| 		private string m_tExpireLazy; | ||||
|  | ||||
| 		private bool m_bExpires = false; | ||||
| 		private ulong m_uUsageCount = 0; | ||||
|  | ||||
| @@ -146,8 +153,13 @@ namespace KeePassLib | ||||
| 		/// </summary> | ||||
| 		public DateTime LocationChanged | ||||
| 		{ | ||||
| 			get { return m_tParentGroupLastMod; } | ||||
| 			set { m_tParentGroupLastMod = value; } | ||||
| 			get { return GetLazyTime(ref m_tParentGroupLastModLazy, ref m_tParentGroupLastMod); } | ||||
| 			set { m_tParentGroupLastMod = value; m_tParentGroupLastModLazy = null; } | ||||
| 		} | ||||
|  | ||||
| 		public void SetLazyLocationChanged(string xmlDateTime) | ||||
| 		{ | ||||
| 			m_tParentGroupLastModLazy = xmlDateTime; | ||||
| 		} | ||||
|  | ||||
| 		/// <summary> | ||||
| @@ -165,17 +177,13 @@ namespace KeePassLib | ||||
| 		/// </summary> | ||||
| 		public DateTime CreationTime | ||||
| 		{ | ||||
| 			get { return m_tCreation; } | ||||
| 			set { m_tCreation = value; } | ||||
| 			get { return GetLazyTime(ref m_tCreationLazy, ref m_tCreation); } | ||||
| 			set { m_tCreation = value; m_tCreationLazy = null; } | ||||
| 		} | ||||
|  | ||||
| 		/// <summary> | ||||
| 		/// The date/time when this group was last modified. | ||||
| 		/// </summary> | ||||
| 		public DateTime LastModificationTime | ||||
| 		public void SetLazyCreationTime(string xmlDateTime) | ||||
| 		{ | ||||
| 			get { return m_tLastMod; } | ||||
| 			set { m_tLastMod = value; } | ||||
| 			m_tCreationLazy = xmlDateTime; | ||||
| 		} | ||||
|  | ||||
| 		/// <summary> | ||||
| @@ -183,17 +191,42 @@ namespace KeePassLib | ||||
| 		/// </summary> | ||||
| 		public DateTime LastAccessTime | ||||
| 		{ | ||||
| 			get { return m_tLastAccess; } | ||||
| 			set { m_tLastAccess = value; } | ||||
| 			get { return GetLazyTime(ref m_tLastAccessLazy, ref m_tLastAccess); } | ||||
| 			set { m_tLastAccess = value; m_tLastAccessLazy = null; } | ||||
| 		} | ||||
|  | ||||
| 		public void SetLazyLastAccessTime(string xmlDateTime) | ||||
| 		{ | ||||
| 			m_tLastAccessLazy = xmlDateTime; | ||||
| 		} | ||||
|  | ||||
| 		/// <summary> | ||||
| 		/// The date/time when this group expires. | ||||
| 		/// The date/time when this group was last modified. | ||||
| 		/// </summary> | ||||
| 		public DateTime LastModificationTime | ||||
| 		{ | ||||
| 			get { return GetLazyTime(ref m_tLastModLazy, ref m_tLastMod); } | ||||
| 			set { m_tLastMod = value; m_tLastModLazy = null; } | ||||
| 		} | ||||
|  | ||||
| 		public void SetLazyLastModificationTime(string xmlDateTime) | ||||
| 		{ | ||||
| 			m_tLastModLazy = xmlDateTime; | ||||
| 		} | ||||
|  | ||||
| 		/// <summary> | ||||
| 		/// The date/time when this group expires. Use the <c>Expires</c> property | ||||
| 		/// to specify if the group does actually expire or not. | ||||
| 		/// </summary> | ||||
| 		public DateTime ExpiryTime | ||||
| 		{ | ||||
| 			get { return m_tExpire; } | ||||
| 			set { m_tExpire = value; } | ||||
| 			get { return GetLazyTime(ref m_tExpireLazy, ref m_tExpire); } | ||||
| 			set { m_tExpire = value; m_tExpireLazy = null; } | ||||
| 		} | ||||
|  | ||||
| 		public void SetLazyExpiryTime(string xmlDateTime) | ||||
| 		{ | ||||
| 			m_tExpireLazy = xmlDateTime; | ||||
| 		} | ||||
|  | ||||
| 		/// <summary> | ||||
| @@ -1422,6 +1455,16 @@ namespace KeePassLib | ||||
| 			} | ||||
| 			m_listGroups.Clear(); | ||||
| 		} | ||||
|  | ||||
| 		private DateTime GetLazyTime(ref string lazyTime, ref DateTime dateTime) | ||||
| 		{ | ||||
| 			if (lazyTime != null) | ||||
| 			{ | ||||
| 				dateTime = TimeUtil.DeserializeUtcOrDefault(lazyTime, m_tLastMod); | ||||
| 				lazyTime = null; | ||||
| 			} | ||||
| 			return dateTime; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	public sealed class PwGroupComparer : IComparer<PwGroup> | ||||
|   | ||||
| @@ -0,0 +1,367 @@ | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Text; | ||||
| using System.IO; | ||||
| using System.Threading; | ||||
| using System.Xml; | ||||
| using System.Diagnostics; | ||||
|  | ||||
| namespace KeePassLib.Serialization | ||||
| { | ||||
| 	public class AsynchronousBufferedXmlReader : XmlReader | ||||
| 	{ | ||||
| 		/// <summary> | ||||
| 		/// An element which indicates the end of the XML document has been reached. | ||||
| 		/// </summary> | ||||
| 		private static readonly Element EndMarker = new Element(); | ||||
|  | ||||
| 		/// <summary> | ||||
| 		/// The next buffered element available for reading. | ||||
| 		/// Volatility: only read/written to by non-buffering thread. Passed to the buffer thread as an initial parameter. | ||||
| 		/// </summary> | ||||
| 		Element mBufferQueueHead = new Element(); // Start off with the pre-document element. No content, yet. | ||||
|  | ||||
| 		private readonly Thread mWorkerThread; | ||||
| 		private readonly AutoResetEvent mWaitForBuffer = new AutoResetEvent(false); | ||||
| 		/// <summary> | ||||
| 		/// True while the reader thread is stalled waiting for buffering. | ||||
| 		/// Volaitlity: Only written by read thread. Only read by buffer thread | ||||
| 		/// </summary> | ||||
| 		private volatile bool mWaitingForBuffer; | ||||
|  | ||||
| #if TRACE | ||||
| 		private Stopwatch mReadWaitTimer = new Stopwatch(); | ||||
| 		private Stopwatch mBufferCompletedTimer = new Stopwatch(); | ||||
| #endif | ||||
|  | ||||
| 		/// <summary> | ||||
| 		/// Testing helper method | ||||
| 		/// </summary> | ||||
| 		/// <param name="input"></param> | ||||
| 		/// <returns></returns> | ||||
| 		public static XmlReader FullyBuffer(Stream input) | ||||
| 		{ | ||||
| 			var reader = new AsynchronousBufferedXmlReader(); | ||||
| 			reader.ReadStreamWorker(input); | ||||
| 			return reader; | ||||
| 		} | ||||
|  | ||||
| 		private AsynchronousBufferedXmlReader() | ||||
| 		{ | ||||
| 			// Once the end is reached, it stays there. | ||||
| 			EndMarker.NextElement = EndMarker; | ||||
| 		} | ||||
|  | ||||
| 		public AsynchronousBufferedXmlReader(Stream input) : this() | ||||
| 		{ | ||||
| 			mWorkerThread = new Thread(ReadStreamWorker) { Name = GetType().Name }; | ||||
| 			mWorkerThread.Start(input); | ||||
| 		} | ||||
|  | ||||
| 		#region Buffering | ||||
| 		private void ReadStreamWorker(object state) | ||||
| 		{ | ||||
| 			var input = (Stream)state; | ||||
|  | ||||
| 			var xr = XmlReader.Create(input, KdbxFile.CreateStdXmlReaderSettings()); | ||||
|  | ||||
| 			/// <summary> | ||||
| 			/// The last buffered element available for reading. | ||||
| 			/// </summary> | ||||
| 			Element bufferQueueTail = mBufferQueueHead; | ||||
|  | ||||
| 			/// <summary> | ||||
| 			/// The element currently being buffered. Not yet available for reading. | ||||
| 			/// </summary> | ||||
| 			Element currentElement = null; | ||||
|  | ||||
| 			while (xr.Read()) | ||||
| 			{ | ||||
| 				switch (xr.NodeType) | ||||
| 				{ | ||||
| 					case XmlNodeType.Element: | ||||
| 						// Start a new element | ||||
| 						if (currentElement != null) | ||||
| 						{ | ||||
| 							// Add the previous current element to the tail of the buffer | ||||
| 							bufferQueueTail.NextElement = currentElement; | ||||
| 							bufferQueueTail = currentElement; | ||||
| 							if (mWaitingForBuffer) mWaitForBuffer.Set(); // Signal that a new element is available in the buffer | ||||
| 						} | ||||
|  | ||||
| 						currentElement = new Element { Name = xr.Name }; | ||||
|  | ||||
| 						// Process attributes - current optimisation, all elements have 0 or 1 attribute | ||||
| 						if (xr.MoveToNextAttribute()) | ||||
| 						{ | ||||
| #if DEBUG | ||||
| 							Debug.Assert(xr.AttributeCount == 1); | ||||
| 							currentElement.AttributeName = xr.Name; | ||||
| #endif | ||||
| 							currentElement.AttributeValue = xr.Value; | ||||
| 						} | ||||
|  | ||||
| 						currentElement.IsEmpty = xr.IsEmptyElement; | ||||
|  | ||||
| 						break; | ||||
|  | ||||
| 					case XmlNodeType.Text: | ||||
| 						currentElement.Value = xr.Value; | ||||
| 						currentElement.IsEmpty = true; // Mark as empty because it will have no end element written for it | ||||
| 						break; | ||||
|  | ||||
| 					case XmlNodeType.EndElement: | ||||
| 						Debug.Assert(currentElement != null, "Ending an element that was never started"); | ||||
| 						 | ||||
| 						// If this is an element with children (not one with a value) add an end element marker to the queue | ||||
| 						if (currentElement.Value == null || currentElement.Name != xr.Name) | ||||
| 						{ | ||||
| 							bufferQueueTail.NextElement = currentElement; | ||||
| 							bufferQueueTail = currentElement; | ||||
| 							if (mWaitingForBuffer) mWaitForBuffer.Set(); // Signal that a new element is available in the buffer | ||||
|  | ||||
| 							currentElement = new Element { Name = xr.Name, IsEndElement = true }; | ||||
| 						} | ||||
| 						break; | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			// Conclude the document, add the final element to the buffer and mark the ending | ||||
| 			currentElement.NextElement = EndMarker; | ||||
| 			bufferQueueTail.NextElement = currentElement; | ||||
| 			bufferQueueTail = currentElement; | ||||
| 			mWaitForBuffer.Set(); // Signal that final element is available in the buffer (regardless of wait flag, to avoid race condition) | ||||
| #if TRACE | ||||
| 			mBufferCompletedTimer.Start(); | ||||
| #endif | ||||
| 		} | ||||
| 		#endregion | ||||
|  | ||||
| 		private class Element | ||||
| 		{ | ||||
| 			/// <summary> | ||||
| 			/// Link to the next buffered element. | ||||
| 			/// Volatility: Written to by buffer thread only. Read by both threads | ||||
| 			/// </summary> | ||||
| 			public volatile Element NextElement; | ||||
| 			 | ||||
| 			public string Name; | ||||
| 			 | ||||
| 			/// <summary> | ||||
| 			/// If this element marks the end of an xml element with child nodes, the IsEndElement will be true, and Value must be null. | ||||
| 			/// </summary> | ||||
| 			public bool IsEndElement; | ||||
|  | ||||
| 			/// <summary> | ||||
| 			/// Set true if this represents an empty element | ||||
| 			/// </summary> | ||||
| 			public bool IsEmpty; | ||||
|  | ||||
| 			/// <summary> | ||||
| 			/// If Value is non-null, then there will be no corresponding Element with IsEndElement created. | ||||
| 			/// </summary> | ||||
| 			public string Value; | ||||
|  | ||||
| 			// Currently KDBX has a maximum of one attribute per element, so no need for a dictionary here, and the name is only used for debug asserts | ||||
| #if DEBUG | ||||
| 			public string AttributeName; | ||||
| #endif | ||||
| 			public string AttributeValue; | ||||
| 		} | ||||
|  | ||||
| 		#region Custom XmlReader implementation for usage by KdbxFile only | ||||
| 		public override bool Read() | ||||
| 		{ | ||||
| 			Element nextElement; | ||||
| 			while ((nextElement = mBufferQueueHead.NextElement) == null) | ||||
| 			{ | ||||
| #if TRACE | ||||
| 				mReadWaitTimer.Start(); | ||||
| #endif | ||||
| 				mWaitingForBuffer = true; | ||||
| 				mWaitForBuffer.WaitOne(); | ||||
| 				mWaitingForBuffer = false; | ||||
|  | ||||
| #if TRACE | ||||
| 				mReadWaitTimer.Stop(); | ||||
| #endif | ||||
| 			} | ||||
| 			mBufferQueueHead = mBufferQueueHead.NextElement; | ||||
|  | ||||
|  | ||||
| #if TRACE | ||||
| 			if (mBufferQueueHead == EndMarker) | ||||
| 			{ | ||||
| 				Debug.WriteLine(String.Format("Asynchronous Buffered XmlReader waited for a total of: {0}ms, buffer completed {1}ms ahead of read", mReadWaitTimer.ElapsedMilliseconds, mBufferCompletedTimer.ElapsedMilliseconds)); | ||||
| 			} | ||||
| #endif | ||||
| 			return mBufferQueueHead != EndMarker; | ||||
| 		} | ||||
|  | ||||
| 		public override string ReadElementString() | ||||
| 		{ | ||||
| 			var result = mBufferQueueHead.Value ?? String.Empty; // ReadElementString returns empty strings for null content | ||||
| 			Read(); // Read element string always skips to the start of the next element | ||||
| 			return result; | ||||
| 		} | ||||
|  | ||||
| 		public override XmlNodeType NodeType | ||||
| 		{ | ||||
| 			get  | ||||
| 			{ | ||||
| 				return mBufferQueueHead.IsEndElement ? XmlNodeType.EndElement : XmlNodeType.Element; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		public override bool IsEmptyElement | ||||
| 		{ | ||||
| 			get | ||||
| 			{ | ||||
| 				return mBufferQueueHead.IsEmpty; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		public override string Name | ||||
| 		{ | ||||
| 			get | ||||
| 			{ | ||||
| 				return mBufferQueueHead.Name; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		public override bool HasAttributes | ||||
| 		{ | ||||
| 			get | ||||
| 			{ | ||||
| 				return mBufferQueueHead.AttributeValue != null; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		public override bool MoveToAttribute(string name) | ||||
| 		{ | ||||
| #if DEBUG | ||||
| 			Debug.Assert(mBufferQueueHead.AttributeName == name); | ||||
| #endif | ||||
|  | ||||
| 			return true; | ||||
| 		} | ||||
|  | ||||
| 		public override string Value | ||||
| 		{ | ||||
| 			get | ||||
| 			{ | ||||
| 				return mBufferQueueHead.AttributeValue; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
|  | ||||
| 		public override bool MoveToElement() | ||||
| 		{ | ||||
| 			return true; | ||||
| 		} | ||||
| 		#endregion | ||||
|  | ||||
| 		#region Unimplemented XmlReader overrides | ||||
|  | ||||
| 		public override int AttributeCount | ||||
| 		{ | ||||
| 			get { throw new NotImplementedException(); } | ||||
| 		} | ||||
|  | ||||
| 		public override string BaseURI | ||||
| 		{ | ||||
| 			get { throw new NotImplementedException(); } | ||||
| 		} | ||||
|  | ||||
| 		public override void Close() | ||||
| 		{ | ||||
| 			throw new NotImplementedException(); | ||||
| 		} | ||||
|  | ||||
| 		public override int Depth | ||||
| 		{ | ||||
| 			get { throw new NotImplementedException(); } | ||||
| 		} | ||||
|  | ||||
| 		public override bool EOF | ||||
| 		{ | ||||
| 			get { throw new NotImplementedException(); } | ||||
| 		} | ||||
|  | ||||
| 		public override string GetAttribute(int i) | ||||
| 		{ | ||||
| 			throw new NotImplementedException(); | ||||
| 		} | ||||
|  | ||||
| 		public override string GetAttribute(string name, string namespaceURI) | ||||
| 		{ | ||||
| 			throw new NotImplementedException(); | ||||
| 		} | ||||
|  | ||||
| 		public override string GetAttribute(string name) | ||||
| 		{ | ||||
| 			throw new NotImplementedException(); | ||||
| 		} | ||||
|  | ||||
| 		public override bool HasValue | ||||
| 		{ | ||||
| 			get { throw new NotImplementedException(); } | ||||
| 		} | ||||
|  | ||||
| 		public override string LocalName | ||||
| 		{ | ||||
| 			get { throw new NotImplementedException(); } | ||||
| 		} | ||||
|  | ||||
| 		public override string LookupNamespace(string prefix) | ||||
| 		{ | ||||
| 			throw new NotImplementedException(); | ||||
| 		} | ||||
|  | ||||
| 		public override bool MoveToAttribute(string name, string ns) | ||||
| 		{ | ||||
| 			throw new NotImplementedException(); | ||||
| 		} | ||||
|  | ||||
| 		public override bool MoveToFirstAttribute() | ||||
| 		{ | ||||
| 			throw new NotImplementedException(); | ||||
| 		} | ||||
|  | ||||
| 		public override bool MoveToNextAttribute() | ||||
| 		{ | ||||
| 			throw new NotImplementedException(); | ||||
| 		} | ||||
|  | ||||
| 		public override XmlNameTable NameTable | ||||
| 		{ | ||||
| 			get { throw new NotImplementedException(); } | ||||
| 		} | ||||
|  | ||||
| 		public override string NamespaceURI | ||||
| 		{ | ||||
| 			get { throw new NotImplementedException(); } | ||||
| 		} | ||||
|  | ||||
| 		public override string Prefix | ||||
| 		{ | ||||
| 			get { throw new NotImplementedException(); } | ||||
| 		} | ||||
|  | ||||
| 		public override bool ReadAttributeValue() | ||||
| 		{ | ||||
| 			throw new NotImplementedException(); | ||||
| 		} | ||||
|  | ||||
| 		public override ReadState ReadState | ||||
| 		{ | ||||
| 			get { throw new NotImplementedException(); } | ||||
| 		} | ||||
|  | ||||
| 		public override void ResolveEntity() | ||||
| 		{ | ||||
| 			throw new NotImplementedException(); | ||||
| 		} | ||||
| 		#endregion | ||||
| 	} | ||||
| } | ||||
| @@ -497,19 +497,19 @@ namespace KeePassLib.Serialization | ||||
| 					Debug.Assert(tl != null); | ||||
|  | ||||
| 					if(xr.Name == ElemLastModTime) | ||||
| 						tl.LastModificationTime = ReadTime(xr); | ||||
| 						tl.SetLazyLastModificationTime(ReadString(xr)); | ||||
| 					else if(xr.Name == ElemCreationTime) | ||||
| 						tl.CreationTime = ReadTime(xr); | ||||
| 						tl.SetLazyCreationTime(ReadString(xr)); | ||||
| 					else if(xr.Name == ElemLastAccessTime) | ||||
| 						tl.LastAccessTime = ReadTime(xr); | ||||
| 						tl.SetLazyLastAccessTime(ReadString(xr)); | ||||
| 					else if(xr.Name == ElemExpiryTime) | ||||
| 						tl.ExpiryTime = ReadTime(xr); | ||||
| 						tl.SetLazyExpiryTime(ReadString(xr)); | ||||
| 					else if(xr.Name == ElemExpires) | ||||
| 						tl.Expires = ReadBool(xr, false); | ||||
| 					else if(xr.Name == ElemUsageCount) | ||||
| 						tl.UsageCount = ReadULong(xr, 0); | ||||
| 					else if(xr.Name == ElemLocationChanged) | ||||
| 						tl.LocationChanged = ReadTime(xr); | ||||
| 						tl.SetLazyLocationChanged(ReadString(xr)); | ||||
| 					else ReadUnknown(xr); | ||||
| 					break; | ||||
|  | ||||
|   | ||||
| @@ -70,6 +70,8 @@ namespace KeePassLib.Serialization | ||||
| 			Debug.Assert(sSource != null); | ||||
| 			if(sSource == null) throw new ArgumentNullException("sSource"); | ||||
|  | ||||
| 			var stopWatch = Stopwatch.StartNew(); | ||||
|  | ||||
| 			m_format = kdbFormat; | ||||
| 			m_slLogger = slLogger; | ||||
|  | ||||
| @@ -127,7 +129,33 @@ namespace KeePassLib.Serialization | ||||
| 				} | ||||
| 				else m_randomStream = null; // No random stream for plain-text files | ||||
|  | ||||
| 				ReadXmlStreamed(readerStream, hashedStream); | ||||
| 				Debug.WriteLine(String.Format("Crypto setup: {0}ms", stopWatch.ElapsedMilliseconds)); | ||||
| 				stopWatch.Restart(); | ||||
|  | ||||
| 				/* | ||||
| 				var memStream = new MemoryStream((int)hashedStream.Length); | ||||
| 				CopyStream(readerStream, memStream); | ||||
| 				readerStream = memStream; | ||||
| 				Debug.WriteLine(String.Format("CopyStream: {0}ms", stopWatch.ElapsedMilliseconds)); | ||||
|  | ||||
|  | ||||
| 				stopWatch.Restart(); | ||||
| 				 */ | ||||
|  | ||||
| 				//var bufferedXmlReader = AsynchronousBufferedXmlReader.FullyBuffer(readerStream); | ||||
| 				//Debug.WriteLine(String.Format("ReadToBuffer: {0}ms", stopWatch.ElapsedMilliseconds)); | ||||
|  | ||||
| 				if (Java.Lang.Runtime.GetRuntime().AvailableProcessors() > 1) | ||||
| 				{ | ||||
| 					ReadDocumentStreamed(new AsynchronousBufferedXmlReader(readerStream), hashedStream); | ||||
| 					Debug.WriteLine(String.Format("ReadDocumentStreamed: {0}ms  multi-threaded", stopWatch.ElapsedMilliseconds)); | ||||
| 				} | ||||
| 				else | ||||
| 				{ | ||||
| 					ReadXmlStreamed(readerStream, hashedStream); | ||||
| 					Debug.WriteLine(String.Format("ReadXmlStreamed: {0}ms single-threaded", stopWatch.ElapsedMilliseconds)); | ||||
| 				} | ||||
| 				stopWatch.Restart(); | ||||
| 				// ReadXmlDom(readerStream); | ||||
|  | ||||
| 				readerStream.Close(); | ||||
| @@ -138,9 +166,26 @@ namespace KeePassLib.Serialization | ||||
| 			{ | ||||
| 				throw new CryptographicException(KLRes.FileCorrupted); | ||||
| 			} | ||||
| 			finally { CommonCleanUpRead(sSource, hashedStream); } | ||||
| 			finally  | ||||
| 			{ | ||||
| 				 | ||||
| 				CommonCleanUpRead(sSource, hashedStream); | ||||
| 				Debug.WriteLine(String.Format("Close and Clean Up: {0}ms", stopWatch.ElapsedMilliseconds)); | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		/* | ||||
| 		public static void CopyStream(Stream input, Stream output) | ||||
| 		{ | ||||
| 			byte[] buffer = new byte[32768]; | ||||
| 			int read; | ||||
| 			while ((read = input.Read(buffer, 0, buffer.Length)) > 0) | ||||
| 			{ | ||||
| 				output.Write(buffer, 0, read); | ||||
| 			} | ||||
| 			output.Seek(0, SeekOrigin.Begin); | ||||
| 		}*/ | ||||
|  | ||||
| 		private void CommonCleanUpRead(Stream sSource, HashingStreamEx hashedStream) | ||||
| 		{ | ||||
| 			hashedStream.Close(); | ||||
|   | ||||
| @@ -167,6 +167,22 @@ namespace KeePassLib.Utility | ||||
| 			return bResult; | ||||
| 		} | ||||
|  | ||||
| 		/// <summary> | ||||
| 		/// Deserializes a UTC XML DateTime to a local time, or returns defaultValue if it could not be parsed | ||||
| 		/// </summary> | ||||
| 		public static DateTime DeserializeUtcOrDefault(string xmlDateTimeString, DateTime defaultValue) | ||||
| 		{ | ||||
| 			try | ||||
| 			{ | ||||
| 				return System.Xml.XmlConvert.ToDateTime(xmlDateTimeString, System.Xml.XmlDateTimeSerializationMode.Local); | ||||
| 			} | ||||
| 			catch(FormatException) | ||||
| 			{ | ||||
| 				return defaultValue; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
|  | ||||
| 		private static DateTime? m_dtUnixRoot = null; | ||||
| 		public static DateTime ConvertUnixTime(double dtUnix) | ||||
| 		{ | ||||
|   | ||||
| @@ -120,6 +120,8 @@ namespace keepass2android | ||||
| 		 | ||||
| 		public void LoadData(Context ctx, IOConnectionInfo iocInfo, String password, String keyfile, UpdateStatus status) | ||||
| 		{ | ||||
| 			var stopWatch = System.Diagnostics.Stopwatch.StartNew(); | ||||
| 			 | ||||
| 			mIoc = iocInfo; | ||||
|  | ||||
| 			KeePassLib.PwDatabase pwDatabase = new KeePassLib.PwDatabase(); | ||||
| @@ -137,9 +139,15 @@ namespace keepass2android | ||||
| 					throw new KeyFileException(); | ||||
| 				} | ||||
| 			} | ||||
| 			 | ||||
|  | ||||
| 			System.Diagnostics.Debug.WriteLine(String.Format("LoadData Pre-open: {0}ms", stopWatch.ElapsedMilliseconds)); | ||||
| 			stopWatch.Restart(); | ||||
|  | ||||
| 			pwDatabase.Open(iocInfo, key, status); | ||||
|  | ||||
| 			System.Diagnostics.Debug.WriteLine(String.Format("LoadData Open: {0}ms", stopWatch.ElapsedMilliseconds)); | ||||
| 			stopWatch.Restart(); | ||||
|  | ||||
| 			if (iocInfo.IsLocalFile()) | ||||
| 			{ | ||||
| 				mLastChangeDate = System.IO.File.GetLastWriteTimeUtc(iocInfo.Path); | ||||
| @@ -155,6 +163,8 @@ namespace keepass2android | ||||
| 			Loaded = true; | ||||
| 			pm = pwDatabase; | ||||
| 			searchHelper = new SearchDbHelper(ctx); | ||||
|  | ||||
| 			System.Diagnostics.Debug.WriteLine(String.Format("LoadData Post-open: {0}ms", stopWatch.ElapsedMilliseconds)); | ||||
| 		} | ||||
|  | ||||
| 		bool quickUnlockEnabled = false; | ||||
|   | ||||
| @@ -204,16 +204,12 @@ namespace keepass2android | ||||
| 				extraGroup.RemoveAllViews(); | ||||
| 			} | ||||
| 			bool hasExtraFields = false; | ||||
| 			foreach (KeyValuePair<string, ProtectedString> pair in mEntry.Strings) | ||||
| 			foreach (var view in from pair in mEntry.Strings where !PwDefs.IsStandardField(pair.Key) orderby pair.Key  | ||||
| 								 select CreateEditSection(pair.Key, pair.Value.ReadString())) | ||||
| 			{ | ||||
| 				String key = pair.Key; | ||||
| 				if (!PwDefs.IsStandardField(key)) | ||||
| 				{ | ||||
| 					//View view = new EntrySection(this, null, key, pair.Value.ReadString()); | ||||
| 					View view = CreateEditSection(key, pair.Value.ReadString()); | ||||
| 					extraGroup.AddView(view); | ||||
| 					hasExtraFields = true; | ||||
| 				} | ||||
| 				//View view = new EntrySection(this, null, key, pair.Value.ReadString()); | ||||
| 				extraGroup.AddView(view); | ||||
| 				hasExtraFields = true; | ||||
| 			} | ||||
| 			FindViewById(Resource.Id.entry_extra_strings_label).Visibility = hasExtraFields ? ViewStates.Visible : ViewStates.Gone; | ||||
| 		} | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 AlexVallat
					AlexVallat