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