View Issue Details

IDProjectCategoryView StatusLast Update
0001927Double CommanderLogicpublic2020-11-30 07:44
ReporterCryHam Assigned Tocordylus  
PriorityhighSeverityminorReproducibilityalways
Status closedResolutionopen 
ProjectionnoneETAnone 
Product Version1.0.0 (trunk)Product Buildtrunk@7564 
Summary0001927: Compare by Contents - Not Implemented from Search result and archives.
DescriptionThe command: File - Compare by Contents, does not work from:
- search results (after find with Alt-f7 and used Feed to listbox)
- archives (e.g. browsing inside a zip)
It shows a messagebox "Not Implemented."
Basically it only works when both files are in a regular view of directory.

In code as: msgWarning(rsMsgNotImplemented);
in file umaincommands.pas, procedure TMainCommands.cm_CompareContents
Not sure what extra code search result needs. But for archives I guess a standard way is to extract to a random temporary file (in system TEMP) and use pass its name to the diff tool.
Would be great to implement it.
Steps To Reproduce(are in description)
TagsNo tags attached.
Attached Files
compare-search-result.diff (1,456 bytes)   
Index: src/umaincommands.pas
===================================================================
--- src/umaincommands.pas	(revision 7831)
+++ src/umaincommands.pas	(working copy)
@@ -2674,7 +2674,7 @@
 begin
   with frmMain do
   begin
-    // For now work only for filesystem.
+    // For now work only for filesystem and search result.
     // Later use temporary file system for other file sources.
 
     try
@@ -2689,8 +2689,9 @@
       end
       else
       begin
-        // For now work only for filesystem.
-        if not (ActiveFrame.FileSource.IsClass(TFileSystemFileSource)) then
+        // For now work only for filesystem and search result.
+        if not (ActiveFrame.FileSource.IsClass(TFileSystemFileSource))
+        and not (ActiveFrame.FileSource.IsClass(TSearchResultFileSource)) then
         begin
           msgWarning(rsMsgNotImplemented);
           Exit;
@@ -2719,8 +2720,9 @@
 
             if NotActiveSelectedFiles.Count = 1 then
             begin
-              // For now work only for filesystem.
-              if not (NotActiveFrame.FileSource.IsClass(TFileSystemFileSource)) then
+              // For now work only for filesystem and search result.
+              if not (NotActiveFrame.FileSource.IsClass(TFileSystemFileSource))
+              and not (NotActiveFrame.FileSource.IsClass(TSearchResultFileSource)) then
               begin
                 msgWarning(rsMsgNotImplemented);
                 Exit;
compare-search-result.diff (1,456 bytes)   
bug1927.patch (38,295 bytes)   
Index: src/fdiffer.pas
===================================================================
--- src/fdiffer.pas	(revision 7907)
+++ src/fdiffer.pas	(working copy)
@@ -29,7 +29,7 @@
   Classes, SysUtils, FileUtil, Forms, Controls, Dialogs, Menus, ComCtrls,
   ActnList, ExtCtrls, EditBtn, Buttons, SynEdit, uSynDiffControls,
   uPariterControls, uDiffOND, uFormCommands, uHotkeyManager, uOSForms,
-  uBinaryDiffViewer;
+  uBinaryDiffViewer, uShowForm;
 
 type
 
@@ -214,6 +214,7 @@
     EncodingList: TStringList;
     ScrollLock: LongInt;
     FShowIdentical: Boolean;
+    FWaitData: TWaitData;
     FCommands: TFormCommands;
     procedure ShowIdentical;
     procedure Clear(bLeft, bRight: Boolean);
@@ -248,7 +249,7 @@
     procedure cm_SaveRight(const Params: array of string);
   end; 
 
-procedure ShowDiffer(const FileNameLeft, FileNameRight: String);
+procedure ShowDiffer(const FileNameLeft, FileNameRight: String; WaitData: TWaitData = nil);
 
 implementation
 
@@ -261,10 +262,11 @@
 const
   HotkeysCategory = 'Differ';
 
-procedure ShowDiffer(const FileNameLeft, FileNameRight: String);
+procedure ShowDiffer(const FileNameLeft, FileNameRight: String; WaitData: TWaitData = nil);
 begin
   with TfrmDiffer.Create(Application) do
   begin
+    FWaitData := WaitData;
     edtFileNameLeft.Text:= FileNameLeft;
     edtFileNameRight.Text:= FileNameRight;
     FShowIdentical:= actAutoCompare.Checked;
@@ -984,6 +986,7 @@
   BinaryViewerRight.SecondViewer:= nil;
   HotMan.UnRegister(Self);
   inherited Destroy;
+  if Assigned(FWaitData) then FWaitData.Done;
 end;
 
 procedure TfrmDiffer.BuildHashList(bLeft, bRight: Boolean);
Index: src/feditor.pas
===================================================================
--- src/feditor.pas	(revision 7907)
+++ src/feditor.pas	(working copy)
@@ -161,7 +161,7 @@
     sEncodingIn,
     sEncodingOut,
     sOriginalText: String;
-    FWaitData: TEditorWaitData;
+    FWaitData: TWaitData;
     FCommands: TFormCommands;
 
     property Commands: TFormCommands read FCommands implements IFormCommands;
@@ -222,8 +222,7 @@
      procedure cm_EditRplc(const {%H-}Params:array of string);
   end;
 
-  procedure ShowEditor(WaitData: TEditorWaitData);
-  function ShowEditor(const sFileName: String): TfrmEditor;
+  procedure ShowEditor(const sFileName: String; WaitData: TWaitData = nil);
 
 implementation
 
@@ -234,29 +233,24 @@
   uLng, uShowMsg, fEditSearch, uGlobs, fOptions, DCClassesUtf8,
   uOSUtils, uConvEncoding, fOptionsToolsEditor, uDCUtils, uClipboard;
 
-function ShowEditor(const sFileName: String): TfrmEditor;
+procedure ShowEditor(const sFileName: String; WaitData: TWaitData = nil);
+var
+  Editor: TfrmEditor;
 begin
-  Result := TfrmEditor.Create(Application);
+  Editor := TfrmEditor.Create(Application);
+  Editor.FWaitData := WaitData;
 
   if sFileName = '' then
-    Result.cm_FileNew([''])
+    Editor.cm_FileNew([''])
   else
   begin
-    if not Result.OpenFile(sFileName) then
+    if not Editor.OpenFile(sFileName) then
       Exit;
   end;
 
-  Result.ShowOnTop;
+  Editor.ShowOnTop;
 end;
 
-procedure ShowEditor(WaitData: TEditorWaitData);
-var
-  Editor: TfrmEditor;
-begin
-  Editor:= ShowEditor(WaitData.FileName);
-  Editor.FWaitData:= WaitData
-end;
-
 procedure TfrmEditor.FormCreate(Sender: TObject);
 var
   i:Integer;
@@ -536,7 +530,7 @@
 begin
   HotMan.UnRegister(Self);
   inherited Destroy;
-  if Assigned(FWaitData) then EditDone(FWaitData);
+  if Assigned(FWaitData) then FWaitData.Done;
 end;
 
 
Index: src/ffileexecuteyourself.pas
===================================================================
--- src/ffileexecuteyourself.pas	(revision 7907)
+++ src/ffileexecuteyourself.pas	(working copy)
@@ -46,13 +46,13 @@
     procedure FormCreate(Sender: TObject);
   private
     FFileSource: IFileSource;
-    FWaitData: TEditorWaitData;
+    FWaitData: TWaitData;
   public
     constructor Create(TheOwner: TComponent; aFileSource: IFileSource; const FileName, FromPath: String); reintroduce;
     destructor Destroy; override;
   end; 
 
-  procedure ShowFileEditExternal(aWaitData: TEditorWaitData);
+  procedure ShowFileEditExternal(const FileName, FromPath: string; aWaitData: TWaitData);
   function ShowFileExecuteYourSelf(aFileView: TFileView; aFile: TFile; bWithAll: Boolean): Boolean;
 
 implementation
@@ -62,13 +62,10 @@
 uses
   LCLProc, uTempFileSystemFileSource, uFileSourceOperation, uShellExecute, DCOSUtils;
 
-procedure ShowFileEditExternal(aWaitData: TEditorWaitData);
-var
-  APath: String;
+procedure ShowFileEditExternal(const FileName, FromPath: string; aWaitData: TWaitData);
 begin
-  APath:= aWaitData.TargetFileSource.CurrentAddress + aWaitData.TargetPath;
   // Create wait window
-  with TfrmFileExecuteYourSelf.Create(Application, nil, ExtractFileName(aWaitData.FileName), APath) do
+  with TfrmFileExecuteYourSelf.Create(Application, nil, FileName, FromPath) do
   begin
     FWaitData:= aWaitData;
     // Show wait window
@@ -155,7 +152,7 @@
   // Delete the temporary file source and all files inside.
   FFileSource:= nil;
   inherited Destroy;
-  if Assigned(FWaitData) then EditDone(FWaitData);
+  if Assigned(FWaitData) then FWaitData.Done;
 end;
 
 end.
Index: src/umaincommands.pas
===================================================================
--- src/umaincommands.pas	(revision 7907)
+++ src/umaincommands.pas	(working copy)
@@ -505,31 +505,13 @@
 procedure TMainCommands.OnEditCopyOutStateChanged(Operation: TFileSourceOperation;
                                                   State: TFileSourceOperationState);
 var
-  aFile: TFile;
   WaitData: TEditorWaitData;
-  aFileSource: ITempFileSystemFileSource;
-  aCopyOutOperation: TFileSourceCopyOperation;
 begin
   if (State = fsosStopped) and (Operation.Result = fsorFinished) then
   begin
-    aCopyOutOperation := Operation as TFileSourceCopyOperation;
-    aFileSource := aCopyOutOperation.TargetFileSource as ITempFileSystemFileSource;
-
     try
-      aFile := aCopyOutOperation.SourceFiles[0];
-
-      WaitData:= TEditorWaitData.Create;
-
+      WaitData := TEditorWaitData.Create(Operation as TFileSourceCopyOperation);
       try
-        WaitData.TargetPath:= aCopyOutOperation.SourceFiles.Path;
-
-        ChangeFileListRoot(aFileSource.FileSystemRoot, aCopyOutOperation.SourceFiles);
-
-        WaitData.FileName:= aFile.FullPath;
-        WaitData.SourceFileSource:= aFileSource;
-        WaitData.FileTime:= mbFileAge(WaitData.FileName);
-        WaitData.TargetFileSource:= aCopyOutOperation.FileSource as IFileSource;
-
         ShowEditorByGlob(WaitData);
       except
         WaitData.Free;
@@ -1737,9 +1719,6 @@
   ActiveFile: TFile = nil;
   AllFiles: TFiles = nil;
   SelectedFiles: TFiles = nil;
-  TempFiles: TFiles = nil;
-  TempFileSource: ITempFileSystemFileSource = nil;
-  Operation: TFileSourceOperation;
   aFileSource: IFileSource;
   sCmd: string = '';
   sParams: string = '';
@@ -1767,44 +1746,8 @@
     // Default to using the file source directly.
     aFileSource := ActiveFrame.FileSource;
 
-    // If files are links to local files
-    if (fspLinksToLocalFiles in ActiveFrame.FileSource.Properties) then
-      begin
-        for I := 0 to SelectedFiles.Count - 1 do
-          begin
-            aFile := SelectedFiles[I];
-            ActiveFrame.FileSource.GetLocalName(aFile);
-          end;
-      end
-    // If files not directly accessible copy them to temp file source.
-    else if not (fspDirectAccess in ActiveFrame.FileSource.Properties) then
-    begin
-      if not (fsoCopyOut in ActiveFrame.FileSource.GetOperationsTypes) then
-      begin
-        msgWarning(rsMsgErrNotSupported);
-        Exit;
-      end;
-
-      TempFiles := SelectedFiles.Clone;
-
-      TempFileSource := TTempFileSystemFileSource.GetFileSource;
-
-      Operation := ActiveFrame.FileSource.CreateCopyOutOperation(
-                       TempFileSource,
-                       TempFiles,
-                       TempFileSource.FileSystemRoot);
-
-      if Assigned(Operation) then
-      begin
-        Operation.AddStateChangedListener([fsosStopped], @OnCopyOutStateChanged);
-        OperationsManager.AddOperation(Operation);
-      end
-      else
-      begin
-        msgWarning(rsMsgErrNotSupported);
-      end;
+    if PrepareData(ActiveFrame.FileSource, SelectedFiles, @OnCopyOutStateChanged) <> pdrSynchronous then
       Exit;
-    end;
 
     try
       aFile := SelectedFiles[0];
@@ -1875,7 +1818,6 @@
     FreeAndNil(sl);
     FreeAndNil(AllFiles);
     FreeAndNil(SelectedFiles);
-    FreeAndNil(TempFiles);
     FreeAndNil(ActiveFile);
   end;
 end;
@@ -2026,10 +1968,7 @@
 var
   i: Integer;
   aFile: TFile;
-  TempFiles: TFiles;
   SelectedFiles: TFiles = nil;
-  Operation: TFileSourceOperation;
-  TempFileSource: ITempFileSystemFileSource = nil;
   sCmd: string = '';
   sParams: string = '';
   sStartPath: string = '';
@@ -2037,44 +1976,9 @@
   with frmMain do
   try
     SelectedFiles := ActiveFrame.CloneSelectedOrActiveFiles;
-    // If files are links to local files
-    if (fspLinksToLocalFiles in ActiveFrame.FileSource.Properties) then
-      begin
-        for I := 0 to SelectedFiles.Count - 1 do
-          begin
-            aFile := SelectedFiles[I];
-            ActiveFrame.FileSource.GetLocalName(aFile);
-          end;
-      end
-    // If files not directly accessible copy them to temp file source.
-    else if not (fspDirectAccess in ActiveFrame.FileSource.Properties) then
-    begin
-      if not (fsoCopyOut in ActiveFrame.FileSource.GetOperationsTypes) then
-      begin
-        msgWarning(rsMsgErrNotSupported);
-        Exit;
-      end;
 
-      TempFiles := SelectedFiles.Clone;
-
-      TempFileSource := TTempFileSystemFileSource.GetFileSource;
-
-      Operation := ActiveFrame.FileSource.CreateCopyOutOperation(
-                       TempFileSource,
-                       TempFiles,
-                       TempFileSource.FileSystemRoot);
-
-      if Assigned(Operation) then
-      begin
-        Operation.AddStateChangedListener([fsosStopped], @OnEditCopyOutStateChanged);
-        OperationsManager.AddOperation(Operation);
-      end
-      else
-      begin
-        msgWarning(rsMsgErrNotSupported);
-      end;
+    if PrepareData(ActiveFrame.FileSource, SelectedFiles, @OnEditCopyOutStateChanged) <> pdrSynchronous then
       Exit;
-    end;
 
     try
       for i := 0 to SelectedFiles.Count - 1 do
@@ -2655,15 +2559,18 @@
 
 procedure TMainCommands.cm_CompareContents(const Params: array of string);
 var
-  FilesToCompare: TStringList = nil;
-  DirsToCompare: TStringList = nil;
+  FilesNumber: Integer = 0;
+  DirsNumber: Integer = 0;
 
-  procedure AddItem(const aFile: TFile);
+  procedure CountFiles(const Files: TFiles);
+  var I: Integer;
   begin
-    if not aFile.IsDirectory then
-      FilesToCompare.Add(aFile.FullPath)
-    else
-      DirsToCompare.Add(aFile.FullPath);
+    if Assigned(Files) then
+      for I := 0 to Files.Count - 1 do
+        if Files[I].IsDirectory then
+          Inc(DirsNumber)
+        else
+          Inc(FilesNumber);
   end;
 
 var
@@ -2671,31 +2578,19 @@
   Param: String;
   ActiveSelectedFiles: TFiles = nil;
   NotActiveSelectedFiles: TFiles = nil;
+  FirstFileSource: IFileSource = nil;
+  FirstFileSourceFiles: TFiles = nil;
+  SecondFileSource: IFileSource = nil;
+  SecondFileSourceFiles: TFiles = nil;
 begin
   with frmMain do
   begin
-    // For now works only for file source with direct access.
-    // Later use temporary file system for other file sources.
-
-    try
-      FilesToCompare := TStringList.Create;
-      DirsToCompare := TStringList.Create;
       Param := GetDefaultParam(Params);
 
       if Param = 'dir' then
-      begin
-        DirsToCompare.Add(FrameLeft.CurrentPath);
-        DirsToCompare.Add(FrameRight.CurrentPath);
-      end
+        ShowDifferByGlob(FrameLeft.CurrentPath, FrameRight.CurrentPath)
       else
       begin
-        // For now works only for file source with direct access.
-        if not (fspDirectAccess in ActiveFrame.FileSource.Properties) then
-        begin
-          msgWarning(rsMsgNotImplemented);
-          Exit;
-        end;
-
         try
           ActiveSelectedFiles := ActiveFrame.CloneSelectedOrActiveFiles;
 
@@ -2719,31 +2614,31 @@
 
             if NotActiveSelectedFiles.Count = 1 then
             begin
-              // For now works only for file source with direct access.
-              if not (fspDirectAccess in NotActiveFrame.FileSource.Properties) then
-              begin
-                msgWarning(rsMsgNotImplemented);
-                Exit;
-              end;
 
               { compare single selected files in both panels }
 
               case gResultingFramePositionAfterCompare of
                 rfpacActiveOnLeft:
-                  begin;
-                    AddItem(ActiveSelectedFiles[0]);
-                    AddItem(NotActiveSelectedFiles[0]);
+                  begin
+                    FirstFileSource := ActiveFrame.FileSource;
+                    FirstFileSourceFiles := ActiveSelectedFiles;
+                    SecondFileSource := NotActiveFrame.FileSource;
+                    SecondFileSourceFiles := NotActiveSelectedFiles;
                   end;
                 rfpacLeftOnLeft:
                   begin
                     if ActiveFrame = FrameLeft then
                     begin
-                      AddItem(ActiveSelectedFiles[0]);
-                      AddItem(NotActiveSelectedFiles[0]);
+                      FirstFileSource := ActiveFrame.FileSource;
+                      FirstFileSourceFiles := ActiveSelectedFiles;
+                      SecondFileSource := NotActiveFrame.FileSource;
+                      SecondFileSourceFiles := NotActiveSelectedFiles;
                     end
                     else begin
-                      AddItem(NotActiveSelectedFiles[0]);
-                      AddItem(ActiveSelectedFiles[0]);
+                      FirstFileSource := NotActiveFrame.FileSource;
+                      FirstFileSourceFiles := NotActiveSelectedFiles;
+                      SecondFileSource := ActiveFrame.FileSource;
+                      SecondFileSourceFiles := ActiveSelectedFiles;
                     end;
                   end;
                end;
@@ -2760,45 +2655,39 @@
           begin
             { compare all selected files in active frame }
 
-            for I := 0 to ActiveSelectedFiles.Count - 1 do
-              AddItem(ActiveSelectedFiles[I]);
+            FirstFileSource := ActiveFrame.FileSource;
+            FirstFileSourceFiles := ActiveSelectedFiles;
           end;
 
+          CountFiles(FirstFileSourceFiles);
+          CountFiles(SecondFileSourceFiles);
+
+          if ((FilesNumber > 0) and (DirsNumber > 0))
+          or ((FilesNumber = 1) or (DirsNumber = 1)) then
+            // Either files or directories must be selected and more than one.
+            MsgWarning(rsMsgInvalidSelection)
+          else if (FilesNumber = 0) and (DirsNumber = 0) then
+            MsgWarning(rsMsgNoFilesSelected)
+          else if (FilesNumber > 2) and not gExternalTools[etDiffer].Enabled then
+            MsgWarning(rsMsgTooManyFilesSelected)
+          else if (DirsNumber > 0) and not gExternalTools[etDiffer].Enabled then
+            MsgWarning(rsMsgNotImplemented)
+          else
+          begin
+            if not Assigned(SecondFileSource) then
+              PrepareToolData(FirstFileSource, FirstFileSourceFiles,
+                              @ShowDifferByGlobList)
+            else
+              PrepareToolData(FirstFileSource, FirstFileSourceFiles,
+                              SecondFileSource, SecondFileSourceFiles,
+                              @ShowDifferByGlobList);
+          end;
+
         finally
           FreeAndNil(ActiveSelectedFiles);
           FreeAndNil(NotActiveSelectedFiles);
         end;
       end;
-
-      if ((FilesToCompare.Count > 0) and (DirsToCompare.Count > 0))
-      or ((FilesToCompare.Count = 1) or (DirsToCompare.Count = 1)) then
-      begin
-         // Either files or directories must be selected and more than one.
-         MsgWarning(rsMsgInvalidSelection);
-      end
-      else if FilesToCompare.Count > 0 then
-      begin
-        if gExternalTools[etDiffer].Enabled then
-          RunExtDiffer(FilesToCompare)
-        else if FilesToCompare.Count = 2 then
-          ShowDiffer(FilesToCompare.Strings[0], FilesToCompare.Strings[1])
-        else
-          MsgWarning(rsMsgTooManyFilesSelected);
-      end
-      else if DirsToCompare.Count > 0 then
-      begin
-        if gExternalTools[etDiffer].Enabled then
-          RunExtDiffer(DirsToCompare)
-        else
-          MsgWarning(rsMsgNotImplemented);
-      end
-      else
-        msgWarning(rsMsgNoFilesSelected);
-
-    finally
-      FreeAndNil(FilesToCompare);
-      FreeAndNil(DirsToCompare);
-    end;
   end;
 end;
 
Index: src/ushowform.pas
===================================================================
--- src/ushowform.pas	(revision 7907)
+++ src/ushowform.pas	(working copy)
@@ -19,27 +19,57 @@
 interface
 
 uses
-  Classes, DCBasicTypes, uFileSource, uFileSourceOperation;
+  Classes, DCBasicTypes, uFileSource, uFileSourceOperation, uFile,
+  uFileSourceCopyOperation;
 
 type
 
+  { TWaitData }
+
+  TWaitData = class
+  public
+    procedure ShowWaitForm; virtual; abstract;
+    procedure Done; virtual; abstract;
+  end;
+
   { TEditorWaitData }
 
-  TEditorWaitData = class
+  TEditorWaitData = class(TWaitData)
   public
-    FileName: String;
+    Files: TFiles;
+    function GetFileList: TStringList;
+  protected
+    FileTimes: array of TFileTime;
     TargetPath: String;
-    FileTime: TFileTime;
     SourceFileSource: IFileSource;
     TargetFileSource: IFileSource;
+    function GetRelativeFileName(const FullPath: string): string;
+    function GetRelativeFileNames: string;
+    function GetFromPath: string;
   public
+    constructor Create(aCopyOutOperation: TFileSourceCopyOperation);
     destructor Destroy; override;
+    procedure ShowWaitForm; override;
+    procedure Done; override;
   protected
     procedure OnCopyInStateChanged(Operation: TFileSourceOperation;
                                    State: TFileSourceOperationState);
   end;
 
-procedure EditDone(WaitData: TEditorWaitData);
+  TToolDataPreparedProc = procedure(const FileList: TStringList; WaitData: TWaitData);
+
+  TPrepareDataResult = (pdrFailed, pdrSynchronous, pdrAsynchronous);
+
+function PrepareData(FileSource: IFileSource; var SelectedFiles: TFiles;
+                     FunctionToCall: TFileSourceOperationStateChangedNotify): TPrepareDataResult;
+
+procedure PrepareToolData(FileSource: IFileSource; var SelectedFiles: TFiles;
+                          FunctionToCall: TToolDataPreparedProc); overload;
+
+procedure PrepareToolData(FileSource1: IFileSource; var SelectedFiles1: TFiles;
+                          FileSource2: IFileSource; var SelectedFiles2: TFiles;
+                          FunctionToCall: TToolDataPreparedProc); overload;
+
 procedure RunExtDiffer(CompareList: TStringList);
 
 procedure ShowEditorByGlob(const sFileName: String);
@@ -46,6 +76,7 @@
 procedure ShowEditorByGlob(WaitData: TEditorWaitData); overload;
 
 procedure ShowDifferByGlob(const LeftName, RightName: String);
+procedure ShowDifferByGlobList(const CompareList: TStringList; WaitData: TWaitData);
 
 procedure ShowViewerByGlob(const sFileName: String);
 procedure ShowViewerByGlobList(const FilesToView: TStringList;
@@ -57,12 +88,24 @@
   SysUtils, Process, DCProcessUtf8, Dialogs, LCLIntf,
   uShellExecute, uGlobs, uOSUtils, fEditor, fViewer, uDCUtils,
   uTempFileSystemFileSource, uLng, fDiffer, uDebug, DCOSUtils, uShowMsg,
-  uFile, uFileSourceCopyOperation, uFileSystemFileSource,
+  DCStrUtils, uFileSourceProperty,
   uFileSourceOperationOptions, uOperationsManager, uFileSourceOperationTypes,
   uMultiArchiveFileSource, fFileExecuteYourSelf;
 
 type
 
+  { TWaitDataDouble }
+
+  TWaitDataDouble = class(TWaitData)
+  private
+    FWaitData1, FWaitData2: TEditorWaitData;
+  public
+    constructor Create(WaitData1: TEditorWaitData; WaitData2: TEditorWaitData);
+    procedure ShowWaitForm; override;
+    procedure Done; override;
+    destructor Destroy; override;
+  end;
+
   { TViewerWaitThread }
 
   TViewerWaitThread = class(TThread)
@@ -76,11 +119,13 @@
     destructor Destroy; override;
   end;
 
-  { TEditorWaitThread }
+  { TExtToolWaitThread }
 
-  TEditorWaitThread = class(TThread)
+  TExtToolWaitThread = class(TThread)
   private
-    FWaitData: TEditorWaitData;
+    FExternalTool: TExternalTool;
+    FFileList: TStringList;
+    FWaitData: TWaitData;
   private
     procedure RunEditDone;
     procedure ShowWaitForm;
@@ -87,7 +132,10 @@
   protected
     procedure Execute; override;
   public
-    constructor Create(WaitData: TEditorWaitData);
+    constructor Create(ExternalTool: TExternalTool;
+                       const FileList: TStringList;
+                       WaitData: TWaitData);
+    destructor Destroy; override;
   end;
 
 procedure RunExtTool(const ExtTool: TExternalToolOptions; sFileName: String);
@@ -147,11 +195,21 @@
 end;
 
 procedure ShowEditorByGlob(WaitData: TEditorWaitData);
+var
+  FileList: TStringList;
 begin
   if gExternalTools[etEditor].Enabled then
-    with TEditorWaitThread.Create(WaitData) do Start
+  begin
+    FileList := TStringList.Create;
+    try
+      FileList.Add(WaitData.Files[0].FullPath);
+      with TExtToolWaitThread.Create(etEditor, FileList, WaitData) do Start;
+    finally
+      FileList.Free
+    end;
+  end
   else begin
-    ShowEditor(WaitData);
+    ShowEditor(WaitData.Files[0].FullPath, WaitData);
   end;
 end;
 
@@ -201,6 +259,19 @@
     ShowDiffer(LeftName, RightName);
 end;
 
+procedure ShowDifferByGlobList(const CompareList: TStringList; WaitData: TWaitData);
+begin
+  if gExternalTools[etDiffer].Enabled then
+  begin
+    if Assigned(WaitData) then
+      with TExtToolWaitThread.Create(etDiffer, CompareList, WaitData) do Start
+    else
+      RunExtDiffer(CompareList);
+  end
+  else
+    ShowDiffer(CompareList[0], CompareList[1], WaitData);
+end;
+
 procedure ShowViewerByGlobList(const FilesToView : TStringList;
                                const aFileSource: IFileSource);
 var
@@ -227,51 +298,147 @@
     ShowViewer(FilesToView, aFileSource);
 end;
 
-procedure EditDone(WaitData: TEditorWaitData);
+procedure TEditorWaitData.Done;
 var
-  Files: TFiles;
+  I: Integer;
   Operation: TFileSourceCopyOperation;
+  DoNotFreeYet: Boolean = False;
 begin
-  with WaitData do
   try
-    // File was modified
-    if mbFileAge(FileName) <> FileTime then
+    for I := Files.Count - 1 downto 0 do
+      if (mbFileAge(Files[I].FullPath) = FileTimes[I]) or
+         not msgYesNo(Format(rsMsgCopyBackward, [GetRelativeFileName(Files[I].FullPath)]) + LineEnding + LineEnding + GetFromPath) then
+        Files.Delete(I);
+
+    // Files were modified
+    if Files.Count > 0 then
     begin
-      if not msgYesNo(Format(rsMsgCopyBackward, [ExtractFileName(FileName)])) then Exit;
       if (fsoCopyIn in TargetFileSource.GetOperationsTypes) and
          (not (TargetFileSource is TMultiArchiveFileSource)) then
       begin
-        Files:= TFiles.Create(SourceFileSource.GetRootDir);
-        Files.Add(TFileSystemFileSource.CreateFileFromFile(FileName));
         Operation:= TargetFileSource.CreateCopyInOperation(SourceFileSource, Files, TargetPath) as TFileSourceCopyOperation;
-        // Copy file back
+        // Copy files back
         if Assigned(Operation) then
         begin
           Operation.AddStateChangedListener([fsosStopped], @OnCopyInStateChanged);
           Operation.FileExistsOption:= fsoofeOverwrite;
           OperationsManager.AddOperation(Operation);
-          WaitData:= nil; // Will be free in operation
+          DoNotFreeYet:= True; // Will be free in operation
         end;
       end
-      else if msgYesNo(rsMsgCouldNotCopyBackward + LineEnding + FileName) then
+      else if msgYesNo(rsMsgCouldNotCopyBackward + LineEnding + GetRelativeFileNames) then
       begin
         (SourceFileSource as ITempFileSystemFileSource).DeleteOnDestroy:= False;
       end;
     end;
   finally
-    WaitData.Free;
+    if not DoNotFreeYet then
+      Free;
   end;
 end;
 
+{ TWaitDataDouble }
+
+constructor TWaitDataDouble.Create(WaitData1: TEditorWaitData; WaitData2: TEditorWaitData);
+begin
+  FWaitData1 := WaitData1;
+  FWaitData2 := WaitData2;
+end;
+
+procedure TWaitDataDouble.ShowWaitForm;
+begin
+  try
+    FWaitData1.ShowWaitForm;
+  finally
+    FWaitData2.ShowWaitForm;
+  end;
+end;
+
+procedure TWaitDataDouble.Done;
+begin
+  try
+    if Assigned(FWaitData1) then
+      FWaitData1.Done;
+  finally
+    FWaitData1 := nil;
+    try
+      if Assigned(FWaitData2) then
+        FWaitData2.Done;
+    finally
+      FWaitData2 := nil;
+      Free;
+    end;
+  end;
+end;
+
+destructor TWaitDataDouble.Destroy;
+begin
+  inherited Destroy;
+  if Assigned(FWaitData1) then
+    FWaitData1.Free;
+  if Assigned(FWaitData2) then
+    FWaitData2.Free;
+end;
+
 { TEditorWaitData }
 
+constructor TEditorWaitData.Create(aCopyOutOperation: TFileSourceCopyOperation);
+var
+  I: Integer;
+  aFileSource: ITempFileSystemFileSource;
+begin
+  aFileSource := aCopyOutOperation.TargetFileSource as ITempFileSystemFileSource;
+  TargetPath := aCopyOutOperation.SourceFiles.Path;
+  Files := aCopyOutOperation.SourceFiles.Clone;
+  ChangeFileListRoot(aFileSource.FileSystemRoot, Files);
+  SetLength(FileTimes, Files.Count);
+  for I := 0 to Files.Count - 1 do
+    FileTimes[I] := mbFileAge(Files[I].FullPath);
+  SourceFileSource := aFileSource;
+  TargetFileSource := aCopyOutOperation.FileSource as IFileSource;
+end;
+
 destructor TEditorWaitData.Destroy;
 begin
   inherited Destroy;
+  Files.Free;
   SourceFileSource:= nil;
   TargetFileSource:= nil;
 end;
 
+function TEditorWaitData.GetRelativeFileName(const FullPath: string): string;
+begin
+  Result := ExtractDirLevel(IncludeTrailingPathDelimiter(Files.Path), FullPath);
+end;
+
+function TEditorWaitData.GetRelativeFileNames: string;
+var
+  I: Integer;
+begin
+  Result := GetRelativeFileName(Files[0].FullPath);
+  for I := 1 to Files.Count - 1 do
+    Result := Result + ', ' + GetRelativeFileName(Files[I].FullPath);
+end;
+
+function TEditorWaitData.GetFromPath: string;
+begin
+  Result := TargetFileSource.CurrentAddress + TargetPath;
+end;
+
+procedure TEditorWaitData.ShowWaitForm;
+begin
+  ShowFileEditExternal(GetRelativeFileNames, GetFromPath, Self);
+end;
+
+function TEditorWaitData.GetFileList: TStringList;
+var
+  I: Integer;
+begin
+  Result := TStringList.Create;
+  for I := 0 to Files.Count - 1 do
+    Result.Add(Files[I].FullPath);
+end;
+
 procedure TEditorWaitData.OnCopyInStateChanged(Operation: TFileSourceOperation;
                                                State: TFileSourceOperationState);
 var
@@ -296,27 +463,29 @@
   end;
 end;
 
-{ TEditorWaitThread }
+{ TExtToolWaitThread }
 
-procedure TEditorWaitThread.RunEditDone;
+procedure TExtToolWaitThread.RunEditDone;
 begin
-  EditDone(FWaitData);
+  FWaitData.Done;
 end;
 
-procedure TEditorWaitThread.ShowWaitForm;
+procedure TExtToolWaitThread.ShowWaitForm;
 begin
-  ShowFileEditExternal(FWaitData);
+  FWaitData.ShowWaitForm;
 end;
 
-procedure TEditorWaitThread.Execute;
+procedure TExtToolWaitThread.Execute;
 var
+  I: Integer;
   StartTime: QWord;
   Process : TProcessUTF8;
   sCmd, sSecureEmptyStr: String;
 begin
+  try
   Process := TProcessUTF8.Create(nil);
-
-  with gExternalTools[etEditor] do
+  try
+  with gExternalTools[FExternalTool] do
   begin
     sCmd := ReplaceEnvVars(Path);
     // TProcess arguments must be enclosed with double quotes and not escaped.
@@ -325,7 +494,8 @@
       sCmd := QuoteStr(sCmd);
       if Parameters <> EmptyStr then
         sCmd := sCmd + ' ' + Parameters;
-      sCmd := sCmd + ' ' + QuoteStr(FWaitData.FileName);
+      for I := 0 to FFileList.Count - 1 do
+        sCmd := sCmd + ' ' + QuoteStr(FFileList[I]);
       sSecureEmptyStr := EmptyStr; // Let's play safe and don't let EmptyStr being passed as "VAR" parameter of "FormatTerminal"
       FormatTerminal(sCmd, sSecureEmptyStr, False);
     end
@@ -334,7 +504,8 @@
       sCmd := '"' + sCmd + '"';
       if Parameters <> EmptyStr then
         sCmd := sCmd + ' ' + Parameters;
-      sCmd := sCmd + ' "' + FWaitData.FileName + '"';
+      for I := 0 to FFileList.Count - 1 do
+        sCmd := sCmd + ' "' + FFileList[I] + '"';
     end;
   end;
 
@@ -342,7 +513,6 @@
   Process.Options := [poWaitOnExit];
   StartTime:= GetTickCount64;
   Process.Execute;
-  Process.Free;
 
   // If an editor closes within gEditWaitTime amount of milliseconds,
   // assume that it's a multiple document editor and show dialog where
@@ -354,17 +524,38 @@
   else begin
     Synchronize(@RunEditDone);
   end;
+
+  finally
+    Process.Free;
+  end;
+  except
+    FWaitData.Free;
+  end;
 end;
 
-constructor TEditorWaitThread.Create(WaitData: TEditorWaitData);
+constructor TExtToolWaitThread.Create(ExternalTool: TExternalTool;
+                                      const FileList: TStringList;
+                                      WaitData: TWaitData);
 begin
   inherited Create(True);
 
   FreeOnTerminate := True;
 
+  FExternalTool := ExternalTool;
+
+  FFileList := TStringList.Create;
+  // Make a copy of list elements.
+  FFileList.Assign(FileList);
+
   FWaitData := WaitData;
 end;
 
+destructor TExtToolWaitThread.Destroy;
+begin
+  FFileList.Free;
+  inherited Destroy;
+end;
+
 { TViewerWaitThread }
 
 constructor TViewerWaitThread.Create(const FilesToView: TStringList; const aFileSource: IFileSource);
@@ -425,4 +616,306 @@
   Process.Free;
 end;
 
+{ PrepareData }
+
+function PrepareData(FileSource: IFileSource; var SelectedFiles: TFiles;
+                     FunctionToCall: TFileSourceOperationStateChangedNotify): TPrepareDataResult;
+var
+  aFile: TFile;
+  I: Integer;
+  TempFiles: TFiles = nil;
+  TempFileSource: ITempFileSystemFileSource = nil;
+  Operation: TFileSourceOperation;
+begin
+  // If files are links to local files
+  if (fspLinksToLocalFiles in FileSource.Properties) then
+    begin
+      for I := 0 to SelectedFiles.Count - 1 do
+        begin
+          aFile := SelectedFiles[I];
+          FileSource.GetLocalName(aFile);
+        end;
+    end
+  // If files not directly accessible copy them to temp file source.
+  else if not (fspDirectAccess in FileSource.Properties) then
+  begin
+    if not (fsoCopyOut in FileSource.GetOperationsTypes) then
+    begin
+      msgWarning(rsMsgErrNotSupported);
+      Exit(pdrFailed);
+    end;
+
+    TempFileSource := TTempFileSystemFileSource.GetFileSource;
+
+    TempFiles := SelectedFiles.Clone;
+    try
+      Operation := FileSource.CreateCopyOutOperation(
+                       TempFileSource,
+                       TempFiles,
+                       TempFileSource.FileSystemRoot);
+    finally
+      TempFiles.Free;
+    end;
+
+    if not Assigned(Operation) then
+    begin
+      msgWarning(rsMsgErrNotSupported);
+      Exit(pdrFailed);
+    end;
+
+    Operation.AddStateChangedListener([fsosStopped], FunctionToCall);
+
+    OperationsManager.AddOperation(Operation);
+
+    Exit(pdrAsynchronous);
+  end;
+  Exit(pdrSynchronous);
+end;
+
+{ TToolDataPreparator }
+
+type
+  TToolDataPreparator = class
+   protected
+    FFunc: TToolDataPreparedProc;
+    FCallOnFail: Boolean;
+    procedure OnCopyOutStateChanged(Operation: TFileSourceOperation;
+                                    State: TFileSourceOperationState);
+   public
+    constructor Create(FunctionToCall: TToolDataPreparedProc; CallOnFail: Boolean = False);
+    procedure Prepare(FileSource: IFileSource; var SelectedFiles: TFiles);
+  end;
+
+constructor TToolDataPreparator.Create(FunctionToCall: TToolDataPreparedProc; CallOnFail: Boolean = False);
+begin
+  FFunc := FunctionToCall;
+  FCallOnFail := CallOnFail;
+end;
+
+procedure TToolDataPreparator.Prepare(FileSource: IFileSource; var SelectedFiles: TFiles);
+var
+  I: Integer;
+  FileList: TStringList;
+begin
+  case PrepareData(FileSource, SelectedFiles, @OnCopyOutStateChanged) of
+  pdrSynchronous:
+    try
+      FileList := TStringList.Create;
+      for I := 0 to SelectedFiles.Count - 1 do
+        FileList.Add(SelectedFiles[i].FullPath);
+      FFunc(FileList, nil);
+    finally
+      Free;
+    end;
+  pdrFailed:
+    try
+      if FCallOnFail then
+        FFunc(nil, nil);
+    finally
+      Free;
+    end;
+  end;
+end;
+
+procedure TToolDataPreparator.OnCopyOutStateChanged(
+  Operation: TFileSourceOperation; State: TFileSourceOperationState);
+var WaitData: TEditorWaitData;
+begin
+  if (State <> fsosStopped) then
+    Exit;
+  try
+    if Operation.Result = fsorFinished then
+    begin
+      WaitData := TEditorWaitData.Create(Operation as TFileSourceCopyOperation);
+      FFunc(WaitData.GetFileList, WaitData);
+    end
+    else
+    begin
+      if FCallOnFail then
+        FFunc(nil, nil);
+    end;
+  finally
+    Free;
+  end;
+end;
+
+{ TToolDataPreparator2 }
+
+type
+  TToolDataPreparator2 = class
+   protected
+    FFunc: TToolDataPreparedProc;
+    FCallOnFail: Boolean;
+    FFailed: Boolean;
+    FFileList1: TStringList;
+    FFileList2: TStringList;
+    FPrepared1: Boolean;
+    FPrepared2: Boolean;
+    FWaitData1: TEditorWaitData;
+    FWaitData2: TEditorWaitData;
+    procedure OnCopyOutStateChanged1(Operation: TFileSourceOperation;
+                                     State: TFileSourceOperationState);
+    procedure OnCopyOutStateChanged2(Operation: TFileSourceOperation;
+                                     State: TFileSourceOperationState);
+    procedure TryFinish;
+   public
+    constructor Create(FunctionToCall: TToolDataPreparedProc; CallOnFail: Boolean = False);
+    procedure Prepare(FileSource1: IFileSource; var SelectedFiles1: TFiles;
+                      FileSource2: IFileSource; var SelectedFiles2: TFiles);
+    destructor Destroy; override;
+  end;
+
+constructor TToolDataPreparator2.Create(FunctionToCall: TToolDataPreparedProc; CallOnFail: Boolean = False);
+begin
+  FFunc := FunctionToCall;
+  FCallOnFail := CallOnFail;
+end;
+
+procedure TToolDataPreparator2.Prepare(FileSource1: IFileSource; var SelectedFiles1: TFiles;
+                                       FileSource2: IFileSource; var SelectedFiles2: TFiles);
+var
+  I: Integer;
+begin
+  case PrepareData(FileSource1, SelectedFiles1, @OnCopyOutStateChanged1) of
+  pdrSynchronous:
+    begin
+      FFileList1 := TStringList.Create;
+      for I := 0 to SelectedFiles1.Count - 1 do
+        FFileList1.Add(SelectedFiles1[I].FullPath);
+      FPrepared1 := True;
+    end;
+  pdrFailed:
+    begin
+      try
+        if FCallOnFail then
+          FFunc(nil, nil);
+      finally
+        Free;
+      end;
+      Exit;
+    end;
+  end;
+
+  case PrepareData(FileSource2, SelectedFiles2, @OnCopyOutStateChanged2) of
+  pdrSynchronous:
+    begin
+      FFileList2 := TStringList.Create;
+      for I := 0 to SelectedFiles2.Count - 1 do
+        FFileList2.Add(SelectedFiles2[I].FullPath);
+      FPrepared2 := True;
+    end;
+  pdrFailed:
+    begin
+      FPrepared2 := True;
+      FFailed := True;
+    end;
+  end;
+
+  TryFinish;
+end;
+
+procedure TToolDataPreparator2.OnCopyOutStateChanged1(
+  Operation: TFileSourceOperation; State: TFileSourceOperationState);
+begin
+  if (State <> fsosStopped) then
+    Exit;
+  FPrepared1 := True;
+  if not FFailed then
+  begin
+    if Operation.Result = fsorFinished then
+    begin
+      FWaitData1 := TEditorWaitData.Create(Operation as TFileSourceCopyOperation);
+      FFileList1 := FWaitData1.GetFileList;
+    end
+    else
+    begin
+      FFailed := True;
+//      if not FPrepared2 and Assigned(FOperation2) then
+//        FOperation2.Stop();
+    end;
+  end;
+  TryFinish;
+end;
+
+procedure TToolDataPreparator2.OnCopyOutStateChanged2(
+  Operation: TFileSourceOperation; State: TFileSourceOperationState);
+begin
+  if (State <> fsosStopped) then
+    Exit;
+  FPrepared2 := True;
+  if not FFailed then
+  begin
+    if Operation.Result = fsorFinished then
+    begin
+      FWaitData2 := TEditorWaitData.Create(Operation as TFileSourceCopyOperation);
+      FFileList2 := FWaitData2.GetFileList;
+    end
+    else
+    begin
+      FFailed := True;
+//      if not FPrepared1 and Assigned(FOperation1) then
+//        FOperation1.Stop();
+    end;
+  end;
+  TryFinish;
+end;
+
+procedure TToolDataPreparator2.TryFinish;
+var
+  s: string;
+  WaitData: TWaitDataDouble;
+begin
+  if FPrepared1 and FPrepared2 then
+  try
+    if FFailed then
+    begin
+      if FCallOnFail then
+        FFunc(nil, nil);
+      Exit;
+    end;
+    if Assigned(FFileList2) then
+      for s in FFileList2 do
+        FFileList1.Append(s);
+    if Assigned(FWaitData1) or Assigned(FWaitData2) then
+    begin
+      WaitData := TWaitDataDouble.Create(FWaitData1, FWaitData2);
+      FWaitData1 := nil;
+      FWaitData2 := nil;
+      FFunc(FFileList1, WaitData);
+    end
+    else
+      FFunc(FFileList1, nil);
+  finally
+    Free;
+  end;
+end;
+
+destructor TToolDataPreparator2.Destroy;
+begin
+  inherited Destroy;
+  if Assigned(FFileList1) then
+     FFileList1.Free;
+  if Assigned(FFileList2) then
+     FFileList2.Free;
+  if Assigned(FWaitData1) then
+     FWaitData1.Free;
+  if Assigned(FWaitData2) then
+     FWaitData2.Free;
+end;
+
+procedure PrepareToolData(FileSource: IFileSource; var SelectedFiles: TFiles;
+                          FunctionToCall: TToolDataPreparedProc);
+begin
+  with TToolDataPreparator.Create(FunctionToCall) do
+    Prepare(FileSource, SelectedFiles);
+end;
+
+procedure PrepareToolData(FileSource1: IFileSource; var SelectedFiles1: TFiles;
+                          FileSource2: IFileSource; var SelectedFiles2: TFiles;
+                          FunctionToCall: TToolDataPreparedProc);
+begin
+  with TToolDataPreparator2.Create(FunctionToCall) do
+    Prepare(FileSource1, SelectedFiles1, FileSource2, SelectedFiles2);
+end;
+
 end.
bug1927.patch (38,295 bytes)   
Fixed in Revision7834,7935
Operating systemWindows, Linux
Widgetset
Architecture64-bit

Activities

accorp

2017-10-07 02:06

reporter   ~0002370

To compare files from search result no extra code is needed, just rewrite condition to allow it.

Alexx2000

2017-10-08 12:02

administrator   ~0002371

In this case is better to check "fspDirectAccess" property. Done (revision 7834).

cordylus

2017-12-17 01:53

developer   ~0002429

Patch for supporting archives and other indirect filesystems, such as FTP. The code to handle such cases was taken from the viewer and editor openers, but the inability to wait for two operations required either additional variables and complex logic directly in the TMainCommands class or creating a separate class for files preparation, which I did. Apart from implementing the feature, the effort resulted in complex refactoring, its goal was to unify files preparation for viewer, editor and differ. This part was not fully met, but the foundations are laid.

cordylus

2017-12-17 02:29

developer   ~0002430

Last edited: 2017-12-17 03:19

Also I intentionally did not make some changes for the purpose of patch readability:
- Almost all of the cm_CompareContents procedure should be indented by two spaces less.
- TEditorWaitData.Done (former EditDone) should be moved to the place of other class members.
- TExtToolWaitThread.Execute (former TEditorWaitThread) could be indented more to take into account two try clauses that it is now wrapped in.

cordylus

2017-12-20 19:55

developer   ~0002442

I've done the corrections mentioned in the previous note, refactored the main procedure a bit to reduce nesting, changed some messages, added support for launching it from directory synchronization tool, and commited.

Issue History

Date Modified Username Field Change
2017-10-06 17:39 CryHam New Issue
2017-10-07 02:06 accorp Note Added: 0002370
2017-10-07 02:06 accorp File Added: compare-search-result.diff
2017-10-08 12:02 Alexx2000 Fixed in Revision => 7834
2017-10-08 12:02 Alexx2000 Note Added: 0002371
2017-10-08 12:02 Alexx2000 Status new => acknowledged
2017-12-17 01:15 cordylus File Added: bug1927.patch
2017-12-17 01:53 cordylus Note Added: 0002429
2017-12-17 02:29 cordylus Note Added: 0002430
2017-12-17 02:31 cordylus Note Edited: 0002430
2017-12-17 03:17 cordylus Note Edited: 0002430
2017-12-17 03:19 cordylus Note Edited: 0002430
2017-12-20 19:55 cordylus Note Added: 0002442
2017-12-20 19:56 cordylus Fixed in Revision 7834 => 7834,7935
2017-12-20 19:56 cordylus Assigned To => cordylus
2017-12-20 19:56 cordylus Status acknowledged => resolved
2020-11-30 07:44 Alexx2000 Status resolved => closed