OSDN Git Service

Issue 74: Add multiple files from commit dialog only adds first two in multi-selection
[tortoisegit/TortoiseGitJp.git] / src / Utils / ShellUpdater.cpp
1 // TortoiseSVN - a Windows shell extension for easy version control\r
2 \r
3 // Copyright (C) 2003-2008 - TortoiseSVN\r
4 \r
5 // This program is free software; you can redistribute it and/or\r
6 // modify it under the terms of the GNU General Public License\r
7 // as published by the Free Software Foundation; either version 2\r
8 // of the License, or (at your option) any later version.\r
9 \r
10 // This program is distributed in the hope that it will be useful,\r
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of\r
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
13 // GNU General Public License for more details.\r
14 \r
15 // You should have received a copy of the GNU General Public License\r
16 // along with this program; if not, write to the Free Software Foundation,\r
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\r
18 //\r
19 #include "StdAfx.h"\r
20 #include "Shellupdater.h"\r
21 #include "../TGitCache/CacheInterface.h"\r
22 #include "Registry.h"\r
23 #include "git.h"\r
24 \r
25 CShellUpdater::CShellUpdater(void)\r
26 {\r
27         m_hInvalidationEvent = CreateEvent(NULL, FALSE, FALSE, _T("TortoiseGitCacheInvalidationEvent"));\r
28 }\r
29 \r
30 CShellUpdater::~CShellUpdater(void)\r
31 {\r
32         Flush();\r
33 \r
34         CloseHandle(m_hInvalidationEvent);\r
35 }\r
36 \r
37 CShellUpdater& CShellUpdater::Instance()\r
38 {\r
39         static CShellUpdater instance;\r
40         return instance;\r
41 }\r
42 \r
43 /** \r
44 * Add a single path for updating.\r
45 * The update will happen at some suitable time in the future\r
46 */\r
47 void CShellUpdater::AddPathForUpdate(const CTGitPath& path)\r
48 {\r
49         // Tell the shell extension to purge its cache - we'll redo this when \r
50         // we actually do the shell-updates, but sometimes there's an earlier update, which\r
51         // might benefit from cache invalidation\r
52         SetEvent(m_hInvalidationEvent);\r
53 \r
54         m_pathsForUpdating.AddPath(path);\r
55 }\r
56 /** \r
57 * Add a list of paths for updating.\r
58 * The update will happen when the list is destroyed, at the end of execution\r
59 */\r
60 void CShellUpdater::AddPathsForUpdate(const CTGitPathList& pathList)\r
61 {\r
62         for(int nPath=0; nPath < pathList.GetCount(); nPath++)\r
63         {\r
64                 AddPathForUpdate(pathList[nPath]);\r
65         }\r
66 }\r
67 \r
68 void CShellUpdater::Flush()\r
69 {\r
70         if(m_pathsForUpdating.GetCount() > 0)\r
71         {\r
72                 ATLTRACE("Flushing shell update list\n");\r
73 \r
74                 UpdateShell();\r
75                 m_pathsForUpdating.Clear();\r
76         }\r
77 }\r
78 \r
79 void CShellUpdater::UpdateShell()\r
80 {\r
81         // Tell the shell extension to purge its cache\r
82         ATLTRACE("Setting cache invalidation event %d\n", GetTickCount());\r
83         SetEvent(m_hInvalidationEvent);\r
84 \r
85         // We use the SVN 'notify' call-back to add items to the list\r
86         // Because this might call-back more than once per file (for example, when committing)\r
87         // it's possible that there may be duplicates in the list.\r
88         // There's no point asking the shell to do more than it has to, so we remove the duplicates before\r
89         // passing the list on\r
90         m_pathsForUpdating.RemoveDuplicates();\r
91 \r
92         // if we use the external cache, we tell the cache directly that something\r
93         // has changed, without the detour via the shell.\r
94         HANDLE hPipe = CreateFile( \r
95                 GetCacheCommandPipeName(),              // pipe name \r
96                 GENERIC_READ |                                  // read and write access \r
97                 GENERIC_WRITE, \r
98                 0,                                                              // no sharing \r
99                 NULL,                                                   // default security attributes\r
100                 OPEN_EXISTING,                                  // opens existing pipe \r
101                 FILE_FLAG_OVERLAPPED,                   // default attributes \r
102                 NULL);                                                  // no template file \r
103 \r
104 \r
105         if (hPipe != INVALID_HANDLE_VALUE) \r
106         {\r
107                 // The pipe connected; change to message-read mode. \r
108                 DWORD dwMode; \r
109 \r
110                 dwMode = PIPE_READMODE_MESSAGE; \r
111                 if(SetNamedPipeHandleState( \r
112                         hPipe,    // pipe handle \r
113                         &dwMode,  // new pipe mode \r
114                         NULL,     // don't set maximum bytes \r
115                         NULL))    // don't set maximum time \r
116                 {\r
117                         CTGitPath path;\r
118                         for(int nPath = 0; nPath < m_pathsForUpdating.GetCount(); nPath++)\r
119                         {\r
120                                 path.SetFromWin(g_Git.m_CurrentDir+_T("\\")+m_pathsForUpdating[nPath].GetWinPathString());\r
121                                 ATLTRACE(_T("Cache Item Update for %s (%d)\n"), path.GetWinPathString(), GetTickCount());\r
122                                 if (!path.IsDirectory())\r
123                                 {\r
124                                         // send notifications to the shell for changed files - folders are updated by the cache itself.\r
125                                         SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATH | SHCNF_FLUSHNOWAIT, path.GetWinPath(), NULL);\r
126                                 }\r
127                                 DWORD cbWritten; \r
128                                 TSVNCacheCommand cmd;\r
129                                 cmd.command = TSVNCACHECOMMAND_CRAWL;\r
130                                 wcsncpy_s(cmd.path, MAX_PATH+1, path.GetDirectory().GetWinPath(), MAX_PATH);\r
131                                 BOOL fSuccess = WriteFile( \r
132                                         hPipe,                  // handle to pipe \r
133                                         &cmd,                   // buffer to write from \r
134                                         sizeof(cmd),    // number of bytes to write \r
135                                         &cbWritten,             // number of bytes written \r
136                                         NULL);                  // not overlapped I/O \r
137 \r
138                                 if (! fSuccess || sizeof(cmd) != cbWritten)\r
139                                 {\r
140                                         DisconnectNamedPipe(hPipe); \r
141                                         CloseHandle(hPipe); \r
142                                         hPipe = INVALID_HANDLE_VALUE;\r
143                                         break;\r
144                                 }\r
145                         }\r
146                         if (hPipe != INVALID_HANDLE_VALUE)\r
147                         {\r
148                                 // now tell the cache we don't need it's command thread anymore\r
149                                 DWORD cbWritten; \r
150                                 TSVNCacheCommand cmd;\r
151                                 cmd.command = TSVNCACHECOMMAND_END;\r
152                                 WriteFile( \r
153                                         hPipe,                  // handle to pipe \r
154                                         &cmd,                   // buffer to write from \r
155                                         sizeof(cmd),    // number of bytes to write \r
156                                         &cbWritten,             // number of bytes written \r
157                                         NULL);                  // not overlapped I/O \r
158                                 DisconnectNamedPipe(hPipe); \r
159                                 CloseHandle(hPipe); \r
160                                 hPipe = INVALID_HANDLE_VALUE;\r
161                         }\r
162                 }\r
163                 else\r
164                 {\r
165                         ATLTRACE("SetNamedPipeHandleState failed"); \r
166                         CloseHandle(hPipe);\r
167                 }\r
168         }\r
169 }\r
170 \r
171 bool CShellUpdater::RebuildIcons()\r
172 {\r
173         const int BUFFER_SIZE = 1024;\r
174         TCHAR *buf = NULL;\r
175         HKEY hRegKey = 0;\r
176         DWORD dwRegValue;\r
177         DWORD dwRegValueTemp;\r
178         DWORD dwSize;\r
179         DWORD_PTR dwResult;\r
180         LONG lRegResult;\r
181         std::wstring sRegValueName;\r
182         std::wstring sDefaultIconSize;\r
183         int iDefaultIconSize;\r
184         bool bResult = false;\r
185 \r
186         lRegResult = RegOpenKeyEx(HKEY_CURRENT_USER, _T("Control Panel\\Desktop\\WindowMetrics"),\r
187                 0, KEY_READ | KEY_WRITE, &hRegKey);\r
188         if (lRegResult != ERROR_SUCCESS)\r
189                 goto Cleanup;\r
190 \r
191         buf = new TCHAR[BUFFER_SIZE];\r
192         if(buf == NULL)\r
193                 goto Cleanup;\r
194 \r
195         // we're going to change the Shell Icon Size value\r
196         sRegValueName = _T("Shell Icon Size");\r
197 \r
198         // Read registry value\r
199         dwSize = BUFFER_SIZE;\r
200         lRegResult = RegQueryValueEx(hRegKey, sRegValueName.c_str(), NULL, NULL, \r
201                 (LPBYTE) buf, &dwSize);\r
202         if (lRegResult != ERROR_FILE_NOT_FOUND)\r
203         {\r
204                 // If registry key doesn't exist create it using system current setting\r
205                 iDefaultIconSize = ::GetSystemMetrics(SM_CXICON);\r
206                 if (0 == iDefaultIconSize)\r
207                         iDefaultIconSize = 32;\r
208                 _sntprintf_s(buf, BUFFER_SIZE, BUFFER_SIZE, _T("%d"), iDefaultIconSize); \r
209         }\r
210         else if (lRegResult != ERROR_SUCCESS)\r
211                 goto Cleanup;\r
212 \r
213         // Change registry value\r
214         dwRegValue = _ttoi(buf);\r
215         dwRegValueTemp = dwRegValue-1;\r
216 \r
217         dwSize = _sntprintf_s(buf, BUFFER_SIZE, BUFFER_SIZE, _T("%d"), dwRegValueTemp) + sizeof(TCHAR); \r
218         lRegResult = RegSetValueEx(hRegKey, sRegValueName.c_str(), 0, REG_SZ, \r
219                 (LPBYTE) buf, dwSize); \r
220         if (lRegResult != ERROR_SUCCESS)\r
221                 goto Cleanup;\r
222 \r
223 \r
224         // Update all windows\r
225         SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, SPI_SETNONCLIENTMETRICS, \r
226                 0, SMTO_ABORTIFHUNG, 5000, &dwResult);\r
227 \r
228         // Reset registry value\r
229         dwSize = _sntprintf_s(buf, BUFFER_SIZE, BUFFER_SIZE, _T("%d"), dwRegValue) + sizeof(TCHAR); \r
230         lRegResult = RegSetValueEx(hRegKey, sRegValueName.c_str(), 0, REG_SZ, \r
231                 (LPBYTE) buf, dwSize); \r
232         if(lRegResult != ERROR_SUCCESS)\r
233                 goto Cleanup;\r
234 \r
235         // Update all windows\r
236         SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, SPI_SETNONCLIENTMETRICS, \r
237                 0, SMTO_ABORTIFHUNG, 5000, &dwResult);\r
238 \r
239         bResult = true;\r
240 \r
241 Cleanup:\r
242         if (hRegKey != 0)\r
243         {\r
244                 RegCloseKey(hRegKey);\r
245         }\r
246         if (buf != NULL)\r
247         {\r
248                 delete [] buf;\r
249         }\r
250 \r
251         return bResult;\r
252 \r
253 }\r