From f157329cab85267a0fdef52d2769c25edfce37df Mon Sep 17 00:00:00 2001 From: AlexVallat Date: Sat, 15 Jun 2013 20:12:46 +0100 Subject: [PATCH] Commit of initial kdbx load perf testing and optimisations --- .../Collections/ProtectedBinaryDictionary.cs | 6 + .../Collections/ProtectedStringDictionary.cs | 5 +- .../Interfaces/ITimeLogger.cs | 14 + .../KeePassLib2Android.csproj | 5 +- src/KeePassLib2Android/PwEntry.cs | 62 ++- src/KeePassLib2Android/PwGroup.cs | 73 +++- .../AsynchronousBufferedXmlReader.cs | 367 ++++++++++++++++++ .../Serialization/KdbxFile.Read.Streamed.cs | 10 +- .../Serialization/KdbxFile.Read.cs | 49 ++- src/KeePassLib2Android/Utility/TimeUtil.cs | 16 + src/keepass2android/Database.cs | 12 +- src/keepass2android/EntryActivity.cs | 14 +- 12 files changed, 587 insertions(+), 46 deletions(-) create mode 100644 src/KeePassLib2Android/Serialization/AsynchronousBufferedXmlReader.cs diff --git a/src/KeePassLib2Android/Collections/ProtectedBinaryDictionary.cs b/src/KeePassLib2Android/Collections/ProtectedBinaryDictionary.cs index c0df6707..0e8230ce 100644 --- a/src/KeePassLib2Android/Collections/ProtectedBinaryDictionary.cs +++ b/src/KeePassLib2Android/Collections/ProtectedBinaryDictionary.cs @@ -40,8 +40,14 @@ namespace KeePassLib.Collections IDeepCloneable, IEnumerable> { + /* private SortedDictionary m_vBinaries = new SortedDictionary(); + */ + + private Dictionary m_vBinaries = + new Dictionary(); + /// /// Get the number of binaries in this entry. diff --git a/src/KeePassLib2Android/Collections/ProtectedStringDictionary.cs b/src/KeePassLib2Android/Collections/ProtectedStringDictionary.cs index ef03ff78..dffede11 100644 --- a/src/KeePassLib2Android/Collections/ProtectedStringDictionary.cs +++ b/src/KeePassLib2Android/Collections/ProtectedStringDictionary.cs @@ -40,8 +40,9 @@ namespace KeePassLib.Collections IDeepCloneable, IEnumerable> { - private SortedDictionary m_vStrings = - new SortedDictionary(); + /*private SortedDictionary m_vStrings = + new SortedDictionary();*/ + private Dictionary m_vStrings = new Dictionary(); /// /// Get the number of strings in this entry. diff --git a/src/KeePassLib2Android/Interfaces/ITimeLogger.cs b/src/KeePassLib2Android/Interfaces/ITimeLogger.cs index 86a6b85d..361abe7a 100644 --- a/src/KeePassLib2Android/Interfaces/ITimeLogger.cs +++ b/src/KeePassLib2Android/Interfaces/ITimeLogger.cs @@ -101,5 +101,19 @@ namespace KeePassLib.Interfaces /// /// Update last modification time. 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 } } diff --git a/src/KeePassLib2Android/KeePassLib2Android.csproj b/src/KeePassLib2Android/KeePassLib2Android.csproj index b01ed8b8..a7fdc014 100644 --- a/src/KeePassLib2Android/KeePassLib2Android.csproj +++ b/src/KeePassLib2Android/KeePassLib2Android.csproj @@ -1,4 +1,4 @@ - + Debug @@ -20,7 +20,7 @@ full False bin\Debug - DEBUG; + TRACE;DEBUG; prompt 4 False @@ -113,6 +113,7 @@ + diff --git a/src/KeePassLib2Android/PwEntry.cs b/src/KeePassLib2Android/PwEntry.cs index b26bec6f..8e587b5a 100644 --- a/src/KeePassLib2Android/PwEntry.cs +++ b/src/KeePassLib2Android/PwEntry.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 /// 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; } /// @@ -195,8 +207,13 @@ namespace KeePassLib /// 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; } /// @@ -204,8 +221,13 @@ namespace KeePassLib /// 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; } /// @@ -213,8 +235,13 @@ namespace KeePassLib /// 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; } /// @@ -223,8 +250,13 @@ namespace KeePassLib /// 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; } /// @@ -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 diff --git a/src/KeePassLib2Android/PwGroup.cs b/src/KeePassLib2Android/PwGroup.cs index 791e0fbe..b5b20c25 100644 --- a/src/KeePassLib2Android/PwGroup.cs +++ b/src/KeePassLib2Android/PwGroup.cs @@ -42,6 +42,7 @@ namespace KeePassLib private PwObjectList m_listEntries = new PwObjectList(); 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 /// 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; } /// @@ -165,17 +177,13 @@ namespace KeePassLib /// 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; } } - /// - /// The date/time when this group was last modified. - /// - public DateTime LastModificationTime + public void SetLazyCreationTime(string xmlDateTime) { - get { return m_tLastMod; } - set { m_tLastMod = value; } + m_tCreationLazy = xmlDateTime; } /// @@ -183,17 +191,42 @@ namespace KeePassLib /// 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; } /// - /// The date/time when this group expires. + /// The date/time when this group was last modified. + /// + 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; + } + + /// + /// The date/time when this group expires. Use the Expires property + /// to specify if the group does actually expire or not. /// 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; } /// @@ -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 diff --git a/src/KeePassLib2Android/Serialization/AsynchronousBufferedXmlReader.cs b/src/KeePassLib2Android/Serialization/AsynchronousBufferedXmlReader.cs new file mode 100644 index 00000000..d10ba1cb --- /dev/null +++ b/src/KeePassLib2Android/Serialization/AsynchronousBufferedXmlReader.cs @@ -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 + { + /// + /// An element which indicates the end of the XML document has been reached. + /// + private static readonly Element EndMarker = new Element(); + + /// + /// 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. + /// + 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); + /// + /// True while the reader thread is stalled waiting for buffering. + /// Volaitlity: Only written by read thread. Only read by buffer thread + /// + private volatile bool mWaitingForBuffer; + +#if TRACE + private Stopwatch mReadWaitTimer = new Stopwatch(); + private Stopwatch mBufferCompletedTimer = new Stopwatch(); +#endif + + /// + /// Testing helper method + /// + /// + /// + 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()); + + /// + /// The last buffered element available for reading. + /// + Element bufferQueueTail = mBufferQueueHead; + + /// + /// The element currently being buffered. Not yet available for reading. + /// + 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 + { + /// + /// Link to the next buffered element. + /// Volatility: Written to by buffer thread only. Read by both threads + /// + public volatile Element NextElement; + + public string Name; + + /// + /// If this element marks the end of an xml element with child nodes, the IsEndElement will be true, and Value must be null. + /// + public bool IsEndElement; + + /// + /// Set true if this represents an empty element + /// + public bool IsEmpty; + + /// + /// If Value is non-null, then there will be no corresponding Element with IsEndElement created. + /// + 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 + } +} diff --git a/src/KeePassLib2Android/Serialization/KdbxFile.Read.Streamed.cs b/src/KeePassLib2Android/Serialization/KdbxFile.Read.Streamed.cs index c572f6ca..4502fb2b 100644 --- a/src/KeePassLib2Android/Serialization/KdbxFile.Read.Streamed.cs +++ b/src/KeePassLib2Android/Serialization/KdbxFile.Read.Streamed.cs @@ -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; diff --git a/src/KeePassLib2Android/Serialization/KdbxFile.Read.cs b/src/KeePassLib2Android/Serialization/KdbxFile.Read.cs index 03456962..65c2aa55 100644 --- a/src/KeePassLib2Android/Serialization/KdbxFile.Read.cs +++ b/src/KeePassLib2Android/Serialization/KdbxFile.Read.cs @@ -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(); diff --git a/src/KeePassLib2Android/Utility/TimeUtil.cs b/src/KeePassLib2Android/Utility/TimeUtil.cs index 94aa4cd2..25c815e1 100644 --- a/src/KeePassLib2Android/Utility/TimeUtil.cs +++ b/src/KeePassLib2Android/Utility/TimeUtil.cs @@ -167,6 +167,22 @@ namespace KeePassLib.Utility return bResult; } + /// + /// Deserializes a UTC XML DateTime to a local time, or returns defaultValue if it could not be parsed + /// + 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) { diff --git a/src/keepass2android/Database.cs b/src/keepass2android/Database.cs index 08aecdf7..6e03e980 100644 --- a/src/keepass2android/Database.cs +++ b/src/keepass2android/Database.cs @@ -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; diff --git a/src/keepass2android/EntryActivity.cs b/src/keepass2android/EntryActivity.cs index 779dd3b8..ff2f51d4 100644 --- a/src/keepass2android/EntryActivity.cs +++ b/src/keepass2android/EntryActivity.cs @@ -204,16 +204,12 @@ namespace keepass2android extraGroup.RemoveAllViews(); } bool hasExtraFields = false; - foreach (KeyValuePair 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; }