1 /*
2   KeePass Password Safe - The Open-Source Password Manager
3   Copyright (C) 2003-2021 Dominik Reichl <dominik.reichl@t-online.de>
4 
5   This program is free software; you can redistribute it and/or modify
6   it under the terms of the GNU General Public License as published by
7   the Free Software Foundation; either version 2 of the License, or
8   (at your option) any later version.
9 
10   This program is distributed in the hope that it will be useful,
11   but WITHOUT ANY WARRANTY; without even the implied warranty of
12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   GNU General Public License for more details.
14 
15   You should have received a copy of the GNU General Public License
16   along with this program; if not, write to the Free Software
17   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18 */
19 
20 using System;
21 using System.Collections.Generic;
22 using System.Diagnostics;
23 using System.Text;
24 using System.Threading;
25 using System.Windows.Forms;
26 
27 using KeePass.Forms;
28 using KeePass.Util.Spr;
29 
30 using KeePassLib;
31 using KeePassLib.Utility;
32 
33 namespace KeePass.UI
34 {
35 	public sealed class PwListItem
36 	{
37 		private static long m_idNext = 1;
38 
39 		private readonly PwEntry m_pe;
40 		public PwEntry Entry
41 		{
42 			get { return m_pe; }
43 		}
44 
45 		private readonly long m_id;
46 		public long ListViewItemID
47 		{
48 			get { return m_id; }
49 		}
50 
PwListItem(PwEntry pe)51 		public PwListItem(PwEntry pe)
52 		{
53 			if(pe == null) throw new ArgumentNullException("pe");
54 
55 			m_pe = pe;
56 
57 			m_id = m_idNext;
58 			unchecked { ++m_idNext; }
59 		}
60 	}
61 
PwTextUpdateDelegate(string strText, PwListItem li)62 	public delegate string PwTextUpdateDelegate(string strText, PwListItem li);
63 
64 	public sealed class AsyncPwListUpdate
65 	{
66 		private readonly ListView m_lv;
67 
68 		private readonly object m_objListEditSync = new object();
69 		public object ListEditSyncObject
70 		{
71 			get { return m_objListEditSync; }
72 		}
73 
74 		private Dictionary<long, bool> m_dValidIDs = new Dictionary<long, bool>();
75 		private readonly object m_objValidIDsSync = new object();
76 
77 		private sealed class LviUpdInfo
78 		{
79 			public ListView ListView { get; set; }
80 
81 			public long UpdateID { get; set; }
82 
83 			public string Text { get; set; }
84 			public PwListItem ListItem { get; set; }
85 			public int IndexHint { get; set; }
86 			public int SubItem { get; set; }
87 
88 			public PwTextUpdateDelegate Function { get; set; }
89 
90 			public object ListEditSyncObject { get; set; }
91 
92 			public object ValidIDsSyncObject { get; set; }
93 			public Dictionary<long, bool> ValidIDs { get; set; }
94 		}
95 
AsyncPwListUpdate(ListView lv)96 		public AsyncPwListUpdate(ListView lv)
97 		{
98 			if(lv == null) throw new ArgumentNullException("lv");
99 
100 			m_lv = lv;
101 		}
102 
Queue(string strText, PwListItem li, int iIndexHint, int iSubItem, PwTextUpdateDelegate f)103 		public void Queue(string strText, PwListItem li, int iIndexHint,
104 			int iSubItem, PwTextUpdateDelegate f)
105 		{
106 			if(strText == null) { Debug.Assert(false); return; }
107 			if(li == null) { Debug.Assert(false); return; }
108 			if(iSubItem < 0) { Debug.Assert(false); return; }
109 			if(f == null) { Debug.Assert(false); return; }
110 
111 			LviUpdInfo state = new LviUpdInfo();
112 			state.ListView = m_lv;
113 			state.UpdateID = unchecked((li.ListViewItemID << 6) + iSubItem);
114 			state.Text = strText;
115 			state.ListItem = li;
116 			state.IndexHint = ((iIndexHint >= 0) ? iIndexHint : 0);
117 			state.SubItem = iSubItem;
118 			state.Function = f;
119 			state.ListEditSyncObject = m_objListEditSync;
120 			state.ValidIDsSyncObject = m_objValidIDsSync;
121 			state.ValidIDs = m_dValidIDs;
122 
123 			lock(m_objValidIDsSync)
124 			{
125 				Debug.Assert(!m_dValidIDs.ContainsKey(state.UpdateID));
126 				m_dValidIDs[state.UpdateID] = true;
127 			}
128 
129 			try
130 			{
131 				if(!ThreadPool.QueueUserWorkItem(new WaitCallback(UpdateItemFn),
132 					state)) throw new InvalidOperationException();
133 			}
134 			catch(Exception)
135 			{
136 				Debug.Assert(false);
137 				lock(m_objValidIDsSync) { m_dValidIDs.Remove(state.UpdateID); }
138 			}
139 		}
140 
141 		/// <summary>
142 		/// Cancel all pending updates. This method is asynchronous,
143 		/// i.e. it returns immediately and the number of queued
144 		/// updates will decrease continually.
145 		/// </summary>
CancelPendingUpdatesAsync()146 		public void CancelPendingUpdatesAsync()
147 		{
148 			lock(m_objValidIDsSync)
149 			{
150 				List<long> vKeys = new List<long>(m_dValidIDs.Keys);
151 				foreach(long lKey in vKeys)
152 				{
153 					m_dValidIDs[lKey] = false;
154 				}
155 			}
156 		}
157 
WaitAll()158 		public void WaitAll()
159 		{
160 			while(true)
161 			{
162 				lock(m_objValidIDsSync)
163 				{
164 					if(m_dValidIDs.Count == 0) break;
165 				}
166 
167 				Thread.Sleep(4);
168 				Application.DoEvents();
169 			}
170 		}
171 
UpdateItemFn(object state)172 		private static void UpdateItemFn(object state)
173 		{
174 			LviUpdInfo lui = (state as LviUpdInfo);
175 			if(lui == null) { Debug.Assert(false); return; }
176 
177 			try // Avoid cross-thread exceptions
178 			{
179 				bool bWork;
180 				lock(lui.ValidIDsSyncObject)
181 				{
182 					if(!lui.ValidIDs.TryGetValue(lui.UpdateID,
183 						out bWork)) { Debug.Assert(false); return; }
184 				}
185 
186 				if(bWork)
187 				{
188 					string strNew = lui.Function(lui.Text, lui.ListItem);
189 					if(strNew == null) { Debug.Assert(false); return; }
190 					if(strNew == lui.Text) return;
191 
192 					// if(lui.ListView.InvokeRequired)
193 					lui.ListView.Invoke(new SetItemTextDelegate(
194 						SetItemText), new object[] { strNew, lui });
195 					// else SetItemText(strNew, lui);
196 				}
197 			}
198 			catch(Exception) { Debug.Assert(false); }
199 			finally
200 			{
201 				try // Avoid cross-thread exceptions
202 				{
203 					lock(lui.ValidIDsSyncObject)
204 					{
205 						if(!lui.ValidIDs.Remove(lui.UpdateID)) { Debug.Assert(false); }
206 					}
207 				}
208 				catch(Exception) { Debug.Assert(false); }
209 			}
210 		}
211 
SetItemTextDelegate(string strText, LviUpdInfo lui)212 		private delegate void SetItemTextDelegate(string strText, LviUpdInfo lui);
213 
SetItemText(string strText, LviUpdInfo lui)214 		private static void SetItemText(string strText, LviUpdInfo lui)
215 		{
216 			try // Avoid cross-thread exceptions
217 			{
218 				long lTargetID = lui.ListItem.ListViewItemID;
219 				int iIndexHint = lui.IndexHint;
220 
221 				lock(lui.ListEditSyncObject)
222 				{
223 					ListView.ListViewItemCollection lvic = lui.ListView.Items;
224 					int nCount = lvic.Count;
225 
226 					// for(int i = 0; i < nCount; ++i)
227 					for(int i = nCount; i > 0; --i)
228 					{
229 						int j = ((iIndexHint + i) % nCount);
230 						ListViewItem lvi = lvic[j];
231 
232 						PwListItem li = (lvi.Tag as PwListItem);
233 						if(li == null) { Debug.Assert(false); continue; }
234 
235 						if(li.ListViewItemID != lTargetID) continue;
236 
237 						lvi.SubItems[lui.SubItem].Text = strText;
238 						break;
239 					}
240 				}
241 			}
242 			catch(Exception) { Debug.Assert(false); }
243 		}
244 
SprCompileFn(string strText, PwListItem li)245 		internal static string SprCompileFn(string strText, PwListItem li)
246 		{
247 			if(string.IsNullOrEmpty(strText)) return string.Empty;
248 
249 			string str = null;
250 			while(str == null)
251 			{
252 				try
253 				{
254 					SprContext ctx = MainForm.GetEntryListSprContext(li.Entry,
255 						Program.MainForm.DocumentManager.SafeFindContainerOf(
256 						li.Entry));
257 					str = SprEngine.Compile(strText, ctx);
258 				}
259 				catch(InvalidOperationException) { } // Probably collection changed
260 				catch(NullReferenceException) { } // Objects disposed already
261 				catch(Exception) { Debug.Assert(false); }
262 			}
263 
264 			if(Program.Config.MainWindow.EntryListShowDerefDataAndRefs && (str != strText))
265 				str += " - " + strText;
266 
267 			return StrUtil.MultiToSingleLine(str);
268 		}
269 	}
270 }
271