1 // PanelOperations.cpp
2 
3 #include "StdAfx.h"
4 
5 #include "../../../Common/DynamicBuffer.h"
6 #include "../../../Common/StringConvert.h"
7 #include "../../../Common/Wildcard.h"
8 
9 #include "../../../Windows/COM.h"
10 #include "../../../Windows/FileName.h"
11 #include "../../../Windows/PropVariant.h"
12 
13 #include "ComboDialog.h"
14 
15 #include "FSFolder.h"
16 #include "FormatUtils.h"
17 #include "LangUtils.h"
18 #include "Panel.h"
19 #include "UpdateCallback100.h"
20 
21 #include "resource.h"
22 
23 using namespace NWindows;
24 using namespace NFile;
25 using namespace NName;
26 
27 #ifndef _UNICODE
28 extern bool g_IsNT;
29 #endif
30 
31 enum EFolderOpType
32 {
33   FOLDER_TYPE_CREATE_FOLDER = 0,
34   FOLDER_TYPE_DELETE = 1,
35   FOLDER_TYPE_RENAME = 2
36 };
37 
38 class CThreadFolderOperations: public CProgressThreadVirt
39 {
40   HRESULT ProcessVirt();
41 public:
42   EFolderOpType OpType;
43   UString Name;
44   UInt32 Index;
45   CRecordVector<UInt32> Indices;
46 
47   CMyComPtr<IFolderOperations> FolderOperations;
48   CMyComPtr<IProgress> UpdateCallback;
49   CUpdateCallback100Imp *UpdateCallbackSpec;
50 
CThreadFolderOperations(EFolderOpType opType)51   CThreadFolderOperations(EFolderOpType opType): OpType(opType) {}
52   HRESULT DoOperation(CPanel &panel, const UString &progressTitle, const UString &titleError);
53 };
54 
ProcessVirt()55 HRESULT CThreadFolderOperations::ProcessVirt()
56 {
57   NCOM::CComInitializer comInitializer;
58   switch (OpType)
59   {
60     case FOLDER_TYPE_CREATE_FOLDER:
61       return FolderOperations->CreateFolder(Name, UpdateCallback);
62     case FOLDER_TYPE_DELETE:
63       return FolderOperations->Delete(&Indices.Front(), Indices.Size(), UpdateCallback);
64     case FOLDER_TYPE_RENAME:
65       return FolderOperations->Rename(Index, Name, UpdateCallback);
66     default:
67       return E_FAIL;
68   }
69 }
70 
71 
DoOperation(CPanel & panel,const UString & progressTitle,const UString & titleError)72 HRESULT CThreadFolderOperations::DoOperation(CPanel &panel, const UString &progressTitle, const UString &titleError)
73 {
74   UpdateCallbackSpec = new CUpdateCallback100Imp;
75   UpdateCallback = UpdateCallbackSpec;
76   UpdateCallbackSpec->ProgressDialog = this;
77 
78   WaitMode = true;
79   Sync.FinalMessage.ErrorMessage.Title = titleError;
80 
81   UpdateCallbackSpec->Init();
82 
83   if (panel._parentFolders.Size() > 0)
84   {
85     const CFolderLink &fl = panel._parentFolders.Back();
86     UpdateCallbackSpec->PasswordIsDefined = fl.UsePassword;
87     UpdateCallbackSpec->Password = fl.Password;
88   }
89 
90   MainWindow = panel._mainWindow; // panel.GetParent()
91   MainTitle = "7-Zip"; // LangString(IDS_APP_TITLE);
92   MainAddTitle = progressTitle + L' ';
93 
94   RINOK(Create(progressTitle, MainWindow));
95   return Result;
96 }
97 
98 #ifndef _UNICODE
99 typedef int (WINAPI * SHFileOperationWP)(LPSHFILEOPSTRUCTW lpFileOp);
100 #endif
101 
102 /*
103 void CPanel::MessageBoxErrorForUpdate(HRESULT errorCode, UINT resourceID)
104 {
105   if (errorCode == E_NOINTERFACE)
106     MessageBox_Error_UnsupportOperation();
107   else
108     MessageBox_Error_HRESULT_Caption(errorCode, LangString(resourceID));
109 }
110 */
111 
DeleteItems(bool NON_CE_VAR (toRecycleBin))112 void CPanel::DeleteItems(bool NON_CE_VAR(toRecycleBin))
113 {
114   CDisableTimerProcessing disableTimerProcessing(*this);
115   CRecordVector<UInt32> indices;
116   GetOperatedItemIndices(indices);
117   if (indices.IsEmpty())
118     return;
119   CSelectedState state;
120   SaveSelectedState(state);
121 
122   #ifndef UNDER_CE
123   // WM6 / SHFileOperationW doesn't ask user! So we use internal delete
124   if (IsFSFolder() && toRecycleBin)
125   {
126     bool useInternalDelete = false;
127     #ifndef _UNICODE
128     if (!g_IsNT)
129     {
130       CDynamicBuffer<CHAR> buffer;
131       FOR_VECTOR (i, indices)
132       {
133         const AString path (GetSystemString(GetItemFullPath(indices[i])));
134         buffer.AddData(path, path.Len() + 1);
135       }
136       *buffer.GetCurPtrAndGrow(1) = 0;
137       SHFILEOPSTRUCTA fo;
138       fo.hwnd = GetParent();
139       fo.wFunc = FO_DELETE;
140       fo.pFrom = (const CHAR *)buffer;
141       fo.pTo = 0;
142       fo.fFlags = 0;
143       if (toRecycleBin)
144         fo.fFlags |= FOF_ALLOWUNDO;
145       // fo.fFlags |= FOF_NOCONFIRMATION;
146       // fo.fFlags |= FOF_NOERRORUI;
147       // fo.fFlags |= FOF_SILENT;
148       // fo.fFlags |= FOF_WANTNUKEWARNING;
149       fo.fAnyOperationsAborted = FALSE;
150       fo.hNameMappings = 0;
151       fo.lpszProgressTitle = 0;
152       /* int res = */ ::SHFileOperationA(&fo);
153     }
154     else
155     #endif
156     {
157       CDynamicBuffer<WCHAR> buffer;
158       unsigned maxLen = 0;
159       const UString prefix = GetFsPath();
160       FOR_VECTOR (i, indices)
161       {
162         // L"\\\\?\\") doesn't work here.
163         const UString path = prefix + GetItemRelPath2(indices[i]);
164         if (path.Len() > maxLen)
165           maxLen = path.Len();
166         buffer.AddData(path, path.Len() + 1);
167       }
168       *buffer.GetCurPtrAndGrow(1) = 0;
169       if (maxLen >= MAX_PATH)
170       {
171         if (toRecycleBin)
172         {
173           MessageBox_Error_LangID(IDS_ERROR_LONG_PATH_TO_RECYCLE);
174           return;
175         }
176         useInternalDelete = true;
177       }
178       else
179       {
180         SHFILEOPSTRUCTW fo;
181         fo.hwnd = GetParent();
182         fo.wFunc = FO_DELETE;
183         fo.pFrom = (const WCHAR *)buffer;
184         fo.pTo = 0;
185         fo.fFlags = 0;
186         if (toRecycleBin)
187           fo.fFlags |= FOF_ALLOWUNDO;
188         fo.fAnyOperationsAborted = FALSE;
189         fo.hNameMappings = 0;
190         fo.lpszProgressTitle = 0;
191         // int res;
192         #ifdef _UNICODE
193         /* res = */ ::SHFileOperationW(&fo);
194         #else
195         SHFileOperationWP shFileOperationW = (SHFileOperationWP)
196           ::GetProcAddress(::GetModuleHandleW(L"shell32.dll"), "SHFileOperationW");
197         if (shFileOperationW == 0)
198           return;
199         /* res = */ shFileOperationW(&fo);
200         #endif
201       }
202     }
203     /*
204     if (fo.fAnyOperationsAborted)
205       MessageBox_Error_HRESULT_Caption(result, LangString(IDS_ERROR_DELETING));
206     */
207     if (!useInternalDelete)
208     {
209       RefreshListCtrl(state);
210       return;
211     }
212   }
213   #endif
214 
215   // DeleteItemsInternal
216 
217   if (!CheckBeforeUpdate(IDS_ERROR_DELETING))
218     return;
219 
220   UInt32 titleID, messageID;
221   UString messageParam;
222   if (indices.Size() == 1)
223   {
224     int index = indices[0];
225     messageParam = GetItemRelPath2(index);
226     if (IsItem_Folder(index))
227     {
228       titleID = IDS_CONFIRM_FOLDER_DELETE;
229       messageID = IDS_WANT_TO_DELETE_FOLDER;
230     }
231     else
232     {
233       titleID = IDS_CONFIRM_FILE_DELETE;
234       messageID = IDS_WANT_TO_DELETE_FILE;
235     }
236   }
237   else
238   {
239     titleID = IDS_CONFIRM_ITEMS_DELETE;
240     messageID = IDS_WANT_TO_DELETE_ITEMS;
241     messageParam = NumberToString(indices.Size());
242   }
243   if (::MessageBoxW(GetParent(), MyFormatNew(messageID, messageParam), LangString(titleID), MB_OKCANCEL | MB_ICONQUESTION) != IDOK)
244     return;
245 
246   CDisableNotify disableNotify(*this);
247   {
248     CThreadFolderOperations op(FOLDER_TYPE_DELETE);
249     op.FolderOperations = _folderOperations;
250     op.Indices = indices;
251     op.DoOperation(*this,
252         LangString(IDS_DELETING),
253         LangString(IDS_ERROR_DELETING));
254   }
255   RefreshTitleAlways();
256   RefreshListCtrl(state);
257 }
258 
OnBeginLabelEdit(LV_DISPINFOW * lpnmh)259 BOOL CPanel::OnBeginLabelEdit(LV_DISPINFOW * lpnmh)
260 {
261   int realIndex = GetRealIndex(lpnmh->item);
262   if (realIndex == kParentIndex)
263     return TRUE;
264   if (IsThereReadOnlyFolder())
265     return TRUE;
266   return FALSE;
267 }
268 
IsCorrectFsName(const UString & name)269 static bool IsCorrectFsName(const UString &name)
270 {
271   const UString lastPart = name.Ptr((unsigned)(name.ReverseFind_PathSepar() + 1));
272   return
273       lastPart != L"." &&
274       lastPart != L"..";
275 }
276 
277 bool CorrectFsPath(const UString &relBase, const UString &path, UString &result);
278 
CorrectFsPath(const UString & path2,UString & result)279 bool CPanel::CorrectFsPath(const UString &path2, UString &result)
280 {
281   return ::CorrectFsPath(GetFsPath(), path2, result);
282 }
283 
OnEndLabelEdit(LV_DISPINFOW * lpnmh)284 BOOL CPanel::OnEndLabelEdit(LV_DISPINFOW * lpnmh)
285 {
286   if (lpnmh->item.pszText == NULL)
287     return FALSE;
288   CDisableTimerProcessing disableTimerProcessing2(*this);
289 
290   if (!CheckBeforeUpdate(IDS_ERROR_RENAMING))
291     return FALSE;
292 
293   UString newName = lpnmh->item.pszText;
294   if (!IsCorrectFsName(newName))
295   {
296     MessageBox_Error_HRESULT(E_INVALIDARG);
297     return FALSE;
298   }
299 
300   if (IsFSFolder())
301   {
302     UString correctName;
303     if (!CorrectFsPath(newName, correctName))
304     {
305       MessageBox_Error_HRESULT(E_INVALIDARG);
306       return FALSE;
307     }
308     newName = correctName;
309   }
310 
311   SaveSelectedState(_selectedState);
312 
313   int realIndex = GetRealIndex(lpnmh->item);
314   if (realIndex == kParentIndex)
315     return FALSE;
316   const UString prefix = GetItemPrefix(realIndex);
317 
318 
319   CDisableNotify disableNotify(*this);
320   {
321     CThreadFolderOperations op(FOLDER_TYPE_RENAME);
322     op.FolderOperations = _folderOperations;
323     op.Index = realIndex;
324     op.Name = newName;
325     /* HRESULTres = */ op.DoOperation(*this,
326         LangString(IDS_RENAMING),
327         LangString(IDS_ERROR_RENAMING));
328     // fixed in 9.26: we refresh list even after errors
329     // (it's more safe, since error can be at different stages, so list can be incorrect).
330     /*
331     if (res != S_OK)
332       return FALSE;
333     */
334   }
335 
336   // Can't use RefreshListCtrl here.
337   // RefreshListCtrlSaveFocused();
338   _selectedState.FocusedName = prefix + newName;
339   _selectedState.FocusedName_Defined = true;
340   _selectedState.SelectFocused = true;
341 
342   // We need clear all items to disable GetText before Reload:
343   // number of items can change.
344   // DeleteListItems();
345   // But seems it can still call GetText (maybe for current item)
346   // so we can't delete items.
347 
348   _dontShowMode = true;
349 
350   PostMsg(kReLoadMessage);
351   return TRUE;
352 }
353 
354 bool Dlg_CreateFolder(HWND wnd, UString &destName);
355 
CreateFolder()356 void CPanel::CreateFolder()
357 {
358   if (IsHashFolder())
359     return;
360 
361   if (!CheckBeforeUpdate(IDS_CREATE_FOLDER_ERROR))
362     return;
363 
364   CDisableTimerProcessing disableTimerProcessing2(*this);
365   CSelectedState state;
366   SaveSelectedState(state);
367 
368   UString newName;
369   if (!Dlg_CreateFolder(GetParent(), newName))
370     return;
371 
372   if (!IsCorrectFsName(newName))
373   {
374     MessageBox_Error_HRESULT(E_INVALIDARG);
375     return;
376   }
377 
378   if (IsFSFolder())
379   {
380     UString correctName;
381     if (!CorrectFsPath(newName, correctName))
382     {
383       MessageBox_Error_HRESULT(E_INVALIDARG);
384       return;
385     }
386     newName = correctName;
387   }
388 
389   HRESULT res;
390   CDisableNotify disableNotify(*this);
391   {
392     CThreadFolderOperations op(FOLDER_TYPE_CREATE_FOLDER);
393     op.FolderOperations = _folderOperations;
394     op.Name = newName;
395     res = op.DoOperation(*this,
396         LangString(IDS_CREATE_FOLDER),
397         LangString(IDS_CREATE_FOLDER_ERROR));
398     /*
399     // fixed for 9.26: we must refresh always
400     if (res != S_OK)
401       return;
402     */
403   }
404   if (res == S_OK)
405   {
406     int pos = newName.Find(WCHAR_PATH_SEPARATOR);
407     if (pos >= 0)
408       newName.DeleteFrom((unsigned)(pos));
409     if (!_mySelectMode)
410       state.SelectedNames.Clear();
411     state.FocusedName = newName;
412     state.FocusedName_Defined = true;
413     state.SelectFocused = true;
414   }
415   RefreshTitleAlways();
416   RefreshListCtrl(state);
417 }
418 
CreateFile()419 void CPanel::CreateFile()
420 {
421   if (IsHashFolder())
422     return;
423 
424   if (!CheckBeforeUpdate(IDS_CREATE_FILE_ERROR))
425     return;
426 
427   CDisableTimerProcessing disableTimerProcessing2(*this);
428   CSelectedState state;
429   SaveSelectedState(state);
430   CComboDialog dlg;
431   LangString(IDS_CREATE_FILE, dlg.Title);
432   LangString(IDS_CREATE_FILE_NAME, dlg.Static);
433   LangString(IDS_CREATE_FILE_DEFAULT_NAME, dlg.Value);
434 
435   if (dlg.Create(GetParent()) != IDOK)
436     return;
437 
438   CDisableNotify disableNotify(*this);
439 
440   UString newName = dlg.Value;
441 
442   if (IsFSFolder())
443   {
444     UString correctName;
445     if (!CorrectFsPath(newName, correctName))
446     {
447       MessageBox_Error_HRESULT(E_INVALIDARG);
448       return;
449     }
450     newName = correctName;
451   }
452 
453   HRESULT result = _folderOperations->CreateFile(newName, 0);
454   if (result != S_OK)
455   {
456     MessageBox_Error_HRESULT_Caption(result, LangString(IDS_CREATE_FILE_ERROR));
457     // MessageBoxErrorForUpdate(result, IDS_CREATE_FILE_ERROR);
458     return;
459   }
460   int pos = newName.Find(WCHAR_PATH_SEPARATOR);
461   if (pos >= 0)
462     newName.DeleteFrom((unsigned)pos);
463   if (!_mySelectMode)
464     state.SelectedNames.Clear();
465   state.FocusedName = newName;
466   state.FocusedName_Defined = true;
467   state.SelectFocused = true;
468   RefreshListCtrl(state);
469 }
470 
RenameFile()471 void CPanel::RenameFile()
472 {
473   if (!CheckBeforeUpdate(IDS_ERROR_RENAMING))
474     return;
475   int index = _listView.GetFocusedItem();
476   if (index >= 0)
477     _listView.EditLabel(index);
478 }
479 
ChangeComment()480 void CPanel::ChangeComment()
481 {
482   if (IsHashFolder())
483     return;
484   if (!CheckBeforeUpdate(IDS_COMMENT))
485     return;
486   CDisableTimerProcessing disableTimerProcessing2(*this);
487   int index = _listView.GetFocusedItem();
488   if (index < 0)
489     return;
490   int realIndex = GetRealItemIndex(index);
491   if (realIndex == kParentIndex)
492     return;
493   CSelectedState state;
494   SaveSelectedState(state);
495   UString comment;
496   {
497     NCOM::CPropVariant propVariant;
498     if (_folder->GetProperty(realIndex, kpidComment, &propVariant) != S_OK)
499       return;
500     if (propVariant.vt == VT_BSTR)
501       comment = propVariant.bstrVal;
502     else if (propVariant.vt != VT_EMPTY)
503       return;
504   }
505   UString name = GetItemRelPath2(realIndex);
506   CComboDialog dlg;
507   dlg.Title = name;
508   dlg.Title += " : ";
509   AddLangString(dlg.Title, IDS_COMMENT);
510   dlg.Value = comment;
511   LangString(IDS_COMMENT2, dlg.Static);
512   if (dlg.Create(GetParent()) != IDOK)
513     return;
514   NCOM::CPropVariant propVariant (dlg.Value);
515 
516   CDisableNotify disableNotify(*this);
517   HRESULT result = _folderOperations->SetProperty(realIndex, kpidComment, &propVariant, NULL);
518   if (result != S_OK)
519   {
520     if (result == E_NOINTERFACE)
521       MessageBox_Error_UnsupportOperation();
522     else
523       MessageBox_Error_HRESULT_Caption(result, L"Set Comment Error");
524   }
525   RefreshListCtrl(state);
526 }
527