Merge https://git01.codeplex.com/forks/alexvallat/keepass2androidperfopt into AlexVallat/Keepass2AndroidPerfOpt
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 | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -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> | ||||
|   | ||||
| @@ -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> | ||||
|   | ||||
| @@ -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 = "<22> " + contextString.Substring(startPos, SearchContextStringMaxLength) + ((startPos + SearchContextStringMaxLength < contextString.Length) ? " <20>" : 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> | ||||
|   | ||||
| @@ -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; | ||||
|  | ||||
|   | ||||
| @@ -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) | ||||
| 		{ | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
| @@ -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); | ||||
| 		} | ||||
|  | ||||
| 		 | ||||
|   | ||||
| @@ -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(); | ||||
|   | ||||
| @@ -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) =>  | ||||
| 					{ | ||||
| 				                                                                                                                 { | ||||
| 					 | ||||
| 					}); | ||||
| 				 | ||||
|   | ||||
| @@ -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; | ||||
| 				 | ||||
|   | ||||
| @@ -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" | ||||
|   | ||||
| @@ -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" | ||||
|   | ||||
| @@ -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> | ||||
|   | ||||
| @@ -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" | ||||
|  /> | ||||
| @@ -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; | ||||
|   | ||||
| @@ -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" /> | ||||
|   | ||||
							
								
								
									
										307
									
								
								src/keepass2android/search/SearchProvider.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										307
									
								
								src/keepass2android/search/SearchProvider.cs
									
									
									
									
									
										Normal 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 | ||||
| 		} | ||||
|  | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @@ -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; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Philipp Crocoll
					Philipp Crocoll