瀏覽代碼

Bug 94: Preserve timestamps of directories (SFTP)

https://winscp.net/tracker/94

Source commit: 342c0cb56287554c059c6461aaf234f889c661ed
Martin Prikryl 10 年之前
父節點
當前提交
048ebddb93

+ 29 - 4
source/core/CopyParam.cpp

@@ -28,6 +28,7 @@ void __fastcall TCopyParamType::Default()
   FileNameCase = ncNoChange;
   PreserveReadOnly = false;
   PreserveTime = true;
+  PreserveTimeDirs = false;
   Rights.Number = TRights::rfDefault;
   PreserveRights = false; // Was True until #106
   IgnorePermErrors = false;
@@ -168,10 +169,29 @@ void __fastcall TCopyParamType::DoGetInfoStr(
     }
   }
 
-  if (PreserveTime != Defaults.PreserveTime)
+  bool AddPreserveTime = (PreserveTime != Defaults.PreserveTime);
+  bool APreserveTimeDirs = PreserveTime && PreserveTimeDirs;
+  if (AddPreserveTime || (APreserveTimeDirs != Defaults.PreserveTimeDirs))
   {
-    ADD(LoadStr(PreserveTime ? COPY_INFO_TIMESTAMP : COPY_INFO_DONT_PRESERVE_TIME),
-      cpaIncludeMaskOnly | cpaNoPreserveTime);
+    UnicodeString Str = LoadStr(PreserveTime ? COPY_INFO_TIMESTAMP : COPY_INFO_DONT_PRESERVE_TIME);
+
+    if (APreserveTimeDirs != Defaults.PreserveTimeDirs)
+    {
+      if (ALWAYS_TRUE(PreserveTimeDirs))
+      {
+        if (FLAGCLEAR(Options, cpaNoPreserveTimeDirs))
+        {
+          Str = FMTLOAD(COPY_INFO_PRESERVE_TIME_DIRS, (Str));
+          AddPreserveTime = true;
+        }
+      }
+      ADD("", cpaIncludeMaskOnly | cpaNoPreserveTime | cpaNoPreserveTimeDirs);
+    }
+
+    if (AddPreserveTime)
+    {
+      ADD(Str, cpaIncludeMaskOnly | cpaNoPreserveTime);
+    }
   }
 
   if ((PreserveRights || PreserveTime) &&
@@ -278,6 +298,7 @@ void __fastcall TCopyParamType::Assign(const TCopyParamType * Source)
   COPY(FileNameCase);
   COPY(PreserveReadOnly);
   COPY(PreserveTime);
+  COPY(PreserveTimeDirs);
   COPY(Rights);
   COPY(AsciiFileMask);
   COPY(TransferMode);
@@ -453,9 +474,10 @@ UnicodeString __fastcall TCopyParamType::GetLogStr() const
   // OpenArray (ARRAYOFCONST) supports only up to 19 arguments, so we had to split it
   return
     FORMAT(
-      L"  PrTime: %s; PrRO: %s; Rght: %s; PrR: %s (%s); FnCs: %s; RIC: %s; "
+      L"  PrTime: %s%s; PrRO: %s; Rght: %s; PrR: %s (%s); FnCs: %s; RIC: %s; "
          "Resume: %s (%d); CalcS: %s; Mask: %s\n",
       (BooleanToEngStr(PreserveTime),
+       UnicodeString(PreserveTime && PreserveTimeDirs ? L"+Dirs" : L""),
        BooleanToEngStr(PreserveReadOnly),
        Rights.Text,
        BooleanToEngStr(PreserveRights),
@@ -569,6 +591,7 @@ void __fastcall TCopyParamType::Load(THierarchicalStorage * Storage)
   FileNameCase = (TFileNameCase)Storage->ReadInteger(L"FileNameCase", FileNameCase);
   PreserveReadOnly = Storage->ReadBool(L"PreserveReadOnly", PreserveReadOnly);
   PreserveTime = Storage->ReadBool(L"PreserveTime", PreserveTime);
+  PreserveTimeDirs = Storage->ReadBool(L"PreserveTimeDirs", PreserveTimeDirs);
   PreserveRights = Storage->ReadBool(L"PreserveRights", PreserveRights);
   IgnorePermErrors = Storage->ReadBool(L"IgnorePermErrors", IgnorePermErrors);
   Rights.Text = Storage->ReadString(L"Text", Rights.Text);
@@ -616,6 +639,7 @@ void __fastcall TCopyParamType::Save(THierarchicalStorage * Storage) const
   Storage->WriteInteger(L"FileNameCase", FileNameCase);
   Storage->WriteBool(L"PreserveReadOnly", PreserveReadOnly);
   Storage->WriteBool(L"PreserveTime", PreserveTime);
+  Storage->WriteBool(L"PreserveTimeDirs", PreserveTimeDirs);
   Storage->WriteBool(L"PreserveRights", PreserveRights);
   Storage->WriteBool(L"IgnorePermErrors", IgnorePermErrors);
   Storage->WriteString(L"Text", Rights.Text);
@@ -650,6 +674,7 @@ bool __fastcall TCopyParamType::operator==(const TCopyParamType & rhp) const
     C(FileNameCase) &&
     C(PreserveReadOnly) &&
     C(PreserveTime) &&
+    C(PreserveTimeDirs) &&
     C(PreserveRights) &&
     C(IgnorePermErrors) &&
     C(Rights) &&

+ 3 - 0
source/core/CopyParam.h

@@ -23,6 +23,7 @@ const int cpaNoIgnorePermErrors = 0x80;
 const int cpaNoNewerOnly        = 0x100;
 const int cpaNoRemoveCtrlZ      = 0x200;
 const int cpaNoRemoveBOM        = 0x400;
+const int cpaNoPreserveTimeDirs = 0x800;
 //---------------------------------------------------------------------------
 struct TUsableCopyParamAttrs
 {
@@ -38,6 +39,7 @@ private:
   TFileNameCase FFileNameCase;
   bool FPreserveReadOnly;
   bool FPreserveTime;
+  bool FPreserveTimeDirs;
   TRights FRights;
   TTransferMode FTransferMode;
   bool FAddXToDirectories;
@@ -105,6 +107,7 @@ public:
   __property TFileNameCase FileNameCase = { read = FFileNameCase, write = FFileNameCase };
   __property bool PreserveReadOnly = { read = FPreserveReadOnly, write = FPreserveReadOnly };
   __property bool PreserveTime = { read = FPreserveTime, write = FPreserveTime };
+  __property bool PreserveTimeDirs = { read = FPreserveTimeDirs, write = FPreserveTimeDirs };
   __property TRights Rights = { read = FRights, write = FRights };
   __property TTransferMode TransferMode = { read = FTransferMode, write = FTransferMode };
   __property UnicodeString LogStr  = { read=GetLogStr };

+ 1 - 0
source/core/FtpFileSystem.cpp

@@ -2299,6 +2299,7 @@ bool __fastcall TFTPFileSystem::IsCapable(int Capability) const
     case fcIgnorePermErrors:
     case fcRemoveCtrlZUpload:
     case fcLocking:
+    case fcPreservingTimestampDirs:
       return false;
 
     default:

+ 1 - 0
source/core/ScpFileSystem.cpp

@@ -460,6 +460,7 @@ bool __fastcall TSCPFileSystem::IsCapable(int Capability) const
     case fcGroupOwnerChangingByID: // by name
     case fcMoveToQueue:
     case fcLocking:
+    case fcPreservingTimestampDirs:
       return false;
 
     default:

+ 7 - 1
source/core/Script.cpp

@@ -964,11 +964,17 @@ void __fastcall TScript::CopyParamParams(TCopyParamType & CopyParam, TScriptProc
   if (Parameters->FindSwitch(L"nopreservetime"))
   {
     CopyParam.PreserveTime = false;
+    CopyParam.PreserveTimeDirs = false;
   }
 
-  if (Parameters->FindSwitch(L"preservetime"))
+  if (Parameters->FindSwitch(L"preservetime", Value))
   {
     CopyParam.PreserveTime = true;
+
+    if (SameText(Value, L"all"))
+    {
+      CopyParam.PreserveTimeDirs = true;
+    }
   }
 
   if (Parameters->FindSwitch(L"nopermissions"))

+ 1 - 1
source/core/SessionInfo.h

@@ -41,7 +41,7 @@ enum TFSCapability { fcUserGroupListing, fcModeChanging, fcGroupChanging,
   fcCheckingSpaceAvailable, fcIgnorePermErrors, fcCalculatingChecksum,
   fcModeChangingUpload, fcPreservingTimestampUpload, fcShellAnyCommand,
   fcSecondaryShell, fcRemoveCtrlZUpload, fcRemoveBOMUpload, fcMoveToQueue,
-  fcLocking,
+  fcLocking, fcPreservingTimestampDirs,
   fcCount };
 //---------------------------------------------------------------------------
 struct TFileSystemInfo

+ 43 - 0
source/core/SftpFileSystem.cpp

@@ -2073,6 +2073,7 @@ bool __fastcall TSFTPFileSystem::IsCapable(int Capability) const
     case fcRemoveCtrlZUpload:
     case fcRemoveBOMUpload:
     case fcMoveToQueue:
+    case fcPreservingTimestampDirs:
       return true;
 
     case fcRename:
@@ -5386,6 +5387,18 @@ void __fastcall TSFTPFileSystem::SFTPDirectorySource(const UnicodeString Directo
   /* TODO : Show error message on failure. */
   if (!OperationProgress->Cancel)
   {
+    if (CopyParam->PreserveTime && CopyParam->PreserveTimeDirs)
+    {
+      TRemoteProperties Properties;
+      Properties.Valid << vpModification;
+
+      FTerminal->OpenLocalFile(
+        ExcludeTrailingBackslash(DirectoryName), GENERIC_READ, NULL, NULL, NULL,
+        &Properties.Modification, &Properties.LastAccess, NULL);
+
+      FTerminal->ChangeFileProperties(DestFullName, NULL, &Properties);
+    }
+
     if (FLAGSET(Params, cpDelete))
     {
       RemoveDir(ApiPath(DirectoryName));
@@ -5564,6 +5577,36 @@ void __fastcall TSFTPFileSystem::SFTPSink(const UnicodeString FileName,
 
       FTerminal->ProcessDirectory(FileName, SFTPSinkFile, &SinkFileParams);
 
+      if (CopyParam->PreserveTime && CopyParam->PreserveTimeDirs)
+      {
+        FTerminal->LogEvent(FORMAT(L"Preserving directory timestamp [%s]",
+          (StandardTimestamp(File->Modification))));
+        int SetFileTimeError = ERROR_SUCCESS;
+        // FILE_FLAG_BACKUP_SEMANTICS is needed to "open" directory
+        HANDLE LocalHandle = CreateFile(ApiPath(DestFullName).c_str(), GENERIC_WRITE,
+          FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0);
+        if (LocalHandle == INVALID_HANDLE_VALUE)
+        {
+          SetFileTimeError = GetLastError();
+        }
+        else
+        {
+          FILETIME AcTime = DateTimeToFileTime(File->LastAccess, FTerminal->SessionData->DSTMode);
+          FILETIME WrTime = DateTimeToFileTime(File->Modification, FTerminal->SessionData->DSTMode);
+          if (!SetFileTime(LocalHandle, NULL, &AcTime, &WrTime))
+          {
+            SetFileTimeError = GetLastError();
+          }
+          CloseHandle(LocalHandle);
+        }
+
+        if (SetFileTimeError != ERROR_SUCCESS)
+        {
+          FTerminal->LogEvent(FORMAT(L"Preserving timestamp failed, ignoring: %s",
+            (SysErrorMessageForError(SetFileTimeError))));
+        }
+      }
+
       // Do not delete directory if some of its files were skip.
       // Throw "skip file" for the directory to avoid attempt to deletion
       // of any parent directory

+ 5 - 3
source/core/Terminal.cpp

@@ -3193,7 +3193,8 @@ TUsableCopyParamAttrs __fastcall TTerminal::UsableCopyParamAttrs(int Params)
     // Synchronize dialog.
     FLAGMASK(!IsCapable[fcModeChangingUpload], cpaNoRights) |
     FLAGMASK(!IsCapable[fcRemoveCtrlZUpload], cpaNoRemoveCtrlZ) |
-    FLAGMASK(!IsCapable[fcRemoveBOMUpload], cpaNoRemoveBOM);
+    FLAGMASK(!IsCapable[fcRemoveBOMUpload], cpaNoRemoveBOM) |
+    FLAGMASK(!IsCapable[fcPreservingTimestampDirs], cpaNoPreserveTimeDirs);
   Result.Download = Result.General | cpaNoClearArchive |
     cpaNoIgnorePermErrors |
     // May be already set in General flags, but it's unconditional here
@@ -4271,7 +4272,7 @@ void __fastcall TTerminal::OpenLocalFile(const UnicodeString FileName,
   }
   FILE_OPERATION_LOOP_END(FMTLOAD(FILE_NOT_EXISTS, (FileName)));
 
-  if ((Attrs & faDirectory) == 0)
+  if (FLAGCLEAR(Attrs, faDirectory) || (AHandle == NULL))
   {
     bool NoHandle = false;
     if (!TryWriteReadOnly && (Access == GENERIC_WRITE) &&
@@ -4283,9 +4284,10 @@ void __fastcall TTerminal::OpenLocalFile(const UnicodeString FileName,
 
     FILE_OPERATION_LOOP_BEGIN
     {
+      DWORD Flags = FLAGMASK(FLAGSET(Attrs, faDirectory), FILE_FLAG_BACKUP_SEMANTICS);
       Handle = CreateFile(ApiPath(FileName).c_str(), Access,
         Access == GENERIC_READ ? FILE_SHARE_READ | FILE_SHARE_WRITE : FILE_SHARE_READ,
-        NULL, OPEN_EXISTING, 0, 0);
+        NULL, OPEN_EXISTING, Flags, 0);
       if (Handle == INVALID_HANDLE_VALUE)
       {
         Handle = 0;

+ 1 - 0
source/core/WebDAVFileSystem.cpp

@@ -649,6 +649,7 @@ bool __fastcall TWebDAVFileSystem::IsCapable(int Capability) const
     case fcRemoveCtrlZUpload:
     case fcRemoveBOMUpload:
     case fcRemoteCopy:
+    case fcPreservingTimestampDirs:
       return false;
 
     case fcLocking:

+ 6 - 6
source/forms/CopyParamCustom.dfm

@@ -6,7 +6,7 @@ object CopyParamCustomDialog: TCopyParamCustomDialog
   BorderIcons = [biSystemMenu, biMinimize, biMaximize, biHelp]
   BorderStyle = bsDialog
   Caption = 'Transfer settings'
-  ClientHeight = 468
+  ClientHeight = 494
   ClientWidth = 420
   Color = clBtnFace
   ParentFont = True
@@ -15,12 +15,12 @@ object CopyParamCustomDialog: TCopyParamCustomDialog
   OnCloseQuery = FormCloseQuery
   DesignSize = (
     420
-    468)
+    494)
   PixelsPerInch = 96
   TextHeight = 13
   object OkButton: TButton
     Left = 168
-    Top = 435
+    Top = 461
     Width = 75
     Height = 25
     Anchors = [akRight, akBottom]
@@ -31,7 +31,7 @@ object CopyParamCustomDialog: TCopyParamCustomDialog
   end
   object CancelButton: TButton
     Left = 252
-    Top = 435
+    Top = 461
     Width = 75
     Height = 25
     Anchors = [akRight, akBottom]
@@ -44,13 +44,13 @@ object CopyParamCustomDialog: TCopyParamCustomDialog
     Left = 0
     Top = 0
     Width = 420
-    Height = 431
+    Height = 456
     HelpType = htKeyword
     TabOrder = 0
   end
   object HelpButton: TButton
     Left = 336
-    Top = 435
+    Top = 461
     Width = 75
     Height = 25
     Anchors = [akRight, akBottom]

+ 8 - 8
source/forms/CopyParamPreset.dfm

@@ -6,7 +6,7 @@ object CopyParamPresetDialog: TCopyParamPresetDialog
   BorderIcons = [biSystemMenu, biMinimize, biMaximize, biHelp]
   BorderStyle = bsDialog
   Caption = 'CopyParamPresetDialog'
-  ClientHeight = 514
+  ClientHeight = 539
   ClientWidth = 675
   Color = clBtnFace
   ParentFont = True
@@ -16,7 +16,7 @@ object CopyParamPresetDialog: TCopyParamPresetDialog
   OnShow = FormShow
   DesignSize = (
     675
-    514)
+    539)
   PixelsPerInch = 96
   TextHeight = 13
   object Label1: TLabel
@@ -29,7 +29,7 @@ object CopyParamPresetDialog: TCopyParamPresetDialog
   end
   object OkButton: TButton
     Left = 423
-    Top = 481
+    Top = 506
     Width = 75
     Height = 25
     Anchors = [akRight, akBottom]
@@ -40,7 +40,7 @@ object CopyParamPresetDialog: TCopyParamPresetDialog
   end
   object CancelButton: TButton
     Left = 507
-    Top = 481
+    Top = 506
     Width = 75
     Height = 25
     Anchors = [akRight, akBottom]
@@ -62,7 +62,7 @@ object CopyParamPresetDialog: TCopyParamPresetDialog
     Left = 2
     Top = 51
     Width = 420
-    Height = 426
+    Height = 451
     HelpType = htKeyword
     TabOrder = 1
   end
@@ -70,13 +70,13 @@ object CopyParamPresetDialog: TCopyParamPresetDialog
     Left = 426
     Top = 91
     Width = 240
-    Height = 384
+    Height = 409
     Anchors = [akLeft, akTop, akRight]
     Caption = 'Autoselection rule'
     TabOrder = 3
     DesignSize = (
       240
-      384)
+      409)
     object Label2: TLabel
       Left = 16
       Top = 20
@@ -187,7 +187,7 @@ object CopyParamPresetDialog: TCopyParamPresetDialog
   end
   object HelpButton: TButton
     Left = 591
-    Top = 481
+    Top = 506
     Width = 75
     Height = 25
     Anchors = [akRight, akBottom]

+ 5 - 0
source/forms/CopyParams.cpp

@@ -80,6 +80,7 @@ void __fastcall TCopyParamsFrame::SetParams(TCopyParamType value)
   PreserveReadOnlyCheck->Checked = value.PreserveReadOnly;
 
   PreserveTimeCheck->Checked = value.PreserveTime;
+  PreserveTimeDirsCheck->Checked = value.PreserveTimeDirs;
 
   CommonCalculateSizeCheck->Checked = value.CalculateSize;
 
@@ -133,6 +134,7 @@ TCopyParamType __fastcall TCopyParamsFrame::GetParams()
   Result.PreserveReadOnly = PreserveReadOnlyCheck->Checked;
 
   Result.PreserveTime = PreserveTimeCheck->Checked;
+  Result.PreserveTimeDirs = PreserveTimeDirsCheck->Checked;
 
   Result.CalculateSize = CommonCalculateSizeCheck->Checked;
 
@@ -198,6 +200,9 @@ void __fastcall TCopyParamsFrame::UpdateControls()
     Enabled);
   EnableControl(PreserveTimeCheck, FLAGCLEAR(CopyParamAttrs, cpaNoPreserveTime) &&
     FLAGCLEAR(CopyParamAttrs, cpaIncludeMaskOnly) && Enabled);
+  EnableControl(PreserveTimeDirsCheck,
+    PreserveTimeCheck->Enabled && FLAGCLEAR(CopyParamAttrs, cpaNoPreserveTimeDirs) &&
+    PreserveTimeCheck->Checked);
   EnableControl(ChangeCaseGroup, FLAGCLEAR(CopyParamAttrs, cpaIncludeMaskOnly) && Enabled);
   EnableControl(IgnorePermErrorsCheck,
     ((PreserveRightsCheck->Enabled && PreserveRightsCheck->Checked) ||

+ 24 - 12
source/forms/CopyParams.dfm

@@ -2,23 +2,23 @@ object CopyParamsFrame: TCopyParamsFrame
   Left = 0
   Top = 0
   Width = 420
-  Height = 431
+  Height = 456
   HelpType = htKeyword
   TabOrder = 0
   object CommonPropertiesGroup: TGroupBox
     Left = 212
     Top = 165
     Width = 201
-    Height = 98
+    Height = 123
     Caption = 'Common options'
     TabOrder = 3
     DesignSize = (
       201
-      98)
+      123)
     object SpeedLabel3: TLabel
       Left = 15
-      Top = 71
-      Width = 68
+      Top = 96
+      Width = 66
       Height = 13
       Caption = '&Speed (KB/s):'
       FocusControl = SpeedCombo
@@ -37,23 +37,23 @@ object CopyParamsFrame: TCopyParamsFrame
     end
     object CommonCalculateSizeCheck: TCheckBox
       Left = 16
-      Top = 46
+      Top = 71
       Width = 175
       Height = 17
       Anchors = [akLeft, akTop, akRight]
       Caption = '&Calculate total size'
       ParentShowHint = False
       ShowHint = True
-      TabOrder = 1
+      TabOrder = 2
       OnClick = ControlChange
     end
     object SpeedCombo: THistoryComboBox
       Left = 106
-      Top = 67
+      Top = 92
       Width = 85
       Height = 21
       AutoComplete = False
-      TabOrder = 2
+      TabOrder = 3
       Text = 'SpeedCombo'
       OnExit = SpeedComboExit
       Items.Strings = (
@@ -67,10 +67,22 @@ object CopyParamsFrame: TCopyParamsFrame
         '16'
         '8')
     end
+    object PreserveTimeDirsCheck: TCheckBox
+      Left = 32
+      Top = 45
+      Width = 159
+      Height = 17
+      Anchors = [akLeft, akTop, akRight]
+      Caption = 'Including directories'
+      ParentShowHint = False
+      ShowHint = True
+      TabOrder = 1
+      OnClick = ControlChange
+    end
   end
   object LocalPropertiesGroup: TGroupBox
     Left = 212
-    Top = 268
+    Top = 293
     Width = 201
     Height = 50
     Caption = 'Download options'
@@ -94,7 +106,7 @@ object CopyParamsFrame: TCopyParamsFrame
     Left = 8
     Top = 165
     Width = 194
-    Height = 153
+    Height = 178
     Caption = 'Upload options'
     TabOrder = 2
     object PreserveRightsCheck: TCheckBox
@@ -272,7 +284,7 @@ object CopyParamsFrame: TCopyParamsFrame
   end
   object OtherGroup: TGroupBox
     Left = 8
-    Top = 322
+    Top = 347
     Width = 405
     Height = 102
     Caption = 'Other'

+ 1 - 0
source/forms/CopyParams.h

@@ -49,6 +49,7 @@ __published:
   TButton *IncludeFileMaskButton;
   TCheckBox *NewerOnlyCheck;
   TCheckBox *RemoveCtrlZAndBOMCheck;
+  TCheckBox *PreserveTimeDirsCheck;
   void __fastcall ControlChange(TObject *Sender);
   void __fastcall ValidateMaskComboExit(TObject *Sender);
   void __fastcall RightsEditButtonClick(TObject *Sender);

+ 1 - 0
source/resource/TextsCore.h

@@ -451,6 +451,7 @@
 #define CODE_CONNECT            551
 #define CODE_YOUR_CODE          552
 #define CODE_PS_ADD_TYPE        553
+#define COPY_INFO_PRESERVE_TIME_DIRS 554
 
 #define CORE_VARIABLE_STRINGS   600
 #define PUTTY_BASED_ON          601

+ 1 - 0
source/resource/TextsCore1.rc

@@ -421,6 +421,7 @@ BEGIN
   CODE_CONNECT, "Connect"
   CODE_YOUR_CODE, "Your code"
   CODE_PS_ADD_TYPE, "Load WinSCP .NET assembly"
+  COPY_INFO_PRESERVE_TIME_DIRS, "%s (including directories)"
 
   CORE_VARIABLE_STRINGS, "CORE_VARIABLE"
   PUTTY_BASED_ON, "SSH and SCP code based on PuTTY %s"