Move dialog: explaining dirs processing slowly depend on symlink combobox
[far2l.git] / far2l / src / copy.cpp
blobfdb678d5fadb994941cbd3d7f11b2957605730bb
1 /*
2 copy.cpp
4 Копирование файлов
5 */
6 /*
7 Copyright (c) 1996 Eugene Roshal
8 Copyright (c) 2000 Far Group
9 All rights reserved.
11 Redistribution and use in source and binary forms, with or without
12 modification, are permitted provided that the following conditions
13 are met:
14 1. Redistributions of source code must retain the above copyright
15 notice, this list of conditions and the following disclaimer.
16 2. Redistributions in binary form must reproduce the above copyright
17 notice, this list of conditions and the following disclaimer in the
18 documentation and/or other materials provided with the distribution.
19 3. The name of the authors may not be used to endorse or promote products
20 derived from this software without specific prior written permission.
22 THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
23 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
24 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
25 IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
26 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
27 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
31 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34 #include "headers.hpp"
36 #include "copy.hpp"
37 #include "lang.hpp"
38 #include "keys.hpp"
39 #include "colors.hpp"
40 #include "dialog.hpp"
41 #include "ctrlobj.hpp"
42 #include "filepanels.hpp"
43 #include "panel.hpp"
44 #include "filelist.hpp"
45 #include "foldtree.hpp"
46 #include "treelist.hpp"
47 #include "chgprior.hpp"
48 #include "scantree.hpp"
49 #include "constitle.hpp"
50 #include "filefilter.hpp"
51 #include "fileview.hpp"
52 #include "TPreRedrawFunc.hpp"
53 #include "syslog.hpp"
54 #include "cddrv.hpp"
55 #include "interf.hpp"
56 #include "keyboard.hpp"
57 #include "palette.hpp"
58 #include "message.hpp"
59 #include "config.hpp"
60 #include "stddlg.hpp"
61 #include "fileattr.hpp"
62 #include "datetime.hpp"
63 #include "dirinfo.hpp"
64 #include "pathmix.hpp"
65 #include "drivemix.hpp"
66 #include "dirmix.hpp"
67 #include "strmix.hpp"
68 #include "panelmix.hpp"
69 #include "processname.hpp"
70 #include "mix.hpp"
71 #include "DlgGuid.hpp"
72 #include "console.hpp"
73 #include "wakeful.hpp"
74 #include <unistd.h>
75 #include <algorithm>
77 #if defined(__APPLE__)
78 #include <AvailabilityMacros.h>
79 #if defined(MAC_OS_X_VERSION_10_12) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_12
80 #include <sys/attr.h>
81 #include <sys/clonefile.h>
82 #define COW_SUPPORTED
83 #endif
85 #elif defined(__linux__) && (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 27))
86 #define COW_SUPPORTED
88 #endif
90 /* Общее время ожидания пользователя */
91 extern long WaitUserTime;
92 /* Для того, что бы время при ожидании пользователя тикало, а remaining/speed нет */
93 static long OldCalcTime;
95 #define PROGRESS_REFRESH_THRESHOLD 200 // msec
97 enum
99 COPY_BUFFER_SIZE = 0x800000,
100 COPY_PIECE_MINIMAL = 0x10000
103 enum
105 COPY_RULE_NUL = 0x0001,
106 COPY_RULE_FILES = 0x0002,
109 static int TotalFiles, TotalFilesToProcess;
111 static clock_t CopyStartTime;
113 static int OrigScrX, OrigScrY;
115 static uint64_t TotalCopySize, TotalCopiedSize; // Общий индикатор копирования
116 static uint64_t CurCopiedSize; // Текущий индикатор копирования
117 static uint64_t TotalSkippedSize; // Общий размер пропущенных файлов
118 static size_t CountTarget; // всего целей.
119 static bool ShowTotalCopySize;
120 static FARString strTotalCopySizeText;
122 static FileFilter *Filter;
123 static int UseFilter = FALSE;
125 static clock_t ProgressUpdateTime; // Last progress bar update time
127 ShellCopyFileExtendedAttributes::ShellCopyFileExtendedAttributes(File &f)
129 _apply = (f.QueryFileExtendedAttributes(_xattr) != FB_NO && !_xattr.empty());
132 void ShellCopyFileExtendedAttributes::ApplyToCopied(File &f)
134 if (_apply) {
135 f.SetFileExtendedAttributes(_xattr);
139 struct CopyDlgParam
141 ShellCopy *thisClass;
142 int AltF10;
143 DWORD FileAttr;
144 int SelCount;
145 bool FolderPresent;
146 bool FilesPresent;
147 FARString strPluginFormat;
148 bool AskRO;
151 enum enumShellCopy
153 ID_SC_TITLE,
154 ID_SC_TARGETTITLE,
155 ID_SC_TARGETEDIT,
156 ID_SC_SEPARATOR1,
157 ID_SC_COMBOTEXT,
158 ID_SC_COMBO,
159 ID_SC_MULTITARGET,
160 ID_SC_COPYACCESSMODE,
161 ID_SC_COPYXATTR,
162 ID_SC_WRITETHROUGH,
163 ID_SC_SPARSEFILES,
164 ID_SC_USECOW,
165 ID_SC_COPYSYMLINK_TEXT,
166 ID_SC_COPYSYMLINK_COMBO,
167 ID_SC_COPYSYMLINK_EXPLAIN_TEXT,
168 ID_SC_SEPARATOR3,
169 ID_SC_USEFILTER,
170 ID_SC_SEPARATOR4,
171 ID_SC_BTNCOPY,
172 ID_SC_BTNTREE,
173 ID_SC_BTNFILTER,
174 ID_SC_BTNCANCEL,
175 ID_SC_SOURCEFILENAME,
178 enum CopyMode
180 CM_ASK,
181 CM_OVERWRITE,
182 CM_SKIP,
183 CM_RENAME,
184 CM_APPEND,
185 CM_ONLYNEWER,
186 CM_SEPARATOR,
187 CM_ASKRO,
190 // CopyProgress start
191 // гнать это отсюда в отдельный файл после разбора кучи глобальных переменных вверху
192 class CopyProgress
194 ConsoleTitle CopyTitle;
195 wakeful W;
196 SMALL_RECT Rect{};
197 wchar_t Bar[100]{};
198 size_t BarSize;
199 bool Move, Total, Time;
200 bool BgInit, ScanBgInit;
201 bool IsCancelled;
202 int Color;
203 int Percents;
204 DWORD LastWriteTime;
205 FARString strSrc, strDst, strFiles, strTime;
206 bool Timer();
207 void Flush();
208 void DrawNames();
209 void CreateScanBackground();
210 void SetProgress(bool TotalProgress, UINT64 CompletedSize, UINT64 TotalSize);
212 public:
213 CopyProgress(bool Move, bool Total, bool Time);
214 void CreateBackground();
215 bool Cancelled() { return IsCancelled; }
216 void SetScanName(const wchar_t *Name);
217 void SetNames(const wchar_t *Src, const wchar_t *Dst);
218 void SetProgressValue(UINT64 CompletedSize, UINT64 TotalSize)
220 return SetProgress(false, CompletedSize, TotalSize);
222 void SetTotalProgressValue(UINT64 CompletedSize, UINT64 TotalSize)
224 return SetProgress(true, CompletedSize, TotalSize);
228 static void GetTimeText(DWORD Time, FARString &strTimeText)
230 DWORD Sec = Time;
231 DWORD Min = Sec / 60;
232 Sec-= (Min * 60);
233 DWORD Hour = Min / 60;
234 Min-= (Hour * 60);
235 strTimeText.Format(L"%02u:%02u:%02u", Hour, Min, Sec);
238 bool CopyProgress::Timer()
240 bool Result = false;
241 DWORD Time = GetProcessUptimeMSec();
243 if (!LastWriteTime || (Time - LastWriteTime >= RedrawTimeout)) {
244 LastWriteTime = Time;
245 Result = true;
248 return Result;
251 void CopyProgress::Flush()
253 if (Timer()) {
254 if (!IsCancelled) {
255 if (CheckForEscSilent()) {
256 LockFrame LF((*FrameManager)[0]);
257 IsCancelled = ConfirmAbortOp() != 0;
261 if (Total || (TotalFilesToProcess == 1)) {
262 CopyTitle.Set(L"{%d%%} %ls",
263 Total ? ToPercent64(TotalCopiedSize >> 8, TotalCopySize >> 8) : Percents,
264 (Move ? Msg::CopyMovingTitle : Msg::CopyCopyingTitle).CPtr());
269 CopyProgress::CopyProgress(bool Move, bool Total, bool Time)
271 BarSize(52),
272 Move(Move),
273 Total(Total),
274 Time(Time),
275 BgInit(false),
276 ScanBgInit(false),
277 IsCancelled(false),
278 Color(FarColorToReal(COL_DIALOGTEXT)),
279 Percents(0),
280 LastWriteTime(0)
283 void CopyProgress::SetScanName(const wchar_t *Name)
285 if (!ScanBgInit) {
286 CreateScanBackground();
289 GotoXY(Rect.Left + 5, Rect.Top + 3);
290 FS << fmt::Cells() << fmt::LeftAlign() << fmt::Size(Rect.Right - Rect.Left - 9) << Name;
291 Flush();
294 void CopyProgress::CreateScanBackground()
296 for (size_t i = 0; i < BarSize; i++) {
297 Bar[i] = L' ';
300 Bar[BarSize] = 0;
301 Message(MSG_LEFTALIGN, 0, (Move ? Msg::MoveDlgTitle : Msg::CopyDlgTitle), Msg::CopyScanning, Bar);
302 int MX1, MY1, MX2, MY2;
303 GetMessagePosition(MX1, MY1, MX2, MY2);
304 Rect.Left = MX1;
305 Rect.Right = MX2;
306 Rect.Top = MY1;
307 Rect.Bottom = MY2;
308 ScanBgInit = true;
311 void CopyProgress::CreateBackground()
313 for (size_t i = 0; i < BarSize; i++) {
314 Bar[i] = L' ';
317 Bar[BarSize] = 0;
319 if (!Total) {
320 if (!Time) {
321 Message(MSG_LEFTALIGN, 0, (Move ? Msg::MoveDlgTitle : Msg::CopyDlgTitle),
322 (Move ? Msg::CopyMoving : Msg::CopyCopying), L"", Msg::CopyTo, L"", Bar, L"\x1", L"");
323 } else {
324 Message(MSG_LEFTALIGN, 0, (Move ? Msg::MoveDlgTitle : Msg::CopyDlgTitle),
325 (Move ? Msg::CopyMoving : Msg::CopyCopying), L"", Msg::CopyTo, L"", Bar, L"\x1", L"",
326 L"\x1", L"");
328 } else {
329 FARString strTotalSeparator(L"\x1 ");
330 strTotalSeparator+= Msg::CopyDlgTotal;
331 strTotalSeparator+= L": ";
332 strTotalSeparator+= strTotalCopySizeText;
333 strTotalSeparator+= L" ";
335 if (!Time) {
336 Message(MSG_LEFTALIGN, 0, (Move ? Msg::MoveDlgTitle : Msg::CopyDlgTitle),
337 (Move ? Msg::CopyMoving : Msg::CopyCopying), L"", Msg::CopyTo, L"", Bar,
338 strTotalSeparator, Bar, L"\x1", L"");
339 } else {
340 Message(MSG_LEFTALIGN, 0, (Move ? Msg::MoveDlgTitle : Msg::CopyDlgTitle),
341 (Move ? Msg::CopyMoving : Msg::CopyCopying), L"", Msg::CopyTo, L"", Bar,
342 strTotalSeparator, Bar, L"\x1", L"", L"\x1", L"");
346 int MX1, MY1, MX2, MY2;
347 GetMessagePosition(MX1, MY1, MX2, MY2);
348 Rect.Left = MX1;
349 Rect.Right = MX2;
350 Rect.Top = MY1;
351 Rect.Bottom = MY2;
352 BgInit = true;
353 DrawNames();
356 void CopyProgress::DrawNames()
358 Text(Rect.Left + 5, Rect.Top + 3, Color, strSrc);
359 Text(Rect.Left + 5, Rect.Top + 5, Color, strDst);
360 Text(Rect.Left + 5, Rect.Top + (Total ? 10 : 8), Color, strFiles);
363 void CopyProgress::SetNames(const wchar_t *Src, const wchar_t *Dst)
365 if (!BgInit) {
366 CreateBackground();
369 FormatString FString;
370 FString << fmt::Cells() << fmt::LeftAlign() << fmt::Size(Rect.Right - Rect.Left - 9) << Src;
371 strSrc = std::move(FString.strValue());
372 FString.Clear();
373 FString << fmt::Cells() << fmt::LeftAlign() << fmt::Size(Rect.Right - Rect.Left - 9) << Dst;
374 strDst = std::move(FString.strValue());
376 if (Total) {
377 strFiles.Format(Msg::CopyProcessedTotal, TotalFiles, TotalFilesToProcess);
378 } else {
379 strFiles.Format(Msg::CopyProcessed, TotalFiles);
382 DrawNames();
383 Flush();
386 void CopyProgress::SetProgress(bool TotalProgress, UINT64 CompletedSize, UINT64 TotalSize)
388 if (!BgInit) {
389 CreateBackground();
392 if (Total == TotalProgress) {
395 UINT64 OldCompletedSize = CompletedSize;
396 UINT64 OldTotalSize = TotalSize;
397 CompletedSize>>= 8;
398 TotalSize>>= 8;
399 CompletedSize = Min(CompletedSize, TotalSize);
400 COORD BarCoord = {(SHORT)(Rect.Left + 5), (SHORT)(Rect.Top + (TotalProgress ? 8 : 6))};
401 size_t BarLength = Rect.Right - Rect.Left - 9 - 5; //-5 для процентов
402 size_t Length = TotalSize
403 ? static_cast<size_t>((TotalSize < 1000000 ? CompletedSize : CompletedSize / 100) * BarLength
404 / (TotalSize < 1000000 ? TotalSize : TotalSize / 100))
405 : BarLength;
407 for (size_t i = 0; i < BarLength; i++) {
408 Bar[i] = BoxSymbols[BS_X_B0];
411 if (TotalSize) {
412 for (size_t i = 0; i < Length; i++) {
413 Bar[i] = BoxSymbols[BS_X_DB];
417 Bar[BarLength] = 0;
418 Percents = ToPercent64(CompletedSize, TotalSize);
419 FormatString strPercents;
420 strPercents << fmt::Expand(4) << Percents << L"%";
421 Text(BarCoord.X, BarCoord.Y, Color, Bar);
422 Text(static_cast<int>(BarCoord.X + BarLength), BarCoord.Y, Color, strPercents);
424 if (Time && (!Total || TotalProgress)) {
425 DWORD WorkTime = GetProcessUptimeMSec() - CopyStartTime;
426 UINT64 SizeLeft = (OldTotalSize > OldCompletedSize) ? (OldTotalSize - OldCompletedSize) : 0;
427 long CalcTime = OldCalcTime;
429 if (WaitUserTime != -1) // -1 => находимся в процессе ожидания ответа юзера
431 OldCalcTime = CalcTime = WorkTime - WaitUserTime;
434 WorkTime/= 1000;
435 CalcTime/= 1000;
437 if (!WorkTime) {
438 strTime.Format(Msg::CopyTimeInfo, L" ", L" ", L" ");
439 } else {
440 if (TotalProgress) {
441 OldCompletedSize = OldCompletedSize - TotalSkippedSize;
444 UINT64 CPS = CalcTime ? OldCompletedSize / CalcTime : 0;
445 DWORD TimeLeft = static_cast<DWORD>(CPS ? SizeLeft / CPS : 0);
446 FARString strSpeed;
447 FileSizeToStr(strSpeed, CPS, 8, COLUMN_FLOATSIZE | COLUMN_COMMAS);
448 FARString strWorkTimeStr, strTimeLeftStr;
449 GetTimeText(WorkTime, strWorkTimeStr);
450 GetTimeText(TimeLeft, strTimeLeftStr);
451 if (strSpeed.At(0) == L' ' && strSpeed.At(strSpeed.GetLength() - 1) >= L'0'
452 && strSpeed.At(strSpeed.GetLength() - 1) <= L'9') {
453 strSpeed.LShift(1);
454 strSpeed+= L" ";
456 strTime.Format(Msg::CopyTimeInfo, strWorkTimeStr.CPtr(), strTimeLeftStr.CPtr(), strSpeed.CPtr());
459 Text(Rect.Left + 5, Rect.Top + (Total ? 12 : 10), Color, strTime);
462 Flush();
464 // CopyProgress end
466 static CopyProgress *CP = nullptr;
469 $ 25.05.2002 IS
470 + Всегда работаем с реальными _длинными_ именами, в результате чего
471 отлавливается ситуация, когда
472 Src="D:\Program Files\filename"
473 Dest="D:\PROGRA~1\filename"
474 ("D:\PROGRA~1" - короткое имя для "D:\Program Files")
475 считается, что имена тоже одинаковые, а раньше считалось,
476 что они разные (функция не знала, что и в первом, и во втором случае
477 путь один и тот же)
478 ! Оптимизация - "велосипед" заменен на DeleteEndSlash
479 ! Убираем всю самодеятельность по проверке имен с разным
480 регистром из функции прочь, потому что это нужно делать только при
481 переименовании, а функция вызывается и при копировании тоже.
482 Теперь функция вернет 1, для случая имен src=path\filename,
483 dest=path\filename (раньше возвращала 2 - т.е. сигнал об ошибке).
486 static int CmpFullNames(const wchar_t *Src, const wchar_t *Dest)
488 FARString strSrcFullName, strDestFullName;
490 // получим полные пути с учетом символических связей
491 ConvertNameToFull(Src, strSrcFullName);
492 ConvertNameToFull(Dest, strDestFullName);
493 DeleteEndSlash(strSrcFullName);
494 DeleteEndSlash(strDestFullName);
496 return !StrCmp(strSrcFullName, strDestFullName);
499 static FARString &GetParentFolder(const wchar_t *Src, FARString &strDest)
501 strDest = Src;
502 CutToSlash(strDest, true);
503 return strDest;
506 static int CmpFullPath(const wchar_t *Src, const wchar_t *Dest)
508 FARString strSrcFullName, strDestFullName;
510 GetParentFolder(Src, strSrcFullName);
511 GetParentFolder(Dest, strDestFullName);
512 DeleteEndSlash(strSrcFullName);
513 DeleteEndSlash(strDestFullName);
515 return !StrCmp(strSrcFullName, strDestFullName);
518 static void GenerateName(FARString &strName, const wchar_t *Path = nullptr)
520 if (Path && *Path) {
521 FARString strTmp = Path;
522 AddEndSlash(strTmp);
523 strTmp+= PointToName(strName);
524 strName = strTmp;
527 FARString strExt = PointToExt(strName);
528 size_t NameLength = strName.GetLength() - strExt.GetLength();
530 for (int i = 1; apiPathExists(strName); i++) {
531 WCHAR Suffix[20] = L"_";
532 _itow(i, Suffix + 1, 10);
533 strName.Truncate(NameLength);
534 strName+= Suffix;
535 strName+= strExt;
539 void PR_ShellCopyMsg()
541 PreRedrawItem preRedrawItem = PreRedraw.Peek();
543 if (preRedrawItem.Param.Param1) {
544 ((CopyProgress *)preRedrawItem.Param.Param1)->CreateBackground();
548 #define USE_PAGE_SIZE 0x1000
549 template <class T>
550 static T AlignPageUp(T v)
552 // todo: use actual system page size
553 uintptr_t al = ((uintptr_t)v) & (USE_PAGE_SIZE - 1);
554 if (al) {
555 v = (T)(((uintptr_t)v) + (USE_PAGE_SIZE - al));
557 return v;
560 ShellCopyBuffer::ShellCopyBuffer()
562 Capacity(AlignPageUp((DWORD)COPY_BUFFER_SIZE)),
563 Size(std::min((DWORD)COPY_PIECE_MINIMAL, Capacity)),
564 // allocate page-aligned memory: IO works faster on that, also direct-io requires buffer to be aligned sometimes
565 // OSX lacks aligned_malloc so do it manually
566 Buffer(new char[Capacity + USE_PAGE_SIZE]),
567 Ptr(AlignPageUp(Buffer))
570 ShellCopyBuffer::~ShellCopyBuffer()
572 delete[] Buffer;
575 ShellCopy::ShellCopy(Panel *SrcPanel, // исходная панель (активная)
576 int Move, // =1 - операция Move
577 int Link, // =1 - Sym/Hard Link
578 int CurrentOnly, // =1 - только текущий файл, под курсором
579 int Ask, // =1 - выводить диалог?
580 int &ToPlugin, // =?
581 const wchar_t *PluginDestPath, bool ToSubdir)
583 RPT(RP_EXACTCOPY)
585 Flags.ErrorMessageFlags = MSG_WARNING | MSG_ERRORTYPE;
586 if (Opt.NotifOpt.OnFileOperation) {
587 Flags.ErrorMessageFlags|= MSG_DISPLAYNOTIFY;
589 Filter = nullptr;
590 DestList.SetParameters(0, 0, ULF_UNIQUE);
591 CopyDlgParam CDP{};
592 if (!(CDP.SelCount = SrcPanel->GetSelCount()))
593 return;
595 FARString strSelName;
597 if (CDP.SelCount == 1) {
598 SrcPanel->GetSelNameCompat(nullptr, CDP.FileAttr); //????
599 SrcPanel->GetSelNameCompat(&strSelName, CDP.FileAttr);
601 if (TestParentFolderName(strSelName))
602 return;
605 // Создадим объект фильтра
606 Filter = new FileFilter(SrcPanel, FFT_COPY);
607 // $ 26.05.2001 OT Запретить перерисовку панелей во время копирования
608 _tran(SysLog(L"call (*FrameManager)[0]->LockRefresh()"));
609 (*FrameManager)[0]->Lock();
611 // Progress bar update threshold
612 CDP.thisClass = this;
613 CDP.AltF10 = 0;
614 CDP.FolderPresent = false;
615 CDP.FilesPresent = false;
616 if (Move)
617 Flags.MOVE = true;
618 if (Link)
619 Flags.LINK = true;
620 if (CurrentOnly)
621 Flags.CURRENTONLY = true;
622 ShowTotalCopySize = Opt.CMOpt.CopyShowTotal != 0;
623 strTotalCopySizeText.Clear();
624 SelectedFolderNameLength = 0;
625 int DestPlugin = ToPlugin;
626 ToPlugin = FALSE;
627 this->SrcPanel = SrcPanel;
628 DestPanel = CtrlObject->Cp()->GetAnotherPanel(SrcPanel);
629 DestPanelMode = DestPlugin ? DestPanel->GetMode() : NORMAL_PANEL;
630 SrcPanelMode = SrcPanel->GetMode();
633 ***********************************************************************
634 *** Prepare Dialog Controls
635 ***********************************************************************
637 int msh = Move ? 1 : 0;
638 int DLG_HEIGHT = 19 + msh, DLG_WIDTH = 76;
640 DialogDataEx CopyDlgData[] = {
641 {DI_DOUBLEBOX, 3, 1, (SHORT)(DLG_WIDTH - 4), (SHORT)(DLG_HEIGHT - 2), {}, 0, Msg::CopyDlgTitle},
642 {DI_TEXT, 5, 2, 0, 2, {}, 0, (Link ? Msg::CMLTargetIN : Msg::CMLTargetTO)},
643 {DI_EDIT, 5, 3, 70, 3, {reinterpret_cast<DWORD_PTR>(L"Copy")}, DIF_FOCUS | DIF_HISTORY | DIF_EDITEXPAND | DIF_USELASTHISTORY | DIF_EDITPATH, L""},
644 {DI_TEXT, 3, 4, 0, 4, {}, DIF_SEPARATOR, L""},
645 {DI_TEXT, 5, 5, 0, 5, {}, 0, Msg::CopyIfFileExist},
646 {DI_COMBOBOX, 29, 5, 70, 5, {}, DIF_DROPDOWNLIST | DIF_LISTNOAMPERSAND | DIF_LISTWRAPMODE, L""},
647 {DI_CHECKBOX, 5, 6, 0, 6, {}, 0, Msg::CopyMultiActions},
648 {DI_CHECKBOX, 5, 7, 0, 7, {}, 0, Msg::CopyAccessMode},
649 {DI_CHECKBOX, 5, 8, 0, 8, {}, 0, Msg::CopyXAttr},
650 {DI_CHECKBOX, 5, 9, 0, 9, {}, 0, Msg::CopyWriteThrough},
651 {DI_CHECKBOX, 5, 10, 0, 10, {}, 0, Msg::CopySparseFiles},
652 {DI_CHECKBOX, 5, 11, 0, 11, {}, 0, Msg::CopyUseCOW},
653 {DI_TEXT, 5, 12, 0, 12, {}, 0, Msg::CopySymLinkText},
654 {DI_COMBOBOX, 29, 12, 70, 12, {}, DIF_DROPDOWNLIST | DIF_LISTNOAMPERSAND | DIF_LISTWRAPMODE, L""},
655 {DI_TEXT, 5, 13, 0, 13, {}, DIF_HIDDEN, Msg::LinkCopyMoveExplainText},
656 {DI_TEXT, 3, (SHORT)(13 + msh), 0, (SHORT)(13 + msh), {}, DIF_SEPARATOR, L""},
657 {DI_CHECKBOX, 5, (SHORT)(14 + msh), 0, (SHORT)(14 + msh), {UseFilter ? BSTATE_CHECKED : BSTATE_UNCHECKED}, DIF_AUTOMATION, Msg::CopyUseFilter},
658 {DI_TEXT, 3, (SHORT)(15 + msh), 0, (SHORT)(15 + msh), {}, DIF_SEPARATOR, L""},
659 {DI_BUTTON, 0, (SHORT)(16 + msh), 0, (SHORT)(16 + msh), {}, DIF_DEFAULT | DIF_CENTERGROUP, Msg::CopyDlgCopy},
660 {DI_BUTTON, 0, (SHORT)(16 + msh), 0, (SHORT)(16 + msh), {}, DIF_CENTERGROUP | DIF_BTNNOCLOSE, Msg::CopyDlgTree},
661 {DI_BUTTON, 0, (SHORT)(16 + msh), 0, (SHORT)(16 + msh), {}, DIF_CENTERGROUP | DIF_BTNNOCLOSE | DIF_AUTOMATION | (UseFilter ? 0 : DIF_DISABLE), Msg::CopySetFilter},
662 {DI_BUTTON, 0, (SHORT)(16 + msh), 0, (SHORT)(16 + msh), {}, DIF_CENTERGROUP, Msg::CopyDlgCancel},
663 {DI_TEXT, 5, 2, 0, 2, {}, DIF_SHOWAMPERSAND, L""}
665 MakeDialogItemsEx(CopyDlgData, CopyDlg);
666 CopyDlg[ID_SC_MULTITARGET].Selected = Opt.CMOpt.MultiCopy;
667 CopyDlg[ID_SC_WRITETHROUGH].Selected = Opt.CMOpt.WriteThrough;
668 CopyDlg[ID_SC_SPARSEFILES].Selected = Opt.CMOpt.SparseFiles;
669 #ifdef COW_SUPPORTED
670 CopyDlg[ID_SC_USECOW].Selected = Opt.CMOpt.UseCOW && (Opt.CMOpt.SparseFiles == 0);
671 #else
672 CopyDlg[ID_SC_USECOW].Flags|= DIF_DISABLE | DIF_HIDDEN;
673 #endif
674 CopyDlg[ID_SC_COPYACCESSMODE].Selected = Opt.CMOpt.CopyAccessMode;
675 CopyDlg[ID_SC_COPYXATTR].Selected = Opt.CMOpt.CopyXAttr;
677 FarList SymLinkHowComboList;
678 FarListItem SymLinkHowTypeItems[3]{};
680 if (Link) {
681 CopyDlg[ID_SC_COMBOTEXT].strData = Msg::LinkType;
682 // CopyDlg[ID_SC_SEPARATOR2].Flags|= DIF_DISABLE|DIF_HIDDEN;
683 CopyDlg[ID_SC_COPYSYMLINK_TEXT].Flags|= DIF_DISABLE | DIF_HIDDEN;
684 CopyDlg[ID_SC_COPYSYMLINK_COMBO].Flags|= DIF_DISABLE | DIF_HIDDEN;
685 CopyDlg[ID_SC_SPARSEFILES].Flags|= DIF_DISABLE | DIF_HIDDEN;
686 CopyDlg[ID_SC_USECOW].Flags|= DIF_DISABLE | DIF_HIDDEN;
688 } else {
689 SymLinkHowTypeItems[0].Text = Msg::LinkCopyAsIs;
690 SymLinkHowTypeItems[1].Text = Msg::LinkCopySmart;
691 SymLinkHowTypeItems[2].Text = Msg::LinkCopyContent;
692 SymLinkHowComboList.ItemsNumber = ARRAYSIZE(SymLinkHowTypeItems);
693 SymLinkHowComboList.Items = SymLinkHowTypeItems;
694 SymLinkHowTypeItems[std::min(Opt.CMOpt.HowCopySymlink, (int)ARRAYSIZE(SymLinkHowTypeItems) - 1)]
695 .Flags|= LIF_SELECTED;
697 CopyDlg[ID_SC_COPYSYMLINK_COMBO].ListItems = &SymLinkHowComboList;
699 if (Move) // секция про перенос
701 CopyDlg[ID_SC_MULTITARGET].Selected = 0;
702 CopyDlg[ID_SC_MULTITARGET].Flags|= DIF_DISABLE;
703 CopyDlg[ID_SC_COPYSYMLINK_EXPLAIN_TEXT].Flags = DIF_DISABLE;
705 // else // секция про копирование
706 // {
707 // CopyDlg[ID_SC_COPYSYMLINKOUTER].Selected=1;
708 // }
711 FARString strCopyStr;
713 if (CDP.SelCount == 1) {
714 if (SrcPanel->GetType() == TREE_PANEL) {
715 FARString strNewDir(strSelName);
716 size_t pos;
718 if (FindLastSlash(pos, strNewDir)) {
719 strNewDir.Truncate(pos);
721 if (!pos || strNewDir.At(pos - 1) == L':')
722 strNewDir+= WGOOD_SLASH;
724 FarChDir(strNewDir);
728 FARString strSelNameShort = strSelName;
729 strCopyStr = (Move ? Msg::MoveFile : (Link ? Msg::LinkFile : Msg::CopyFile));
730 TruncPathStr(strSelNameShort,
731 static_cast<int>(
732 CopyDlg[ID_SC_TITLE].X2 - CopyDlg[ID_SC_TITLE].X1 - strCopyStr.GetLength() - 7));
733 strCopyStr+= L" " + strSelNameShort;
735 // Если копируем одиночный файл, то запрещаем использовать фильтр
736 if (!(CDP.FileAttr & FILE_ATTRIBUTE_DIRECTORY)) {
737 CopyDlg[ID_SC_USEFILTER].Selected = 0;
738 CopyDlg[ID_SC_USEFILTER].Flags|= DIF_DISABLE;
740 } else // Объектов несколько!
742 auto NOper = Msg::CopyFiles;
744 if (Move)
745 NOper = Msg::MoveFiles;
746 else if (Link)
747 NOper = Msg::LinkFiles;
749 // коррекция языка - про окончания
750 FormatString StrItems;
751 StrItems << CDP.SelCount;
752 size_t LenItems = StrItems.strValue().GetLength();
753 auto NItems = Msg::CMLItemsA;
755 if (LenItems > 0) {
756 if ((LenItems >= 2 && StrItems[LenItems - 2] == '1') || StrItems[LenItems - 1] >= '5'
757 || StrItems[LenItems - 1] == '0')
758 NItems = Msg::CMLItemsS;
759 else if (StrItems[LenItems - 1] == '1')
760 NItems = Msg::CMLItems0;
763 strCopyStr.Format(NOper, CDP.SelCount, NItems.CPtr());
766 CopyDlg[ID_SC_SOURCEFILENAME].strData = strCopyStr;
767 CopyDlg[ID_SC_TITLE].strData =
768 (Move ? Msg::MoveDlgTitle : (Link ? Msg::LinkDlgTitle : Msg::CopyDlgTitle));
769 CopyDlg[ID_SC_BTNCOPY].strData =
770 (Move ? Msg::CopyDlgRename : (Link ? Msg::CopyDlgLink : Msg::CopyDlgCopy));
772 if (DestPanelMode == PLUGIN_PANEL) {
773 // Если противоположная панель - плагин, то дисаблим OnlyNewer //?????
775 CDP.CopySecurity=2;
776 CopyDlg[ID_SC_ACCOPY].Selected=0;
777 CopyDlg[ID_SC_ACINHERIT].Selected=0;
778 CopyDlg[ID_SC_ACLEAVE].Selected=1;
779 CopyDlg[ID_SC_ACCOPY].Flags|=DIF_DISABLE;
780 CopyDlg[ID_SC_ACINHERIT].Flags|=DIF_DISABLE;
781 CopyDlg[ID_SC_ACLEAVE].Flags|=DIF_DISABLE;
785 FARString strDestDir;
786 DestPanel->GetCurDir(strDestDir);
787 if (ToSubdir) {
788 AddEndSlash(strDestDir);
789 FARString strSubdir;
790 DestPanel->GetCurName(strSubdir);
791 strDestDir+= strSubdir;
793 FARString strSrcDir;
794 SrcPanel->GetCurDir(strSrcDir);
796 if (CurrentOnly) {
797 // При копировании только элемента под курсором берем его имя в кавычки, если оно содержит разделители.
798 CopyDlg[ID_SC_TARGETEDIT].strData = strSelName;
800 if (!Move && CopyDlg[ID_SC_TARGETEDIT].strData.ContainsAnyOf(",;")) {
801 Unquote(CopyDlg[ID_SC_TARGETEDIT].strData); // уберем все лишние кавычки
802 InsertQuote(CopyDlg[ID_SC_TARGETEDIT].strData); // возьмем в кавычки, т.к. могут быть разделители
804 } else {
805 switch (DestPanelMode) {
806 case NORMAL_PANEL: {
807 if ((strDestDir.IsEmpty() || !DestPanel->IsVisible() || !StrCmp(strSrcDir, strDestDir))
808 && CDP.SelCount == 1)
809 CopyDlg[ID_SC_TARGETEDIT].strData = strSelName;
810 else {
811 CopyDlg[ID_SC_TARGETEDIT].strData = strDestDir;
812 AddEndSlash(CopyDlg[ID_SC_TARGETEDIT].strData);
816 $ 19.07.2003 IS
817 Если цель содержит разделители, то возьмем ее в кавычки, дабы не получить
818 ерунду при F5, Enter в панелях, когда пользователь включит MultiCopy
820 if (!Move && CopyDlg[ID_SC_TARGETEDIT].strData.ContainsAnyOf(",;")) {
821 Unquote(CopyDlg[ID_SC_TARGETEDIT].strData); // уберем все лишние кавычки
822 InsertQuote(CopyDlg[ID_SC_TARGETEDIT].strData); // возьмем в кавычки, т.к. могут быть разделители
825 break;
828 case PLUGIN_PANEL: {
829 OpenPluginInfo Info;
830 DestPanel->GetOpenPluginInfo(&Info);
831 FARString strFormat = Info.Format;
832 CopyDlg[ID_SC_TARGETEDIT].strData = strFormat + L":";
834 while (CopyDlg[ID_SC_TARGETEDIT].strData.GetLength() < 2)
835 CopyDlg[ID_SC_TARGETEDIT].strData+= L":";
837 CDP.strPluginFormat = CopyDlg[ID_SC_TARGETEDIT].strData;
838 CDP.strPluginFormat.Upper();
839 break;
844 FARString strInitDestDir = CopyDlg[ID_SC_TARGETEDIT].strData;
845 // Для фильтра
846 FAR_FIND_DATA_EX fd;
847 SrcPanel->GetSelNameCompat(nullptr, CDP.FileAttr);
849 bool AddSlash = false;
851 while (SrcPanel->GetSelNameCompat(&strSelName, CDP.FileAttr, &fd)) {
852 if (UseFilter) {
853 if (!Filter->FileInFilter(fd))
854 continue;
857 if (CDP.FileAttr & FILE_ATTRIBUTE_DIRECTORY) {
858 CDP.FolderPresent = true;
859 AddSlash = true;
860 // break;
861 } else {
862 CDP.FilesPresent = true;
866 if (Link) // рулесы по поводу линков (предварительные!)
868 for (int i = ID_SC_SEPARATOR3; i <= ID_SC_BTNCANCEL; i++) {
869 CopyDlg[i].Y1-= 3;
870 CopyDlg[i].Y2-= 3;
872 CopyDlg[ID_SC_TITLE].Y2-= 3;
873 DLG_HEIGHT-= 3;
876 // корректирем позицию " to"
877 CopyDlg[ID_SC_TARGETTITLE].X1 = CopyDlg[ID_SC_TARGETTITLE].X2 =
878 CopyDlg[ID_SC_SOURCEFILENAME].X1 + (int)CopyDlg[ID_SC_SOURCEFILENAME].strData.GetLength();
881 $ 15.06.2002 IS
882 Обработка копирования мышкой - в этом случае диалог не показывается,
883 но переменные все равно инициализируются. Если произойдет неудачная
884 компиляция списка целей, то покажем диалог.
886 FARString strCopyDlgValue;
887 if (!Ask) {
888 strCopyDlgValue = CopyDlg[ID_SC_TARGETEDIT].strData;
889 Unquote(strCopyDlgValue);
890 InsertQuote(strCopyDlgValue);
892 if (!DestList.Set(strCopyDlgValue))
893 Ask = TRUE;
897 ***********************************************************************
898 *** Вывод и обработка диалога
899 ***********************************************************************
901 if (Ask) {
902 FarList ComboList;
903 FarListItem LinkTypeItems[2] = {}, CopyModeItems[8] = {};
905 if (Link) {
906 ComboList.ItemsNumber = ARRAYSIZE(LinkTypeItems);
907 ComboList.Items = LinkTypeItems;
908 ComboList.Items[0].Text = Msg::LinkTypeHardlink;
909 ComboList.Items[1].Text = Msg::LinkTypeSymlink;
911 //ComboList.Items[CDP.FilesPresent ? 0 : 1].Flags|= LIF_SELECTED;
912 ComboList.Items[(Opt.MakeLinkSuggestSymlinkAlways || CDP.FolderPresent) ? 1 : 0].Flags|= LIF_SELECTED;
913 } else {
914 ComboList.ItemsNumber = ARRAYSIZE(CopyModeItems);
915 ComboList.Items = CopyModeItems;
916 ComboList.Items[CM_ASK].Text = Msg::CopyAsk;
917 ComboList.Items[CM_OVERWRITE].Text = Msg::CopyOverwrite;
918 ComboList.Items[CM_SKIP].Text = Msg::CopySkipOvr;
919 ComboList.Items[CM_RENAME].Text = Msg::CopyRename;
920 ComboList.Items[CM_APPEND].Text = Msg::CopyAppend;
921 ComboList.Items[CM_ONLYNEWER].Text = Msg::CopyOnlyNewerFiles;
922 ComboList.Items[CM_ASKRO].Text = Msg::CopyAskRO;
923 // if uncehcked in Options->Confirmations then disable variants & set only Overwrite
924 if ( (Move && !Opt.Confirm.Move) || (!Move && !Opt.Confirm.Copy) ) {
925 ComboList.Items[CM_OVERWRITE].Flags= LIF_SELECTED;
926 CopyDlg[ID_SC_COMBO].Flags|= DIF_DISABLE;
927 CopyDlg[ID_SC_COMBOTEXT].Flags|= DIF_DISABLE;
929 else
930 ComboList.Items[CM_ASK].Flags = LIF_SELECTED;
931 ComboList.Items[CM_SEPARATOR].Flags = LIF_SEPARATOR;
933 if (Opt.Confirm.RO) {
934 ComboList.Items[CM_ASKRO].Flags = LIF_CHECKED;
938 CopyDlg[ID_SC_COMBO].ListItems = &ComboList;
940 Dialog Dlg(CopyDlg, ARRAYSIZE(CopyDlg), CopyDlgProc, (LONG_PTR)&CDP);
941 Dlg.SetHelp(Link ? L"HardSymLink" : L"CopyFiles");
942 Dlg.SetId(Link ? HardSymLinkId : (Move ? MoveFilesId : CopyFilesId));
943 Dlg.SetPosition(-1, -1, DLG_WIDTH, DLG_HEIGHT);
944 Dlg.SetAutomation(ID_SC_USEFILTER, ID_SC_BTNFILTER, DIF_DISABLE, DIF_NONE, DIF_NONE, DIF_DISABLE);
945 // Dlg.Show();
946 // $ 02.06.2001 IS + Проверим список целей и поднимем тревогу, если он содержит ошибки
947 int DlgExitCode;
949 for (;;) {
950 Dlg.ClearDone();
951 Dlg.Process();
952 DlgExitCode = Dlg.GetExitCode();
953 // Рефреш текущему времени для фильтра сразу после выхода из диалога
954 Filter->UpdateCurrentTime();
956 if (DlgExitCode == ID_SC_BTNCOPY) {
958 $ 03.08.2001 IS
959 Запомним строчку из диалога и начинаем ее мучить в зависимости от
960 состояния опции мультикопирования
962 strCopyDlgValue = CopyDlg[ID_SC_TARGETEDIT].strData;
963 if (!Move) {
964 Opt.CMOpt.MultiCopy = CopyDlg[ID_SC_MULTITARGET].Selected;
966 Opt.CMOpt.WriteThrough = CopyDlg[ID_SC_WRITETHROUGH].Selected;
967 Opt.CMOpt.CopyAccessMode = CopyDlg[ID_SC_COPYACCESSMODE].Selected;
968 Opt.CMOpt.CopyXAttr = CopyDlg[ID_SC_COPYXATTR].Selected;
969 Opt.CMOpt.SparseFiles = CopyDlg[ID_SC_SPARSEFILES].Selected;
970 Opt.CMOpt.UseCOW = CopyDlg[ID_SC_USECOW].Selected;
972 if (!CopyDlg[ID_SC_MULTITARGET].Selected || !strCopyDlgValue.ContainsAnyOf(",;")) // отключено multi*
974 // уберем лишние кавычки
975 Unquote(strCopyDlgValue);
976 // добавим кавычки, чтобы "список" удачно скомпилировался вне
977 // зависимости от наличия разделителей в оном
978 InsertQuote(strCopyDlgValue);
981 if (DestList.Set(strCopyDlgValue)) {
982 // Запомнить признак использования фильтра. KM
983 UseFilter = CopyDlg[ID_SC_USEFILTER].Selected;
984 break;
985 } else {
986 Message(MSG_WARNING, 1, Msg::Warning, Msg::CopyIncorrectTargetList, Msg::Ok);
988 } else
989 break;
992 if (DlgExitCode == ID_SC_BTNCANCEL || DlgExitCode < 0
993 || (CopyDlg[ID_SC_BTNCOPY].Flags & DIF_DISABLE)) {
994 if (DestPlugin)
995 ToPlugin = -1;
997 return;
1002 ***********************************************************************
1003 *** Стадия подготовки данных после диалога
1004 ***********************************************************************
1006 Flags.WRITETHROUGH = Flags.COPYACCESSMODE = Flags.COPYXATTR = Flags.SPARSEFILES = Flags.USECOW = false;
1007 Flags.SYMLINK = COPY_SYMLINK_ASIS;
1008 ReadOnlyDelMode = ReadOnlyOvrMode = OvrMode = SkipMode = SkipDeleteMode = -1;
1010 if (Link) {
1011 switch (CopyDlg[ID_SC_COMBO].ListPos) {
1012 case 0:
1013 RPT = RP_HARDLINK;
1014 break;
1015 case 1:
1016 RPT = RP_JUNCTION;
1017 break;
1018 case 2:
1019 RPT = RP_SYMLINK;
1020 break;
1021 case 3:
1022 RPT = RP_SYMLINKFILE;
1023 break;
1024 case 4:
1025 RPT = RP_SYMLINKDIR;
1026 break;
1028 } else {
1029 ReadOnlyOvrMode = CDP.AskRO ? -1 : 1;
1031 switch (CopyDlg[ID_SC_COMBO].ListPos) {
1032 case CM_ASK:
1033 OvrMode = -1;
1034 break;
1035 case CM_OVERWRITE:
1036 OvrMode = 1;
1037 break;
1038 case CM_SKIP:
1039 OvrMode = 3;
1040 ReadOnlyOvrMode = CDP.AskRO ? -1 : 3;
1041 break;
1042 case CM_RENAME:
1043 OvrMode = 5;
1044 break;
1045 case CM_APPEND:
1046 OvrMode = 7;
1047 break;
1048 case CM_ONLYNEWER:
1049 Flags.ONLYNEWERFILES = true;
1050 break;
1053 Opt.CMOpt.HowCopySymlink = CopyDlg[ID_SC_COPYSYMLINK_COMBO].ListPos;
1054 switch (Opt.CMOpt.HowCopySymlink) {
1055 case 1:
1056 Flags.SYMLINK = COPY_SYMLINK_SMART;
1057 break;
1058 case 2:
1059 Flags.SYMLINK = COPY_SYMLINK_ASFILE;
1060 break;
1064 if (DestPlugin && !StrCmp(CopyDlg[ID_SC_TARGETEDIT].strData, strInitDestDir)) {
1065 ToPlugin = 1;
1066 return;
1069 if (Opt.CMOpt.WriteThrough)
1070 Flags.WRITETHROUGH = true;
1071 if (Opt.CMOpt.CopyAccessMode)
1072 Flags.COPYACCESSMODE = true;
1073 if (Opt.CMOpt.CopyXAttr)
1074 Flags.COPYXATTR = true;
1075 if (Opt.CMOpt.SparseFiles)
1076 Flags.SPARSEFILES = true;
1077 if (Opt.CMOpt.UseCOW && Opt.CMOpt.SparseFiles == 0)
1078 Flags.USECOW = true;
1080 if (CDP.SelCount == 1)
1081 AddSlash = false; //???
1083 if (DestPlugin == 2) {
1084 if (PluginDestPath)
1085 strCopyDlgValue = PluginDestPath;
1087 return;
1090 if ((Opt.Diz.UpdateMode == DIZ_UPDATE_IF_DISPLAYED && SrcPanel->IsDizDisplayed())
1091 || Opt.Diz.UpdateMode == DIZ_UPDATE_ALWAYS) {
1092 CtrlObject->Cp()->LeftPanel->ReadDiz();
1093 CtrlObject->Cp()->RightPanel->ReadDiz();
1096 DestPanel->CloseFile();
1097 strDestDizPath.Clear();
1098 SrcPanel->SaveSelection();
1099 // нужно ли показывать время копирования?
1100 bool ShowCopyTime = (Opt.CMOpt.CopyTimeRule & COPY_RULE_FILES) != 0;
1102 ***********************************************************************
1103 **** Здесь все подготовительные операции закончены, можно приступать
1104 **** к процессу Copy/Move/Link
1105 ***********************************************************************
1107 int NeedDizUpdate = FALSE;
1108 int NeedUpdateAPanel = FALSE;
1109 Flags.UPDATEPPANEL = true;
1111 ЕСЛИ ПРИНЯТЬ В КАЧЕСТВЕ РАЗДЕЛИТЕЛЯ ПУТЕЙ, НАПРИМЕР ';',
1112 то нужно парсить CopyDlgValue на предмет MultiCopy и
1113 вызывать CopyFileTree нужное количество раз.
1116 Flags.MOVE = false;
1118 if (DestList.Set(strCopyDlgValue)) // если список успешно "скомпилировался"
1120 const wchar_t *NamePtr;
1121 FARString strNameTmp;
1122 // посчитаем количество целей.
1123 CountTarget = DestList.GetTotal();
1124 TotalFiles = 0;
1125 TotalCopySize = TotalCopiedSize = TotalSkippedSize = 0;
1126 ProgressUpdateTime = 0;
1128 // Запомним время начала
1129 if (ShowCopyTime) {
1130 CopyStartTime = GetProcessUptimeMSec();
1131 WaitUserTime = OldCalcTime = 0;
1134 if (CountTarget > 1)
1135 Move = 0;
1137 for (size_t DLI = 0; nullptr != (NamePtr = DestList.Get(DLI)); ++DLI) {
1138 CurCopiedSize = 0;
1139 strNameTmp = NamePtr;
1141 if (!StrCmp(strNameTmp, L"..") && IsLocalRootPath(strSrcDir)) {
1142 if (!Message(MSG_WARNING, 2, Msg::Error,
1143 ((!Move ? Msg::CannotCopyToTwoDot : Msg::CannotMoveToTwoDot)),
1144 Msg::CannotCopyMoveToTwoDot, Msg::CopySkip, Msg::CopyCancel))
1145 continue;
1147 break;
1150 if (DestList.IsLastElement(DLI)) { // для последней операции нужно учесть моменты связанные с операцией Move.
1151 Flags.COPYLASTTIME = true;
1152 if (Move)
1153 Flags.MOVE = true;
1156 // Если выделенных элементов больше 1 и среди них есть каталог, то всегда
1157 // делаем так, чтобы на конце был '/'
1158 // делаем так не всегда, а только когда NameTmp не является маской.
1159 if (AddSlash && !strNameTmp.ContainsAnyOf("*?"))
1160 AddEndSlash(strNameTmp);
1162 if (CDP.SelCount == 1 && !CDP.FolderPresent) {
1163 ShowTotalCopySize = false;
1164 TotalFilesToProcess = 1;
1167 if (Move) {
1168 if (CDP.SelCount == 1 && CDP.FolderPresent
1169 && CheckUpdateAnotherPanel(SrcPanel, strSelName)) {
1170 NeedUpdateAPanel = TRUE;
1174 CP = new CopyProgress(Move != 0, ShowTotalCopySize, ShowCopyTime);
1175 // Обнулим инфу про дизы
1176 strDestDizPath.Clear();
1177 Flags.DIZREAD = false;
1178 // сохраним выделение
1179 SrcPanel->SaveSelection();
1180 const auto OldFlagsSYMLINK = Flags.SYMLINK;
1181 // собственно - один проход копирования
1182 // Mantis#45: Необходимо привести копирование ссылок на папки с NTFS на FAT к более логичному виду
1184 // todo: If dst does not support symlinks
1185 // Flags.SYMLINK = COPY_SYMLINK_ASFILE;
1187 PreRedraw.Push(PR_ShellCopyMsg);
1188 PreRedrawItem preRedrawItem = PreRedraw.Peek();
1189 preRedrawItem.Param.Param1 = CP;
1190 PreRedraw.SetParam(preRedrawItem.Param);
1191 int I = CopyFileTree(strNameTmp);
1192 PreRedraw.Pop();
1193 Flags.SYMLINK = OldFlagsSYMLINK;
1195 if (I == COPY_CANCEL) {
1196 NeedDizUpdate = TRUE;
1197 break;
1200 // если "есть порох в пороховницах" - восстановим выделение
1201 if (!DestList.IsLastElement(DLI))
1202 SrcPanel->RestoreSelection();
1204 // Позаботимся о дизах.
1205 if (!strDestDizPath.IsEmpty()) {
1206 FARString strDestDizName;
1207 DestDiz.GetDizName(strDestDizName);
1208 DWORD Attr = apiGetFileAttributes(strDestDizName);
1209 int DestReadOnly = (Attr != INVALID_FILE_ATTRIBUTES && (Attr & FILE_ATTRIBUTE_READONLY));
1211 if (DestList.IsLastElement(DLI)) // Скидываем только во время последней Op.
1212 if (Move && !DestReadOnly)
1213 SrcPanel->FlushDiz();
1215 DestDiz.Flush(strDestDizPath);
1219 _LOGCOPYR(else SysLog(L"Error: DestList.Set(CopyDlgValue) return FALSE"));
1222 ***********************************************************************
1223 *** заключительеая стадия процесса
1224 *** восстанавливаем/дизим/редравим
1225 ***********************************************************************
1228 if (NeedDizUpdate) // при мультикопировании может быть обрыв, но нам все
1229 { // равно нужно апдейтить дизы!
1230 if (!strDestDizPath.IsEmpty()) {
1231 FARString strDestDizName;
1232 DestDiz.GetDizName(strDestDizName);
1233 DWORD Attr = apiGetFileAttributes(strDestDizName);
1234 int DestReadOnly = (Attr != INVALID_FILE_ATTRIBUTES && (Attr & FILE_ATTRIBUTE_READONLY));
1236 if (Move && !DestReadOnly)
1237 SrcPanel->FlushDiz();
1239 DestDiz.Flush(strDestDizPath);
1243 SrcPanel->Update(UPDATE_KEEP_SELECTION);
1245 if (CDP.SelCount == 1 && !strRenamedName.IsEmpty())
1246 SrcPanel->GoToFile(strRenamedName);
1248 if (NeedUpdateAPanel && CDP.FileAttr != INVALID_FILE_ATTRIBUTES
1249 && (CDP.FileAttr & FILE_ATTRIBUTE_DIRECTORY) && DestPanelMode != PLUGIN_PANEL) {
1250 FARString strTmpSrcDir;
1251 SrcPanel->GetCurDir(strTmpSrcDir);
1252 DestPanel->SetCurDir(strTmpSrcDir, FALSE);
1255 // проверим "нужность" апдейта пассивной панели
1256 if (Flags.UPDATEPPANEL) {
1257 DestPanel->SortFileList(TRUE);
1258 DestPanel->Update(UPDATE_KEEP_SELECTION | UPDATE_SECONDARY);
1261 if (SrcPanelMode == PLUGIN_PANEL)
1262 SrcPanel->SetPluginModified();
1264 CtrlObject->Cp()->Redraw();
1266 if (Opt.NotifOpt.OnFileOperation) {
1267 DisplayNotification(Msg::FileOperationComplete, strSelName); // looks like strSelName is best choice
1271 LONG_PTR WINAPI CopyDlgProc(HANDLE hDlg, int Msg, int Param1, LONG_PTR Param2)
1273 #define DM_CALLTREE (DM_USER + 1)
1274 #define DM_SWITCHRO (DM_USER + 2)
1275 CopyDlgParam *DlgParam = (CopyDlgParam *)SendDlgMessage(hDlg, DM_GETDLGDATA, 0, 0);
1277 switch (Msg) {
1278 case DN_INITDIALOG:
1279 SendDlgMessage(hDlg, DM_SETCOMBOBOXEVENT, ID_SC_COMBO, CBET_KEY | CBET_MOUSE);
1280 SendDlgMessage(hDlg, DM_SETMOUSEEVENTNOTIFY, TRUE, 0);
1281 break;
1282 case DM_SWITCHRO: {
1283 FarListGetItem LGI = {CM_ASKRO};
1284 SendDlgMessage(hDlg, DM_LISTGETITEM, ID_SC_COMBO, (LONG_PTR)&LGI);
1286 if (LGI.Item.Flags & LIF_CHECKED)
1287 LGI.Item.Flags&= ~LIF_CHECKED;
1288 else
1289 LGI.Item.Flags|= LIF_CHECKED;
1291 SendDlgMessage(hDlg, DM_LISTUPDATE, ID_SC_COMBO, (LONG_PTR)&LGI);
1292 SendDlgMessage(hDlg, DM_REDRAW, 0, 0);
1293 return TRUE;
1295 case DN_BTNCLICK: {
1297 if (Param1 == ID_SC_USEFILTER) // "Use filter"
1299 UseFilter = (int)Param2;
1300 return TRUE;
1303 if (Param1 == ID_SC_BTNTREE) // Tree
1305 SendDlgMessage(hDlg, DM_CALLTREE, 0, 0);
1306 return FALSE;
1307 } else if (Param1 == ID_SC_BTNCOPY) {
1308 SendDlgMessage(hDlg, DM_CLOSE, ID_SC_BTNCOPY, 0);
1309 } else if (Param1 == ID_SC_SPARSEFILES) {
1310 SendDlgMessage(hDlg, DM_SETCHECK, ID_SC_USECOW, BSTATE_UNCHECKED);
1311 } else if (Param1 == ID_SC_USECOW) {
1312 SendDlgMessage(hDlg, DM_SETCHECK, ID_SC_SPARSEFILES, BSTATE_UNCHECKED);
1315 else if(Param1 == ID_SC_ONLYNEWER && (DlgParam->thisClass->Flags.LINK))
1317 // подсократим код путем эмуляции телодвижений в строке ввода :-))
1318 SendDlgMessage(hDlg,DN_EDITCHANGE,ID_SC_TARGETEDIT,0);
1321 else if (Param1 == ID_SC_BTNFILTER) // Filter
1323 Filter->FilterEdit();
1324 return TRUE;
1327 break;
1329 case DM_KEY: // по поводу дерева!
1331 if (Param2 == KEY_ALTF10 || Param2 == KEY_F10 || Param2 == KEY_SHIFTF10) {
1332 DlgParam->AltF10 = Param2 == KEY_ALTF10 ? 1 : (Param2 == KEY_SHIFTF10 ? 2 : 0);
1333 SendDlgMessage(hDlg, DM_CALLTREE, DlgParam->AltF10, 0);
1334 return TRUE;
1337 if (Param1 == ID_SC_COMBO) {
1338 if (Param2 == KEY_ENTER || Param2 == KEY_NUMENTER || Param2 == KEY_INS
1339 || Param2 == KEY_NUMPAD0 || Param2 == KEY_SPACE) {
1340 if (SendDlgMessage(hDlg, DM_LISTGETCURPOS, ID_SC_COMBO, 0) == CM_ASKRO)
1341 return SendDlgMessage(hDlg, DM_SWITCHRO, 0, 0);
1344 } break;
1346 case DN_LISTHOTKEY:
1347 if (Param1 == ID_SC_COMBO) {
1348 if (SendDlgMessage(hDlg, DM_LISTGETCURPOS, ID_SC_COMBO, 0) == CM_ASKRO) {
1349 SendDlgMessage(hDlg, DM_SWITCHRO, 0, 0);
1350 return TRUE;
1353 break;
1354 case DN_MOUSEEVENT:
1356 if (SendDlgMessage(hDlg, DM_GETDROPDOWNOPENED, ID_SC_COMBO, 0)) {
1357 MOUSE_EVENT_RECORD *mer = (MOUSE_EVENT_RECORD *)Param2;
1359 if (SendDlgMessage(hDlg, DM_LISTGETCURPOS, ID_SC_COMBO, 0) == CM_ASKRO && mer->dwButtonState
1360 && !(mer->dwEventFlags & MOUSE_MOVED)) {
1361 SendDlgMessage(hDlg, DM_SWITCHRO, 0, 0);
1362 return FALSE;
1366 break;
1368 case DM_CALLTREE: {
1370 $ 13.10.2001 IS
1371 + При мультикопировании добавляем выбранный в "дереве" каталог к уже
1372 существующему списку через точку с запятой.
1373 - Баг: при мультикопировании выбранный в "дереве" каталог не
1374 заключался в кавычки, если он содержал в своем
1375 имени символы-разделители.
1376 - Баг: неправильно работало Shift-F10, если строка ввода содержала
1377 слеш на конце.
1378 - Баг: неправильно работало Shift-F10 при мультикопировании -
1379 показывался корневой каталог, теперь показывается самый первый каталог
1380 в списке.
1382 BOOL MultiCopy = SendDlgMessage(hDlg, DM_GETCHECK, ID_SC_MULTITARGET, 0) == BSTATE_CHECKED;
1383 FARString strOldFolder;
1384 int nLength;
1385 FarDialogItemData Data;
1386 nLength = (int)SendDlgMessage(hDlg, DM_GETTEXTLENGTH, ID_SC_TARGETEDIT, 0);
1387 Data.PtrData = strOldFolder.GetBuffer(nLength + 1);
1388 Data.PtrLength = nLength;
1389 SendDlgMessage(hDlg, DM_GETTEXT, ID_SC_TARGETEDIT, (LONG_PTR)&Data);
1390 strOldFolder.ReleaseBuffer();
1391 FARString strNewFolder;
1393 if (DlgParam->AltF10 == 2) {
1394 strNewFolder = strOldFolder;
1396 if (MultiCopy) {
1397 UserDefinedList DestList(0, 0, ULF_UNIQUE);
1399 if (DestList.Set(strOldFolder)) {
1400 const wchar_t *NamePtr = DestList.Get(0);
1402 if (NamePtr)
1403 strNewFolder = NamePtr;
1407 if (strNewFolder.IsEmpty())
1408 DlgParam->AltF10 = -1;
1409 else // убираем лишний слеш
1410 DeleteEndSlash(strNewFolder);
1413 if (DlgParam->AltF10 != -1) {
1415 FARString strNewFolder2;
1416 FolderTree::Present(strNewFolder2,
1417 (DlgParam->AltF10 == 1
1418 ? MODALTREE_PASSIVE
1419 : (DlgParam->AltF10 == 2 ? MODALTREE_FREE : MODALTREE_ACTIVE)),
1420 FALSE, FALSE);
1421 strNewFolder = strNewFolder2;
1424 if (!strNewFolder.IsEmpty()) {
1425 AddEndSlash(strNewFolder);
1427 if (MultiCopy) // мультикопирование
1429 // Добавим кавычки, если имя каталога содержит символы-разделители
1430 if (strNewFolder.ContainsAnyOf(";,"))
1431 InsertQuote(strNewFolder);
1433 if (strOldFolder.GetLength())
1434 strOldFolder+= L";"; // добавим разделитель к непустому списку
1436 strOldFolder+= strNewFolder;
1437 strNewFolder = strOldFolder;
1440 SendDlgMessage(hDlg, DM_SETTEXTPTR, ID_SC_TARGETEDIT, (LONG_PTR)strNewFolder.CPtr());
1441 SendDlgMessage(hDlg, DM_SETFOCUS, ID_SC_TARGETEDIT, 0);
1445 DlgParam->AltF10 = 0;
1446 return TRUE;
1448 case DN_CLOSE: {
1449 if (Param1 == ID_SC_BTNCOPY) {
1450 FarListGetItem LGI = {CM_ASKRO};
1451 SendDlgMessage(hDlg, DM_LISTGETITEM, ID_SC_COMBO, (LONG_PTR)&LGI);
1453 if (LGI.Item.Flags & LIF_CHECKED)
1454 DlgParam->AskRO = TRUE;
1456 } break;
1459 return DefDlgProc(hDlg, Msg, Param1, Param2);
1462 ShellCopy::~ShellCopy()
1464 _tran(SysLog(L"[%p] ShellCopy::~ShellCopy(), CopyBuffer=%p", this, CopyBuffer));
1466 // $ 26.05.2001 OT Разрешить перерисовку панелей
1467 _tran(SysLog(L"call (*FrameManager)[0]->UnlockRefresh()"));
1468 (*FrameManager)[0]->Unlock();
1469 (*FrameManager)[0]->Refresh();
1471 if (Filter) // Уничтожим объект фильтра
1472 delete Filter;
1474 if (CP) {
1475 delete CP;
1476 CP = nullptr;
1480 COPY_CODES ShellCopy::CopyFileTree(const wchar_t *Dest)
1482 ChangePriority ChPriority(ChangePriority::NORMAL);
1483 // SaveScreen SaveScr;
1484 DWORD DestAttr = INVALID_FILE_ATTRIBUTES;
1485 FARString strSelName;
1486 int Length;
1487 DWORD FileAttr;
1489 if (!(Length = StrLength(Dest)) || !StrCmp(Dest, L"."))
1490 return COPY_FAILURE; //????
1492 SetCursorType(FALSE, 0);
1494 if (!TotalCopySize) {
1495 strTotalCopySizeText.Clear();
1497 // ! Не сканируем каталоги при создании линков
1498 if (ShowTotalCopySize && !Flags.LINK && !CalcTotalSize())
1499 return COPY_FAILURE;
1500 } else {
1501 CurCopiedSize = 0;
1504 // Создание структуры каталогов в месте назначения
1505 FARString strNewPath = Dest;
1507 if (!IsSlash(strNewPath.At(strNewPath.GetLength() - 1)) && SrcPanel->GetSelCount() > 1
1508 && !strNewPath.ContainsAnyOf("*?")
1509 && apiGetFileAttributes(strNewPath) == INVALID_FILE_ATTRIBUTES) {
1510 switch (Message(FMSG_WARNING, 3, Msg::Warning, strNewPath, Msg::CopyDirectoryOrFile,
1511 Msg::CopyDirectoryOrFileDirectory, Msg::CopyDirectoryOrFileFile, Msg::Cancel)) {
1512 case 0:
1513 AddEndSlash(strNewPath);
1514 break;
1515 case -2:
1516 case -1:
1517 case 2:
1518 return COPY_CANCEL;
1522 size_t pos;
1524 if (FindLastSlash(pos, strNewPath)) {
1525 strNewPath.Truncate(pos + 1);
1527 DWORD Attr = apiGetFileAttributes(strNewPath);
1529 if (Attr == INVALID_FILE_ATTRIBUTES) {
1530 if (apiCreateDirectory(strNewPath, nullptr))
1531 TreeList::AddTreeName(strNewPath);
1532 else
1533 CreatePath(strNewPath);
1534 } else if (!(Attr & FILE_ATTRIBUTE_DIRECTORY)) {
1535 Message(MSG_WARNING, 1, Msg::Error, Msg::CopyCannotCreateFolder, strNewPath, Msg::Ok);
1536 return COPY_FAILURE;
1540 DestAttr = apiGetFileAttributes(Dest);
1542 // Выставим признак "Тот же диск"
1543 bool AllowMoveByOS = false;
1545 if (Flags.MOVE) {
1546 FARString strTmpSrcDir;
1547 SrcPanel->GetCurDir(strTmpSrcDir);
1548 AllowMoveByOS = (CheckDisksProps(strTmpSrcDir, Dest, CHECKEDPROPS_ISSAMEDISK)) != 0;
1551 // Основной цикл копирования одной порции.
1552 SrcPanel->GetSelNameCompat(nullptr, FileAttr);
1553 while (SrcPanel->GetSelNameCompat(&strSelName, FileAttr)) {
1554 SelectedPanelItems.emplace_back();
1555 ConvertNameToFull(strSelName, SelectedPanelItems.back());
1558 SrcPanel->GetSelNameCompat(nullptr, FileAttr);
1560 while (SrcPanel->GetSelNameCompat(&strSelName, FileAttr)) {
1561 FARString strDest = Dest;
1563 if (FileAttr & FILE_ATTRIBUTE_DIRECTORY)
1564 SelectedFolderNameLength = (int)strSelName.GetLength();
1565 else
1566 SelectedFolderNameLength = 0;
1568 if (strDest.ContainsAnyOf("*?"))
1569 ConvertWildcards(strSelName, strDest, SelectedFolderNameLength);
1571 DestAttr = apiGetFileAttributes(strDest);
1573 FARString strDestPath = strDest;
1574 FAR_FIND_DATA_EX SrcData;
1575 SrcData.Clear();
1576 int CopyCode = COPY_SUCCESS, KeepPathPos;
1577 Flags.OVERWRITENEXT = false;
1579 KeepPathPos = (int)(PointToName(strSelName) - strSelName.CPtr());
1581 if (RPT == RP_JUNCTION || RPT == RP_SYMLINK || RPT == RP_SYMLINKFILE || RPT == RP_SYMLINKDIR) {
1582 switch (MkSymLink(strSelName, strDest, RPT, true)) {
1583 case 2:
1584 break;
1585 case 1:
1587 // Отметим (Ins) несколько каталогов, ALT-F6 Enter - выделение с папок не снялось.
1588 if (!Flags.CURRENTONLY && Flags.COPYLASTTIME)
1589 SrcPanel->ClearLastGetSelection();
1591 continue;
1592 case 0:
1593 return COPY_FAILURE;
1595 } else {
1596 // проверка на вшивость ;-)
1598 if (!apiGetFindDataForExactPathName(strSelName, SrcData)) {
1599 strDestPath = strSelName;
1600 CP->SetNames(strSelName, strDestPath);
1602 if (Message(MSG_WARNING, 2, Msg::Error, Msg::CopyCannotFind, strSelName, Msg::Skip,
1603 Msg::Cancel)
1604 == 1) {
1605 return COPY_FAILURE;
1608 continue;
1610 /// fprintf(stderr, "!!!!! RPT=%x for '%ls'\n", (SrcData.dwFileAttributes&FILE_ATTRIBUTE_REPARSE_POINT), strSelName.CPtr());
1613 // KeepPathPos=PointToName(SelName)-SelName;
1615 // Мувим?
1616 if (Flags.MOVE) {
1617 // Тыкс, а как на счет "тот же диск"?
1618 if (KeepPathPos && PointToName(strDest) == strDest) {
1619 strDestPath = strSelName;
1620 strDestPath.Truncate(KeepPathPos);
1621 strDestPath+= strDest;
1622 AllowMoveByOS = true;
1625 if (UseFilter || !AllowMoveByOS || // can't move across different devices
1626 // if any symlinks copy may occur - parse whole tree
1627 ((SrcData.dwFileAttributes
1628 & (FILE_ATTRIBUTE_REPARSE_POINT | FILE_ATTRIBUTE_DIRECTORY))
1629 != 0
1630 && Flags.SYMLINK != COPY_SYMLINK_ASIS)) {
1631 CopyCode = COPY_FAILURE;
1632 } else {
1633 CopyCode = ShellCopyOneFile(strSelName, SrcData, strDestPath, KeepPathPos, 1);
1635 if (CopyCode == COPY_SUCCESS_MOVE) {
1636 if (!strDestDizPath.IsEmpty()) {
1637 if (!strRenamedName.IsEmpty()) {
1638 DestDiz.DeleteDiz(strSelName);
1639 SrcPanel->CopyDiz(strSelName, strRenamedName, &DestDiz);
1640 } else {
1641 if (strCopiedName.IsEmpty())
1642 strCopiedName = strSelName;
1644 SrcPanel->CopyDiz(strSelName, strCopiedName, &DestDiz);
1645 SrcPanel->DeleteDiz(strSelName);
1649 continue;
1652 if (CopyCode == COPY_CANCEL)
1653 return COPY_CANCEL;
1655 if (CopyCode == COPY_NEXT) {
1656 uint64_t CurSize = SrcData.nFileSize;
1657 TotalCopiedSize = TotalCopiedSize - CurCopiedSize + CurSize;
1658 TotalSkippedSize = TotalSkippedSize + CurSize - CurCopiedSize;
1659 continue;
1662 if (!Flags.MOVE || CopyCode == COPY_FAILURE)
1663 Flags.OVERWRITENEXT = true;
1667 if (!Flags.MOVE || CopyCode == COPY_FAILURE) {
1668 FARString strCopyDest = strDest;
1670 CopyCode = ShellCopyOneFile(strSelName, SrcData, strCopyDest, KeepPathPos, 0);
1672 Flags.OVERWRITENEXT = false;
1674 if (CopyCode == COPY_CANCEL)
1675 return COPY_CANCEL;
1677 if (CopyCode != COPY_SUCCESS) {
1678 uint64_t CurSize = SrcData.nFileSize;
1680 if (CopyCode != COPY_NOFILTER) //????
1681 TotalCopiedSize = TotalCopiedSize - CurCopiedSize + CurSize;
1683 if (CopyCode == COPY_NEXT)
1684 TotalSkippedSize = TotalSkippedSize + CurSize - CurCopiedSize;
1686 continue;
1690 if (CopyCode == COPY_SUCCESS && !strDestDizPath.IsEmpty()) {
1691 if (strCopiedName.IsEmpty())
1692 strCopiedName = strSelName;
1694 SrcPanel->CopyDiz(strSelName, strCopiedName, &DestDiz);
1698 Mantis#44 - Потеря данных при копировании ссылок на папки
1699 если каталог (или нужно копировать симлинк) - придется рекурсивно спускаться...
1701 if (RPT != RP_SYMLINKFILE && (SrcData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0
1702 && ((SrcData.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) == 0
1703 || ((SrcData.dwFileAttributes
1704 & (FILE_ATTRIBUTE_REPARSE_POINT | FILE_ATTRIBUTE_BROKEN))
1705 == FILE_ATTRIBUTE_REPARSE_POINT
1706 && Flags.SYMLINK == COPY_SYMLINK_ASFILE))) {
1707 int SubCopyCode;
1708 FARString strSubName;
1709 FARString strFullName;
1710 ScanTree ScTree(TRUE, TRUE, Flags.SYMLINK == COPY_SYMLINK_ASFILE);
1711 strSubName = strSelName;
1712 strSubName+= WGOOD_SLASH;
1714 if (DestAttr == INVALID_FILE_ATTRIBUTES)
1715 KeepPathPos = (int)strSubName.GetLength();
1717 int NeedRename = !((SrcData.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
1718 && Flags.SYMLINK == COPY_SYMLINK_ASFILE && Flags.MOVE);
1719 ScTree.SetFindPath(strSubName, L"*", FSCANTREE_FILESFIRST);
1720 while (ScTree.GetNextName(&SrcData, strFullName)) {
1721 if (UseFilter && (SrcData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
1723 Просто пропустить каталог недостаточно - если каталог помечен в
1724 фильтре как некопируемый, то следует пропускать и его и всё его
1725 содержимое.
1727 if (!Filter->FileInFilter(SrcData)) {
1728 ScTree.SkipDir();
1729 continue;
1733 int AttemptToMove = FALSE;
1735 if (Flags.MOVE && !UseFilter && AllowMoveByOS
1736 && (SrcData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0
1737 && ((SrcData.dwFileAttributes
1738 & (FILE_ATTRIBUTE_REPARSE_POINT | FILE_ATTRIBUTE_BROKEN))
1739 == 0
1740 || Flags.SYMLINK == COPY_SYMLINK_ASIS)) {
1741 AttemptToMove = TRUE;
1742 int Ret = COPY_SUCCESS;
1743 FARString strCopyDest = strDest;
1745 Ret = ShellCopyOneFile(strFullName, SrcData, strCopyDest, KeepPathPos,
1746 NeedRename);
1748 switch (Ret) // 1
1750 case COPY_CANCEL:
1751 return COPY_CANCEL;
1752 case COPY_NEXT: {
1753 uint64_t CurSize = SrcData.nFileSize;
1754 TotalCopiedSize = TotalCopiedSize - CurCopiedSize + CurSize;
1755 TotalSkippedSize = TotalSkippedSize + CurSize - CurCopiedSize;
1756 continue;
1758 case COPY_SUCCESS_MOVE: {
1759 continue;
1761 case COPY_SUCCESS:
1763 if (!NeedRename) // вариант при перемещении содержимого симлинка с опцией "копировать содержимое сим..."
1765 uint64_t CurSize = SrcData.nFileSize;
1766 TotalCopiedSize = TotalCopiedSize - CurCopiedSize + CurSize;
1767 TotalSkippedSize = TotalSkippedSize + CurSize - CurCopiedSize;
1768 continue; // ... т.к. мы ЭТО не мувили, а скопировали, то все, на этом закончим бодаться с этим файлов
1773 int SaveOvrMode = OvrMode;
1775 if (AttemptToMove)
1776 OvrMode = 1;
1778 FARString strCopyDest = strDest;
1780 SubCopyCode = ShellCopyOneFile(strFullName, SrcData, strCopyDest, KeepPathPos, 0);
1782 if (AttemptToMove)
1783 OvrMode = SaveOvrMode;
1786 if (SubCopyCode == COPY_CANCEL)
1787 return COPY_CANCEL;
1789 if (SubCopyCode == COPY_NEXT) {
1790 uint64_t CurSize = SrcData.nFileSize;
1791 TotalCopiedSize = TotalCopiedSize - CurCopiedSize + CurSize;
1792 TotalSkippedSize = TotalSkippedSize + CurSize - CurCopiedSize;
1795 if (SubCopyCode == COPY_SUCCESS) {
1796 if (Flags.MOVE) {
1797 if (SrcData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
1798 if (ScTree.IsDirSearchDone()
1799 || ((SrcData.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
1800 && (Flags.SYMLINK != COPY_SYMLINK_ASFILE))) {
1801 TemporaryMakeWritable tmw(strFullName);
1802 // if (SrcData.dwFileAttributes & FILE_ATTRIBUTE_READONLY)
1803 // apiMakeWritable(strFullName); //apiSetFileAttributes(strFullName,FILE_ATTRIBUTE_NORMAL);
1805 if (apiRemoveDirectory(strFullName))
1806 TreeList::DelTreeName(strFullName);
1810 здесь нужны проверка на FSCANTREE_INSIDEJUNCTION, иначе
1811 при мовинге будет удаление файла, что крайне неправильно!
1813 else if (!ScTree.IsInsideSymlink()) {
1814 if (DeleteAfterMove(strFullName, SrcData.dwFileAttributes) == COPY_CANCEL)
1815 return COPY_CANCEL;
1821 if (Flags.MOVE && CopyCode == COPY_SUCCESS) {
1822 TemporaryMakeWritable tmw(strSelName);
1823 // if (FileAttr & FILE_ATTRIBUTE_READONLY)
1824 // apiMakeWritable(strSelName); //apiSetFileAttributes(strSelName,FILE_ATTRIBUTE_NORMAL);
1826 if (apiRemoveDirectory(strSelName)) {
1827 TreeList::DelTreeName(strSelName);
1829 if (!strDestDizPath.IsEmpty())
1830 SrcPanel->DeleteDiz(strSelName);
1833 } else if (Flags.MOVE && CopyCode == COPY_SUCCESS) {
1834 int DeleteCode;
1836 if ((DeleteCode = DeleteAfterMove(strSelName, FileAttr)) == COPY_CANCEL)
1837 return COPY_CANCEL;
1839 if (DeleteCode == COPY_SUCCESS && !strDestDizPath.IsEmpty())
1840 SrcPanel->DeleteDiz(strSelName);
1843 if (!Flags.CURRENTONLY && Flags.COPYLASTTIME) {
1844 SrcPanel->ClearLastGetSelection();
1849 SetEnqueuedDirectoriesAttributes();
1851 return COPY_SUCCESS; // COPY_SUCCESS_MOVE???
1854 void ShellCopy::EnqueueDirectoryAttributes(const FAR_FIND_DATA_EX &SrcData, FARString &strDest)
1856 DirectoriesAttributes.emplace_back();
1857 auto &cdb = DirectoriesAttributes.back();
1858 cdb.Path = strDest.GetMB();
1859 cdb.ftUnixAccessTime = SrcData.ftUnixAccessTime;
1860 cdb.ftUnixModificationTime = SrcData.ftUnixModificationTime;
1861 cdb.dwUnixMode = SrcData.dwUnixMode;
1864 void ShellCopy::SetEnqueuedDirectoriesAttributes()
1866 std::sort(DirectoriesAttributes.begin(), DirectoriesAttributes.end(),
1867 [&](const CopiedDirectory &a, const CopiedDirectory &b) -> bool {
1868 return b.Path < a.Path;
1870 for (const auto &cd : DirectoriesAttributes) {
1871 // fprintf(stderr, "!!! '%s'\n", cd.Path.c_str());
1872 struct timespec ts[2] = {};
1873 WINPORT(FileTime_Win32ToUnix)(&cd.ftUnixAccessTime, &ts[0]);
1874 WINPORT(FileTime_Win32ToUnix)(&cd.ftUnixModificationTime, &ts[1]);
1875 if (sdc_utimens(cd.Path.c_str(), ts) == -1) {
1876 fprintf(stderr, "sdc_utimens error %d for '%s'\n", errno, cd.Path.c_str());
1878 if (Flags.COPYACCESSMODE) {
1879 if (sdc_chmod(cd.Path.c_str(), cd.dwUnixMode) == -1) {
1880 fprintf(stderr, "sdc_chmod mode=0%o error %d for '%s'\n", cd.dwUnixMode, errno,
1881 cd.Path.c_str());
1885 DirectoriesAttributes.clear();
1888 bool ShellCopy::IsSymlinkTargetAlsoCopied(const wchar_t *SymLink)
1890 if (!SymLink || !*SymLink)
1891 return false;
1893 FARString strTarget;
1894 ConvertNameToReal(SymLink, strTarget);
1895 for (const auto &strItem : SelectedPanelItems) {
1896 if (strTarget == strItem) {
1897 // fprintf(stderr, "%s('%ls'): TRUE, '%ls' matches '%ls'\n", __FUNCTION__, SymLink, strTarget.CPtr(), strItem.CPtr());
1898 return true;
1900 FARString strItemReal;
1901 ConvertNameToReal(strItem, strItemReal);
1903 if (strTarget.GetLength() > strItemReal.GetLength()
1904 && strTarget[strItemReal.GetLength()] == GOOD_SLASH && strTarget.Begins(strItemReal)) {
1905 // fprintf(stderr, "%s('%ls'): TRUE, '%ls' under '%ls'\n", __FUNCTION__, SymLink, strTarget.CPtr(), strItemReal.CPtr());
1906 return true;
1910 // fprintf(stderr, "IsSymlinkTargetAlsoCopied('%ls'): FALSE, '%ls'\n", SymLink, strTarget.CPtr());
1911 return false;
1914 COPY_CODES
1915 ShellCopy::CreateSymLink(const char *Target, const wchar_t *NewName, const FAR_FIND_DATA_EX &SrcData)
1917 if (apiIsDevNull(NewName))
1918 return COPY_SUCCESS;
1920 int r = sdc_symlink(Target, Wide2MB(NewName).c_str());
1921 if (r == 0)
1922 return COPY_SUCCESS;
1924 if (errno == EEXIST) {
1925 int RetCode = 0, Append = 0;
1926 FARString strNewName = NewName, strTarget = Target;
1927 if (AskOverwrite(SrcData, strTarget, NewName, 0, 0, 0, 0, Append, strNewName, RetCode)) {
1928 if (strNewName == NewName) {
1929 fprintf(stderr, "CreateSymLink('%s', '%ls') - overwriting and strNewName='%ls'\n", Target,
1930 NewName, strNewName.CPtr());
1931 sdc_remove(strNewName.GetMB().c_str());
1933 } else {
1934 fprintf(stderr, "CreateSymLink('%s', '%ls') - renaming and strNewName='%ls'\n", Target,
1935 NewName, strNewName.CPtr());
1937 return CreateSymLink(Target, strNewName.CPtr(), SrcData);
1940 return (COPY_CODES)RetCode;
1943 switch (Message(MSG_WARNING, 3, Msg::Error, Msg::CopyCannotCreateSymlinkAskCopyContents, NewName,
1944 Msg::Yes, Msg::Skip, Msg::Cancel)) {
1945 case 0:
1946 Flags.SYMLINK = COPY_SYMLINK_ASFILE;
1947 return COPY_RETRY;
1949 case 1:
1950 return COPY_FAILURE;
1952 case 2:
1953 default:
1954 return COPY_CANCEL;
1958 static bool InSameDirectory(const wchar_t *ExistingName, const wchar_t *NewName) // #1334
1960 FARString strExistingDir, strNewDir;
1961 ConvertNameToFull(ExistingName, strExistingDir);
1962 ConvertNameToFull(NewName, strNewDir);
1963 CutToSlash(strExistingDir);
1964 CutToSlash(strNewDir);
1965 return strExistingDir == strNewDir;
1968 COPY_CODES
1969 ShellCopy::CopySymLink(const wchar_t *ExistingName, const wchar_t *NewName, const FAR_FIND_DATA_EX &SrcData)
1971 FARString strExistingName;
1972 ConvertNameToFull(ExistingName, strExistingName);
1974 // fprintf(stderr, "CopySymLink('%ls', '%ls', '%ls') '%ls'\n", Root, ExistingName, NewName, strRealName.CPtr());
1975 const std::string &mbExistingName = strExistingName.GetMB();
1976 char LinkTarget[PATH_MAX + 1];
1977 ssize_t r = sdc_readlink(mbExistingName.c_str(), LinkTarget, sizeof(LinkTarget) - 1);
1978 if (r <= 0 || r >= (ssize_t)sizeof(LinkTarget) || LinkTarget[0] == 0) {
1979 fprintf(stderr, "CopySymLink: r=%ld errno=%u from sdc_readlink('%ls')\n", (long)r, errno,
1980 strExistingName.CPtr());
1981 return COPY_FAILURE;
1984 LinkTarget[r] = 0;
1987 create exactly same symlink as existing one in following cases:
1988 - if settings specifies to not be smart
1989 - if existing symlink is relative
1990 - if existing symlink points to unexisting destination that is also out or set of files being copied
1991 note that in case of being smart and if symlink is relative then caller
1992 guarantees that its target is within copied tree, so link will be valid
1994 if (Flags.SYMLINK != COPY_SYMLINK_SMART
1995 || (LinkTarget[0] != GOOD_SLASH && !InSameDirectory(ExistingName, NewName))
1996 || ((SrcData.dwFileAttributes & FILE_ATTRIBUTE_BROKEN) != 0
1997 && !IsSymlinkTargetAlsoCopied(ExistingName))) {
1998 FARString strNewName;
1999 ConvertNameToFull(NewName, strNewName);
2000 return CreateSymLink(LinkTarget, strNewName.CPtr(), SrcData);
2004 this is a case of smart linking - create symlink that relatively points to _new_ target
2005 by applying to new link relative path from existing symlink to its existing target
2007 FARString strRealName;
2008 ConvertNameToReal(ExistingName, strRealName);
2009 std::vector<std::string> partsRealName, partsExistingName;
2010 StrExplode(partsRealName, strRealName.GetMB(), "/");
2011 StrExplode(partsExistingName, strExistingName.GetMB(), "/");
2013 size_t common_anchestors_count = 0;
2014 while (common_anchestors_count < partsRealName.size()
2015 && common_anchestors_count < partsExistingName.size()
2016 && partsRealName[common_anchestors_count] == partsExistingName[common_anchestors_count]) {
2017 ++common_anchestors_count;
2020 std::string relative_target;
2021 for (size_t i = common_anchestors_count; i + 1 < partsExistingName.size(); ++i) {
2022 relative_target+= "../";
2024 for (size_t i = common_anchestors_count; i < partsRealName.size(); ++i) {
2025 relative_target+= partsRealName[i];
2026 if (i + 1 < partsRealName.size()) {
2027 relative_target+= GOOD_SLASH;
2030 if (relative_target.empty()) {
2031 fprintf(stderr, "CopySymLink: empty relative_target strRealName='%ls' strExistingName='%ls'\n",
2032 strRealName.CPtr(), strExistingName.CPtr());
2035 return CreateSymLink(relative_target.c_str(), NewName, SrcData);
2038 COPY_CODES ShellCopy::ShellCopyOneFile(const wchar_t *Src, const FAR_FIND_DATA_EX &SrcData,
2039 FARString &strDest, int KeepPathPos, int Rename)
2041 for (;;) {
2042 COPY_CODES out = ShellCopyOneFileNoRetry(Src, SrcData, strDest, KeepPathPos, Rename);
2043 if (out != COPY_RETRY)
2044 return out;
2048 COPY_CODES ShellCopy::ShellCopyOneFileNoRetry(const wchar_t *Src, const FAR_FIND_DATA_EX &SrcData,
2049 FARString &strDest, int KeepPathPos, int Rename)
2051 CurCopiedSize = 0; // Сбросить текущий прогресс
2053 if (CP->Cancelled()) {
2054 return (COPY_CANCEL);
2057 if (UseFilter) {
2058 if (!Filter->FileInFilter(SrcData))
2059 return COPY_NOFILTER;
2062 FARString strDestPath = strDest;
2063 const wchar_t *NamePtr = PointToName(strDestPath);
2064 DWORD DestAttr = (strDestPath == WGOOD_SLASH || !*NamePtr || TestParentFolderName(NamePtr))
2065 ? FILE_ATTRIBUTE_DIRECTORY
2066 : INVALID_FILE_ATTRIBUTES;
2068 FAR_FIND_DATA_EX DestData;
2069 DestData.Clear();
2070 if (DestAttr == INVALID_FILE_ATTRIBUTES) {
2071 if (apiGetFindDataForExactPathName(strDestPath, DestData))
2072 DestAttr = DestData.dwFileAttributes;
2075 int SameName = 0, Append = 0;
2077 if (DestAttr != INVALID_FILE_ATTRIBUTES && (DestAttr & FILE_ATTRIBUTE_DIRECTORY)) {
2078 int CmpCode = CmpFullNames(Src, strDestPath);
2080 if (CmpCode && SrcData.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT && RPT == RP_EXACTCOPY
2081 && Flags.SYMLINK != COPY_SYMLINK_ASFILE) {
2082 CmpCode = 0;
2085 if (CmpCode == 1) // TODO: error check
2087 SameName = 1;
2089 if (Rename) {
2090 CmpCode = !StrCmp(PointToName(Src), PointToName(strDestPath));
2093 if (CmpCode == 1) {
2094 SetMessageHelp(L"ErrCopyItSelf");
2095 Message(MSG_WARNING, 1, Msg::Error, Msg::CannotCopyFolderToItself1, Src,
2096 Msg::CannotCopyFolderToItself2, Msg::Ok);
2097 return (COPY_CANCEL);
2101 if (!SameName) {
2102 int Length = (int)strDestPath.GetLength();
2104 if (!IsSlash(strDestPath.At(Length - 1)) && strDestPath.At(Length - 1) != L':')
2105 strDestPath+= WGOOD_SLASH;
2107 const wchar_t *PathPtr = Src + KeepPathPos;
2109 if (*PathPtr && !KeepPathPos && PathPtr[1] == L':')
2110 PathPtr+= 2;
2112 if (IsSlash(*PathPtr))
2113 PathPtr++;
2115 strDestPath+= PathPtr;
2117 if (!apiGetFindDataForExactPathName(strDestPath, DestData))
2118 DestAttr = INVALID_FILE_ATTRIBUTES;
2119 else
2120 DestAttr = DestData.dwFileAttributes;
2124 SetDestDizPath(strDestPath);
2126 CP->SetProgressValue(0, 0);
2127 CP->SetNames(Src, strDestPath);
2129 const bool copy_sym_link = (RPT == RP_EXACTCOPY
2130 && (SrcData.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0
2131 && ((SrcData.dwFileAttributes & FILE_ATTRIBUTE_BROKEN) != 0
2132 || (Flags.SYMLINK != COPY_SYMLINK_ASFILE
2133 && (Flags.SYMLINK != COPY_SYMLINK_SMART || IsSymlinkTargetAlsoCopied(Src)
2134 || InSameDirectory(Src, strDestPath)))));
2136 if ((SrcData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0 || copy_sym_link) {
2137 if (!Rename)
2138 strCopiedName = PointToName(strDestPath);
2140 if (DestAttr != INVALID_FILE_ATTRIBUTES) {
2141 if ((DestAttr & FILE_ATTRIBUTE_DIRECTORY) && !SameName) {
2142 FARString strSrcFullName;
2143 ConvertNameToFull(Src, strSrcFullName);
2144 return (!StrCmp(strDestPath, strSrcFullName) ? COPY_NEXT : COPY_SUCCESS);
2147 int Type = apiGetFileTypeByName(strDestPath);
2149 if (Type == FILE_TYPE_CHAR || Type == FILE_TYPE_PIPE)
2150 return (Rename ? COPY_NEXT : COPY_SUCCESS);
2153 if ((SrcData.dwFileAttributes & (FILE_ATTRIBUTE_REPARSE_POINT | FILE_ATTRIBUTE_DIRECTORY))
2154 == FILE_ATTRIBUTE_DIRECTORY) {
2156 Enqueue attributes before creating directory, so even if will fail (like directory exists)
2157 but ignored then still will still try apply them on whole copy process finish successfully
2159 EnqueueDirectoryAttributes(SrcData, strDestPath);
2162 if (Rename) {
2163 FARString strSrcFullName, strDestFullName;
2164 ConvertNameToFull(Src, strSrcFullName);
2166 // Пытаемся переименовать, пока не отменят
2167 for (;;) {
2168 BOOL SuccessMove = apiMoveFile(Src, strDestPath);
2170 if (SuccessMove) {
2171 if (PointToName(strDestPath) == strDestPath.CPtr())
2172 strRenamedName = strDestPath;
2173 else
2174 strCopiedName = PointToName(strDestPath);
2176 ConvertNameToFull(strDest, strDestFullName);
2177 TreeList::RenTreeName(strSrcFullName, strDestFullName);
2178 return (SameName ? COPY_NEXT : COPY_SUCCESS_MOVE);
2179 } else {
2180 int MsgCode = Message(Flags.ErrorMessageFlags, 3, Msg::Error,
2181 Msg::CopyCannotRenameFolder, Src, Msg::CopyRetry, Msg::CopyIgnore,
2182 Msg::CopyCancel);
2184 switch (MsgCode) {
2185 case 0:
2186 continue;
2187 case 1: {
2188 if (apiCreateDirectory(strDestPath, nullptr)) {
2189 if (PointToName(strDestPath) == strDestPath.CPtr())
2190 strRenamedName = strDestPath;
2191 else
2192 strCopiedName = PointToName(strDestPath);
2194 TreeList::AddTreeName(strDestPath);
2195 return (COPY_SUCCESS);
2198 default:
2199 return (COPY_CANCEL);
2200 } /* switch */
2201 } /* else */
2202 } /* while */
2203 } // if (Rename)
2204 if (RPT != RP_SYMLINKFILE
2205 && (SrcData.dwFileAttributes & (FILE_ATTRIBUTE_REPARSE_POINT | FILE_ATTRIBUTE_DIRECTORY))
2206 == FILE_ATTRIBUTE_DIRECTORY) {
2207 while (!apiCreateDirectory(strDestPath, nullptr)) {
2208 int MsgCode = Message(Flags.ErrorMessageFlags, 3, Msg::Error, Msg::CopyCannotCreateFolder,
2209 strDestPath, Msg::CopyRetry, Msg::CopySkip, Msg::CopyCancel);
2211 if (MsgCode)
2212 return ((MsgCode == -2 || MsgCode == 2) ? COPY_CANCEL : COPY_NEXT);
2215 // [ ] Copy contents of symbolic links
2216 if (copy_sym_link) {
2217 COPY_CODES CopyRetCode = CopySymLink(Src, strDestPath, SrcData);
2218 if (CopyRetCode != COPY_SUCCESS && CopyRetCode != COPY_SUCCESS_MOVE)
2219 return CopyRetCode;
2221 if (SrcData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
2222 TreeList::AddTreeName(strDestPath);
2223 return COPY_SUCCESS;
2226 if (DestAttr != INVALID_FILE_ATTRIBUTES && !(DestAttr & FILE_ATTRIBUTE_DIRECTORY)) {
2227 if (SrcData.nFileSize == DestData.nFileSize) {
2228 int CmpCode = CmpFullNames(Src, strDestPath);
2230 if (CmpCode && SrcData.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT && RPT == RP_EXACTCOPY
2231 && Flags.SYMLINK != COPY_SYMLINK_ASFILE) {
2232 CmpCode = 0;
2235 if (CmpCode == 1) // TODO: error check
2237 SameName = 1;
2239 if (Rename) {
2240 CmpCode = !StrCmp(PointToName(Src), PointToName(strDestPath));
2243 if (CmpCode == 1 && !Rename) {
2244 Message(MSG_WARNING, 1, Msg::Error, Msg::CannotCopyFileToItself1, Src,
2245 Msg::CannotCopyFileToItself2, Msg::Ok);
2246 return (COPY_CANCEL);
2250 int RetCode = 0;
2251 FARString strNewName;
2253 if (!AskOverwrite(SrcData, Src, strDestPath, DestAttr, SameName, Rename, (Flags.LINK ? 0 : 1), Append,
2254 strNewName, RetCode)) {
2255 return ((COPY_CODES)RetCode);
2258 if (RetCode == COPY_RETRY) {
2259 strDest = strNewName;
2261 if (CutToSlash(strNewName) && apiGetFileAttributes(strNewName) == INVALID_FILE_ATTRIBUTES) {
2262 CreatePath(strNewName);
2265 return COPY_RETRY;
2269 for (;;) {
2270 int CopyCode = 0;
2271 uint64_t SaveTotalSize = TotalCopiedSize;
2273 if (Rename) {
2274 int MoveCode = FALSE, AskDelete;
2276 if (!Append) {
2277 FARString strSrcFullName;
2278 ConvertNameToFull(Src, strSrcFullName);
2280 MoveCode = apiMoveFileEx(strSrcFullName, strDestPath,
2281 SameName ? MOVEFILE_COPY_ALLOWED : MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING);
2283 if (!MoveCode && WINPORT(GetLastError)() == ERROR_NOT_SAME_DEVICE) {
2284 return COPY_FAILURE;
2287 if (ShowTotalCopySize && MoveCode) {
2288 TotalCopiedSize+= SrcData.nFileSize;
2289 CP->SetTotalProgressValue(TotalCopiedSize, TotalCopySize);
2292 AskDelete = 0;
2293 } else {
2294 do {
2295 CopyCode = ShellCopyFile(Src, SrcData, strDestPath, Append);
2296 } while (CopyCode == COPY_RETRY);
2298 switch (CopyCode) {
2299 case COPY_SUCCESS:
2300 MoveCode = TRUE;
2301 break;
2302 case COPY_FAILUREREAD:
2303 case COPY_FAILURE:
2304 MoveCode = FALSE;
2305 break;
2306 case COPY_CANCEL:
2307 return COPY_CANCEL;
2308 case COPY_NEXT:
2309 return COPY_NEXT;
2312 AskDelete = 1;
2315 if (MoveCode) {
2316 if (DestAttr == INVALID_FILE_ATTRIBUTES || !(DestAttr & FILE_ATTRIBUTE_DIRECTORY)) {
2317 if (PointToName(strDestPath) == strDestPath.CPtr())
2318 strRenamedName = strDestPath;
2319 else
2320 strCopiedName = PointToName(strDestPath);
2323 TotalFiles++;
2325 if (AskDelete && DeleteAfterMove(Src, SrcData.dwFileAttributes) == COPY_CANCEL)
2326 return COPY_CANCEL;
2328 return (COPY_SUCCESS_MOVE);
2330 } else {
2331 do {
2332 CopyCode = ShellCopyFile(Src, SrcData, strDestPath, Append);
2333 } while (CopyCode == COPY_RETRY);
2335 if (Append && DestData.dwUnixMode != 0
2336 && (CopyCode != COPY_SUCCESS || DestData.dwUnixMode != SrcData.dwUnixMode)) {
2337 const std::string &mbDestPath = strDestPath.GetMB();
2338 sdc_chmod(mbDestPath.c_str(), DestData.dwUnixMode);
2340 if (CopyCode == COPY_SUCCESS) {
2341 strCopiedName = PointToName(strDestPath);
2342 TotalFiles++;
2343 return COPY_SUCCESS;
2344 } else if (CopyCode == COPY_CANCEL || CopyCode == COPY_NEXT) {
2345 return ((COPY_CODES)CopyCode);
2349 //????
2350 if (CopyCode == COPY_FAILUREREAD)
2351 return COPY_FAILURE;
2353 //????
2354 FARString strMsg1, strMsg2;
2355 FarLangMsg MsgMCannot = Flags.LINK ? Msg::CannotLink : Flags.MOVE ? Msg::CannotMove : Msg::CannotCopy;
2356 strMsg1 = Src;
2357 strMsg2 = strDestPath;
2358 InsertQuote(strMsg1);
2359 InsertQuote(strMsg2);
2361 int MsgCode;
2363 if (SkipMode != -1)
2364 MsgCode = SkipMode;
2365 else {
2366 MsgCode = Message(Flags.ErrorMessageFlags, 4, Msg::Error, MsgMCannot, strMsg1,
2367 Msg::CannotCopyTo, strMsg2, Msg::CopyRetry, Msg::CopySkip, Msg::CopySkipAll,
2368 Msg::CopyCancel);
2371 switch (MsgCode) {
2372 case -1:
2373 case 1:
2374 return COPY_NEXT;
2375 case 2:
2376 SkipMode = 1;
2377 return COPY_NEXT;
2378 case -2:
2379 case 3:
2380 return COPY_CANCEL;
2383 TotalCopiedSize = SaveTotalSize;
2384 int RetCode;
2385 FARString strNewName;
2387 if (!AskOverwrite(SrcData, Src, strDestPath, DestAttr, SameName, Rename, (Flags.LINK ? 0 : 1), Append,
2388 strNewName, RetCode))
2389 return ((COPY_CODES)RetCode);
2391 if (RetCode == COPY_RETRY) {
2392 strDest = strNewName;
2394 if (CutToSlash(strNewName) && apiGetFileAttributes(strNewName) == INVALID_FILE_ATTRIBUTES) {
2395 CreatePath(strNewName);
2398 return COPY_RETRY;
2403 int ShellCopy::DeleteAfterMove(const wchar_t *Name, DWORD Attr)
2405 if (Attr & FILE_ATTRIBUTE_READONLY) {
2406 int MsgCode;
2408 if (!Opt.Confirm.RO)
2409 ReadOnlyDelMode = 1;
2411 if (ReadOnlyDelMode != -1)
2412 MsgCode = ReadOnlyDelMode;
2413 else
2414 MsgCode = Message(MSG_WARNING, 5, Msg::Warning, Msg::CopyFileRO, Name, Msg::CopyAskDelete,
2415 Msg::CopyDeleteRO, Msg::CopyDeleteAllRO, Msg::CopySkipRO, Msg::CopySkipAllRO,
2416 Msg::CopyCancelRO);
2418 switch (MsgCode) {
2419 case 1:
2420 ReadOnlyDelMode = 1;
2421 break;
2422 case 2:
2423 return (COPY_NEXT);
2424 case 3:
2425 ReadOnlyDelMode = 3;
2426 return (COPY_NEXT);
2427 case -1:
2428 case -2:
2429 case 4:
2430 return (COPY_CANCEL);
2434 TemporaryMakeWritable tmw(Name);
2436 while ((Attr & FILE_ATTRIBUTE_DIRECTORY) ? !apiRemoveDirectory(Name) : !apiDeleteFile(Name)) {
2437 int MsgCode;
2439 if (SkipDeleteMode != -1)
2440 MsgCode = SkipDeleteMode;
2441 else
2442 MsgCode = Message(Flags.ErrorMessageFlags, 4, Msg::Error, Msg::CannotDeleteFile, Name,
2443 Msg::DeleteRetry, Msg::DeleteSkip, Msg::DeleteSkipAll, Msg::DeleteCancel);
2445 switch (MsgCode) {
2446 case 1:
2447 return COPY_NEXT;
2448 case 2:
2449 SkipDeleteMode = 1;
2450 return COPY_NEXT;
2451 case -1:
2452 case -2:
2453 case 3:
2454 return (COPY_CANCEL);
2458 return (COPY_SUCCESS);
2461 static void ProgressUpdate(bool force, const FAR_FIND_DATA_EX &SrcData, const wchar_t *DestName)
2463 if (force || GetProcessUptimeMSec() - ProgressUpdateTime >= PROGRESS_REFRESH_THRESHOLD) {
2464 CP->SetProgressValue(CurCopiedSize, SrcData.nFileSize);
2466 if (ShowTotalCopySize) {
2467 CP->SetTotalProgressValue(TotalCopiedSize, TotalCopySize);
2470 CP->SetNames(SrcData.strFileName, DestName);
2472 ProgressUpdateTime = GetProcessUptimeMSec();
2476 /////////////////////////////////////////////////////////// BEGIN OF ShellFileTransfer
2478 ShellFileTransfer::ShellFileTransfer(const wchar_t *SrcName, const FAR_FIND_DATA_EX &SrcData,
2479 const FARString &strDestName, bool Append, ShellCopyBuffer &CopyBuffer, COPY_FLAGS &Flags)
2481 _SrcName(SrcName), _strDestName(strDestName), _CopyBuffer(CopyBuffer), _Flags(Flags), _SrcData(SrcData)
2483 if (!_SrcFile.Open(SrcName, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,
2484 OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN))
2485 throw ErrnoSaver();
2487 if (_Flags.COPYXATTR) {
2488 _XAttrCopyPtr.reset(new ShellCopyFileExtendedAttributes(_SrcFile));
2491 if (_Flags.COPYACCESSMODE) { // force S_IWUSR for a while file being copied, it will be removed afterwards if not needed
2492 _ModeToCreateWith = _SrcData.dwUnixMode | S_IWUSR;
2495 _DstFlags = FILE_FLAG_SEQUENTIAL_SCAN;
2496 if (Flags.WRITETHROUGH) {
2497 _DstFlags|= FILE_FLAG_WRITE_THROUGH;
2499 #ifdef __linux__ // anyway OSX doesn't have O_DIRECT
2500 if (SrcData.nFileSize > 32 * USE_PAGE_SIZE) // just empiric
2501 _DstFlags|= FILE_FLAG_NO_BUFFERING;
2502 #endif
2505 bool DstOpened = _DestFile.Open(_strDestName, GENERIC_WRITE, FILE_SHARE_READ,
2506 _Flags.COPYACCESSMODE ? &_ModeToCreateWith : nullptr, (Append ? OPEN_EXISTING : CREATE_ALWAYS),
2507 _DstFlags);
2509 if ((_DstFlags & (FILE_FLAG_WRITE_THROUGH | FILE_FLAG_NO_BUFFERING)) != 0) {
2510 if (!DstOpened) {
2511 _DstFlags&= ~(FILE_FLAG_WRITE_THROUGH | FILE_FLAG_NO_BUFFERING);
2512 DstOpened = _DestFile.Open(_strDestName, GENERIC_WRITE, FILE_SHARE_READ,
2513 _Flags.COPYACCESSMODE ? &_ModeToCreateWith : nullptr,
2514 (Append ? OPEN_EXISTING : CREATE_ALWAYS), _DstFlags);
2515 if (DstOpened) {
2516 Flags.WRITETHROUGH = false;
2517 fprintf(stderr, "COPY: unbuffered FAILED: 0x%x\n",
2518 _DstFlags & (FILE_FLAG_WRITE_THROUGH | FILE_FLAG_NO_BUFFERING));
2520 } /* else
2521 fprintf(stderr, "COPY: unbuffered OK: 0x%x\n", DstFlags & (FILE_FLAG_WRITE_THROUGH|FILE_FLAG_NO_BUFFERING));*/
2524 if (!DstOpened) {
2525 ErrnoSaver ErSr;
2526 _SrcFile.Close();
2527 _LOGCOPYR(SysLog(L"return COPY_FAILURE -> %d CreateFile=-1, LastError=%d (0x%08X)", __LINE__,
2528 _localLastError, _localLastError));
2529 throw ErSr;
2532 if (Append) {
2533 _AppendPos = 0;
2534 if (!_DestFile.SetPointer(0, &_AppendPos, FILE_END)) {
2535 ErrnoSaver ErSr;
2536 _SrcFile.Close();
2537 _DestFile.SetEnd();
2538 _DestFile.Close();
2539 throw ErSr;
2541 } else if (SrcData.nFileSize > (uint64_t)_CopyBuffer.Size && !_Flags.SPARSEFILES && !_Flags.USECOW) {
2542 _DestFile.AllocationHint(SrcData.nFileSize);
2546 ShellFileTransfer::~ShellFileTransfer()
2548 if (!_Done)
2549 try {
2550 fprintf(stderr, "~ShellFileTransfer: discarding '%ls'\n", _strDestName.CPtr());
2551 _SrcFile.Close();
2552 CP->SetProgressValue(0, 0);
2553 CurCopiedSize = 0; // Сбросить текущий прогресс
2555 if (_AppendPos != -1) {
2556 _DestFile.SetPointer(_AppendPos, nullptr, FILE_BEGIN);
2559 _DestFile.SetEnd();
2560 _DestFile.Close();
2562 if (_AppendPos == -1) {
2563 TemporaryMakeWritable tmw(_strDestName);
2564 apiDeleteFile(_strDestName);
2567 ProgressUpdate(true, _SrcData, _strDestName);
2568 } catch (std::exception &ex) {
2569 fprintf(stderr, "~ShellFileTransfer: %s\n", ex.what());
2570 } catch (...) {
2571 fprintf(stderr, "~ShellFileTransfer: ...\n");
2575 void ShellFileTransfer::Do()
2577 CP->SetProgressValue(0, 0);
2579 for (;;) {
2580 ProgressUpdate(false, _SrcData, _strDestName);
2582 if (OrigScrX != ScrX || OrigScrY != ScrY) {
2583 OrigScrX = ScrX;
2584 OrigScrY = ScrY;
2585 PR_ShellCopyMsg();
2588 if (CP->Cancelled())
2589 return;
2591 _Stopwatch = (_SrcData.nFileSize - CurCopiedSize > (uint64_t)_CopyBuffer.Size)
2592 ? GetProcessUptimeMSec()
2593 : 0;
2595 DWORD BytesWritten = PieceCopy();
2596 if (BytesWritten == 0)
2597 break;
2599 CurCopiedSize+= BytesWritten;
2601 if (_Stopwatch != 0 && BytesWritten == _CopyBuffer.Size) {
2602 _Stopwatch = GetProcessUptimeMSec() - _Stopwatch;
2603 if (_Stopwatch < 100) {
2604 if (_CopyBuffer.Size < _CopyBuffer.Capacity) {
2605 _CopyBuffer.Size = std::min(_CopyBuffer.Size * 2, _CopyBuffer.Capacity);
2606 fprintf(stderr, "CopyPieceSize increased to %d\n", _CopyBuffer.Size);
2608 } else if (_Stopwatch >= 1000 && _CopyBuffer.Size > (int)COPY_PIECE_MINIMAL) {
2609 _CopyBuffer.Size = std::max(_CopyBuffer.Size / 2, (DWORD)COPY_PIECE_MINIMAL);
2610 fprintf(stderr, "CopyPieceSize decreased to %d\n", _CopyBuffer.Size);
2614 if (ShowTotalCopySize)
2615 TotalCopiedSize+= BytesWritten;
2618 _SrcFile.Close();
2620 if (!apiIsDevNull(_strDestName)) // avoid sudo prompt when copying to /dev/null
2622 if (_LastWriteWasHole) {
2623 while (!_DestFile.SetEnd()) {
2624 RetryCancel(Msg::CopyWriteError, _strDestName);
2628 if (_XAttrCopyPtr)
2629 _XAttrCopyPtr->ApplyToCopied(_DestFile);
2631 if (_Flags.COPYACCESSMODE
2632 && (_ModeToCreateWith != _SrcData.dwUnixMode || (g_umask & _SrcData.dwUnixMode) != 0))
2633 _DestFile.Chmod(_SrcData.dwUnixMode);
2635 _DestFile.SetTime(nullptr, nullptr, &_SrcData.ftLastWriteTime, nullptr);
2638 if (!_DestFile.Close()) {
2640 #1387
2641 if file located on old samba share then in out of space condition
2642 write()-s succeed but close() reports error
2644 throw ErrnoSaver();
2647 _Done = true;
2649 ProgressUpdate(false, _SrcData, _strDestName);
2652 void ShellFileTransfer::RetryCancel(const wchar_t *Text, const wchar_t *Object)
2654 ErrnoSaver ErSr;
2655 _Stopwatch = 0; // UI messes timings
2656 const int MsgCode =
2657 Message(_Flags.ErrorMessageFlags, 2, Msg::Error, Text, Object, Msg::Retry, Msg::Cancel);
2659 PR_ShellCopyMsg();
2661 if (MsgCode != 0)
2662 throw ErSr;
2665 // returns std:::pair<OffsetOfNextHole, SizeOfNextHole> (SizeOfNextHole==0 means no holes found)
2666 static std::pair<DWORD, DWORD> LookupNextHole(const unsigned char *Data, DWORD Size, uint64_t Offset)
2668 const DWORD Alignment = 0x1000; // must be power of 2
2669 const DWORD OffsetMisalignment = DWORD(Offset) & (Alignment - 1);
2671 DWORD i = 0;
2672 if (OffsetMisalignment) {
2673 i+= Alignment - OffsetMisalignment;
2676 for (; i < Size; i+= Alignment) {
2677 DWORD ZeroesCount = 0;
2678 while (i + ZeroesCount < Size && Data[i + ZeroesCount] == 0) {
2679 ++ZeroesCount;
2681 if (ZeroesCount >= Alignment) {
2682 return std::make_pair(i, (ZeroesCount / Alignment) * Alignment);
2686 return std::make_pair(Size, (DWORD)0);
2689 DWORD ShellFileTransfer::PieceCopy()
2691 #if defined(COW_SUPPORTED) && defined(__linux__)
2692 if (_Flags.USECOW)
2693 for (;;) {
2694 ssize_t sz = copy_file_range(_SrcFile.Descriptor(), nullptr, _DestFile.Descriptor(), nullptr,
2695 _CopyBuffer.Size, 0);
2697 if (sz >= 0)
2698 return (DWORD)sz;
2700 if (errno == EXDEV) {
2701 fprintf(stderr, "copy_file_range returned EXDEV, fallback to usual copy\n");
2702 break;
2705 RetryCancel(Msg::CopyWriteError, _strDestName);
2707 #endif
2709 DWORD BytesRead, BytesWritten;
2711 while (!_SrcFile.Read(_CopyBuffer.Ptr, _CopyBuffer.Size, &BytesRead)) {
2712 RetryCancel(Msg::CopyReadError, _SrcName);
2715 if (BytesRead == 0)
2716 return BytesRead;
2718 DWORD WriteSize = BytesRead;
2719 if ((_DstFlags & FILE_FLAG_NO_BUFFERING) != 0)
2720 WriteSize = AlignPageUp(WriteSize);
2722 BytesWritten = 0;
2723 if (_Flags.SPARSEFILES) {
2724 while (BytesWritten < WriteSize) {
2725 const unsigned char *Data = (const unsigned char *)_CopyBuffer.Ptr + BytesWritten;
2726 const std::pair<DWORD, DWORD> &NH =
2727 LookupNextHole(Data, WriteSize - BytesWritten, CurCopiedSize + BytesWritten);
2728 DWORD LeadingNonzeroesWritten = NH.first ? PieceWrite(Data, NH.first) : 0;
2729 BytesWritten+= LeadingNonzeroesWritten;
2730 if (NH.second && LeadingNonzeroesWritten == NH.first) {
2731 // fprintf(stderr, "!!! HOLE of size %x\n", SR.second);
2732 BytesWritten+= PieceWriteHole(NH.second);
2735 } else
2736 BytesWritten = PieceWrite(_CopyBuffer.Ptr, WriteSize);
2738 if (BytesWritten > BytesRead) {
2740 likely we written bit more due to no_buffering requires aligned io
2741 move backward and correct file size
2743 if (!_DestFile.SetPointer((INT64)BytesRead - (INT64)WriteSize, nullptr, FILE_CURRENT))
2744 throw ErrnoSaver();
2745 if (!_DestFile.SetEnd())
2746 throw ErrnoSaver();
2747 return BytesRead;
2750 if (BytesWritten < BytesRead) { // if written less than read then need to rewind source file by difference
2751 if (!_SrcFile.SetPointer((INT64)BytesWritten - (INT64)BytesRead, nullptr, FILE_CURRENT))
2752 throw ErrnoSaver();
2755 return BytesWritten;
2758 DWORD ShellFileTransfer::PieceWriteHole(DWORD Size)
2760 while (!_DestFile.SetPointer(Size, nullptr, FILE_CURRENT)) {
2761 RetryCancel(Msg::CopyWriteError, _strDestName);
2763 _LastWriteWasHole = true;
2764 return Size;
2767 DWORD ShellFileTransfer::PieceWrite(const void *Data, DWORD Size)
2769 DWORD BytesWritten = 0;
2770 while (!_DestFile.Write(Data, Size, &BytesWritten)) {
2771 RetryCancel(Msg::CopyWriteError, _strDestName);
2773 _LastWriteWasHole = false;
2774 return BytesWritten;
2777 /////////////////////////////////////////////////////////// END OF ShellFileTransfer
2779 static dev_t GetRDev(FARString SrcName)
2781 struct stat st{};
2782 if (sdc_stat(SrcName.GetMB().c_str(), &st) == 0) {
2783 return st.st_rdev;
2785 return 0;
2788 int ShellCopy::ShellCopyFile(const wchar_t *SrcName, const FAR_FIND_DATA_EX &SrcData, FARString &strDestName,
2789 int Append)
2791 OrigScrX = ScrX;
2792 OrigScrY = ScrY;
2794 if (Flags.LINK) {
2795 if (RPT == RP_HARDLINK) {
2796 apiDeleteFile(strDestName); // BUGBUG
2797 return (MkHardLink(SrcName, strDestName) ? COPY_SUCCESS : COPY_FAILURE);
2798 } else {
2799 return (MkSymLink(SrcName, strDestName, RPT, true) ? COPY_SUCCESS : COPY_FAILURE);
2802 if (SrcData.dwFileAttributes & (FILE_ATTRIBUTE_DEVICE_FIFO | FILE_ATTRIBUTE_DEVICE_BLOCK | FILE_ATTRIBUTE_DEVICE_CHAR)) {
2803 int r = (SrcData.dwFileAttributes & FILE_ATTRIBUTE_DEVICE_FIFO)
2804 ? sdc_mkfifo(strDestName.GetMB().c_str(), SrcData.dwUnixMode)
2805 : sdc_mknod(strDestName.GetMB().c_str(), SrcData.dwUnixMode, GetRDev(SrcName));
2806 if (r == -1) {
2807 _localLastError = errno;
2808 return COPY_FAILURE;
2810 return COPY_SUCCESS;
2813 try {
2814 #if defined(COW_SUPPORTED) && defined(__APPLE__)
2815 if (Flags.USECOW) {
2816 const std::string mbSrc = Wide2MB(SrcName);
2817 const std::string &mbDest = strDestName.GetMB();
2818 int r = clonefile(mbSrc.c_str(), mbDest.c_str(), 0);
2819 if (r == 0) {
2820 // fprintf(stderr, "CoW succeeded for '%s' -> '%s'\n", mbSrc.c_str(), mbDest.c_str());
2821 CurCopiedSize = SrcData.nFileSize;
2822 if (ShowTotalCopySize)
2823 TotalCopiedSize+= SrcData.nFileSize;
2825 ProgressUpdate(false, SrcData, strDestName);
2826 return CP->Cancelled() ? COPY_CANCEL : COPY_SUCCESS;
2829 ErrnoSaver ErSr;
2830 if (ErSr.Get() != EXDEV && ErSr.Get() != ENOTSUP)
2831 throw ErSr;
2833 fprintf(stderr, "Skip CoW errno=%d for '%s' -> '%s'\n", ErSr.Get(), mbSrc.c_str(),
2834 mbDest.c_str());
2836 #endif
2838 ShellFileTransfer(SrcName, SrcData, strDestName, Append != 0, CopyBuffer, Flags).Do();
2839 return CP->Cancelled() ? COPY_CANCEL : COPY_SUCCESS;
2840 } catch (ErrnoSaver &ErSr) {
2841 _localLastError = ErSr.Get();
2844 return CP->Cancelled() ? COPY_CANCEL : COPY_FAILURE;
2847 void ShellCopy::SetDestDizPath(const wchar_t *DestPath)
2849 if (!Flags.DIZREAD) {
2850 ConvertNameToFull(DestPath, strDestDizPath);
2851 CutToSlash(strDestDizPath);
2853 if (strDestDizPath.IsEmpty())
2854 strDestDizPath = L".";
2856 if ((Opt.Diz.UpdateMode == DIZ_UPDATE_IF_DISPLAYED && !SrcPanel->IsDizDisplayed())
2857 || Opt.Diz.UpdateMode == DIZ_NOT_UPDATE)
2858 strDestDizPath.Clear();
2860 if (!strDestDizPath.IsEmpty())
2861 DestDiz.Read(strDestDizPath);
2863 Flags.DIZREAD = true;
2867 enum WarnDlgItems
2869 WDLG_BORDER,
2870 WDLG_TEXT,
2871 WDLG_FILENAME,
2872 WDLG_SEPARATOR,
2873 WDLG_SRCFILEBTN,
2874 WDLG_DSTFILEBTN,
2875 WDLG_SEPARATOR2,
2876 WDLG_CHECKBOX,
2877 WDLG_SEPARATOR3,
2878 WDLG_OVERWRITE,
2879 WDLG_SKIP,
2880 WDLG_RENAME,
2881 WDLG_APPEND,
2882 WDLG_CANCEL,
2885 #define DM_OPENVIEWER DM_USER + 33
2887 LONG_PTR WINAPI WarnDlgProc(HANDLE hDlg, int Msg, int Param1, LONG_PTR Param2)
2889 switch (Msg) {
2890 case DM_OPENVIEWER: {
2891 LPCWSTR ViewName = nullptr;
2892 FARString **WFN = reinterpret_cast<FARString **>(SendDlgMessage(hDlg, DM_GETDLGDATA, 0, 0));
2894 if (WFN) {
2895 switch (Param1) {
2896 case WDLG_SRCFILEBTN:
2897 ViewName = *WFN[0];
2898 break;
2899 case WDLG_DSTFILEBTN:
2900 ViewName = *WFN[1];
2901 break;
2904 FileViewer Viewer(
2905 // а этот трюк не даст пользователю сменить текущий каталог по CtrlF10 и этим ввести в заблуждение копир: TODODODO
2906 std::make_shared<FileHolder>(ViewName, true),
2907 FALSE, FALSE, TRUE, -1, nullptr, nullptr, FALSE);
2908 Viewer.SetDynamicallyBorn(FALSE);
2909 FrameManager->ExecuteModalEV();
2910 FrameManager->ProcessKey(KEY_CONSOLE_BUFFER_RESIZE);
2912 } break;
2913 case DN_CTLCOLORDLGITEM: {
2914 if (Param1 == WDLG_FILENAME) {
2915 int Color = FarColorToReal(COL_WARNDIALOGTEXT) & 0xFF;
2916 return ((Param2 & 0xFF00FF00) | (Color << 16) | Color);
2918 } break;
2919 case DN_BTNCLICK: {
2920 switch (Param1) {
2921 case WDLG_SRCFILEBTN:
2922 case WDLG_DSTFILEBTN:
2923 SendDlgMessage(hDlg, DM_OPENVIEWER, Param1, 0);
2924 break;
2925 case WDLG_RENAME: {
2926 FARString **WFN =
2927 reinterpret_cast<FARString **>(SendDlgMessage(hDlg, DM_GETDLGDATA, 0, 0));
2928 FARString strDestName = *WFN[1];
2929 GenerateName(strDestName, *WFN[2]);
2931 if (SendDlgMessage(hDlg, DM_GETCHECK, WDLG_CHECKBOX, 0) == BSTATE_UNCHECKED) {
2932 int All = BSTATE_UNCHECKED;
2934 if (GetString(Msg::CopyRenameTitle, Msg::CopyRenameText, nullptr, strDestName,
2935 *WFN[1], L"CopyAskOverwrite",
2936 FIB_BUTTONS | FIB_NOAMPERSAND | FIB_EXPANDENV | FIB_CHECKBOX, &All,
2937 Msg::CopyRememberChoice)) {
2938 if (All != BSTATE_UNCHECKED) {
2939 *WFN[2] = *WFN[1];
2940 CutToSlash(*WFN[2]);
2943 SendDlgMessage(hDlg, DM_SETCHECK, WDLG_CHECKBOX, All);
2944 } else {
2945 return TRUE;
2947 } else {
2948 *WFN[1] = strDestName;
2950 } break;
2952 } break;
2953 case DN_KEY: {
2954 if ((Param1 == WDLG_SRCFILEBTN || Param1 == WDLG_DSTFILEBTN) && Param2 == KEY_F3) {
2955 SendDlgMessage(hDlg, DM_OPENVIEWER, Param1, 0);
2957 } break;
2960 return DefDlgProc(hDlg, Msg, Param1, Param2);
2963 int ShellCopy::AskOverwrite(const FAR_FIND_DATA_EX &SrcData, const wchar_t *SrcName, const wchar_t *DestName,
2964 DWORD DestAttr, int SameName, int Rename, int AskAppend, int &Append, FARString &strNewName,
2965 int &RetCode)
2967 enum
2969 WARN_DLG_HEIGHT = 13,
2970 WARN_DLG_WIDTH = 72,
2972 DialogDataEx WarnCopyDlgData[] = {
2973 {DI_DOUBLEBOX, 3, 1, WARN_DLG_WIDTH - 4, WARN_DLG_HEIGHT - 2, {}, 0, Msg::Warning},
2974 {DI_TEXT, 5, 2, WARN_DLG_WIDTH - 6, 2, {}, DIF_CENTERTEXT, Msg::CopyFileExist},
2975 {DI_EDIT, 5, 3, WARN_DLG_WIDTH - 6, 3, {}, DIF_READONLY, (wchar_t *)DestName},
2976 {DI_TEXT, 3, 4, 0, 4, {}, DIF_SEPARATOR, L""},
2977 {DI_BUTTON, 5, 5, WARN_DLG_WIDTH - 6, 5, {}, DIF_BTNNOCLOSE | DIF_NOBRACKETS, L""},
2978 {DI_BUTTON, 5, 6, WARN_DLG_WIDTH - 6, 6, {}, DIF_BTNNOCLOSE | DIF_NOBRACKETS, L""},
2979 {DI_TEXT, 3, 7, 0, 7, {}, DIF_SEPARATOR, L""},
2980 {DI_CHECKBOX, 5, 8, 0, 8, {}, DIF_FOCUS, Msg::CopyRememberChoice},
2981 {DI_TEXT, 3, 9, 0, 9, {}, DIF_SEPARATOR, L""},
2982 {DI_BUTTON, 0, 10, 0, 10, {}, DIF_DEFAULT | DIF_CENTERGROUP, Msg::CopyOverwrite},
2983 {DI_BUTTON, 0, 10, 0, 10, {}, DIF_CENTERGROUP, Msg::CopySkipOvr},
2984 {DI_BUTTON, 0, 10, 0, 10, {}, DIF_CENTERGROUP, Msg::CopyRename},
2985 {DI_BUTTON, 0, 10, 0, 10, {}, DIF_CENTERGROUP | (AskAppend ? 0 : (DIF_DISABLE | DIF_HIDDEN)), Msg::CopyAppend},
2986 {DI_BUTTON, 0, 10, 0, 10, {}, DIF_CENTERGROUP,Msg::CopyCancelOvr}
2988 FAR_FIND_DATA_EX DestData;
2989 DestData.Clear();
2990 int DestDataFilled = FALSE;
2991 Append = FALSE;
2993 if (DestAttr == INVALID_FILE_ATTRIBUTES)
2994 if ((DestAttr = apiGetFileAttributes(DestName)) == INVALID_FILE_ATTRIBUTES)
2995 return TRUE;
2997 if (DestAttr & FILE_ATTRIBUTE_DIRECTORY)
2998 return TRUE;
3000 int MsgCode = OvrMode;
3001 FARString strDestName = DestName;
3003 if (OvrMode == -1) {
3004 int Type;
3006 if ((!Opt.Confirm.Copy && !Rename) || (!Opt.Confirm.Move && Rename) || SameName
3007 || (Type = apiGetFileTypeByName(DestName)) == FILE_TYPE_CHAR || Type == FILE_TYPE_PIPE
3008 || Flags.OVERWRITENEXT)
3009 MsgCode = 1;
3010 else {
3011 DestData.Clear();
3012 apiGetFindDataForExactPathName(DestName, DestData);
3013 DestDataFilled = TRUE;
3015 if (Flags.ONLYNEWERFILES) {
3016 // сравним время
3017 int64_t RetCompare = FileTimeDifference(&DestData.ftLastWriteTime, &SrcData.ftLastWriteTime);
3019 if (RetCompare < 0)
3020 MsgCode = 0;
3021 else
3022 MsgCode = 2;
3023 } else {
3024 FormatString strSrcFileStr, strDestFileStr;
3025 uint64_t SrcSize = SrcData.nFileSize;
3026 FILETIME SrcLastWriteTime = SrcData.ftLastWriteTime;
3027 if (Flags.SYMLINK == COPY_SYMLINK_ASFILE
3028 && (SrcData.dwFileAttributes & (FILE_ATTRIBUTE_REPARSE_POINT | FILE_ATTRIBUTE_BROKEN))
3029 == FILE_ATTRIBUTE_REPARSE_POINT) {
3030 FARString RealName = SrcName;
3031 FAR_FIND_DATA_EX FindData;
3032 apiGetFindDataForExactPathName(RealName, FindData);
3033 SrcSize = FindData.nFileSize;
3034 SrcLastWriteTime = FindData.ftLastWriteTime;
3036 FormatString strSrcSizeText;
3037 strSrcSizeText << SrcSize;
3038 uint64_t DestSize = DestData.nFileSize;
3039 FormatString strDestSizeText;
3040 strDestSizeText << DestSize;
3041 FARString strDateText, strTimeText;
3042 ConvertDate(SrcLastWriteTime, strDateText, strTimeText, 8, FALSE, FALSE, TRUE, TRUE);
3043 strSrcFileStr
3044 << fmt::Cells() << fmt::LeftAlign() << fmt::Expand(17) << Msg::CopySource << L" "
3045 << fmt::Size(25) << strSrcSizeText << L" " << strDateText << L" " << strTimeText;
3046 ConvertDate(DestData.ftLastWriteTime, strDateText, strTimeText, 8, FALSE, FALSE, TRUE, TRUE);
3047 strDestFileStr << fmt::Cells() << fmt::LeftAlign() << fmt::Expand(17) << Msg::CopyDest << L" "
3048 << fmt::Size(25) << strDestSizeText << L" " << strDateText << L" " << strTimeText;
3050 WarnCopyDlgData[WDLG_SRCFILEBTN].Data = strSrcFileStr;
3051 WarnCopyDlgData[WDLG_DSTFILEBTN].Data = strDestFileStr;
3052 MakeDialogItemsEx(WarnCopyDlgData, WarnCopyDlg);
3053 FARString strFullSrcName;
3054 ConvertNameToFull(SrcName, strFullSrcName);
3055 FARString *WFN[] = {&strFullSrcName, &strDestName, &strRenamedFilesPath};
3056 Dialog WarnDlg(WarnCopyDlg, ARRAYSIZE(WarnCopyDlg), WarnDlgProc, (LONG_PTR)&WFN);
3057 WarnDlg.SetDialogMode(DMODE_WARNINGSTYLE);
3058 WarnDlg.SetPosition(-1, -1, WARN_DLG_WIDTH, WARN_DLG_HEIGHT);
3059 WarnDlg.SetHelp(L"CopyAskOverwrite");
3060 WarnDlg.SetId(CopyOverwriteId);
3061 WarnDlg.Process();
3063 switch (WarnDlg.GetExitCode()) {
3064 case WDLG_OVERWRITE:
3065 MsgCode = WarnCopyDlg[WDLG_CHECKBOX].Selected ? 1 : 0;
3066 break;
3067 case WDLG_SKIP:
3068 MsgCode = WarnCopyDlg[WDLG_CHECKBOX].Selected ? 3 : 2;
3069 break;
3070 case WDLG_RENAME:
3071 MsgCode = WarnCopyDlg[WDLG_CHECKBOX].Selected ? 5 : 4;
3072 break;
3073 case WDLG_APPEND:
3074 MsgCode = WarnCopyDlg[WDLG_CHECKBOX].Selected ? 7 : 6;
3075 break;
3076 case -1:
3077 case -2:
3078 case WDLG_CANCEL:
3079 MsgCode = 8;
3080 break;
3086 switch (MsgCode) {
3087 case 1:
3088 OvrMode = 1;
3089 case 0:
3090 break;
3091 case 3:
3092 OvrMode = 2;
3093 case 2:
3094 RetCode = COPY_NEXT;
3095 return FALSE;
3096 case 5:
3097 OvrMode = 5;
3098 GenerateName(strDestName, strRenamedFilesPath);
3099 case 4:
3100 RetCode = COPY_RETRY;
3101 strNewName = strDestName;
3102 break;
3103 case 7:
3104 OvrMode = 6;
3105 case 6:
3106 Append = TRUE;
3107 break;
3108 case -1:
3109 case -2:
3110 case 8:
3111 RetCode = COPY_CANCEL;
3112 return FALSE;
3115 if (RetCode != COPY_RETRY) {
3116 if ((DestAttr & FILE_ATTRIBUTE_READONLY) && !Flags.OVERWRITENEXT) {
3117 int MsgCode = 0;
3119 if (!SameName) {
3120 if (ReadOnlyOvrMode != -1) {
3121 MsgCode = ReadOnlyOvrMode;
3122 } else {
3123 if (!DestDataFilled) {
3124 DestData.Clear();
3125 apiGetFindDataForExactPathName(DestName, DestData);
3128 FARString strDateText, strTimeText;
3129 FormatString strSrcFileStr, strDestFileStr;
3130 uint64_t SrcSize = SrcData.nFileSize;
3131 FormatString strSrcSizeText;
3132 strSrcSizeText << SrcSize;
3133 uint64_t DestSize = DestData.nFileSize;
3134 FormatString strDestSizeText;
3135 strDestSizeText << DestSize;
3136 ConvertDate(SrcData.ftLastWriteTime, strDateText, strTimeText, 8, FALSE, FALSE, TRUE,
3137 TRUE);
3138 strSrcFileStr
3139 << fmt::Cells() << fmt::LeftAlign() << fmt::Expand(17) << Msg::CopySource << L" "
3140 << fmt::Size(25) << strSrcSizeText << L" " << strDateText << L" " << strTimeText;
3141 ConvertDate(DestData.ftLastWriteTime, strDateText, strTimeText, 8, FALSE, FALSE, TRUE,
3142 TRUE);
3143 strDestFileStr
3144 << fmt::Cells() << fmt::LeftAlign() << fmt::Expand(17) << Msg::CopyDest << L" "
3145 << fmt::Size(25) << strDestSizeText << L" " << strDateText << L" " << strTimeText;
3146 WarnCopyDlgData[WDLG_SRCFILEBTN].Data = strSrcFileStr;
3147 WarnCopyDlgData[WDLG_DSTFILEBTN].Data = strDestFileStr;
3148 WarnCopyDlgData[WDLG_TEXT].Data = Msg::CopyFileRO;
3149 WarnCopyDlgData[WDLG_OVERWRITE].Data = (Append ? Msg::CopyAppend : Msg::CopyOverwrite);
3150 WarnCopyDlgData[WDLG_RENAME].Type = DI_TEXT;
3151 WarnCopyDlgData[WDLG_RENAME].Data = L"";
3152 WarnCopyDlgData[WDLG_APPEND].Type = DI_TEXT;
3153 WarnCopyDlgData[WDLG_APPEND].Data = L"";
3154 MakeDialogItemsEx(WarnCopyDlgData, WarnCopyDlg);
3155 FARString strSrcName;
3156 ConvertNameToFull(SrcData.strFileName, strSrcName);
3157 LPCWSTR WFN[2] = {strSrcName, DestName};
3158 Dialog WarnDlg(WarnCopyDlg, ARRAYSIZE(WarnCopyDlg), WarnDlgProc, (LONG_PTR)&WFN);
3159 WarnDlg.SetDialogMode(DMODE_WARNINGSTYLE);
3160 WarnDlg.SetPosition(-1, -1, WARN_DLG_WIDTH, WARN_DLG_HEIGHT);
3161 WarnDlg.SetHelp(L"CopyFiles");
3162 WarnDlg.SetId(CopyReadOnlyId);
3163 WarnDlg.Process();
3165 switch (WarnDlg.GetExitCode()) {
3166 case WDLG_OVERWRITE:
3167 MsgCode = WarnCopyDlg[WDLG_CHECKBOX].Selected ? 1 : 0;
3168 break;
3169 case WDLG_SKIP:
3170 MsgCode = WarnCopyDlg[WDLG_CHECKBOX].Selected ? 3 : 2;
3171 break;
3172 case -1:
3173 case -2:
3174 case WDLG_CANCEL:
3175 MsgCode = 8;
3176 break;
3181 switch (MsgCode) {
3182 case 1:
3183 ReadOnlyOvrMode = 1;
3184 case 0:
3185 break;
3186 case 3:
3187 ReadOnlyOvrMode = 2;
3188 case 2:
3189 RetCode = COPY_NEXT;
3190 return FALSE;
3191 case -1:
3192 case -2:
3193 case 8:
3194 RetCode = COPY_CANCEL;
3195 return FALSE;
3199 if (!SameName
3200 && (DestAttr & (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM)))
3201 apiMakeWritable(DestName);
3204 return TRUE;
3207 BOOL ShellCopySecuryMsg(const wchar_t *Name)
3209 static clock_t PrepareSecuryStartTime;
3211 if (!Name || !*Name
3212 || (static_cast<DWORD>(GetProcessUptimeMSec() - PrepareSecuryStartTime)
3213 > Opt.ShowTimeoutDACLFiles)) {
3214 static int Width = 30;
3215 int WidthTemp;
3216 if (Name && *Name) {
3217 PrepareSecuryStartTime = GetProcessUptimeMSec(); // Первый файл рисуется всегда
3218 WidthTemp = Max(StrLength(Name), 30);
3219 } else
3220 Width = WidthTemp = 30;
3222 // ширина месага - 38%
3223 WidthTemp = Min(WidthTemp, WidthNameForMessage);
3224 Width = Max(Width, WidthTemp);
3226 FARString strOutFileName = Name; //??? nullptr ???
3227 TruncPathStr(strOutFileName, Width);
3228 CenterStr(strOutFileName, strOutFileName, Width + 4);
3229 Message(0, 0, Msg::MoveDlgTitle, Msg::CopyPrepareSecury, strOutFileName);
3231 if (CP->Cancelled()) {
3232 return FALSE;
3236 PreRedrawItem preRedrawItem = PreRedraw.Peek();
3237 preRedrawItem.Param.Param1 = Name;
3238 PreRedraw.SetParam(preRedrawItem.Param);
3239 return TRUE;
3242 bool ShellCopy::CalcTotalSize()
3244 FARString strSelName;
3245 DWORD FileAttr;
3246 uint64_t FileSize;
3247 // Для фильтра
3248 FAR_FIND_DATA_EX fd;
3249 PreRedraw.Push(PR_ShellCopyMsg);
3250 PreRedrawItem preRedrawItem = PreRedraw.Peek();
3251 preRedrawItem.Param.Param1 = CP;
3252 PreRedraw.SetParam(preRedrawItem.Param);
3253 TotalCopySize = CurCopiedSize = 0;
3254 TotalFilesToProcess = 0;
3255 SrcPanel->GetSelNameCompat(nullptr, FileAttr);
3257 while (SrcPanel->GetSelNameCompat(&strSelName, FileAttr, &fd)) {
3258 if ((FileAttr & FILE_ATTRIBUTE_REPARSE_POINT) && Flags.SYMLINK != COPY_SYMLINK_ASFILE)
3259 continue;
3261 if (FileAttr & FILE_ATTRIBUTE_DIRECTORY) {
3263 uint32_t DirCount, FileCount, ClusterSize;
3264 uint64_t PhysicalSize;
3265 CP->SetScanName(strSelName);
3266 int __Ret = GetDirInfo(L"", strSelName, DirCount, FileCount, FileSize, PhysicalSize,
3267 ClusterSize, -1, Filter,
3268 ((Flags.SYMLINK == COPY_SYMLINK_ASFILE) ? GETDIRINFO_SCANSYMLINK : 0)
3269 | (UseFilter ? GETDIRINFO_USEFILTER : 0));
3271 if (__Ret <= 0) {
3272 ShowTotalCopySize = false;
3273 PreRedraw.Pop();
3274 return FALSE;
3277 if (FileCount > 0) {
3278 TotalCopySize+= FileSize;
3279 TotalFilesToProcess+= FileCount;
3282 } else {
3283 // Подсчитаем количество файлов
3284 if (UseFilter) {
3285 if (!Filter->FileInFilter(fd))
3286 continue;
3289 FileSize = SrcPanel->GetLastSelectedSize();
3291 if (FileSize != (uint64_t)-1) {
3292 TotalCopySize+= FileSize;
3293 TotalFilesToProcess++;
3298 // INFO: Это для варианта, когда "ВСЕГО = общий размер * количество целей"
3299 TotalCopySize = TotalCopySize * CountTarget;
3300 InsertCommas(TotalCopySize, strTotalCopySizeText);
3301 PreRedraw.Pop();
3302 return true;