浏览代码

On Synchronization checklist window Move command can be used with a set of orphaned files and new folder in source directory

... to create that directory on the target side and move the orphaned files there

Source commit: 11d34258171557f7d9e38ee80132d000f77c939e
Martin Prikryl 1 年之前
父节点
当前提交
19ae9e1dca

+ 9 - 5
source/core/Terminal.cpp

@@ -4005,6 +4005,14 @@ bool TTerminal::FileExists(const UnicodeString & FileName)
   return (File.get() != NULL);
   return (File.get() != NULL);
 }
 }
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
+bool TTerminal::DirectoryExists(const UnicodeString & FileName)
+{
+  std::unique_ptr<TRemoteFile> File(TryReadFile(FileName));
+  return
+    (File.get() != NULL) &&
+    File->IsDirectory;
+}
+//---------------------------------------------------------------------------
 void __fastcall TTerminal::AnnounceFileListOperation()
 void __fastcall TTerminal::AnnounceFileListOperation()
 {
 {
   FFileSystem->AnnounceFileListOperation();
   FFileSystem->AnnounceFileListOperation();
@@ -7535,11 +7543,7 @@ void __fastcall TTerminal::SourceRobust(
 bool __fastcall TTerminal::CreateTargetDirectory(
 bool __fastcall TTerminal::CreateTargetDirectory(
   const UnicodeString & DirectoryPath, int Attrs, const TCopyParamType * CopyParam)
   const UnicodeString & DirectoryPath, int Attrs, const TCopyParamType * CopyParam)
 {
 {
-  std::unique_ptr<TRemoteFile> File(TryReadFile(DirectoryPath));
-  bool DoCreate =
-    (File.get() == NULL) ||
-    !File->IsDirectory; // just try to create and make it fail
-  File.reset(NULL);
+  bool DoCreate = !DirectoryExists(DirectoryPath);
   if (DoCreate)
   if (DoCreate)
   {
   {
     TRemoteProperties Properties;
     TRemoteProperties Properties;

+ 2 - 1
source/core/Terminal.h

@@ -261,7 +261,6 @@ private:
   bool __fastcall GetStoredCredentialsTried();
   bool __fastcall GetStoredCredentialsTried();
   inline bool __fastcall InTransaction();
   inline bool __fastcall InTransaction();
   void __fastcall SaveCapabilities(TFileSystemInfo & FileSystemInfo);
   void __fastcall SaveCapabilities(TFileSystemInfo & FileSystemInfo);
-  bool __fastcall CreateTargetDirectory(const UnicodeString & DirectoryPath, int Attrs, const TCopyParamType * CopyParam);
   static UnicodeString __fastcall SynchronizeModeStr(TSynchronizeMode Mode);
   static UnicodeString __fastcall SynchronizeModeStr(TSynchronizeMode Mode);
   static UnicodeString __fastcall SynchronizeParamsStr(int Params);
   static UnicodeString __fastcall SynchronizeParamsStr(int Params);
 
 
@@ -554,6 +553,7 @@ public:
   TRemoteFile * ReadFile(const UnicodeString & FileName);
   TRemoteFile * ReadFile(const UnicodeString & FileName);
   TRemoteFile * TryReadFile(const UnicodeString & FileName);
   TRemoteFile * TryReadFile(const UnicodeString & FileName);
   bool FileExists(const UnicodeString & FileName);
   bool FileExists(const UnicodeString & FileName);
+  bool DirectoryExists(const UnicodeString & FileName);
   void __fastcall ReadSymlink(TRemoteFile * SymlinkFile, TRemoteFile *& File);
   void __fastcall ReadSymlink(TRemoteFile * SymlinkFile, TRemoteFile *& File);
   bool __fastcall CopyToLocal(
   bool __fastcall CopyToLocal(
     TStrings * FilesToCopy, const UnicodeString & TargetDir, const TCopyParamType * CopyParam, int Params,
     TStrings * FilesToCopy, const UnicodeString & TargetDir, const TCopyParamType * CopyParam, int Params,
@@ -564,6 +564,7 @@ public:
   int __fastcall CopyToParallel(TParallelOperation * ParallelOperation, TFileOperationProgressType * OperationProgress);
   int __fastcall CopyToParallel(TParallelOperation * ParallelOperation, TFileOperationProgressType * OperationProgress);
   void __fastcall LogParallelTransfer(TParallelOperation * ParallelOperation);
   void __fastcall LogParallelTransfer(TParallelOperation * ParallelOperation);
   void __fastcall CreateDirectory(const UnicodeString & DirName, const TRemoteProperties * Properties);
   void __fastcall CreateDirectory(const UnicodeString & DirName, const TRemoteProperties * Properties);
+  bool __fastcall CreateTargetDirectory(const UnicodeString & DirectoryPath, int Attrs, const TCopyParamType * CopyParam);
   void __fastcall CreateLink(const UnicodeString FileName, const UnicodeString PointTo, bool Symbolic);
   void __fastcall CreateLink(const UnicodeString FileName, const UnicodeString PointTo, bool Symbolic);
   void __fastcall DeleteFile(UnicodeString FileName,
   void __fastcall DeleteFile(UnicodeString FileName,
     const TRemoteFile * File = NULL, void * Params = NULL);
     const TRemoteFile * File = NULL, void * Params = NULL);

+ 49 - 19
source/forms/CustomScpExplorer.cpp

@@ -4480,13 +4480,10 @@ void __fastcall TCustomScpExplorerForm::LockFiles(TStrings * FileList, bool Lock
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 bool TCustomScpExplorerForm::DoDirectoryExists(void * Session, const UnicodeString & Directory)
 bool TCustomScpExplorerForm::DoDirectoryExists(void * Session, const UnicodeString & Directory)
 {
 {
-  bool Result = false;
   TTerminal * ATerminal = (Session == NULL) ? Terminal : reinterpret_cast<TTerminal *>(Session);
   TTerminal * ATerminal = (Session == NULL) ? Terminal : reinterpret_cast<TTerminal *>(Session);
-  if (IsActiveTerminal(ATerminal))
-  {
-    std::unique_ptr<TRemoteFile> File(ATerminal->TryReadFile(Directory));
-    Result = File && File->IsDirectory;
-  }
+  bool Result =
+    IsActiveTerminal(ATerminal) &&
+    ATerminal->DirectoryExists(Directory);
   return Result;
   return Result;
 }
 }
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
@@ -6394,24 +6391,34 @@ void __fastcall TCustomScpExplorerForm::DoSynchronizeChecklistCalculateSize(
 }
 }
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 void __fastcall TCustomScpExplorerForm::DoSynchronizeMove(
 void __fastcall TCustomScpExplorerForm::DoSynchronizeMove(
-  TOperationSide Side, const UnicodeString & FileName, const UnicodeString & NewFileName, TRemoteFile * RemoteFile)
+  TOperationSide Side, TStrings * FileList, const UnicodeString & NewFileName, bool TargetIsDirectory, void * Token)
 {
 {
   DebugAssert(!IsLocalBrowserMode());
   DebugAssert(!IsLocalBrowserMode());
   TAutoBatch AutoBatch(this);
   TAutoBatch AutoBatch(this);
   TAutoFlag AutoOperationFlag(FAutoOperation);
   TAutoFlag AutoOperationFlag(FAutoOperation);
 
 
+  TSynchronizeParams & Params = *static_cast<TSynchronizeParams *>(Token);
+
   if (Side == osRemote)
   if (Side == osRemote)
   {
   {
-    std::unique_ptr<TStrings> FileList(new TStringList());
-    FileList->AddObject(FileName, RemoteFile);
-    UnicodeString Target = UnixExtractFileDir(NewFileName);
-    UnicodeString FileMask = DelimitFileNameMask(UnixExtractFileName(NewFileName));
+    UnicodeString Target;
+    UnicodeString FileMask;
+    if (TargetIsDirectory)
+    {
+      Target = NewFileName;
+      Terminal->CreateTargetDirectory(Target, faDirectory, Params.CopyParam);
+    }
+    else
+    {
+      Target = UnixExtractFileDir(NewFileName);
+      FileMask = DelimitFileNameMask(UnixExtractFileName(NewFileName));
+    }
 
 
     RemoteDirView->SaveSelection();
     RemoteDirView->SaveSelection();
     RemoteDirView->SaveSelectedNames();
     RemoteDirView->SaveSelectedNames();
     try
     try
     {
     {
-      Terminal->MoveFiles(FileList.get(), Target, FileMask, false);
+      Terminal->MoveFiles(FileList, Target, FileMask, false);
     }
     }
     catch(...)
     catch(...)
     {
     {
@@ -6422,16 +6429,39 @@ void __fastcall TCustomScpExplorerForm::DoSynchronizeMove(
   }
   }
   else if (DebugAlwaysTrue(Side == osLocal))
   else if (DebugAlwaysTrue(Side == osLocal))
   {
   {
-    if (!MoveFile(ApiPath(FileName).c_str(), ApiPath(NewFileName).c_str()))
+    std::unique_ptr<TStrings> Directories(CreateSortedStringList());
+    if (TargetIsDirectory)
     {
     {
-      throw EOSExtException(FMTLOAD(RENAME_FILE_ERROR, (FileName, NewFileName)));
+      if (!ForceDirectories(ApiPath(NewFileName)))
+      {
+        throw EOSExtException(FMTLOAD(CREATE_DIR_ERROR, (NewFileName)));
+      }
+      Directories->Add(NewFileName);
     }
     }
-    UnicodeString Directory = ExtractFileDir(FileName);
-    ReloadLocalDirectory(Directory);
-    UnicodeString NewDirectory = ExtractFileDir(NewFileName);
-    if (!SamePaths(Directory, NewDirectory))
+    else
+    {
+      Directories->Add(ExtractFileDir(NewFileName));
+    }
+
+    for (int Index = 0; Index < FileList->Count; Index++)
+    {
+      UnicodeString FileName = FileList->Strings[Index];
+      UnicodeString Directory = ExtractFileDir(FileName);
+      Directories->Add(Directory);
+      UnicodeString NewFilePath = NewFileName;
+      if (TargetIsDirectory)
+      {
+        NewFilePath = TPath::Combine(NewFilePath, ExtractFileName(FileName));
+      }
+      if (!MoveFile(ApiPath(FileName).c_str(), ApiPath(NewFilePath).c_str()))
+      {
+        throw EOSExtException(FMTLOAD(RENAME_FILE_ERROR, (FileName, NewFilePath)));
+      }
+    }
+
+    for (int Index = 0; Index < Directories->Count; Index++)
     {
     {
-      ReloadLocalDirectory(NewDirectory);
+      ReloadLocalDirectory(Directories->Strings[Index]);
     }
     }
   }
   }
 }
 }

+ 1 - 1
source/forms/CustomScpExplorer.h

@@ -716,7 +716,7 @@ protected:
   void __fastcall DoSynchronizeChecklistCalculateSize(
   void __fastcall DoSynchronizeChecklistCalculateSize(
     TSynchronizeChecklist * Checklist, const TSynchronizeChecklist::TItemList & Items, void * Token);
     TSynchronizeChecklist * Checklist, const TSynchronizeChecklist::TItemList & Items, void * Token);
   void __fastcall DoSynchronizeMove(
   void __fastcall DoSynchronizeMove(
-    TOperationSide Side, const UnicodeString & FileName, const UnicodeString & NewFileName, TRemoteFile * RemoteFile);
+    TOperationSide Side, TStrings * FileList, const UnicodeString & NewFileName, bool TargetIsDirectory, void * Token);
   void __fastcall DoSynchronizeBrowse(TOperationSide Side, TSynchronizeChecklist::TAction Action, const TSynchronizeChecklist::TItem * Item);
   void __fastcall DoSynchronizeBrowse(TOperationSide Side, TSynchronizeChecklist::TAction Action, const TSynchronizeChecklist::TItem * Item);
   void __fastcall FullSynchronize(
   void __fastcall FullSynchronize(
     TSynchronizeParams & Params, TProcessedSynchronizationChecklistItem OnProcessedItem,
     TSynchronizeParams & Params, TProcessedSynchronizationChecklistItem OnProcessedItem,

+ 228 - 111
source/forms/SynchronizeChecklist.cpp

@@ -140,6 +140,128 @@ void __fastcall TSynchronizeChecklistDialog::UpdateCaption()
   }
   }
   Caption = Manager->FormatFormCaptionWithSession(this, Title);
   Caption = Manager->FormatFormCaptionWithSession(this, Title);
 }
 }
+//---------------------------------------------------------------------------
+static bool IsTransferNewAction(TSynchronizeChecklist::TAction Action)
+{
+  return (Action == TSynchronizeChecklist::saUploadNew) || (Action == TSynchronizeChecklist::saDownloadNew);
+}
+//---------------------------------------------------------------------------
+static TSynchronizeChecklist::TAction GetOppositeMoveAction(TSynchronizeChecklist::TAction Action1)
+{
+  switch (Action1)
+  {
+    case TSynchronizeChecklist::saUploadNew:
+      return TSynchronizeChecklist::saDeleteRemote;
+    case TSynchronizeChecklist::saDownloadNew:
+      return TSynchronizeChecklist::saDeleteLocal;
+    case TSynchronizeChecklist::saDeleteRemote:
+      return TSynchronizeChecklist::saUploadNew;
+    case TSynchronizeChecklist::saDeleteLocal:
+      return TSynchronizeChecklist::saDownloadNew;
+    default:
+      return TSynchronizeChecklist::saNone;
+  }
+}
+//---------------------------------------------------------------------
+struct TMoveActionData
+{
+  TSynchronizeChecklist::TAction Action;
+  bool AllItemsFiles;
+  bool AllItemsDirectories;
+  int ItemsCount;
+  std::unique_ptr<TStrings> FileList;
+  typedef std::vector<const TSynchronizeChecklist::TItem *> TChecklistItems;
+  TChecklistItems ChecklistItems;
+
+  TMoveActionData()
+  {
+    Action = TSynchronizeChecklist::saNone;
+    AllItemsFiles = true;
+    AllItemsDirectories = true;
+    ItemsCount = 0;
+  }
+
+  bool Collect(const TSynchronizeChecklist::TItem * ChecklistItem, TSynchronizeChecklist::TAction AAction, bool CollectFileList)
+  {
+    bool Result = (ItemsCount == 0) || (Action == AAction);
+    if (Result)
+    {
+      Action = AAction;
+      if (ChecklistItem->IsDirectory)
+      {
+        AllItemsFiles = false;
+      }
+      else
+      {
+        AllItemsDirectories = false;
+      }
+      ItemsCount++;
+
+      if (CollectFileList)
+      {
+        UnicodeString FileName;
+        TObject * Object;
+        if (Action == TSynchronizeChecklist::saDeleteRemote)
+        {
+          FileName = UnixCombinePaths(ChecklistItem->Remote.Directory, ChecklistItem->Remote.FileName);
+          Object = ChecklistItem->RemoteFile;
+        }
+        else if (Action == TSynchronizeChecklist::saDeleteLocal)
+        {
+          FileName = CombinePaths(ChecklistItem->Local.Directory, ChecklistItem->Local.FileName);
+        }
+
+        bool CollectFile = !FileName.IsEmpty();
+        DebugAssert(CollectFile == IsTransferNewAction(GetOppositeMoveAction(Action)));
+        if (CollectFile)
+        {
+          if (FileList.get() == NULL)
+          {
+            FileList.reset(new TStringList());
+          }
+          FileList->AddObject(FileName, Object);
+        }
+
+        ChecklistItems.push_back(ChecklistItem);
+      }
+    }
+    return Result;
+  }
+
+  bool IsOnlyDirectoryTransferAction()
+  {
+    return
+      IsTransferNewAction(Action) &&
+      (ItemsCount == 1) &&
+      AllItemsDirectories &&
+      DebugAlwaysTrue(!AllItemsFiles);
+  }
+};
+//---------------------------------------------------------------------
+struct TMoveData
+{
+  TMoveActionData FirstAction;
+  TMoveActionData SecondAction;
+  bool ThreeActions;
+
+  TMoveData(bool CollectFileList)
+  {
+    ThreeActions = false;
+    FCollectFileList = CollectFileList;
+  }
+
+  void Collect(const TSynchronizeChecklist::TItem * ChecklistItem, TSynchronizeChecklist::TAction Action)
+  {
+    if (!FirstAction.Collect(ChecklistItem, Action, FCollectFileList) &&
+        !SecondAction.Collect(ChecklistItem, Action, FCollectFileList))
+    {
+      ThreeActions = true;
+    }
+  }
+
+private:
+  bool FCollectFileList;
+};
 //---------------------------------------------------------------------
 //---------------------------------------------------------------------
 void __fastcall TSynchronizeChecklistDialog::UpdateControls()
 void __fastcall TSynchronizeChecklistDialog::UpdateControls()
 {
 {
@@ -152,11 +274,13 @@ void __fastcall TSynchronizeChecklistDialog::UpdateControls()
   bool AnyBoth = false;
   bool AnyBoth = false;
   bool AnyNonBoth = false;
   bool AnyNonBoth = false;
   bool AnyDirectory = false;
   bool AnyDirectory = false;
-  TListItem * Item = ListView->Selected;
-  while (Item != NULL)
+  TMoveData MoveData(false);
+  TListItem * Item = NULL;
+  while (IterateSelectedItems(Item))
   {
   {
     const TSynchronizeChecklist::TItem * ChecklistItem = GetChecklistItem(Item);
     const TSynchronizeChecklist::TItem * ChecklistItem = GetChecklistItem(Item);
     TSynchronizeChecklist::TAction Action = GetChecklistItemAction(ChecklistItem);
     TSynchronizeChecklist::TAction Action = GetChecklistItemAction(ChecklistItem);
+
     if ((Action == TSynchronizeChecklist::saUploadUpdate) ||
     if ((Action == TSynchronizeChecklist::saUploadUpdate) ||
         (Action == TSynchronizeChecklist::saDownloadUpdate))
         (Action == TSynchronizeChecklist::saDownloadUpdate))
     {
     {
@@ -167,6 +291,8 @@ void __fastcall TSynchronizeChecklistDialog::UpdateControls()
       AnyNonBoth = true;
       AnyNonBoth = true;
     }
     }
 
 
+    MoveData.Collect(ChecklistItem, Action);
+
     if (Item->Checked)
     if (Item->Checked)
     {
     {
       AllUnchecked = false;
       AllUnchecked = false;
@@ -179,9 +305,9 @@ void __fastcall TSynchronizeChecklistDialog::UpdateControls()
     {
     {
       AnyDirectory = true;
       AnyDirectory = true;
     }
     }
-    Item = ListView->GetNextItem(Item, sdAll, TItemStates() << isSelected);
   }
   }
 
 
+  int SelCount = ListView->SelCount;
   EnableControl(OkButton, (FChecked[0] > 0) && !FSynchronizing);
   EnableControl(OkButton, (FChecked[0] > 0) && !FSynchronizing);
   EnableControl(CancelButton, !FSynchronizing);
   EnableControl(CancelButton, !FSynchronizing);
   EnableControl(HelpButton, !FSynchronizing);
   EnableControl(HelpButton, !FSynchronizing);
@@ -192,17 +318,28 @@ void __fastcall TSynchronizeChecklistDialog::UpdateControls()
   CheckDirectoryAction->Enabled = CheckAllAction->Enabled; // sic
   CheckDirectoryAction->Enabled = CheckAllAction->Enabled; // sic
   UncheckDirectoryAction->Enabled = UncheckAllAction->Enabled; // sic
   UncheckDirectoryAction->Enabled = UncheckAllAction->Enabled; // sic
   CustomCommandsAction->Enabled = AnyBoth && !AnyNonBoth && DebugAlwaysTrue(!FSynchronizing);
   CustomCommandsAction->Enabled = AnyBoth && !AnyNonBoth && DebugAlwaysTrue(!FSynchronizing);
-  ReverseAction->Enabled = (ListView->SelCount > 0) && DebugAlwaysTrue(!FSynchronizing);
-  MoveAction->Enabled = (GetMoveItems() != TSynchronizeMoveItems());
-  CalculateSizeAction->Enabled = (ListView->SelCount > 0) && AnyDirectory && DebugAlwaysTrue(!FSynchronizing);
+  ReverseAction->Enabled = (SelCount > 0) && DebugAlwaysTrue(!FSynchronizing);
+  MoveAction->Enabled =
+    // All actions are of exactly two and opposite types
+    (MoveData.SecondAction.Action != TSynchronizeChecklist::saNone) &&
+    !MoveData.ThreeActions &&
+    (GetOppositeMoveAction(MoveData.FirstAction.Action) == MoveData.SecondAction.Action) &&
+    // ... and there are exactly two of them of the same file/dir type
+    (((SelCount == 2) && (MoveData.FirstAction.AllItemsFiles == MoveData.SecondAction.AllItemsFiles)) ||
+    // ... or the "transfer new" side is exactly one directory
+     MoveData.FirstAction.IsOnlyDirectoryTransferAction() ||
+     MoveData.SecondAction.IsOnlyDirectoryTransferAction());
+  CalculateSizeAction->Enabled = (SelCount > 0) && AnyDirectory && DebugAlwaysTrue(!FSynchronizing);
   CalculateSizeAllAction->Enabled = (FDirectories > 0) && !FSynchronizing;
   CalculateSizeAllAction->Enabled = (FDirectories > 0) && !FSynchronizing;
   TSynchronizeChecklist::TAction SelectedItemAction =
   TSynchronizeChecklist::TAction SelectedItemAction =
-    (ListView->SelCount == 1) ? GetChecklistItemAction(GetChecklistItem(ListView->Selected)) : TSynchronizeChecklist::saNone;
-  BrowseLocalAction->Enabled = (ListView->SelCount == 1) && (SelectedItemAction != TSynchronizeChecklist::saDeleteRemote);
-  BrowseRemoteAction->Enabled = (ListView->SelCount == 1) && (SelectedItemAction != TSynchronizeChecklist::saDeleteLocal);
+    (SelCount == 1) ? GetChecklistItemAction(GetChecklistItem(ListView->Selected)) : TSynchronizeChecklist::saNone;
+  BrowseLocalAction->Enabled = (SelCount == 1) && (SelectedItemAction != TSynchronizeChecklist::saDeleteRemote);
+  BrowseRemoteAction->Enabled = (SelCount == 1) && (SelectedItemAction != TSynchronizeChecklist::saDeleteLocal);
 
 
-  SelectAllAction->Enabled = (ListView->SelCount < ListView->Items->Count) && !FSynchronizing;
-  FindMoveCandidateAction->Enabled = (ListView->Items->Count > 0) && !FSynchronizing;
+  int Count = ListView->Items->Count;
+  DebugAssert(FTotals[0] == Count);
+  SelectAllAction->Enabled = (SelCount < Count) && !FSynchronizing;
+  FindMoveCandidateAction->Enabled = (Count > 0) && !FSynchronizing;
 }
 }
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 bool __fastcall TSynchronizeChecklistDialog::GetWindowParams(UnicodeString & WindowParams)
 bool __fastcall TSynchronizeChecklistDialog::GetWindowParams(UnicodeString & WindowParams)
@@ -788,16 +925,26 @@ void __fastcall TSynchronizeChecklistDialog::UncheckAllActionExecute(TObject * /
   CheckAll(false);
   CheckAll(false);
 }
 }
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
+bool TSynchronizeChecklistDialog::IterateItems(TListItem *& Item, TItemStates States)
+{
+  Item = ListView->GetNextItem(Item, sdAll, States);
+  return (Item != NULL);
+}
+//---------------------------------------------------------------------------
+bool TSynchronizeChecklistDialog::IterateSelectedItems(TListItem *& Item)
+{
+  return IterateItems(Item, TItemStates() << isSelected);
+}
+//---------------------------------------------------------------------------
 void __fastcall TSynchronizeChecklistDialog::Check(bool Check)
 void __fastcall TSynchronizeChecklistDialog::Check(bool Check)
 {
 {
   FChangingItemMass = true;
   FChangingItemMass = true;
   try
   try
   {
   {
-    TListItem * Item = ListView->Selected;
-    while (Item != NULL)
+    TListItem * Item = NULL;
+    while (IterateSelectedItems(Item))
     {
     {
       Item->Checked = Check;
       Item->Checked = Check;
-      Item = ListView->GetNextItem(Item, sdAll, TItemStates() << isSelected);
     }
     }
   }
   }
   __finally
   __finally
@@ -991,10 +1138,8 @@ void __fastcall TSynchronizeChecklistDialog::CustomCommandsActionExecute(
   TStrings * RemoteFileList = new TStringList();
   TStrings * RemoteFileList = new TStringList();
   try
   try
   {
   {
-    TListItem * Item = ListView->Selected;
-    DebugAssert(Item != NULL);
-
-    while (Item != NULL)
+    TListItem * Item = NULL;
+    while (IterateSelectedItems(Item))
     {
     {
       const TSynchronizeChecklist::TItem * ChecklistItem = GetChecklistItem(Item);
       const TSynchronizeChecklist::TItem * ChecklistItem = GetChecklistItem(Item);
 
 
@@ -1013,8 +1158,6 @@ void __fastcall TSynchronizeChecklistDialog::CustomCommandsActionExecute(
         ChecklistItem->Remote.FileName;
         ChecklistItem->Remote.FileName;
 
 
       RemoteFileList->AddObject(RemotePath, ChecklistItem->RemoteFile);
       RemoteFileList->AddObject(RemotePath, ChecklistItem->RemoteFile);
-
-      Item = ListView->GetNextItem(Item, sdAll, TItemStates() << isSelected);
     }
     }
   }
   }
   catch(...)
   catch(...)
@@ -1063,8 +1206,8 @@ void __fastcall TSynchronizeChecklistDialog::ReverseActionExecute(TObject * /*Se
 {
 {
   TAutoFlag Flag(FChangingItemMass);
   TAutoFlag Flag(FChangingItemMass);
 
 
-  TListItem * Item = ListView->Selected;
-  while (Item != NULL)
+  TListItem * Item = NULL;
+  while (IterateSelectedItems(Item))
   {
   {
     const TSynchronizeChecklist::TItem * ChecklistItem = GetChecklistItem(Item);
     const TSynchronizeChecklist::TItem * ChecklistItem = GetChecklistItem(Item);
     TSynchronizeChecklist::TAction & Action = GetChecklistItemAction(ChecklistItem);
     TSynchronizeChecklist::TAction & Action = GetChecklistItemAction(ChecklistItem);
@@ -1089,8 +1232,6 @@ void __fastcall TSynchronizeChecklistDialog::ReverseActionExecute(TObject * /*Se
 
 
       LoadItem(Item);
       LoadItem(Item);
     }
     }
-
-    Item = ListView->GetNextItem(Item, sdAll, TItemStates() << isSelected);
   }
   }
 
 
   Flag.Release();
   Flag.Release();
@@ -1218,7 +1359,7 @@ void TSynchronizeChecklistDialog::CalculateSize(bool All)
   }
   }
   TSynchronizeChecklist::TItemList Items;
   TSynchronizeChecklist::TItemList Items;
   TListItem * Item = NULL;
   TListItem * Item = NULL;
-  while ((Item = ListView->GetNextItem(Item, sdAll, States)) != NULL)
+  while (IterateItems(Item, States))
   {
   {
     const TSynchronizeChecklist::TItem * ChecklistItem = GetChecklistItem(Item);
     const TSynchronizeChecklist::TItem * ChecklistItem = GetChecklistItem(Item);
     Items.push_back(ChecklistItem);
     Items.push_back(ChecklistItem);
@@ -1248,69 +1389,6 @@ void __fastcall TSynchronizeChecklistDialog::CalculateSizeAllActionExecute(TObje
   CalculateSize(true);
   CalculateSize(true);
 }
 }
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
-TSynchronizeChecklist::TAction TSynchronizeChecklistDialog::GetOppositeMoveAction(TSynchronizeChecklist::TAction Action1)
-{
-  switch (Action1)
-  {
-    case TSynchronizeChecklist::saUploadNew:
-      return TSynchronizeChecklist::saDeleteRemote;
-    case TSynchronizeChecklist::saDownloadNew:
-      return TSynchronizeChecklist::saDeleteLocal;
-    case TSynchronizeChecklist::saDeleteRemote:
-      return TSynchronizeChecklist::saUploadNew;
-    case TSynchronizeChecklist::saDeleteLocal:
-      return TSynchronizeChecklist::saDownloadNew;
-    default:
-      return TSynchronizeChecklist::saNone;
-  }
-}
-//---------------------------------------------------------------------------
-bool TSynchronizeChecklistDialog::IsTransferNewAction(TSynchronizeChecklist::TAction Action)
-{
-  return (Action == TSynchronizeChecklist::saUploadNew) || (Action == TSynchronizeChecklist::saDownloadNew);
-}
-//---------------------------------------------------------------------------
-TSynchronizeChecklistDialog::TSynchronizeMoveItems __fastcall TSynchronizeChecklistDialog::GetMoveItems()
-{
-  if ((ListView->SelCount != 2) || DebugAlwaysFalse(FSynchronizing))
-  {
-    return TSynchronizeMoveItems();
-  }
-  else
-  {
-    TListItem * Item1 = ListView->Selected;
-    const TSynchronizeChecklist::TItem * ChecklistItem1 = GetChecklistItem(DebugNotNull(Item1));
-    TListItem * Item2 = ListView->GetNextItem(ListView->Selected, sdAll, TItemStates() << isSelected);
-    const TSynchronizeChecklist::TItem * ChecklistItem2 = GetChecklistItem(DebugNotNull(Item2));
-
-    if (ChecklistItem1->IsDirectory != ChecklistItem2->IsDirectory)
-    {
-      return TSynchronizeMoveItems();
-    }
-    else
-    {
-      TSynchronizeChecklist::TAction Action1 = GetChecklistItemAction(ChecklistItem1);
-      TSynchronizeChecklist::TAction Action2 = GetChecklistItemAction(ChecklistItem2);
-
-      if (GetOppositeMoveAction(Action1) == Action2)
-      {
-        if (IsTransferNewAction(Action1))
-        {
-          return TSynchronizeMoveItems(ChecklistItem1, ChecklistItem2);
-        }
-        else
-        {
-          return TSynchronizeMoveItems(ChecklistItem2, ChecklistItem1);
-        }
-      }
-      else
-      {
-        return TSynchronizeMoveItems();
-      }
-    }
-  }
-}
-//---------------------------------------------------------------------------
 void __fastcall TSynchronizeChecklistDialog::DeleteItem(const TSynchronizeChecklist::TItem * ChecklistItem)
 void __fastcall TSynchronizeChecklistDialog::DeleteItem(const TSynchronizeChecklist::TItem * ChecklistItem)
 {
 {
   TListItem * Item = FChecklistToListViewMap[ChecklistItem];
   TListItem * Item = FChecklistToListViewMap[ChecklistItem];
@@ -1344,50 +1422,89 @@ void __fastcall TSynchronizeChecklistDialog::DeleteItem(const TSynchronizeCheckl
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 void __fastcall TSynchronizeChecklistDialog::MoveActionExecute(TObject *)
 void __fastcall TSynchronizeChecklistDialog::MoveActionExecute(TObject *)
 {
 {
-  TSynchronizeMoveItems MoveItems = GetMoveItems();
-  TSynchronizeChecklist::TAction Action2 = GetChecklistItemAction(MoveItems.second);
+  TMoveData MoveData(true);
+  TListItem * Item = NULL;
+  while (IterateSelectedItems(Item))
+  {
+    const TSynchronizeChecklist::TItem * ChecklistItem = GetChecklistItem(Item);
+    TSynchronizeChecklist::TAction Action = GetChecklistItemAction(ChecklistItem);
+    MoveData.Collect(ChecklistItem, Action);
+  }
+
+  TMoveActionData * TransferAction = &MoveData.FirstAction;
+  TMoveActionData * DeleteAction = &MoveData.SecondAction;
+  if (IsTransferNewAction(DeleteAction->Action))
+  {
+    std::swap(TransferAction, DeleteAction);
+  }
+  DebugAssert(IsTransferNewAction(TransferAction->Action));
+  DebugAssert(GetOppositeMoveAction(DeleteAction->Action) == TransferAction->Action);
 
 
   TOperationSide Side;
   TOperationSide Side;
-  UnicodeString FileName;
   UnicodeString NewFileName;
   UnicodeString NewFileName;
-  TRemoteFile * RemoteFile;
-  if (Action2 == TSynchronizeChecklist::saDeleteRemote)
+  DebugAssert(TransferAction->ChecklistItems.size() == 1);
+  const TSynchronizeChecklist::TItem * TransferChecklistItem = TransferAction->ChecklistItems[0];
+  if (DeleteAction->Action == TSynchronizeChecklist::saDeleteRemote)
   {
   {
     Side = osRemote;
     Side = osRemote;
-    FileName = UnixCombinePaths(MoveItems.second->Remote.Directory, MoveItems.second->Remote.FileName);
-    NewFileName = UnixCombinePaths(MoveItems.first->Remote.Directory, MoveItems.first->Local.FileName);
-    RemoteFile = MoveItems.second->RemoteFile;
+    NewFileName = UnixCombinePaths(TransferChecklistItem->Remote.Directory, TransferChecklistItem->Local.FileName);
   }
   }
-  else if (Action2 == TSynchronizeChecklist::saDeleteLocal)
+  else if (DebugAlwaysTrue(DeleteAction->Action == TSynchronizeChecklist::saDeleteLocal))
   {
   {
     Side = osLocal;
     Side = osLocal;
-    FileName = CombinePaths(MoveItems.second->Local.Directory, MoveItems.second->Local.FileName);
-    NewFileName = CombinePaths(MoveItems.first->Local.Directory, MoveItems.first->Remote.FileName);
-    RemoteFile = NULL;
+    NewFileName = CombinePaths(TransferChecklistItem->Local.Directory, TransferChecklistItem->Remote.FileName);
   }
   }
-  else
+
+  if (DebugAlwaysTrue(!NewFileName.IsEmpty()))
   {
   {
-    DebugFail();
-    Abort();
-  }
+    bool Move = false;
+    bool TargetIsDirectory;
+    if ((TransferAction->AllItemsDirectories == DeleteAction->AllItemsDirectories) &&
+        DebugAlwaysTrue(TransferAction->ItemsCount == 1) &&
+        DebugAlwaysTrue(DeleteAction->ItemsCount == 1))
+    {
+      Move = true;
+      TargetIsDirectory = false;
+    }
+    else if (DebugAlwaysTrue(TransferAction->AllItemsDirectories && (TransferAction->ItemsCount == 1)))
+    {
+      TargetIsDirectory = true;
+      Move = true;
+    }
 
 
-  FOnSynchronizeMove(Side, FileName, NewFileName, RemoteFile);
+    if (DebugAlwaysTrue(Move))
+    {
+      FOnSynchronizeMove(Side, DeleteAction->FileList.get(), NewFileName, TargetIsDirectory, FToken);
 
 
-  DeleteItem(MoveItems.first);
-  DeleteItem(MoveItems.second);
-  UpdateControls();
+      TMoveActionData::TChecklistItems::const_iterator I = DeleteAction->ChecklistItems.begin();
+      while (I != DeleteAction->ChecklistItems.end())
+      {
+        DeleteItem(*I);
+        I++;
+      }
+      if (!TargetIsDirectory)
+      {
+        DeleteItem(TransferChecklistItem);
+      }
+      else
+      {
+        // The remaning "transfer" item
+        ListView->Selected->MakeVisible(false);
+      }
+      UpdateControls();
+    }
+  }
 }
 }
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 void __fastcall TSynchronizeChecklistDialog::CheckDirectory(bool Check)
 void __fastcall TSynchronizeChecklistDialog::CheckDirectory(bool Check)
 {
 {
   std::unique_ptr<TStringList> Directories(new TStringList());
   std::unique_ptr<TStringList> Directories(new TStringList());
-  TListItem * Item = ListView->Selected;
-  while (Item != NULL)
+  TListItem * Item = NULL;
+  while (IterateSelectedItems(Item))
   {
   {
     const TSynchronizeChecklist::TItem * ChecklistItem = GetChecklistItem(Item);
     const TSynchronizeChecklist::TItem * ChecklistItem = GetChecklistItem(Item);
     // It does not matter if we use local or remote directory
     // It does not matter if we use local or remote directory
     Directories->Add(IncludeTrailingBackslash(ChecklistItem->Local.Directory));
     Directories->Add(IncludeTrailingBackslash(ChecklistItem->Local.Directory));
-    Item = ListView->GetNextItem(Item, sdAll, TItemStates() << isSelected);
   }
   }
 
 
   TAutoFlag ChangingItemMassSwitch(FChangingItemMass);
   TAutoFlag ChangingItemMassSwitch(FChangingItemMass);

+ 2 - 4
source/forms/SynchronizeChecklist.h

@@ -193,8 +193,6 @@ protected:
   void __fastcall CountItemSize(const TSynchronizeChecklist::TItem * ChecklistItem, int Factor);
   void __fastcall CountItemSize(const TSynchronizeChecklist::TItem * ChecklistItem, int Factor);
   void __fastcall CountItem(const TSynchronizeChecklist::TItem * ChecklistItem, int Factor);
   void __fastcall CountItem(const TSynchronizeChecklist::TItem * ChecklistItem, int Factor);
   void __fastcall CountItemTotal(const TSynchronizeChecklist::TItem * ChecklistItem, int Factor);
   void __fastcall CountItemTotal(const TSynchronizeChecklist::TItem * ChecklistItem, int Factor);
-  typedef std::pair<const TSynchronizeChecklist::TItem *, const TSynchronizeChecklist::TItem *> TSynchronizeMoveItems;
-  TSynchronizeMoveItems __fastcall GetMoveItems();
   void __fastcall DeleteItem(const TSynchronizeChecklist::TItem * ChecklistItem);
   void __fastcall DeleteItem(const TSynchronizeChecklist::TItem * ChecklistItem);
   void __fastcall CheckDirectory(bool Check);
   void __fastcall CheckDirectory(bool Check);
   void __fastcall DoBrowse(TOperationSide Side);
   void __fastcall DoBrowse(TOperationSide Side);
@@ -203,8 +201,8 @@ protected:
   DYNAMIC void __fastcall KeyDown(Word & Key, TShiftState Shift);
   DYNAMIC void __fastcall KeyDown(Word & Key, TShiftState Shift);
   void CalculateSize(bool All);
   void CalculateSize(bool All);
   TIEListViewColProperties * GetColProperties();
   TIEListViewColProperties * GetColProperties();
-  TSynchronizeChecklist::TAction GetOppositeMoveAction(TSynchronizeChecklist::TAction Action1);
-  bool IsTransferNewAction(TSynchronizeChecklist::TAction Action);
+  bool IterateItems(TListItem *& Item, TItemStates States);
+  bool IterateSelectedItems(TListItem *& Item);
 };
 };
 //----------------------------------------------------------------------------
 //----------------------------------------------------------------------------
 #endif
 #endif

+ 1 - 1
source/windows/WinInterface.h

@@ -395,7 +395,7 @@ typedef void __fastcall (__closure *TFullSynchronizeEvent)(
 typedef void __fastcall (__closure *TSynchronizeChecklistCalculateSize)
 typedef void __fastcall (__closure *TSynchronizeChecklistCalculateSize)
   (TSynchronizeChecklist * Checklist, const TSynchronizeChecklist::TItemList & Items, void * Token);
   (TSynchronizeChecklist * Checklist, const TSynchronizeChecklist::TItemList & Items, void * Token);
 typedef void __fastcall (__closure *TSynchronizeMoveEvent)(
 typedef void __fastcall (__closure *TSynchronizeMoveEvent)(
-  TOperationSide Side, const UnicodeString & FileName, const UnicodeString & NewFileName, TRemoteFile * RemoteFile);
+  TOperationSide Side, TStrings * FileList, const UnicodeString & NewFileName, bool TargetIsDirectory, void * Token);
 typedef void __fastcall (__closure *TSynchronizeBrowseEvent)(
 typedef void __fastcall (__closure *TSynchronizeBrowseEvent)(
   TOperationSide Side, TSynchronizeChecklist::TAction Action, const TSynchronizeChecklist::TItem * Item);
   TOperationSide Side, TSynchronizeChecklist::TAction Action, const TSynchronizeChecklist::TItem * Item);
 bool __fastcall DoSynchronizeChecklistDialog(TSynchronizeChecklist * Checklist,
 bool __fastcall DoSynchronizeChecklistDialog(TSynchronizeChecklist * Checklist,