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