Bläddra i källkod

Bug 99: When saving edited/opened file, warn if it was modified meanwhile externally

https://winscp.net/tracker/99

Source commit: 992032004bbf1599bc5e02c91ba036fb7ecb68f7
Martin Prikryl 2 år sedan
förälder
incheckning
10b9d6638a

+ 50 - 5
source/forms/CustomScpExplorer.cpp

@@ -3317,7 +3317,7 @@ void __fastcall TCustomScpExplorerForm::EditNew(TOperationSide Side)
           false, MaskParams);
 
         CustomExecuteFile(Side, ExecuteFileBy, LocalFileName, TargetFileName,
-          ExternalEditor, RootTempDir, RemoteDirectory, NewFile);
+          ExternalEditor, RootTempDir, RemoteDirectory, NewFile, TDateTime());
       }
     }
   }
@@ -3338,7 +3338,7 @@ bool __fastcall TCustomScpExplorerForm::RemoteExecuteForceText(
 void __fastcall TCustomScpExplorerForm::CustomExecuteFile(TOperationSide Side,
   TExecuteFileBy ExecuteFileBy, UnicodeString FileName, UnicodeString OriginalFileName,
   const TEditorData * ExternalEditor, UnicodeString LocalRootDirectory,
-  UnicodeString RemoteDirectory, bool NewFile)
+  UnicodeString RemoteDirectory, bool NewFile, const TDateTime & SourceTimestamp)
 {
   DebugAssert(!WinConfiguration->DisableOpenEdit);
   DebugAssert((ExecuteFileBy == efExternalEditor) ==
@@ -3359,6 +3359,11 @@ void __fastcall TCustomScpExplorerForm::CustomExecuteFile(TOperationSide Side,
     Data->LocalRootDirectory = LocalRootDirectory;
     Data->OriginalFileName = OriginalFileName;
     Data->Command = L""; // will be changed later for external editor
+    // Empty SourceTimestamp indicates that checking was disabled when the file was opened
+    if (WinConfiguration->EditorCheckNotModified)
+    {
+      Data->SourceTimestamp = SourceTimestamp;
+    }
   }
 
   if (ExecuteFileBy == efInternalEditor)
@@ -3784,7 +3789,7 @@ void __fastcall TCustomScpExplorerForm::ExecuteFile(TOperationSide Side,
     Configuration->Usage->Inc(Counter);
 
     CustomExecuteFile(Side, ExecuteFileBy, LocalFileName, OriginalFileName,
-      ExternalEditor, LocalRootDirectory, RemoteDirectory, false);
+      ExternalEditor, LocalRootDirectory, RemoteDirectory, false, MaskParams.Modification);
   }
 }
 //---------------------------------------------------------------------------
@@ -3865,6 +3870,11 @@ void __fastcall TCustomScpExplorerForm::TemporaryFileCopyParam(TCopyParamType &
   CopyParam.FileMask = L"";
 }
 //---------------------------------------------------------------------------
+bool TCustomScpExplorerForm::EditorCheckNotModified(const TEditedFileData * Data)
+{
+  return WinConfiguration->EditorCheckNotModified && (Data->SourceTimestamp != TDateTime());
+}
+//---------------------------------------------------------------------------
 void __fastcall TCustomScpExplorerForm::ExecutedFileChanged(
   const UnicodeString & FileName, TEditedFileData * Data, HANDLE UploadCompleteEvent, bool & Retry)
 {
@@ -3927,6 +3937,25 @@ void __fastcall TCustomScpExplorerForm::ExecutedFileChanged(
     }
   }
 
+  if (EditorCheckNotModified(Data))
+  {
+    UnicodeString RemoteFilePath = UnixCombinePaths(Data->RemoteDirectory, Data->OriginalFileName);
+    std::unique_ptr<TRemoteFile> File(Terminal->TryReadFile(RemoteFilePath));
+    if (File.get() != NULL)
+    {
+      AppLogFmt(L"Edited remote file timestamp: %s, Original timestamp: %s", (StandardTimestamp(File->Modification), StandardTimestamp(Data->SourceTimestamp)));
+
+      if (File->Modification != Data->SourceTimestamp)
+      {
+        UnicodeString Message = MainInstructions(LoadStr(EDIT_CHANGED_EXTERNALLY));
+        if (MessageDialog(Message, qtConfirmation, qaOK | qaCancel) != qaOK)
+        {
+          Abort();
+        }
+      }
+    }
+  }
+
   TStrings * FileList = new TStringList();
   try
   {
@@ -3981,7 +4010,7 @@ void __fastcall TCustomScpExplorerForm::ExecutedFileChanged(
 }
 //---------------------------------------------------------------------------
 void __fastcall TCustomScpExplorerForm::ExecutedFileReload(
-  const UnicodeString FileName, const TEditedFileData * Data)
+  const UnicodeString & FileName, TEditedFileData * Data)
 {
   // Sanity check, we should not be busy otherwise user would not be able to click Reload button.
   DebugAssert(!NonVisualDataModule->Busy);
@@ -4021,6 +4050,11 @@ void __fastcall TCustomScpExplorerForm::ExecutedFileReload(
 
     TemporarilyDownloadFiles(FileList.get(), Data->ForceText, RootTempDir, TempDir, true, true, false);
 
+    if (EditorCheckNotModified(Data))
+    {
+      Data->SourceTimestamp = File->Modification;
+    }
+
     // sanity check, the target file name should be still the same
     DebugAssert(ExtractFileName(FileName) == FileList->Strings[0]);
   }
@@ -4156,8 +4190,19 @@ void __fastcall TCustomScpExplorerForm::ExecutedFileEarlyClosed(
   }
 }
 //---------------------------------------------------------------------------
-void __fastcall TCustomScpExplorerForm::ExecutedFileUploadComplete(TObject * Sender)
+void __fastcall TCustomScpExplorerForm::ExecutedFileUploadComplete(TEditedFileData * Data, TObject * Sender)
 {
+  if (EditorCheckNotModified(Data))
+  {
+    UnicodeString RemoteFilePath = UnixCombinePaths(Data->RemoteDirectory, Data->OriginalFileName);
+    std::unique_ptr<TRemoteFile> File(Terminal->TryReadFile(RemoteFilePath));
+    if (File.get() != NULL)
+    {
+      Data->SourceTimestamp = File->Modification;
+      AppLogFmt(L"Uploaded edited remote file timestamp: %s: ", (StandardTimestamp(Data->SourceTimestamp)));
+    }
+  }
+
   EditorFormFileUploadComplete(DebugNotNull(dynamic_cast<TForm *>(Sender)));
 }
 //---------------------------------------------------------------------------

+ 4 - 4
source/forms/CustomScpExplorer.h

@@ -461,11 +461,11 @@ protected:
   void __fastcall OperationComplete(const TDateTime & StartTime);
   void __fastcall ExecutedFileChanged(
     const UnicodeString & FileName, TEditedFileData * Data, HANDLE UploadCompleteEvent, bool & Retry);
-  void __fastcall ExecutedFileReload(const UnicodeString FileName,
-    const TEditedFileData * Data);
+  void __fastcall ExecutedFileReload(const UnicodeString & FileName, TEditedFileData * Data);
   void __fastcall ExecutedFileEarlyClosed(const TEditedFileData * Data,
     bool & KeepOpen);
-  void __fastcall ExecutedFileUploadComplete(TObject * Sender);
+  void __fastcall ExecutedFileUploadComplete(TEditedFileData * Data, TObject * Sender);
+  bool EditorCheckNotModified(const TEditedFileData * Data);
   void __fastcall CMDialogChar(TMessage & AMessage);
   inline void __fastcall WMAppCommand(TMessage & Message);
   inline void __fastcall WMSysCommand(TMessage & Message);
@@ -558,7 +558,7 @@ protected:
   void __fastcall CustomExecuteFile(TOperationSide Side,
     TExecuteFileBy ExecuteFileBy, UnicodeString FileName, UnicodeString OriginalFileName,
     const TEditorData * ExternalEditor, UnicodeString LocalRootDirectory,
-    UnicodeString RemoteDirectory, bool NewFile);
+    UnicodeString RemoteDirectory, bool NewFile, const TDateTime & SourceTimestamp);
   void __fastcall ExecuteFile(TOperationSide Side,
     TExecuteFileBy ExecuteFileBy, const TEditorData * ExternalEditor,
     UnicodeString FullFileName, TObject * Object,

+ 2 - 0
source/forms/Preferences.cpp

@@ -397,6 +397,7 @@ void __fastcall TPreferencesDialog::LoadConfiguration()
     FEditorBackgroundColor = WinConfiguration->Editor.BackgroundColor;
     (*FEditorList) = *WinConfiguration->EditorList;
     UpdateEditorListView();
+    BOOLPROP(EditorCheckNotModified);
 
     FCopyParams = GUIConfiguration->DefaultCopyParam;
     ResumeOnButton->Checked = GUIConfiguration->DefaultCopyParam.ResumeSupport == rsOn;
@@ -788,6 +789,7 @@ void __fastcall TPreferencesDialog::SaveConfiguration()
     WinConfiguration->Editor.FontColor = FEditorFont->Color;
     WinConfiguration->Editor.BackgroundColor = FEditorBackgroundColor;
     WinConfiguration->EditorList = FEditorList;
+    BOOLPROP(EditorCheckNotModified);
 
     // overwrites only TCopyParamType fields
     CopyParam = FCopyParams;

+ 29 - 8
source/forms/Preferences.dfm

@@ -1073,18 +1073,18 @@ object PreferencesDialog: TPreferencesDialog
           Left = 8
           Top = 8
           Width = 389
-          Height = 404
+          Height = 344
           Anchors = [akLeft, akTop, akRight, akBottom]
           Caption = 'Editor preference'
           TabOrder = 0
           DesignSize = (
             389
-            404)
+            344)
           object EditorListView3: TListView
             Left = 16
             Top = 24
             Width = 356
-            Height = 307
+            Height = 247
             Anchors = [akLeft, akTop, akRight, akBottom]
             Columns = <
               item
@@ -1120,7 +1120,7 @@ object PreferencesDialog: TPreferencesDialog
           end
           object AddEditorButton: TButton
             Left = 16
-            Top = 337
+            Top = 277
             Width = 83
             Height = 25
             Anchors = [akLeft, akBottom]
@@ -1130,7 +1130,7 @@ object PreferencesDialog: TPreferencesDialog
           end
           object EditEditorButton: TButton
             Left = 112
-            Top = 337
+            Top = 277
             Width = 83
             Height = 25
             Anchors = [akLeft, akBottom]
@@ -1140,7 +1140,7 @@ object PreferencesDialog: TPreferencesDialog
           end
           object UpEditorButton: TButton
             Left = 290
-            Top = 337
+            Top = 277
             Width = 83
             Height = 25
             Anchors = [akRight, akBottom]
@@ -1150,7 +1150,7 @@ object PreferencesDialog: TPreferencesDialog
           end
           object DownEditorButton: TButton
             Left = 290
-            Top = 368
+            Top = 308
             Width = 83
             Height = 25
             Anchors = [akRight, akBottom]
@@ -1160,7 +1160,7 @@ object PreferencesDialog: TPreferencesDialog
           end
           object RemoveEditorButton: TButton
             Left = 16
-            Top = 368
+            Top = 308
             Width = 83
             Height = 25
             Anchors = [akLeft, akBottom]
@@ -1169,6 +1169,27 @@ object PreferencesDialog: TPreferencesDialog
             OnClick = RemoveEditorButtonClick
           end
         end
+        object EditingOptionsGroup: TGroupBox
+          Left = 8
+          Top = 358
+          Width = 389
+          Height = 54
+          Caption = 'Editing options'
+          TabOrder = 1
+          DesignSize = (
+            389
+            54)
+          object EditorCheckNotModifiedCheck: TCheckBox
+            Left = 16
+            Top = 21
+            Width = 357
+            Height = 17
+            Anchors = [akLeft, akTop, akRight]
+            Caption = '&Check that edited remote file has not changed before saving it'
+            TabOrder = 0
+            OnClick = ControlChange
+          end
+        end
       end
       object IntegrationSheet: TTabSheet
         Tag = 9

+ 2 - 0
source/forms/Preferences.h

@@ -342,6 +342,8 @@ __published:
   TUpDownEdit *LocalPortNumberMaxEdit;
   TCheckBox *AlwaysSortDirectoriesByNameCheck;
   TLabel *LogProtocolHintLabel;
+  TGroupBox *EditingOptionsGroup;
+  TCheckBox *EditorCheckNotModifiedCheck;
   void __fastcall FormShow(TObject *Sender);
   void __fastcall ControlChange(TObject *Sender);
   void __fastcall EditorFontButtonClick(TObject *Sender);

+ 1 - 0
source/resource/TextsWin.h

@@ -161,6 +161,7 @@
 #define CLOSE_SESSION_WORKSPACE 1371
 #define CLOSE_WORKSPACE         1372
 #define TOO_MANY_SESSIONS       1373
+#define EDIT_CHANGED_EXTERNALLY 1374
 
 #define WIN_INFORMATION_STRINGS 1400
 #define COMPARE_NO_DIFFERENCES  1402

+ 1 - 0
source/resource/TextsWin1.rc

@@ -165,6 +165,7 @@ BEGIN
         CLOSE_SESSION_WORKSPACE, "Terminate session '%s' and close application without saving a workspace?"
         CLOSE_WORKSPACE, "Close application without saving a workspace?"
         TOO_MANY_SESSIONS, "**Do you really want to open another tab?**\nYou have %d tabs opened already. Please consider closing some tabs first to free resources of your computer."
+        EDIT_CHANGED_EXTERNALLY, "The remote file has been changed while you were editing it. Do you want to overwrite it anyway?"
 
         WIN_INFORMATION_STRINGS, "WIN_INFORMATION"
         COMPARE_NO_DIFFERENCES, "No differences found."

+ 1 - 1
source/windows/EditorManager.cpp

@@ -403,7 +403,7 @@ void __fastcall TEditorManager::UploadComplete(int Index)
     }
     else if ((FileData->Token != NULL) && (FOnFileUploadComplete != NULL))
     {
-      FOnFileUploadComplete(FileData->Token);
+      FOnFileUploadComplete(FileData->Data, FileData->Token);
     }
   }
 }

+ 3 - 2
source/windows/EditorManager.h

@@ -21,16 +21,17 @@ struct TEditedFileData
   UnicodeString SessionName;
   UnicodeString OriginalFileName;
   UnicodeString Command;
+  TDateTime SourceTimestamp;
 };
 //---------------------------------------------------------------------------
 typedef void __fastcall (__closure * TEditedFileChangedEvent)
   (const UnicodeString & FileName, TEditedFileData * Data, HANDLE CompleteEvent, bool & Retry);
 typedef void __fastcall (__closure * TEditedFileReloadEvent)
-  (const UnicodeString FileName, const TEditedFileData * Data);
+  (const UnicodeString & FileName, TEditedFileData * Data);
 typedef void __fastcall (__closure * TEditedFileEarlyClosedEvent)
   (const TEditedFileData * Data, bool & KeepOpen);
 typedef void __fastcall (__closure * TEditedFileUploadComplete)
-  (TObject * Token);
+  (TEditedFileData * Data, TObject * Token);
 //---------------------------------------------------------------------------
 typedef void __fastcall (__closure * TEditedFileProcessEvent)
   (const UnicodeString FileName, TEditedFileData * Data, TObject * Token, void * Arg);

+ 7 - 0
source/windows/WinConfiguration.cpp

@@ -628,6 +628,7 @@ void __fastcall TWinConfiguration::Default()
   AllowWindowPrint = false;
   StoreTransition = stInit;
   QueueTransferLimitMax = 9;
+  EditorCheckNotModified = false;
   FirstRun = StandardDatestamp();
 
   FEditor.Font.FontName = DefaultFixedWidthFontName;
@@ -1086,6 +1087,7 @@ THierarchicalStorage * TWinConfiguration::CreateScpStorage(bool & SessionList)
     KEY(Bool,     AllowWindowPrint); \
     KEY(Integer,  StoreTransition); \
     KEY(Integer,  QueueTransferLimitMax); \
+    KEY(Bool,     EditorCheckNotModified); \
     KEY(String,   FirstRun); \
   ); \
   BLOCK(L"Interface\\Editor", CANCREATE, \
@@ -2784,6 +2786,11 @@ void TWinConfiguration::SetQueueTransferLimitMax(int value)
   SET_CONFIG_PROPERTY(QueueTransferLimitMax);
 }
 //---------------------------------------------------------------------------
+void TWinConfiguration::SetEditorCheckNotModified(bool value)
+{
+  SET_CONFIG_PROPERTY(EditorCheckNotModified);
+}
+//---------------------------------------------------------------------------
 void TWinConfiguration::SetFirstRun(const UnicodeString & value)
 {
   SET_CONFIG_PROPERTY(FirstRun);

+ 3 - 0
source/windows/WinConfiguration.h

@@ -477,6 +477,7 @@ private:
   bool FAllowWindowPrint;
   TStoreTransition FStoreTransition;
   int FQueueTransferLimitMax;
+  bool FEditorCheckNotModified;
   UnicodeString FFirstRun;
   int FDontDecryptPasswords;
   int FMasterPasswordSession;
@@ -597,6 +598,7 @@ private:
   void __fastcall SetAllowWindowPrint(bool value);
   void SetStoreTransition(TStoreTransition value);
   void SetQueueTransferLimitMax(int value);
+  void SetEditorCheckNotModified(bool value);
   void SetFirstRun(const UnicodeString & value);
   int __fastcall GetLocaleCompletenessTreshold();
 
@@ -799,6 +801,7 @@ public:
   __property bool AllowWindowPrint = { read = FAllowWindowPrint, write = SetAllowWindowPrint };
   __property TStoreTransition StoreTransition = { read = FStoreTransition, write = SetStoreTransition };
   __property int QueueTransferLimitMax = { read = FQueueTransferLimitMax, write = SetQueueTransferLimitMax };
+  __property bool EditorCheckNotModified = { read = FEditorCheckNotModified, write = SetEditorCheckNotModified };
   __property UnicodeString FirstRun = { read = FFirstRun, write = SetFirstRun };
   __property LCID DefaultLocale = { read = FDefaultLocale };
   __property int LocaleCompletenessTreshold = { read = GetLocaleCompletenessTreshold };