1 /////////////////////////////////////////////////////////////////////////////
2 // see Merge.cpp for license (GPLv2+) statement
4 /////////////////////////////////////////////////////////////////////////////
8 * @brief Implementation of methods of CDirView that copy/move/delete files
10 // ID line follows -- this is updated by SVN
11 // $Id: DirActions.cpp 6572 2009-03-18 18:51:20Z kimmov $
13 // It would be nice to make this independent of the UI (CDirView)
14 // but it needs access to the list of selected items.
15 // One idea would be to provide an iterator over them.
18 #include "DirActions.h"
20 #include "UnicodeString.h"
22 #include "ShellFileOperations.h"
24 #include "FileActionScript.h"
26 #include "FileFilterHelper.h"
27 #include "coretools.h"
28 #include "OptionsMgr.h"
30 static void ThrowConfirmCopy(const CDiffContext& ctxt, int origin, int destination, int count,
31 const String& src, const String& dest, bool destIsSide);
32 static void ThrowConfirmMove(const CDiffContext& ctxt, int origin, int destination, int count,
33 const String& src, const String& dest, bool destIsSide);
34 static void ThrowConfirmationNeededException(const CDiffContext& ctxt, const String &caption, const String &question,
35 int origin, int destination, size_t count,
36 const String& src, const String& dest, bool destIsSide);
38 ContentsChangedException::ContentsChangedException(const String& failpath)
40 m_msg = string_format_string1(
41 _("Operation aborted!\n\nFolder contents at disks has changed, path\n%1\nwas not found.\n\nPlease refresh the compare."),
46 * @brief Ask user a confirmation for copying item(s).
47 * Shows a confirmation dialog for copy operation. Depending ont item count
48 * dialog shows full paths to items (single item) or base paths of compare
50 * @param [in] origin Origin side of the item(s).
51 * @param [in] destination Destination side of the item(s).
52 * @param [in] count Number of items.
53 * @param [in] src Source path.
54 * @param [in] dest Destination path.
55 * @param [in] destIsSide Is destination path either of compare sides?
56 * @return true if copy should proceed, false if aborted.
58 static void ThrowConfirmCopy(const CDiffContext& ctxt, int origin, int destination, size_t count,
59 const String& src, const String& dest, bool destIsSide)
61 String caption = _("Confirm Copy");
62 String strQuestion = count == 1 ? _("Are you sure you want to copy:") :
63 string_format(_("Are you sure you want to copy %d items:").c_str(), count);
65 ThrowConfirmationNeededException(ctxt, caption, strQuestion, origin,
66 destination, count, src, dest, destIsSide);
70 * @brief Ask user a confirmation for moving item(s).
71 * Shows a confirmation dialog for move operation. Depending ont item count
72 * dialog shows full paths to items (single item) or base paths of compare
74 * @param [in] origin Origin side of the item(s).
75 * @param [in] destination Destination side of the item(s).
76 * @param [in] count Number of items.
77 * @param [in] src Source path.
78 * @param [in] dest Destination path.
79 * @param [in] destIsSide Is destination path either of compare sides?
80 * @return true if copy should proceed, false if aborted.
82 static void ThrowConfirmMove(const CDiffContext& ctxt, int origin, int destination, size_t count,
83 const String& src, const String& dest, bool destIsSide)
85 String caption = _("Confirm Move");
86 String strQuestion = count == 1 ? _("Are you sure you want to move:") :
87 string_format(_("Are you sure you want to move %d items:").c_str(), count);
89 ThrowConfirmationNeededException(ctxt, caption, strQuestion, origin,
90 destination, count, src, dest, destIsSide);
94 * @brief Show a (copy/move) confirmation dialog.
95 * @param [in] caption Caption of the dialog.
96 * @param [in] question Guestion to ask from user.
97 * @param [in] origin Origin side of the item(s).
98 * @param [in] destination Destination side of the item(s).
99 * @param [in] count Number of items.
100 * @param [in] src Source path.
101 * @param [in] dest Destination path.
102 * @param [in] destIsSide Is destination path either of compare sides?
103 * @return true if copy should proceed, false if aborted.
105 static void ThrowConfirmationNeededException(const CDiffContext& ctxt, const String &caption, const String &question,
106 int origin, int destination, size_t count,
107 const String& src, const String& dest, bool destIsSide)
109 ConfirmationNeededException exp;
113 exp.m_caption = caption.c_str();
116 sOrig = _("From left:");
117 else if (origin == ctxt.GetCompareDirs() - 1)
118 sOrig = _("From right:");
120 sOrig = _("From middle:");
124 // Copy to left / right
125 if (destination == 0)
126 sDest = _("To left:");
127 else if (destination == ctxt.GetCompareDirs() - 1)
128 sDest = _("To right:");
130 sDest = _("To middle:");
134 // Copy left/right to..
139 if (paths_DoesPathExist(src) == IS_EXISTING_DIR)
140 strSrc = paths_AddTrailingSlash(src);
141 String strDest(dest);
142 if (paths_DoesPathExist(dest) == IS_EXISTING_DIR)
143 strDest = paths_AddTrailingSlash(dest);
145 exp.m_question = question;
146 exp.m_fromText = sOrig;
147 exp.m_toText = sDest;
148 exp.m_fromPath = strSrc;
149 exp.m_toPath = strDest;
155 * @brief Confirm actions with user as appropriate
156 * (type, whether single or multiple).
158 void ConfirmActionList(const CDiffContext& ctxt, const FileActionScript & actionList)
160 // TODO: We need better confirmation for file actions.
161 // Maybe we should show a list of files with actions done..
162 FileActionItem item = actionList.GetHeadActionItem();
164 bool bDestIsSide = true;
166 // special handling for the single item case, because it is probably the most common,
167 // and we can give the user exact details easily for it
170 case FileAction::ACT_COPY:
171 if (item.UIResult == FileActionItem::UI_DONT_CARE)
174 if (actionList.GetActionItemCount() == 1)
176 ThrowConfirmCopy(ctxt, item.UIOrigin, item.UIDestination,
177 actionList.GetActionItemCount(), item.src, item.dest,
182 String src = ctxt.GetPath(item.UIOrigin);
187 dst = ctxt.GetPath(item.UIDestination);
191 if (!actionList.m_destBase.empty())
192 dst = actionList.m_destBase;
197 ThrowConfirmCopy(ctxt, item.UIOrigin, item.UIDestination,
198 actionList.GetActionItemCount(), src, dst, bDestIsSide);
202 case FileAction::ACT_DEL:
205 case FileAction::ACT_MOVE:
207 if (actionList.GetActionItemCount() == 1)
209 ThrowConfirmMove(ctxt, item.UIOrigin, item.UIDestination,
210 actionList.GetActionItemCount(), item.src, item.dest,
215 String src = ctxt.GetPath(item.UIOrigin);;
218 if (!actionList.m_destBase.empty())
219 dst = actionList.m_destBase;
223 ThrowConfirmMove(ctxt, item.UIOrigin, item.UIDestination,
224 actionList.GetActionItemCount(), src, dst, bDestIsSide);
230 LogErrorString(_T("Unknown fileoperation in CDirView::ConfirmActionList()"));
231 throw "Unknown fileoperation in ConfirmActionList()";
237 * @brief Update results for FileActionItem.
238 * This functions is called to update DIFFITEM after FileActionItem.
239 * @param [in] act Action that was done.
240 * @param [in] pos List position for DIFFITEM affected.
242 UPDATEITEM_TYPE UpdateDiffAfterOperation(const FileActionItem & act, CDiffContext& ctxt, DIFFITEM &di)
244 bool bUpdateSrc = false;
245 bool bUpdateDest = false;
246 bool bRemoveItem = false;
248 // Use FileActionItem types for simplicity for now.
249 // Better would be to use FileAction contained, since it is not
251 switch (act.UIResult)
253 case FileActionItem::UI_SYNC:
256 di.diffcode.setSideFlag(act.UIDestination);
258 SetDiffCompare(di, DIFFCODE::NOCMP);
260 SetDiffCompare(di, DIFFCODE::SAME);
261 SetDiffCounts(di, 0, 0);
264 case FileActionItem::UI_DEL:
265 if (di.diffcode.isSideOnly(act.UIOrigin))
267 ctxt.RemoveDiff(reinterpret_cast<uintptr_t>(&di));
272 di.diffcode.unsetSideFlag(act.UIOrigin);
273 SetDiffCompare(di, DIFFCODE::NOCMP);
280 ctxt.UpdateStatusFromDisk(reinterpret_cast<uintptr_t>(&di), act.UIOrigin);
282 ctxt.UpdateStatusFromDisk(reinterpret_cast<uintptr_t>(&di), act.UIDestination);
285 return UPDATEITEM_REMOVE;
286 if (bUpdateSrc | bUpdateDest)
287 return UPDATEITEM_UPDATE;
288 return UPDATEITEM_NONE;
292 * @brief Find the CDiffContext diffpos of an item from its left & right paths
293 * @return POSITION to item, NULL if not found.
294 * @note Filenames must be same, if they differ NULL is returned.
296 uintptr_t FindItemFromPaths(const CDiffContext& ctxt, const String& pathLeft, const String& pathRight)
298 String file1 = paths_FindFileName(pathLeft);
299 String file2 = paths_FindFileName(pathRight);
301 // Filenames must be identical
302 if (string_compare_nocase(file1, file2) != 0)
305 String path1(pathLeft, 0, pathLeft.length() - file1.length()); // include trailing backslash
306 String path2(pathRight, 0, pathRight.length() - file2.length()); // include trailing backslash
308 // Path can contain (because of difftools?) '/' and '\'
309 // so for comparing purposes, convert whole path to use '\\'
310 replace_char(&*path1.begin(), '/', '\\');
311 replace_char(&*path2.begin(), '/', '\\');
313 String base1 = ctxt.GetLeftPath(); // include trailing backslash
314 if (path1.compare(0, base1.length(), base1.c_str()) != 0)
316 path1.erase(0, base1.length()); // turn into relative path
317 if (String::size_type length = path1.length())
318 path1.resize(length - 1); // remove trailing backslash
320 String base2 = ctxt.GetRightPath(); // include trailing backslash
321 if (path2.compare(0, base2.length(), base2.c_str()) != 0)
323 path2.erase(0, base2.length()); // turn into relative path
324 if (String::size_type length = path2.length())
325 path2.resize(length - 1); // remove trailing backslash
327 uintptr_t pos = ctxt.GetFirstDiffPosition();
328 while (uintptr_t currentPos = pos) // Save our current pos before getting next
330 const DIFFITEM &di = ctxt.GetNextDiffPosition(pos);
331 if (di.diffFileInfo[0].path == path1 &&
332 di.diffFileInfo[1].path == path2 &&
333 di.diffFileInfo[0].filename == file1 &&
334 di.diffFileInfo[1].filename == file2)
342 /// is it possible to copy item to left ?
343 bool IsItemCopyable(const DIFFITEM & di, int index)
345 // don't let them mess with error items
346 if (di.diffcode.isResultError()) return false;
347 // can't copy same items
348 if (di.diffcode.isResultSame()) return false;
349 // impossible if not existing
350 if (!di.diffcode.isExists(index)) return false;
351 // everything else can be copied to other side
355 /// is it possible to delete item ?
356 bool IsItemDeletable(const DIFFITEM & di, int index)
358 // don't let them mess with error items
359 if (di.diffcode.isResultError()) return false;
360 // impossible if not existing
361 if (!di.diffcode.isExists(index)) return false;
362 // everything else can be deleted
366 /// is it possible to delete both items ?
367 bool IsItemDeletableOnBoth(const CDiffContext& ctxt, const DIFFITEM & di)
369 // don't let them mess with error items
370 if (di.diffcode.isResultError()) return false;
371 // impossible if only on right or left
372 for (int i = 0; i < ctxt.GetCompareDirs(); ++i)
373 if (!di.diffcode.isExists(i)) return false;
375 // everything else can be deleted on both
380 * @brief Determine if item can be opened.
381 * Basically we only disable opening unique files at the moment.
382 * Unique folders can be opened since we ask for creating matching folder
384 * @param [in] di DIFFITEM for item to check.
385 * @return true if the item can be opened, false otherwise.
387 bool IsItemOpenable(const CDiffContext& ctxt, const DIFFITEM & di, bool treemode)
389 if (treemode && ctxt.m_bRecursive)
391 if (di.diffcode.isDirectory() || !IsItemExistAll(ctxt, di))
396 if (!di.diffcode.isDirectory() && !IsItemExistAll(ctxt, di))
401 /// is it possible to compare these two items?
402 bool AreItemsOpenable(const CDiffContext& ctxt, SELECTIONTYPE selectionType, const DIFFITEM & di1, const DIFFITEM & di2)
404 String sLeftBasePath = ctxt.GetPath(0);
405 String sRightBasePath = ctxt.GetPath(1);
407 // Must be both directory or neither
408 if (di1.diffcode.isDirectory() != di2.diffcode.isDirectory()) return false;
410 switch (selectionType)
412 case SELECTIONTYPE_NORMAL:
413 // Must be on different sides, or one on one side & one on both
414 if (di1.diffcode.isSideFirstOnly() && (di2.diffcode.isSideSecondOnly() ||
415 di2.diffcode.isSideBoth()))
417 if (di1.diffcode.isSideSecondOnly() && (di2.diffcode.isSideFirstOnly() ||
418 di2.diffcode.isSideBoth()))
420 if (di1.diffcode.isSideBoth() && (di2.diffcode.isSideFirstOnly() ||
421 di2.diffcode.isSideSecondOnly()))
424 case SELECTIONTYPE_LEFT1LEFT2:
425 if (di1.diffcode.isExists(0) && di2.diffcode.isExists(0))
428 case SELECTIONTYPE_RIGHT1RIGHT2:
429 if (di1.diffcode.isExists(1) && di2.diffcode.isExists(1))
432 case SELECTIONTYPE_LEFT1RIGHT2:
433 if (di1.diffcode.isExists(0) && di2.diffcode.isExists(1))
436 case SELECTIONTYPE_LEFT2RIGHT1:
437 if (di1.diffcode.isExists(1) && di2.diffcode.isExists(0))
442 // Allow to compare items if left & right path refer to same directory
443 // (which means there is effectively two files involved). No need to check
444 // side flags. If files weren't on both sides, we'd have no DIFFITEMs.
445 if (string_compare_nocase(sLeftBasePath, sRightBasePath) == 0)
450 /// is it possible to compare these three items?
451 bool AreItemsOpenable(const CDiffContext& ctxt, const DIFFITEM & di1, const DIFFITEM & di2, const DIFFITEM & di3)
453 String sLeftBasePath = ctxt.GetPath(0);
454 String sMiddleBasePath = ctxt.GetPath(1);
455 String sRightBasePath = ctxt.GetPath(2);
456 String sLeftPath1 = paths_ConcatPath(di1.getFilepath(0, sLeftBasePath), di1.diffFileInfo[0].filename);
457 String sLeftPath2 = paths_ConcatPath(di2.getFilepath(0, sLeftBasePath), di2.diffFileInfo[0].filename);
458 String sLeftPath3 = paths_ConcatPath(di3.getFilepath(0, sLeftBasePath), di3.diffFileInfo[0].filename);
459 String sMiddlePath1 = paths_ConcatPath(di1.getFilepath(1, sMiddleBasePath), di1.diffFileInfo[1].filename);
460 String sMiddlePath2 = paths_ConcatPath(di2.getFilepath(1, sMiddleBasePath), di2.diffFileInfo[1].filename);
461 String sMiddlePath3 = paths_ConcatPath(di3.getFilepath(1, sMiddleBasePath), di3.diffFileInfo[1].filename);
462 String sRightPath1 = paths_ConcatPath(di1.getFilepath(2, sRightBasePath), di1.diffFileInfo[2].filename);
463 String sRightPath2 = paths_ConcatPath(di2.getFilepath(2, sRightBasePath), di2.diffFileInfo[2].filename);
464 String sRightPath3 = paths_ConcatPath(di3.getFilepath(2, sRightBasePath), di3.diffFileInfo[2].filename);
465 // Must not be binary (unless archive)
468 (di1.diffcode.isBin() || di2.diffcode.isBin() || di3.diffcode.isBin())
471 && (sLeftPath1.empty() || ArchiveGuessFormat(sLeftPath1))
472 && (sMiddlePath1.empty() || ArchiveGuessFormat(sMiddlePath1))
473 && (sLeftPath2.empty() || ArchiveGuessFormat(sLeftPath2))
474 && (sMiddlePath2.empty() || ArchiveGuessFormat(sMiddlePath2))
475 && (sLeftPath2.empty() || ArchiveGuessFormat(sLeftPath2))
476 && (sMiddlePath2.empty() || ArchiveGuessFormat(sMiddlePath2)) /* FIXME: */
483 // Must be both directory or neither
484 if (di1.diffcode.isDirectory() != di2.diffcode.isDirectory() && di1.diffcode.isDirectory() != di3.diffcode.isDirectory()) return false;
486 // Must be on different sides, or one on one side & one on both
487 if (di1.diffcode.isExists(0) && di2.diffcode.isExists(1) && di3.diffcode.isExists(2))
489 if (di1.diffcode.isExists(0) && di2.diffcode.isExists(2) && di3.diffcode.isExists(1))
491 if (di1.diffcode.isExists(1) && di2.diffcode.isExists(0) && di3.diffcode.isExists(2))
493 if (di1.diffcode.isExists(1) && di2.diffcode.isExists(2) && di3.diffcode.isExists(0))
495 if (di1.diffcode.isExists(2) && di2.diffcode.isExists(0) && di3.diffcode.isExists(1))
497 if (di1.diffcode.isExists(2) && di2.diffcode.isExists(1) && di3.diffcode.isExists(0))
500 // Allow to compare items if left & right path refer to same directory
501 // (which means there is effectively two files involved). No need to check
502 // side flags. If files weren't on both sides, we'd have no DIFFITEMs.
503 if (string_compare_nocase(sLeftBasePath, sMiddleBasePath) == 0 && string_compare_nocase(sLeftBasePath, sRightBasePath) == 0)
508 /// is it possible to open item ?
509 bool IsItemOpenableOn(const DIFFITEM & di, int index)
511 // impossible if not existing
512 if (!di.diffcode.isExists(index)) return false;
514 // everything else can be opened on right
518 /// is it possible to open left ... item ?
519 bool IsItemOpenableOnWith(const DIFFITEM & di, int index)
521 return (!di.diffcode.isDirectory() && IsItemOpenableOn(di, index));
523 /// is it possible to copy to... left item?
524 bool IsItemCopyableToOn(const DIFFITEM & di, int index)
526 // impossible if only on right
527 if (!di.diffcode.isExists(index)) return false;
529 // everything else can be copied to from left
533 // When navigating differences, do we stop at this one ?
534 bool IsItemNavigableDiff(const CDiffContext& ctxt, const DIFFITEM & di)
536 // Not a valid diffitem, one of special items (e.g "..")
537 if (di.diffcode.diffcode == 0)
539 if (di.diffcode.isResultFiltered() || di.diffcode.isResultError())
541 if (!di.diffcode.isResultDiff() && IsItemExistAll(ctxt, di))
546 bool IsItemExistAll(const CDiffContext& ctxt, const DIFFITEM & di)
548 // Not a valid diffitem, one of special items (e.g "..")
549 if (di.diffcode.diffcode == 0)
551 if (ctxt.GetCompareDirs() == 2)
552 return di.diffcode.isSideBoth();
554 return di.diffcode.isSideAll();
559 * @brief Determines if the user wants to see given item.
560 * This function determines what items to show and what items to hide. There
561 * are lots of combinations, but basically we check if menuitem is enabled or
562 * disabled and show/hide matching items. For non-recursive compare we never
563 * hide folders as that would disable user browsing into them. And we even
564 * don't really know if folders are identical or different as we haven't
566 * @param [in] di Item to check.
567 * @return true if item should be shown, false if not.
568 * @sa CDirDoc::Redisplay()
570 bool IsShowable(const CDiffContext& ctxt, const DIFFITEM & di, const DirViewFilterSettings& filter)
572 if (di.customFlags1 & ViewCustomFlags::HIDDEN)
575 if (di.diffcode.isResultFiltered())
577 // Treat SKIPPED as a 'super'-flag. If item is skipped and user
578 // wants to see skipped items show item regardless of other flags
579 return filter.show_skipped;
582 if (di.diffcode.isDirectory())
584 // Subfolders in non-recursive compare can only be skipped or unique
585 if (!ctxt.m_bRecursive)
587 // left/right filters
588 if (di.diffcode.isSideFirstOnly() && !filter.show_unique_left)
590 if (di.diffcode.isSideSecondOnly() && !filter.show_unique_right)
594 if (di.diffcode.isResultError() && FALSE/* !GetMainFrame()->m_bShowErrors FIXME:*/)
597 else // recursive mode (including tree-mode)
599 // left/right filters
600 if (di.diffcode.isSideFirstOnly() && !filter.show_unique_left)
602 if (di.diffcode.isSideSecondOnly() && !filter.show_unique_right)
605 // ONLY filter folders by result (identical/different) for tree-view.
606 // In the tree-view we show subfolders with identical/different
607 // status. The flat view only shows files inside folders. So if we
608 // filter by status the files inside folder are filtered too and
609 // users see files appearing/disappearing without clear logic.
610 if (filter.tree_mode)
613 if (di.diffcode.isResultError() && FALSE/* !GetMainFrame()->m_bShowErrors FIXME:*/)
617 if (di.diffcode.isResultSame() && !filter.show_identical)
619 if (di.diffcode.isResultDiff() && !filter.show_different)
626 // left/right filters
627 if (di.diffcode.isSideFirstOnly() && !filter.show_unique_left)
629 if (di.diffcode.isSideSecondOnly() && !filter.show_unique_right)
633 if (di.diffcode.isBin() && !filter.show_binaries)
637 if (di.diffcode.isResultSame() && !filter.show_identical)
639 if (di.diffcode.isResultError() /* && !GetMainFrame()->m_bShowErrors FIXME:*/)
641 if (di.diffcode.isResultDiff() && !filter.show_different)
648 * @brief Open one selected item.
649 * @param [in] pos1 Item position.
650 * @param [in,out] di1 Pointer to first diffitem.
651 * @param [in,out] di2 Pointer to second diffitem.
652 * @param [in,out] di3 Pointer to third diffitem.
653 * @param [out] paths First/Second/Third paths.
654 * @param [out] sel1 Item's selection index in listview.
655 * @param [in,out] isDir Is item folder?
656 * return false if there was error or item was completely processed.
658 bool GetOpenOneItem(const CDiffContext& ctxt, uintptr_t pos1, const DIFFITEM **di1, const DIFFITEM **di2, const DIFFITEM **di3,
659 PathContext & paths, int & sel1, bool & isdir, String& errmsg)
661 *di1 = &ctxt.GetDiffAt(pos1);
665 paths = GetItemFileNames(ctxt, **di1);
667 if ((*di1)->diffcode.isDirectory())
670 if (isdir && ((*di1)->diffcode.isExistsFirst() && (*di1)->diffcode.isExistsSecond() && (*di1)->diffcode.isExistsThird()))
672 // Check both folders exist. If either folder is missing that means
673 // folder has been changed behind our back, so we just tell user to
674 // refresh the compare.
675 PATH_EXISTENCE path1Exists = paths_DoesPathExist(paths[0]);
676 PATH_EXISTENCE path2Exists = paths_DoesPathExist(paths[1]);
677 if (path1Exists != IS_EXISTING_DIR || path2Exists != IS_EXISTING_DIR)
679 String invalid = path1Exists == IS_EXISTING_DIR ? paths[0] : paths[1];
680 errmsg = string_format_string1(
681 _("Operation aborted!\n\nFolder contents at disks has changed, path\n%1\nwas not found.\n\nPlease refresh the compare."),
691 * @brief Open two selected items.
692 * @param [in] pos1 First item position.
693 * @param [in] pos2 Second item position.
694 * @param [in,out] di1 Pointer to first diffitem.
695 * @param [in,out] di2 Pointer to second diffitem.
696 * @param [out] paths First/Second/Third paths.
697 * @param [out] sel1 First item's selection index in listview.
698 * @param [out] sel2 Second item's selection index in listview.
699 * @param [in,out] isDir Is item folder?
700 * return false if there was error or item was completely processed.
702 bool GetOpenTwoItems(const CDiffContext& ctxt, SELECTIONTYPE selectionType, uintptr_t pos1, uintptr_t pos2, const DIFFITEM **di1, const DIFFITEM **di2,
703 PathContext & paths, int & sel1, int & sel2, bool & isDir, String& errmsg)
705 String pathLeft, pathRight;
707 // Two items selected, get their info
708 *di1 = &ctxt.GetDiffAt(pos1);
709 *di2 = &ctxt.GetDiffAt(pos2);
711 // Check for binary & side compatibility & file/dir compatibility
712 if (!AreItemsOpenable(ctxt, selectionType, **di1, **di2))
718 switch (selectionType)
720 case SELECTIONTYPE_NORMAL:
721 // Ensure that di1 is on left (swap if needed)
722 if ((*di1)->diffcode.isSideSecondOnly() || ((*di1)->diffcode.isSideBoth() &&
723 (*di2)->diffcode.isSideFirstOnly()))
725 const DIFFITEM * temp = *di1;
732 // Fill in pathLeft & pathRight
733 GetItemFileNames(ctxt, **di1, pathLeft, temp);
734 GetItemFileNames(ctxt, **di2, temp, pathRight);
736 case SELECTIONTYPE_LEFT1LEFT2:
737 GetItemFileNames(ctxt, **di1, pathLeft, temp);
738 GetItemFileNames(ctxt, **di2, pathRight, temp);
740 case SELECTIONTYPE_RIGHT1RIGHT2:
741 GetItemFileNames(ctxt, **di1, temp, pathLeft);
742 GetItemFileNames(ctxt, **di2, temp, pathRight);
744 case SELECTIONTYPE_LEFT1RIGHT2:
745 GetItemFileNames(ctxt, **di1, pathLeft, temp);
746 GetItemFileNames(ctxt, **di2, temp, pathRight);
748 case SELECTIONTYPE_LEFT2RIGHT1:
749 GetItemFileNames(ctxt, **di1, temp, pathRight);
750 GetItemFileNames(ctxt, **di2, pathLeft, temp);
754 if ((*di1)->diffcode.isDirectory())
757 if (GetPairComparability(PathContext(pathLeft, pathRight)) != IS_EXISTING_DIR)
759 errmsg = _("The selected folder is invalid.");
764 paths.SetLeft(pathLeft);
765 paths.SetRight(pathRight);
771 * @brief Open three selected items.
772 * @param [in] pos1 First item position.
773 * @param [in] pos2 Second item position.
774 * @param [in] pos3 Third item position.
775 * @param [in,out] di1 Pointer to first diffitem.
776 * @param [in,out] di2 Pointer to second diffitem.
777 * @param [in,out] di3 Pointer to third diffitem.
778 * @param [out] paths First/Second/Third paths.
779 * @param [out] sel1 First item's selection index in listview.
780 * @param [out] sel2 Second item's selection index in listview.
781 * @param [out] sel3 Third item's selection index in listview.
782 * @param [in,out] isDir Is item folder?
783 * return false if there was error or item was completely processed.
785 bool GetOpenThreeItems(const CDiffContext& ctxt, uintptr_t pos1, uintptr_t pos2, uintptr_t pos3, const DIFFITEM **di1, const DIFFITEM **di2, const DIFFITEM **di3,
786 PathContext & paths, int & sel1, int & sel2, int & sel3, bool & isDir, String& errmsg)
788 String pathLeft, pathMiddle, pathRight;
792 // Two items selected, get their info
793 *di1 = &ctxt.GetDiffAt(pos1);
794 *di2 = &ctxt.GetDiffAt(pos2);
796 // Check for binary & side compatibility & file/dir compatibility
797 if (!::AreItemsOpenable(ctxt, **di1, **di2, **di2) &&
798 !::AreItemsOpenable(ctxt, **di1, **di1, **di2))
802 // Ensure that di1 is on left (swap if needed)
803 if ((*di1)->diffcode.isExists(0) && (*di1)->diffcode.isExists(1) && (*di2)->diffcode.isExists(2))
810 else if ((*di1)->diffcode.isExists(0) && (*di1)->diffcode.isExists(2) && (*di2)->diffcode.isExists(1))
815 else if ((*di1)->diffcode.isExists(1) && (*di1)->diffcode.isExists(2) && (*di2)->diffcode.isExists(0))
817 std::swap(*di1, *di2);
818 std::swap(sel1, sel2);
822 else if ((*di2)->diffcode.isExists(0) && (*di2)->diffcode.isExists(1) && (*di1)->diffcode.isExists(2))
824 std::swap(*di1, *di2);
825 std::swap(sel1, sel2);
831 else if ((*di2)->diffcode.isExists(0) && (*di2)->diffcode.isExists(2) && (*di1)->diffcode.isExists(1))
833 std::swap(*di1, *di2);
834 std::swap(sel1, sel2);
838 else if ((*di2)->diffcode.isExists(1) && (*di2)->diffcode.isExists(2) && (*di1)->diffcode.isExists(0))
846 // Three items selected, get their info
847 *di1 = &ctxt.GetDiffAt(pos1);
848 *di2 = &ctxt.GetDiffAt(pos2);
849 *di3 = &ctxt.GetDiffAt(pos3);
851 // Check for binary & side compatibility & file/dir compatibility
852 if (!::AreItemsOpenable(ctxt, **di1, **di2, **di3))
856 // Ensure that di1 is on left (swap if needed)
857 if ((*di1)->diffcode.isExists(0) && (*di2)->diffcode.isExists(1) && (*di3)->diffcode.isExists(2))
860 else if ((*di1)->diffcode.isExists(0) && (*di2)->diffcode.isExists(2) && (*di3)->diffcode.isExists(1))
862 std::swap(*di2, *di3);
863 std::swap(sel2, sel3);
865 else if ((*di1)->diffcode.isExists(1) && (*di2)->diffcode.isExists(0) && (*di3)->diffcode.isExists(2))
867 std::swap(*di1, *di2);
868 std::swap(sel1, sel2);
870 else if ((*di1)->diffcode.isExists(1) && (*di2)->diffcode.isExists(2) && (*di3)->diffcode.isExists(0))
872 std::swap(*di1, *di3);
873 std::swap(sel1, sel3);
874 std::swap(*di2, *di3);
875 std::swap(sel2, sel3);
877 else if ((*di1)->diffcode.isExists(2) && (*di2)->diffcode.isExists(0) && (*di3)->diffcode.isExists(1))
879 std::swap(*di1, *di2);
880 std::swap(sel1, sel2);
881 std::swap(*di2, *di3);
882 std::swap(sel2, sel3);
884 else if ((*di1)->diffcode.isExists(2) && (*di2)->diffcode.isExists(1) && (*di3)->diffcode.isExists(0))
886 std::swap(*di1, *di3);
887 std::swap(sel1, sel3);
891 // Fill in pathLeft & & pathMiddle & pathRight
892 PathContext pathsTemp = GetItemFileNames(ctxt, **di1);
893 pathLeft = pathsTemp[0];
894 pathsTemp = GetItemFileNames(ctxt, **di2);
895 pathMiddle = pathsTemp[1];
896 pathsTemp = GetItemFileNames(ctxt, **di3);
897 pathRight = pathsTemp[2];
899 if ((*di1)->diffcode.isDirectory())
902 if (GetPairComparability(PathContext(pathLeft, pathMiddle, pathRight)) != IS_EXISTING_DIR)
904 errmsg = _("The selected folder is invalid.");
909 paths.SetLeft(pathLeft.c_str());
910 paths.SetRight(pathRight.c_str());
916 * @brief Get the file names on both sides for specified item.
917 * @note Return empty strings if item is special item.
919 void GetItemFileNames(const CDiffContext& ctxt, const DIFFITEM & di, String& strLeft, String& strRight)
921 const String leftrelpath = paths_ConcatPath(di.diffFileInfo[0].path, di.diffFileInfo[0].filename);
922 const String rightrelpath = paths_ConcatPath(di.diffFileInfo[1].path, di.diffFileInfo[1].filename);
923 const String & leftpath = ctxt.GetPath(0);
924 const String & rightpath = ctxt.GetPath(1);
925 strLeft = paths_ConcatPath(leftpath, leftrelpath);
926 strRight = paths_ConcatPath(rightpath, rightrelpath);
929 String GetItemFileName(const CDiffContext& ctxt, const DIFFITEM & di, int index)
931 return paths_ConcatPath(ctxt.GetPath(index), paths_ConcatPath(di.diffFileInfo[index].path, di.diffFileInfo[index].filename));
934 PathContext GetItemFileNames(const CDiffContext& ctxt, const DIFFITEM & di)
937 for (int nIndex = 0; nIndex < ctxt.GetCompareDirs(); nIndex++)
939 const String relpath = paths_ConcatPath(di.diffFileInfo[nIndex].path, di.diffFileInfo[nIndex].filename);
940 const String & path = ctxt.GetPath(nIndex);
941 paths.SetPath(nIndex, paths_ConcatPath(path, relpath));
947 * @brief Return image index appropriate for this row
949 int GetColImage(const CDiffContext&ctxt, const DIFFITEM & di)
951 // Must return an image index into image list created above in OnInitDialog
952 if (di.diffcode.isResultError())
953 return DIFFIMG_ERROR;
954 if (di.diffcode.isResultAbort())
955 return DIFFIMG_ABORT;
956 if (di.diffcode.isResultFiltered())
957 return (di.diffcode.isDirectory() ? DIFFIMG_DIRSKIP : DIFFIMG_SKIP);
958 if (di.diffcode.isSideFirstOnly())
959 return (di.diffcode.isDirectory() ? DIFFIMG_LDIRUNIQUE : DIFFIMG_LUNIQUE);
960 if (di.diffcode.isSideSecondOnly())
961 return (ctxt.GetCompareDirs() < 3 ?
962 (di.diffcode.isDirectory() ? DIFFIMG_RDIRUNIQUE : DIFFIMG_RUNIQUE) :
963 (di.diffcode.isDirectory() ? DIFFIMG_MDIRUNIQUE : DIFFIMG_MUNIQUE));
964 if (di.diffcode.isSideThirdOnly())
965 return (di.diffcode.isDirectory() ? DIFFIMG_RDIRUNIQUE : DIFFIMG_RUNIQUE);
966 if (ctxt.GetCompareDirs() == 3)
968 if (!di.diffcode.isExists(0))
969 return (di.diffcode.isDirectory() ? DIFFIMG_LDIRMISSING : DIFFIMG_LMISSING);
970 if (!di.diffcode.isExists(1))
971 return (di.diffcode.isDirectory() ? DIFFIMG_MDIRMISSING : DIFFIMG_MMISSING);
972 if (!di.diffcode.isExists(2))
973 return (di.diffcode.isDirectory() ? DIFFIMG_RDIRMISSING : DIFFIMG_RMISSING);
975 if (di.diffcode.isResultSame())
977 if (di.diffcode.isDirectory())
978 return DIFFIMG_DIRSAME;
981 if (di.diffcode.isText())
982 return DIFFIMG_TEXTSAME;
983 else if (di.diffcode.isBin())
984 return DIFFIMG_BINSAME;
990 if (di.diffcode.isResultDiff())
992 if (di.diffcode.isDirectory())
993 return DIFFIMG_DIRDIFF;
996 if (di.diffcode.isText())
997 return DIFFIMG_TEXTDIFF;
998 else if (di.diffcode.isBin())
999 return DIFFIMG_BINDIFF;
1001 return DIFFIMG_DIFF;
1004 return (di.diffcode.isDirectory() ? DIFFIMG_DIR : DIFFIMG_ABORT);
1008 * @brief Set side status of diffitem
1009 * @note This does not update UI - ReloadItemStatus() does
1010 * @sa CDirDoc::ReloadItemStatus()
1012 void SetDiffSide(DIFFITEM& di, unsigned diffcode)
1014 SetDiffStatus(di, diffcode, DIFFCODE::SIDEFLAGS);
1018 * @brief Set compare status of diffitem
1019 * @note This does not update UI - ReloadItemStatus() does
1020 * @sa CDirDoc::ReloadItemStatus()
1022 void SetDiffCompare(DIFFITEM& di, unsigned diffcode)
1024 SetDiffStatus(di, diffcode, DIFFCODE::COMPAREFLAGS);
1028 * @brief Set status for diffitem
1029 * @param diffcode New code
1030 * @param mask Defines allowed set of flags to change
1031 * @param idx Item's index to list in UI
1033 void SetDiffStatus(DIFFITEM& di, unsigned diffcode, unsigned mask)
1035 // TODO: Why is the update broken into these pieces ?
1036 // Someone could figure out these pieces and probably simplify this.
1038 // Update DIFFITEM code (comparison result)
1039 assert(! ((~mask) & diffcode) ); // make sure they only set flags in their mask
1040 di.diffcode.diffcode &= (~mask); // remove current data
1041 di.diffcode.diffcode |= diffcode; // add new data
1043 // update DIFFITEM time (and other disk info), and tell views
1046 void SetDiffCounts(DIFFITEM& di, unsigned diffs, unsigned ignored)
1048 di.nidiffs = ignored; // see StoreDiffResult() in DirScan.cpp
1053 * @brief Set item's view-flag.
1054 * @param [in] key Item fow which flag is set.
1055 * @param [in] flag Flag value to set.
1056 * @param [in] mask Mask for possible flag values.
1058 void SetItemViewFlag(DIFFITEM& di, unsigned flag, unsigned mask)
1060 unsigned curFlags = di.customFlags1;
1061 curFlags &= ~mask; // Zero bits masked
1063 di.customFlags1 = curFlags;
1067 * @brief Set all item's view-flag.
1068 * @param [in] flag Flag value to set.
1069 * @param [in] mask Mask for possible flag values.
1071 void SetItemViewFlag(CDiffContext& ctxt, unsigned flag, unsigned mask)
1073 uintptr_t pos = ctxt.GetFirstDiffPosition();
1077 UINT curFlags = ctxt.GetCustomFlags1(pos);
1078 curFlags &= ~mask; // Zero bits masked
1080 ctxt.SetCustomFlags1(pos, curFlags);
1081 ctxt.GetNextDiffPosition(pos);
1086 * @brief Mark selected items as needing for rescan.
1087 * @return Count of items to rescan.
1089 void MarkForRescan(DIFFITEM &di)
1091 SetDiffStatus(di, 0, DIFFCODE::TEXTFLAGS | DIFFCODE::SIDEFLAGS | DIFFCODE::COMPAREFLAGS);
1092 SetDiffStatus(di, DIFFCODE::NEEDSCAN, DIFFCODE::SCANFLAGS);
1096 * @brief Return string such as "15 of 30 Files Affected" or "30 Files Affected"
1098 String FormatFilesAffectedString(int nFilesAffected, int nFilesTotal)
1100 if (nFilesAffected == nFilesTotal)
1101 return string_format_string1(_("(%1 Files Affected)"), NumToStr(nFilesTotal));
1103 return string_format_string2(_("(%1 of %2 Files Affected)"), NumToStr(nFilesAffected), NumToStr(nFilesTotal));
1106 String FormatMenuItemString(const String& fmt1, const String& fmt2, int count, int total)
1109 return string_format_string1(fmt1, NumToStr(total));
1111 return string_format_string2(fmt2, NumToStr(count), NumToStr(total));
1114 String FormatMenuItemString(SIDE_TYPE src, SIDE_TYPE dst, int count, int total)
1117 if (src == SIDE_LEFT && dst == SIDE_RIGHT)
1119 fmt1 = _("Left to Right (%1)");
1120 fmt2 = _("Left to Right (%1 of %2)");
1122 else if (src == SIDE_LEFT && dst == SIDE_MIDDLE)
1124 fmt1 = _("Left to Middle (%1)");
1125 fmt2 = _("Left to middle (%1 of %2)");
1127 else if (src == SIDE_MIDDLE && dst == SIDE_LEFT)
1129 fmt1 = _("Middle to Left (%1)");
1130 fmt2 = _("Middle to Left (%1 of %2)");
1132 else if (src == SIDE_MIDDLE && dst == SIDE_RIGHT)
1134 fmt1 = _("Middle to Right (%1)");
1135 fmt2 = _("Middle to Right (%1 of %2)");
1137 else if (src == SIDE_RIGHT && dst == SIDE_LEFT)
1139 fmt1 = _("Right to Left (%1)");
1140 fmt2 = _("Right to Left (%1 of %2)");
1142 else if (src == SIDE_RIGHT && dst == SIDE_MIDDLE)
1144 fmt1 = _("Right to Middle (%1)");
1145 fmt2 = _("Right to Middle (%1 of %2)");
1147 return FormatMenuItemString(fmt1, fmt2, count, total);
1150 String FormatMenuItemString(SIDE_TYPE src, int count, int total)
1153 if (src == SIDE_LEFT)
1155 fmt1 = _("Left (%1)");
1156 fmt2 = _("Left (%1 of %2)");
1158 else if (src == SIDE_MIDDLE)
1160 fmt1 = _("Middle (%1)");
1161 fmt2 = _("Middle (%1 of %2)");
1163 else if (src == SIDE_RIGHT)
1165 fmt1 = _("Right (%1)");
1166 fmt2 = _("Right (%1 of %2)");
1168 return FormatMenuItemString(fmt1, fmt2, count, total);
1171 String FormatMenuItemStringBoth(int count, int total)
1173 return FormatMenuItemString(_("Both (%1)"), _("Both (%1 of %2)"), count, total);
1176 String FormatMenuItemStringTo(SIDE_TYPE src, int count, int total)
1179 if (src == SIDE_LEFT)
1181 fmt1 = _("Left to... (%1)");
1182 fmt2 = _("Left to... (%1 of %2)");
1184 else if (src == SIDE_MIDDLE)
1186 fmt1 = _("Middle to... (%1)");
1187 fmt2 = _("Middle to... (%1 of %2)");
1189 else if (src == SIDE_RIGHT)
1191 fmt1 = _("Right to... (%1)");
1192 fmt2 = _("Right to... (%1 of %2)");
1194 return FormatMenuItemString(fmt1, fmt2, count, total);
1198 * @brief Rename a file without moving it to different directory.
1200 * @param szOldFileName [in] Full path of file to rename.
1201 * @param szNewFileName [in] New file name (without the path).
1203 * @return true if file was renamed successfully.
1205 bool RenameOnSameDir(const String& szOldFileName, const String& szNewFileName)
1207 bool bSuccess = false;
1209 if (DOES_NOT_EXIST != paths_DoesPathExist(szOldFileName))
1211 String sFullName = paths_ConcatPath(paths_GetPathOnly(szOldFileName), szNewFileName);
1213 // No need to rename if new file already exist.
1214 if ((sFullName != szOldFileName) ||
1215 (DOES_NOT_EXIST == paths_DoesPathExist(sFullName)))
1217 ShellFileOperations fileOp;
1218 fileOp.SetOperation(FO_RENAME, 0);
1219 fileOp.AddSourceAndDestination(szOldFileName, sFullName);
1220 bSuccess = fileOp.Run();
1232 * @brief Convert number to string.
1233 * Converts number to string, with commas between digits in
1234 * locale-appropriate manner.
1236 String NumToStr(int n)
1238 return locality::NumToLocaleStr(n);
1241 void ExpandSubdirs(CDiffContext& ctxt, DIFFITEM& dip)
1243 dip.customFlags1 |= ViewCustomFlags::EXPANDED;
1244 uintptr_t diffpos = ctxt.GetFirstChildDiffPosition(reinterpret_cast<uintptr_t>(&dip));
1247 DIFFITEM &di = ctxt.GetNextDiffRefPosition(diffpos);
1248 if (!di.IsAncestor(&dip))
1250 if (di.HasChildren())
1251 di.customFlags1 |= ViewCustomFlags::EXPANDED;
1255 void ExpandAllSubdirs(CDiffContext& ctxt)
1257 uintptr_t diffpos = ctxt.GetFirstDiffPosition();
1260 DIFFITEM &di = ctxt.GetNextDiffRefPosition(diffpos);
1261 di.customFlags1 |= ViewCustomFlags::EXPANDED;
1265 void CollapseAllSubdirs(CDiffContext& ctxt)
1267 uintptr_t diffpos = ctxt.GetFirstDiffPosition();
1270 DIFFITEM &di = ctxt.GetNextDiffRefPosition(diffpos);
1271 di.customFlags1 &= ~ViewCustomFlags::EXPANDED;
1275 DirViewTreeState *SaveTreeState(const CDiffContext& ctxt)
1277 DirViewTreeState *pTreeState = new DirViewTreeState();
1278 uintptr_t diffpos = ctxt.GetFirstDiffPosition();
1281 const DIFFITEM &di = ctxt.GetNextDiffPosition(diffpos);
1282 if (di.HasChildren())
1284 String relpath = paths_ConcatPath(di.diffFileInfo[0].path, di.diffFileInfo[0].filename);
1285 pTreeState->insert(std::pair<String, bool>(relpath, !!(di.customFlags1 & ViewCustomFlags::EXPANDED)));
1291 void RestoreTreeState(CDiffContext& ctxt, DirViewTreeState *pTreeState)
1293 uintptr_t diffpos = ctxt.GetFirstDiffPosition();
1296 DIFFITEM &di = ctxt.GetNextDiffRefPosition(diffpos);
1297 if (di.HasChildren())
1299 String relpath = paths_ConcatPath(di.diffFileInfo[0].path, di.diffFileInfo[0].filename);
1300 std::map<String, bool>::iterator p = pTreeState->find(relpath);
1301 if (p != pTreeState->end())
1303 di.customFlags1 &= ~ViewCustomFlags::EXPANDED;
1304 di.customFlags1 |= (p->second ? ViewCustomFlags::EXPANDED : 0);
1311 * @brief Tell if user may use ".." and move to parents directory.
1312 * This function checks if current compare's parent folders should
1313 * be allowed to open. I.e. if current compare folders are:
1314 * - C:\Work\Project1 and
1315 * - C:\Work\Project2
1316 * we check if C:\Work and C:\Work should be allowed to opened.
1317 * For regular folders we allow opening if both folders exist.
1318 * @param [out] leftParent Left parent folder to open.
1319 * @param [out] rightParent Right parent folder to open.
1320 * @return Info if opening parent folders should be enabled:
1321 * - No : upward RESTRICTED
1322 * - ParentIsRegularPath : upward ENABLED
1323 * - ParentIsTempPath : upward ENABLED
1325 AllowUpwardDirectory::ReturnCode
1326 CheckAllowUpwardDirectory(const CDiffContext& ctxt, const CTempPathContext *pTempPathContext, PathContext &pathsParent)
1328 const String & path0 = ctxt.GetNormalizedPath(0);
1329 const String & path1 = ctxt.GetNormalizedPath(1);
1330 const String & path2 = ctxt.GetCompareDirs() > 2 ? ctxt.GetNormalizedPath(2) : _T("");
1332 // If we have temp context it means we are comparing archives
1333 if (pTempPathContext)
1335 String name0 = paths_FindFileName(path0);
1336 String name1 = paths_FindFileName(path1);
1337 String name2 = (ctxt.GetCompareDirs() > 2) ? paths_FindFileName(path2) : _T("");
1339 /* FIXME: for 3way diff*/
1340 String::size_type cchLeftRoot = pTempPathContext->m_strRoot[0].length();
1341 if (path0.length() <= cchLeftRoot)
1343 pathsParent.SetSize(ctxt.GetCompareDirs());
1344 if (pTempPathContext->m_pParent)
1346 pathsParent[0] = pTempPathContext->m_pParent->m_strRoot[0];
1347 pathsParent[1] = pTempPathContext->m_pParent->m_strRoot[1];
1348 if (GetPairComparability(PathContext(pathsParent[0], pathsParent[1])) != IS_EXISTING_DIR)
1349 return AllowUpwardDirectory::Never;
1350 return AllowUpwardDirectory::ParentIsTempPath;
1352 pathsParent[0] = pTempPathContext->m_strDisplayRoot[0];
1353 pathsParent[1] = pTempPathContext->m_strDisplayRoot[1];
1354 if (!ctxt.m_piFilterGlobal->includeFile(pathsParent[0], pathsParent[1]))
1355 return AllowUpwardDirectory::Never;
1356 if (string_compare_nocase(name0, _T("ORIGINAL")) == 0 && string_compare_nocase(name1, _T("ALTERED")) == 0)
1358 pathsParent[0] = paths_GetParentPath(pathsParent[0]);
1359 pathsParent[1] = paths_GetParentPath(pathsParent[1]);
1361 name0 = paths_FindFileName(pathsParent[0]);
1362 name1 = paths_FindFileName(pathsParent[1]);
1363 if (string_compare_nocase(name0, name1) == 0)
1365 pathsParent[0] = paths_GetParentPath(pathsParent[0]);
1366 pathsParent[1] = paths_GetParentPath(pathsParent[1]);
1367 if (GetPairComparability(PathContext(pathsParent[0], pathsParent[1])) != IS_EXISTING_DIR)
1368 return AllowUpwardDirectory::Never;
1369 return AllowUpwardDirectory::ParentIsTempPath;
1371 return AllowUpwardDirectory::No;
1376 // If regular parent folders exist, allow opening them
1377 pathsParent.SetSize(ctxt.GetCompareDirs());
1378 pathsParent[0] = paths_GetParentPath(path0);
1379 pathsParent[1] = paths_GetParentPath(path1);
1380 if (ctxt.GetCompareDirs() > 2)
1381 pathsParent[2] = paths_GetParentPath(path2);
1382 if (GetPairComparability(pathsParent) != IS_EXISTING_DIR)
1383 return AllowUpwardDirectory::Never;
1384 return AllowUpwardDirectory::ParentIsRegularPath;