This commit is contained in:
Philipp Crocoll 2013-07-09 10:13:26 +02:00
commit d90407fd2d
21 changed files with 652 additions and 117 deletions

View File

@ -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.

View File

@ -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.

View File

@ -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
}
}

View File

@ -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>

View File

@ -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>
@ -333,6 +365,7 @@ namespace KeePassLib
peNew.m_uuid = m_uuid; // PwUuid is immutable
peNew.m_pParentGroup = m_pParentGroup;
peNew.m_tParentGroupLastMod = m_tParentGroupLastMod;
peNew.m_tParentGroupLastModLazy = m_tParentGroupLastModLazy;
peNew.m_listStrings = m_listStrings.CloneDeep();
peNew.m_listBinaries = m_listBinaries.CloneDeep();
@ -351,6 +384,11 @@ namespace KeePassLib
peNew.m_tExpire = m_tExpire;
peNew.m_bExpires = m_bExpires;
peNew.m_uUsageCount = m_uUsageCount;
peNew.m_tCreationLazy = m_tCreationLazy;
peNew.m_tLastModLazy = m_tLastModLazy;
peNew.m_tLastAccessLazy = m_tLastAccessLazy;
peNew.m_tExpireLazy = m_tExpireLazy;
peNew.m_strOverrideUrl = m_strOverrideUrl;
@ -365,6 +403,7 @@ namespace KeePassLib
peNew.m_uuid = m_uuid; // PwUuid is immutable
peNew.m_tParentGroupLastMod = m_tParentGroupLastMod;
peNew.m_tParentGroupLastModLazy = m_tParentGroupLastModLazy;
// Do not assign m_pParentGroup
return peNew;
@ -417,7 +456,7 @@ namespace KeePassLib
if((pwOpt & PwCompareOptions.IgnoreParentGroup) == PwCompareOptions.None)
{
if(m_pParentGroup != pe.m_pParentGroup) return false;
if(!bIgnoreLastMod && (m_tParentGroupLastMod != pe.m_tParentGroupLastMod))
if(!bIgnoreLastMod && (LocationChanged != pe.LocationChanged))
return false;
}
@ -461,10 +500,10 @@ namespace KeePassLib
if(m_clrForeground != pe.m_clrForeground) return false;
if(m_clrBackground != pe.m_clrBackground) return false;
if(m_tCreation != pe.m_tCreation) return false;
if(!bIgnoreLastMod && (m_tLastMod != pe.m_tLastMod)) return false;
if(!bIgnoreLastAccess && (m_tLastAccess != pe.m_tLastAccess)) return false;
if(m_tExpire != pe.m_tExpire) return false;
if(CreationTime != pe.CreationTime) return false;
if(!bIgnoreLastMod && (LastModificationTime != pe.LastModificationTime)) return false;
if(!bIgnoreLastAccess && (LastAccessTime != pe.LastAccessTime)) return false;
if(ExpiryTime != pe.ExpiryTime) return false;
if(m_bExpires != pe.m_bExpires) return false;
if(!bIgnoreLastAccess && (m_uUsageCount != pe.m_uUsageCount)) return false;
@ -494,14 +533,14 @@ namespace KeePassLib
{
Debug.Assert(peTemplate != null); if(peTemplate == null) throw new ArgumentNullException("peTemplate");
if(bOnlyIfNewer && (peTemplate.m_tLastMod < m_tLastMod)) return;
if(bOnlyIfNewer && (peTemplate.LastModificationTime < LastModificationTime)) return;
// Template UUID should be the same as the current one
Debug.Assert(m_uuid.EqualsValue(peTemplate.m_uuid));
m_uuid = peTemplate.m_uuid;
if(bAssignLocationChanged)
m_tParentGroupLastMod = peTemplate.m_tParentGroupLastMod;
m_tParentGroupLastMod = peTemplate.LocationChanged;
m_listStrings = peTemplate.m_listStrings;
m_listBinaries = peTemplate.m_listBinaries;
@ -514,10 +553,10 @@ namespace KeePassLib
m_clrForeground = peTemplate.m_clrForeground;
m_clrBackground = peTemplate.m_clrBackground;
m_tCreation = peTemplate.m_tCreation;
m_tLastMod = peTemplate.m_tLastMod;
m_tLastAccess = peTemplate.m_tLastAccess;
m_tExpire = peTemplate.m_tExpire;
m_tCreation = peTemplate.CreationTime;
m_tLastMod = peTemplate.LastModificationTime;
m_tLastAccess = peTemplate.LastAccessTime;
m_tExpire = peTemplate.ExpiryTime;
m_bExpires = peTemplate.m_bExpires;
m_uUsageCount = peTemplate.m_uUsageCount;
@ -844,6 +883,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>

View File

@ -35,6 +35,11 @@ namespace KeePassLib
/// </summary>
public sealed class PwGroup : ITimeLogger, IStructureItem, IDeepCloneable<PwGroup>
{
private const int SearchContextStringMaxLength = 50; // Note, doesn't include elipsis, if added
public const string SearchContextUuid = "Uuid";
public const string SearchContextParentGroup = "Parent Group";
public const string SearchContextTags = "Tags";
public const bool DefaultAutoTypeEnabled = true;
public const bool DefaultSearchingEnabled = true;
@ -42,6 +47,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 +60,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 +158,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 +182,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 +196,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>
@ -344,6 +382,7 @@ namespace KeePassLib
pg.m_listEntries = m_listEntries.CloneDeep();
pg.m_pParentGroup = m_pParentGroup;
pg.m_tParentGroupLastMod = m_tParentGroupLastMod;
pg.m_tParentGroupLastModLazy = m_tParentGroupLastModLazy;
pg.m_strName = m_strName;
pg.m_strNotes = m_strNotes;
@ -358,6 +397,11 @@ namespace KeePassLib
pg.m_bExpires = m_bExpires;
pg.m_uUsageCount = m_uUsageCount;
pg.m_tCreationLazy = m_tCreationLazy;
pg.m_tLastModLazy = m_tLastModLazy;
pg.m_tLastAccessLazy = m_tLastAccessLazy;
pg.m_tExpireLazy = m_tExpireLazy;
pg.m_bIsExpanded = m_bIsExpanded;
pg.m_bVirtual = m_bVirtual;
@ -398,7 +442,7 @@ namespace KeePassLib
{
Debug.Assert(pgTemplate != null); if(pgTemplate == null) throw new ArgumentNullException("pgTemplate");
if(bOnlyIfNewer && (pgTemplate.m_tLastMod < m_tLastMod)) return;
if(bOnlyIfNewer && (pgTemplate.LastModificationTime < LastModificationTime)) return;
// Template UUID should be the same as the current one
Debug.Assert(m_uuid.EqualsValue(pgTemplate.m_uuid));
@ -413,10 +457,10 @@ namespace KeePassLib
m_pwIcon = pgTemplate.m_pwIcon;
m_pwCustomIconID = pgTemplate.m_pwCustomIconID;
m_tCreation = pgTemplate.m_tCreation;
m_tLastMod = pgTemplate.m_tLastMod;
m_tLastAccess = pgTemplate.m_tLastAccess;
m_tExpire = pgTemplate.m_tExpire;
m_tCreation = pgTemplate.CreationTime;
m_tLastMod = pgTemplate.LastModificationTime;
m_tLastAccess = pgTemplate.LastAccessTime;
m_tExpire = pgTemplate.ExpiryTime;
m_bExpires = pgTemplate.m_bExpires;
m_uUsageCount = pgTemplate.m_uUsageCount;
@ -673,6 +717,18 @@ namespace KeePassLib
/// be stored.</param>
public void SearchEntries(SearchParameters sp, PwObjectList<PwEntry> listStorage,
IStatusLogger slStatus)
{
}
/// <summary>
/// Search this group and all subgroups for entries.
/// </summary>
/// <param name="sp">Specifies the search method.</param>
/// <param name="listStorage">Entry list in which the search results will
/// be stored.</param>
/// <param name="resultContexts">Dictionary that will be populated with text fragments indicating the context of why each entry (keyed by Uuid) was returned</param>
public void SearchEntries(SearchParameters sp, PwObjectList<PwEntry> listStorage, IDictionary<PwUuid, String> resultContexts,
IStatusLogger slStatus)
{
if(sp == null) { Debug.Assert(false); return; }
if(listStorage == null) { Debug.Assert(false); return; }
@ -683,7 +739,7 @@ namespace KeePassLib
if((lTerms.Count <= 1) || sp.RegularExpression)
{
if(slStatus != null) uTotalEntries = GetEntriesCount(true);
SearchEntriesSingle(sp, listStorage, slStatus, ref uCurEntries,
SearchEntriesSingle(sp, listStorage, resultContexts , slStatus, ref uCurEntries,
uTotalEntries);
return;
}
@ -715,7 +771,7 @@ namespace KeePassLib
bNegate = (sp.SearchString.Length > 0);
}
if(!pg.SearchEntriesSingle(sp, pgNew.Entries, slStatus,
if(!pg.SearchEntriesSingle(sp, pgNew.Entries, resultContexts, slStatus,
ref uCurEntries, uTotalEntries))
{
pg = null;
@ -740,7 +796,7 @@ namespace KeePassLib
}
private bool SearchEntriesSingle(SearchParameters spIn,
PwObjectList<PwEntry> listStorage, IStatusLogger slStatus,
PwObjectList<PwEntry> listStorage, IDictionary<PwUuid, String> resultContexts, IStatusLogger slStatus,
ref ulong uCurEntries, ulong uTotalEntries)
{
SearchParameters sp = spIn.Clone();
@ -823,42 +879,42 @@ namespace KeePassLib
if(strKey == PwDefs.TitleField)
{
if(bTitle) SearchEvalAdd(sp, kvp.Value.ReadString(),
rx, pe, listStorage);
rx, pe, listStorage, resultContexts, strKey);
}
else if(strKey == PwDefs.UserNameField)
{
if(bUserName) SearchEvalAdd(sp, kvp.Value.ReadString(),
rx, pe, listStorage);
rx, pe, listStorage, resultContexts, strKey);
}
else if(strKey == PwDefs.PasswordField)
{
if(bPassword) SearchEvalAdd(sp, kvp.Value.ReadString(),
rx, pe, listStorage);
rx, pe, listStorage, resultContexts, strKey);
}
else if(strKey == PwDefs.UrlField)
{
if(bUrl) SearchEvalAdd(sp, kvp.Value.ReadString(),
rx, pe, listStorage);
rx, pe, listStorage, resultContexts, strKey);
}
else if(strKey == PwDefs.NotesField)
{
if(bNotes) SearchEvalAdd(sp, kvp.Value.ReadString(),
rx, pe, listStorage);
rx, pe, listStorage, resultContexts, strKey);
}
else if(bOther)
SearchEvalAdd(sp, kvp.Value.ReadString(),
rx, pe, listStorage);
rx, pe, listStorage, resultContexts, strKey);
// An entry can match only once => break if we have added it
if(listStorage.UCount > uInitialResults) break;
}
if(bUuids && (listStorage.UCount == uInitialResults))
SearchEvalAdd(sp, pe.Uuid.ToHexString(), rx, pe, listStorage);
SearchEvalAdd(sp, pe.Uuid.ToHexString(), rx, pe, listStorage, resultContexts, SearchContextUuid);
if(bGroupName && (listStorage.UCount == uInitialResults) &&
(pe.ParentGroup != null))
SearchEvalAdd(sp, pe.ParentGroup.Name, rx, pe, listStorage);
SearchEvalAdd(sp, pe.ParentGroup.Name, rx, pe, listStorage, resultContexts, SearchContextParentGroup);
if(bTags)
{
@ -866,7 +922,7 @@ namespace KeePassLib
{
if(listStorage.UCount != uInitialResults) break; // Match
SearchEvalAdd(sp, strTag, rx, pe, listStorage);
SearchEvalAdd(sp, strTag, rx, pe, listStorage, resultContexts, SearchContextTags);
}
}
@ -880,28 +936,59 @@ namespace KeePassLib
}
private static void SearchEvalAdd(SearchParameters sp, string strDataField,
Regex rx, PwEntry pe, PwObjectList<PwEntry> lResults)
Regex rx, PwEntry pe, PwObjectList<PwEntry> lResults, IDictionary<PwUuid, String> resultContexts, string contextFieldName)
{
bool bMatch = false;
int matchPos;
if(rx == null)
bMatch = (strDataField.IndexOf(sp.SearchString,
sp.ComparisonMode) >= 0);
else bMatch = rx.IsMatch(strDataField);
if (rx == null)
{
matchPos = strDataField.IndexOf(sp.SearchString, sp.ComparisonMode);
bMatch = matchPos >= 0;
}
else
{
var match = rx.Match(strDataField);
bMatch = match.Success;
matchPos = match.Index;
}
if(!bMatch && (sp.DataTransformationFn != null))
{
string strCmp = sp.DataTransformationFn(strDataField, pe);
if(!object.ReferenceEquals(strCmp, strDataField))
{
if(rx == null)
bMatch = (strCmp.IndexOf(sp.SearchString,
sp.ComparisonMode) >= 0);
else bMatch = rx.IsMatch(strCmp);
if (rx == null)
{
matchPos = strCmp.IndexOf(sp.SearchString, sp.ComparisonMode);
bMatch = matchPos >= 0;
}
else
{
var match = rx.Match(strCmp);
bMatch = match.Success;
matchPos = match.Index;
}
}
}
if(bMatch) lResults.Add(pe);
if (bMatch)
{
lResults.Add(pe);
if (resultContexts != null)
{
// Trim the value if necessary
var contextString = strDataField;
if (contextString.Length > SearchContextStringMaxLength)
{
// Start 10% before actual data, and don't run over
var startPos = Math.Min(matchPos - (SearchContextStringMaxLength / 10), contextString.Length - SearchContextStringMaxLength);
contextString = "… " + contextString.Substring(startPos, SearchContextStringMaxLength) + ((startPos + SearchContextStringMaxLength < contextString.Length) ? " …" : null);
}
resultContexts[pe.Uuid] = contextFieldName + ": " + contextString;
}
}
}
public List<string> BuildEntryTagsList()
@ -1422,6 +1509,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>

View File

@ -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;

View File

@ -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)
{

View File

@ -15,8 +15,9 @@ This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. This file
along with Keepass2Android. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using KeePassLib;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using KeePassLib;
using KeePassLib.Collections;
using KeePassLib.Interfaces;
using KeePassLib.Utility;
@ -40,9 +41,9 @@ namespace keepass2android
{
SearchParameters sp = new SearchParameters {SearchString = str};
return Search(database, sp);
return Search(database, sp, null);
}
public PwGroup Search(Database database, SearchParameters sp)
public PwGroup Search(Database database, SearchParameters sp, IDictionary<PwUuid, String> resultContexts)
{
if(sp.RegularExpression) // Validate regular expression
@ -56,7 +57,7 @@ namespace keepass2android
PwObjectList<PwEntry> listResults = pgResults.Entries;
database.Root.SearchEntries(sp, listResults, new NullStatusLogger());
database.Root.SearchEntries(sp, listResults, resultContexts, new NullStatusLogger());
return pgResults;

View File

@ -160,9 +160,9 @@ namespace keepass2android
}
public PwGroup Search(SearchParameters searchParams)
public PwGroup Search(SearchParameters searchParams, IDictionary<PwUuid, String> resultContexts)
{
return SearchHelper.Search(this, searchParams);
return SearchHelper.Search(this, searchParams, resultContexts);
}

View File

@ -18,6 +18,7 @@ This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. This file
using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using Android.App;
using Android.Content;
@ -151,7 +152,7 @@ namespace keepass2android
Finish();
}
}
private String getDateTime(DateTime dt) {
return dt.ToString ("g", CultureInfo.CurrentUICulture);
@ -178,16 +179,12 @@ namespace keepass2android
extraGroup.RemoveAllViews();
}
bool hasExtraFields = false;
foreach (KeyValuePair<string, ProtectedString> pair in Entry.Strings)
foreach (var view in from pair in Entry.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;
}
@ -276,8 +273,8 @@ namespace keepass2android
return Android.Net.Uri.Parse("content://" + AttachmentContentProvider.Authority + "/"
+ filename);
}
return fileUri;
}
return fileUri;
}
void OpenBinaryFile(Android.Net.Uri uri)
{
@ -323,17 +320,17 @@ namespace keepass2android
builder.SetMessage(GetString(Resource.String.SaveAttachmentDialog_text));
builder.SetPositiveButton(GetString(Resource.String.SaveAttachmentDialog_save), (dlgSender, dlgEvt) =>
{
{
WriteBinaryToFile(btnSender.Text, false);
});
builder.SetNegativeButton(GetString(Resource.String.SaveAttachmentDialog_open), (dlgSender, dlgEvt) =>
{
{
Android.Net.Uri newUri = WriteBinaryToFile(btnSender.Text, true);
if (newUri != null)
{
if (newUri != null)
{
OpenBinaryFile(newUri);
}
}
});
Dialog dialog = builder.Create();

View File

@ -118,7 +118,7 @@ namespace keepass2android
entryId = new PwUuid(MemUtil.HexStringToByteArray(uuidBytes));
State.ParentGroup = null;
if (entryId == PwUuid.Zero)
if (entryId.EqualsValue(PwUuid.Zero))
{
String groupId = i.GetStringExtra(KeyParent);
@ -487,12 +487,12 @@ namespace keepass2android
builder.SetMessage(GetString(Resource.String.AskOverwriteBinary));
builder.SetPositiveButton(GetString(Resource.String.AskOverwriteBinary_yes), (dlgSender, dlgEvt) =>
{
{
AddBinary(filename, true);
});
builder.SetNegativeButton(GetString(Resource.String.AskOverwriteBinary_no), (dlgSender, dlgEvt) =>
{
{
AddBinary(filename, false);
});
@ -532,9 +532,9 @@ namespace keepass2android
try
{
byte[] vBytes = File.ReadAllBytes(filename);
ProtectedBinary pb = new ProtectedBinary(false, vBytes);
ProtectedBinary pb = new ProtectedBinary(false, vBytes);
State.Entry.Binaries.Set(strItem, pb);
}
}
catch(Exception exAttach)
{
Toast.MakeText(this, GetString(Resource.String.AttachFailed)+" "+exAttach.Message, ToastLength.Long).Show();
@ -562,13 +562,13 @@ namespace keepass2android
builder.SetMessage(GetString(Resource.String.AskDiscardChanges));
builder.SetPositiveButton(GetString(Android.Resource.String.Yes), (dlgSender, dlgEvt) =>
{
Finish();
{
Finish();
});
builder.SetNegativeButton(GetString(Android.Resource.String.No), (dlgSender, dlgEvt) =>
{
{
});

View File

@ -194,7 +194,12 @@ namespace keepass2android
MenuInflater inflater = MenuInflater;
inflater.Inflate(Resource.Menu.group, menu);
var searchManager = (SearchManager)GetSystemService(Context.SearchService);
var searchView = (SearchView)menu.FindItem(Resource.Id.menu_search).ActionView;
searchView.SetSearchableInfo(searchManager.GetSearchableInfo(ComponentName));
return true;
}
@ -238,8 +243,9 @@ namespace keepass2android
SetResult(KeePass.ExitLock);
Finish();
return true;
case Resource.Id.menu_search:
case Resource.Id.menu_search_advanced:
OnSearchRequested();
return true;

View File

@ -20,6 +20,12 @@
android:icon="@android:drawable/ic_menu_search"
android:title="@string/menu_search"
android:showAsAction="ifRoom"
android:actionViewClass="android.widget.SearchView"
/>
<item android:id="@+id/menu_search_advanced"
android:icon="@android:drawable/ic_menu_search"
android:title="@string/menu_search_advanced"
android:showAsAction="never"
/>
<item android:id="@+id/menu_lock"
android:icon="@android:drawable/ic_lock_lock"

View File

@ -24,9 +24,16 @@
android:icon="@android:drawable/ic_lock_lock"
android:title="@string/menu_lock"
/>
<item android:id="@+id/menu_search"
android:icon="@android:drawable/ic_menu_search"
android:title="@string/menu_search"
<item android:id="@+id/menu_search"
android:icon="@android:drawable/ic_menu_search"
android:title="@string/menu_search"
android:showAsAction="ifRoom"
android:actionViewClass="android.widget.SearchView"
/>
<item android:id="@+id/menu_search_advanced"
android:icon="@android:drawable/ic_menu_search"
android:title="@string/menu_search_advanced"
android:showAsAction="never"
/>
<item android:id="@+id/menu_app_settings"
android:icon="@android:drawable/ic_menu_preferences"

View File

@ -129,6 +129,7 @@
<string name="menu_open">Open</string>
<string name="menu_rename">Rename</string>
<string name="menu_search">Search</string>
<string name="menu_search_advanced">Advanced Search</string>
<string name="menu_url">Go to URL</string>
<string name="minus">Minus</string>
<string name="never">Never</string>

View File

@ -21,4 +21,9 @@
android:label="@string/search_label"
android:hint="@string/search_hint"
android:searchMode="showSearchLabelAsBadge"
android:searchSuggestAuthority="keepass2android.search.SearchProvider"
android:searchSuggestSelection=" ?"
android:searchSuggestThreshold="2"
android:searchSuggestIntentAction="android.intent.action.VIEW"
android:searchSuggestIntentData="content://keepass2android.EntryActivity"
/>

View File

@ -53,13 +53,13 @@ namespace keepass2android
public Drawable GetIconDrawable (Resources res, PwDatabase db, PwIcon icon, PwUuid customIconId)
{
if (customIconId != PwUuid.Zero) {
if (!customIconId.EqualsValue(PwUuid.Zero)) {
return GetIconDrawable (res, db, customIconId);
}
return GetIconDrawable (res, icon);
}
private static void InitBlank (Resources res)
private static void InitBlank (Resources res)
{
if (_blank == null) {
_blank = res.GetDrawable (Resource.Drawable.ic99_blank);
@ -85,7 +85,7 @@ namespace keepass2android
public Drawable GetIconDrawable (Resources res, PwDatabase db, PwUuid icon)
{
InitBlank (res);
if (icon == PwUuid.Zero) {
if (icon.EqualsValue(PwUuid.Zero)) {
return _blank;
}
Drawable draw;

View File

@ -85,6 +85,7 @@
<Compile Include="app\App.cs" />
<Compile Include="fileselect\FileSelectActivity.cs" />
<Compile Include="fileselect\FileDbHelper.cs" />
<Compile Include="search\SearchProvider.cs" />
<Compile Include="Utils\Util.cs" />
<Compile Include="intents\Intents.cs" />
<Compile Include="fileselect\BrowserDialog.cs" />

View File

@ -0,0 +1,307 @@
/*
This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. This file is based on Keepassdroid, Copyright Brian Pellin.
Keepass2Android is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
Keepass2Android 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with Keepass2Android. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.Linq;
using Android.App;
using Android.Content;
using Android.Content.Res;
using Android.Database;
using Android.Graphics;
using Android.Graphics.Drawables;
using Android.OS;
using Android.Runtime;
using KeePassLib;
using KeePassLib.Utility;
using System.Threading;
using System.Collections.Generic;
namespace keepass2android.search
{
[ContentProvider(new [] { SearchProvider.Authority })]
public class SearchProvider : ContentProvider
{
private enum UriMatches
{
NoMatch = UriMatcher.NoMatch,
GetIcon,
GetSuggestions
}
public const string Authority = "keepass2android.search.SearchProvider";
private const string GetIconPathQuery = "get_icon";
private const string IconIdParameter = "IconId";
private const string CustomIconUuidParameter = "CustomIconUuid";
private Database _db;
private static UriMatcher UriMatcher = BuildUriMatcher();
static UriMatcher BuildUriMatcher()
{
var matcher = new UriMatcher(UriMatcher.NoMatch);
// to get definitions...
matcher.AddURI(Authority, GetIconPathQuery, (int)UriMatches.GetIcon);
matcher.AddURI(Authority, SearchManager.SuggestUriPathQuery, (int)UriMatches.GetSuggestions);
return matcher;
}
public override bool OnCreate()
{
_db = App.Kp2a.GetDb();
return true;
}
public override Android.Database.ICursor Query(Android.Net.Uri uri, string[] projection, string selection, string[] selectionArgs, string sortOrder)
{
if (_db.Open) // Can't show suggestions if the database is locked!
{
switch ((UriMatches)UriMatcher.Match(uri))
{
case UriMatches.GetSuggestions:
var searchString = selectionArgs[0];
if (!String.IsNullOrEmpty(searchString))
{
try
{
var resultsContexts = new Dictionary<PwUuid, String>();
var result = _db.Search(new SearchParameters { SearchString = searchString }, resultsContexts );
return new GroupCursor(result, resultsContexts);
}
catch (Exception e)
{
System.Diagnostics.Debug.WriteLine("Failed to search for suggestions: " + e.Message);
}
}
break;
case UriMatches.GetIcon:
return null; // This will be handled by OpenAssetFile
default:
return null;
//throw new ArgumentException("Unknown Uri: " + uri, "uri");
}
}
return null;
}
public override ParcelFileDescriptor OpenFile(Android.Net.Uri uri, string mode)
{
switch ((UriMatches)UriMatcher.Match(uri))
{
case UriMatches.GetIcon:
var iconId = (PwIcon)Enum.Parse(typeof(PwIcon), uri.GetQueryParameter(IconIdParameter));
var customIconUuid = new PwUuid(MemUtil.HexStringToByteArray(uri.GetQueryParameter(CustomIconUuidParameter)));
var iconDrawable = _db.DrawableFactory.GetIconDrawable(App.Context.Resources, _db.KpDatabase, iconId, customIconUuid) as BitmapDrawable;
if (iconDrawable != null)
{
var pipe = ParcelFileDescriptor.CreatePipe();
var outStream = new OutputStreamInvoker(new ParcelFileDescriptor.AutoCloseOutputStream(pipe[1]));
ThreadPool.QueueUserWorkItem(state =>
{
iconDrawable.Bitmap.Compress(Bitmap.CompressFormat.Png, 100, outStream);
outStream.Close();
});
return pipe[0];
}
// Couldn't get an icon for some reason.
return null;
default:
throw new ArgumentException("Unknown Uri: " + uri, "uri");
}
}
public override string GetType(Android.Net.Uri uri)
{
switch ((UriMatches)UriMatcher.Match(uri))
{
case UriMatches.GetSuggestions:
return SearchManager.SuggestMimeType;
case UriMatches.GetIcon:
return "image/png";
default:
throw new ArgumentException("Unknown Uri: " + uri, "uri");
}
}
#region Unimplemented
public override int Delete(Android.Net.Uri uri, string selection, string[] selectionArgs)
{
throw new NotImplementedException();
}
public override Android.Net.Uri Insert(Android.Net.Uri uri, ContentValues values)
{
throw new NotImplementedException();
}
public override int Update(Android.Net.Uri uri, ContentValues values, string selection, string[] selectionArgs)
{
throw new NotImplementedException();
}
#endregion
private class GroupCursor : AbstractCursor
{
private static readonly string[] ColumnNames = new[] { Android.Provider.BaseColumns.Id,
SearchManager.SuggestColumnText1,
SearchManager.SuggestColumnText2,
SearchManager.SuggestColumnIcon1,
SearchManager.SuggestColumnIntentDataId,
};
private readonly PwGroup mGroup;
private readonly IDictionary<PwUuid, String> mResultContexts;
public GroupCursor(PwGroup group, IDictionary<PwUuid, String> resultContexts)
{
System.Diagnostics.Debug.Assert(!group.Groups.Any(), "Expecting a flat list of groups");
mGroup = group;
mResultContexts = resultContexts;
}
public override int Count
{
get { return (int)Math.Min(mGroup.GetEntriesCount(false), int.MaxValue); }
}
public override string[] GetColumnNames()
{
return ColumnNames;
}
public override FieldType GetType(int column)
{
switch (column)
{
case 0: // _ID
return FieldType.Integer;
default:
return base.GetType(column); // Ends up as string
}
}
private PwEntry CurrentEntry
{
get
{
return mGroup.Entries.GetAt((uint)MPos);
}
}
public override long GetLong(int column)
{
switch (column)
{
case 0: // _ID
return MPos;
default:
throw new FormatException();
}
}
public override string GetString(int column)
{
switch (column)
{
case 0: // _ID
return MPos.ToString();
case 1: // SuggestColumnText1
return CurrentEntry.Strings.ReadSafe(PwDefs.TitleField);
case 2: // SuggestColumnText2
string context;
if (mResultContexts.TryGetValue(CurrentEntry.Uuid, out context))
{
context = Internationalise(context);
return context;
}
return null;
case 3: // SuggestColumnIcon1
var builder = new Android.Net.Uri.Builder();
builder.Scheme(ContentResolver.SchemeContent);
builder.Authority(Authority);
builder.Path(GetIconPathQuery);
builder.AppendQueryParameter(IconIdParameter, CurrentEntry.IconId.ToString());
builder.AppendQueryParameter(CustomIconUuidParameter, CurrentEntry.CustomIconUuid.ToHexString());
return builder.Build().ToString();
case 4: // SuggestColumnIntentDataId
return CurrentEntry.Uuid.ToHexString();
default:
return null;
}
}
private string Internationalise(string context)
{
// Some context names can be internationalised.
var splitPos = context.IndexOf(':');
var rawName = context.Substring(0, splitPos);
int intlResourceId = 0;
switch (rawName)
{
case PwDefs.TitleField:
// We will already be showing Title, so ignore it entirely so it doesn't double-appear
return null;
case PwDefs.UserNameField:
intlResourceId = Resource.String.entry_user_name;
break;
case PwDefs.UrlField:
intlResourceId = Resource.String.entry_url;
break;
case PwDefs.NotesField:
intlResourceId = Resource.String.entry_comment;
break;
case PwGroup.SearchContextTags:
intlResourceId = Resource.String.entry_tags;
break;
default:
// Other fields aren't part of the default SearchParameters, so we won't ever get them as context anyway
break;
}
if (intlResourceId > 0)
{
return App.Context.GetString(intlResourceId) + context.Substring(splitPos);
}
return context;
}
public override bool IsNull(int column)
{
return false;
}
#region Data types appearing in no columns
public override int GetInt(int column) { throw new FormatException(); }
public override double GetDouble(int column) { throw new FormatException(); }
public override float GetFloat(int column) { throw new FormatException(); }
public override short GetShort(int column) { throw new FormatException(); }
#endregion
}
}
}

View File

@ -22,13 +22,14 @@ using Android.OS;
using Android.Widget;
using keepass2android.view;
using KeePassLib;
using Android.Support.V4.App;
namespace keepass2android.search
{
/// <summary>
/// Activity to show search results
/// </summary>
[Activity (Label = "@string/app_name", Theme="@style/NoTitleBar")]
[Activity (Label = "@string/app_name", Theme="@style/NoTitleBar", LaunchMode=Android.Content.PM.LaunchMode.SingleTop)]
[MetaData("android.app.searchable",Resource="@xml/searchable")]
[IntentFilter(new[]{Intent.ActionSearch}, Categories=new[]{Intent.CategoryDefault})]
public class SearchResults : GroupBaseActivity
@ -44,24 +45,44 @@ namespace keepass2android.search
}
SetResult(KeePass.ExitNormal);
ProcessIntent(Intent);
}
protected override void OnNewIntent(Intent intent)
{
ProcessIntent(intent);
}
private void ProcessIntent(Intent intent)
{
_db = App.Kp2a.GetDb();
// Likely the app has been killed exit the activity
if ( ! _db.Open ) {
Finish();
}
Query(getSearch(Intent));
if (intent.Action == Intent.ActionView)
{
var entryIntent = new Intent(this, typeof(EntryActivity));
entryIntent.PutExtra(EntryActivity.KeyEntry, intent.Data.LastPathSegment);
Finish(); // Close this activity so that the entry activity is navigated to from the main activity, not this one.
StartActivity(entryIntent);
}
else
{
// Action may either by ActionSearch (from search widget) or null (if called from SearchActivity directly)
Query(getSearch(intent));
}
}
private void Query (SearchParameters searchParams)
{
try {
Group = _db.Search (searchParams);
Group = _db.Search (searchParams, null);
} catch (Exception e) {
Toast.MakeText(this,e.Message, ToastLength.Long).Show();
Finish();
@ -111,8 +132,8 @@ namespace keepass2android.search
Finish();
return true;
}
return false;
return false;
}
}
}
}