mirror of
https://github.com/doublecmd/doublecmd.git
synced 2026-06-21 09:58:13 +00:00
Merge 1fa52a2c70 into 54d6654ad1
This commit is contained in:
commit
2cfa24119b
4 changed files with 148 additions and 11 deletions
|
|
@ -69,7 +69,7 @@ implementation
|
|||
|
||||
uses
|
||||
LazUTF8, DCBasicTypes, DCDateTimeUtils, DCStrUtils, DCOSUtils, CTypes,
|
||||
DCClassesUtf8, DCFileAttributes, DCConvertEncoding;
|
||||
DCClassesUtf8, DCFileAttributes, DCConvertEncoding{$IFDEF UNIX}, BaseUnix{$ENDIF};
|
||||
|
||||
const
|
||||
READ_BUFFER_SIZE = 131072;
|
||||
|
|
@ -228,12 +228,46 @@ var
|
|||
TotalBytesToWrite: Int64 = 0;
|
||||
TargetHandle: PLIBSSH2_SFTP_HANDLE = nil;
|
||||
Flags: cint = LIBSSH2_FXF_CREAT or LIBSSH2_FXF_WRITE;
|
||||
{$IFDEF UNIX}
|
||||
LocalStat: BaseUnix.TStat;
|
||||
UploadAttrs: LIBSSH2_SFTP_ATTRIBUTES;
|
||||
LinkTarget: String;
|
||||
{$ENDIF}
|
||||
begin
|
||||
if FCopySCP then begin
|
||||
Result:= inherited StoreFile(FileName, Restore);
|
||||
Exit;
|
||||
end;
|
||||
|
||||
{$IFDEF UNIX}
|
||||
// If the local source is a symlink, recreate it on the remote.
|
||||
// On failure (server refused), fall through to a normal content upload.
|
||||
if fpLStat(FDirectFileName, LocalStat) = 0 then
|
||||
begin
|
||||
if FPS_ISLNK(LocalStat.st_mode) then
|
||||
begin
|
||||
LinkTarget:= fpReadLink(FDirectFileName);
|
||||
if Length(LinkTarget) > 0 then
|
||||
begin
|
||||
// Remove any existing destination; sftp_symlink does not overwrite.
|
||||
repeat
|
||||
FLastError:= libssh2_sftp_unlink(FSFTPSession, PAnsiChar(FileName));
|
||||
if FLastError = LIBSSH2_ERROR_EAGAIN then FSock.CanRead(10);
|
||||
until FLastError <> LIBSSH2_ERROR_EAGAIN;
|
||||
repeat
|
||||
FLastError:= libssh2_sftp_symlink(FSFTPSession, PAnsiChar(LinkTarget), PAnsiChar(FileName));
|
||||
if FLastError = LIBSSH2_ERROR_EAGAIN then FSock.CanRead(10);
|
||||
until FLastError <> LIBSSH2_ERROR_EAGAIN;
|
||||
if FLastError = 0 then
|
||||
begin
|
||||
Result:= True;
|
||||
Exit;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
{$ENDIF}
|
||||
|
||||
SendStream := TFileStreamEx.Create(FDirectFileName, fmOpenRead or fmShareDenyWrite);
|
||||
|
||||
TargetName:= PWideChar(ServerToClient(FileName));
|
||||
|
|
@ -304,6 +338,22 @@ begin
|
|||
FreeMem(FBuffer);
|
||||
Result:= FileClose(TargetHandle) and Result;
|
||||
libssh2_session_set_blocking(FSession, 1);
|
||||
{$IFDEF UNIX}
|
||||
if Result then
|
||||
begin
|
||||
if FpStat(FDirectFileName, LocalStat) = 0 then
|
||||
begin
|
||||
FillChar(UploadAttrs, SizeOf(UploadAttrs), 0);
|
||||
UploadAttrs.permissions:= LocalStat.st_mode;
|
||||
UploadAttrs.flags:= LIBSSH2_SFTP_ATTR_PERMISSIONS;
|
||||
libssh2_sftp_setstat(FSFTPSession, PAnsiChar(FileName), @UploadAttrs);
|
||||
UploadAttrs.uid:= LocalStat.st_uid;
|
||||
UploadAttrs.gid:= LocalStat.st_gid;
|
||||
UploadAttrs.flags:= LIBSSH2_SFTP_ATTR_UIDGID;
|
||||
libssh2_sftp_setstat(FSFTPSession, PAnsiChar(FileName), @UploadAttrs);
|
||||
end;
|
||||
end;
|
||||
{$ENDIF}
|
||||
end;
|
||||
end;
|
||||
|
||||
|
|
@ -315,6 +365,9 @@ var
|
|||
RetrStream: TFileStreamEx;
|
||||
TotalBytesToRead: Int64 = 0;
|
||||
SourceHandle: PLIBSSH2_SFTP_HANDLE;
|
||||
{$IFDEF UNIX}
|
||||
DownloadAttrs: LIBSSH2_SFTP_ATTRIBUTES;
|
||||
{$ENDIF}
|
||||
begin
|
||||
if FCopySCP then begin
|
||||
Result:= inherited RetrieveFile(FileName, FileSize, Restore);
|
||||
|
|
@ -380,6 +433,20 @@ begin
|
|||
finally
|
||||
RetrStream.Free;
|
||||
libssh2_session_set_blocking(FSession, 1);
|
||||
{$IFDEF UNIX}
|
||||
if Result then
|
||||
begin
|
||||
if libssh2_sftp_stat(FSFTPSession, PAnsiChar(FileName), @DownloadAttrs) = 0 then
|
||||
begin
|
||||
if (DownloadAttrs.flags and LIBSSH2_SFTP_ATTR_PERMISSIONS) <> 0 then
|
||||
begin
|
||||
FpChmod(FDirectFileName, DownloadAttrs.permissions and $0FFF);
|
||||
if (DownloadAttrs.flags and LIBSSH2_SFTP_ATTR_UIDGID) <> 0 then
|
||||
FpChown(FDirectFileName, DownloadAttrs.uid, DownloadAttrs.gid);
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
{$ENDIF}
|
||||
end;
|
||||
end;
|
||||
|
||||
|
|
@ -404,6 +471,7 @@ var
|
|||
Return: Integer;
|
||||
FindRec: PFindRec absolute Handle;
|
||||
Attributes: LIBSSH2_SFTP_ATTRIBUTES;
|
||||
LinkAttrs: LIBSSH2_SFTP_ATTRIBUTES;
|
||||
AFileName: array[0..1023] of AnsiChar;
|
||||
AFullData: array[0..2047] of AnsiChar;
|
||||
begin
|
||||
|
|
@ -425,6 +493,9 @@ begin
|
|||
FindData.ftLastAccessTime:= TWfxFileTime(UnixFileTimeToWinTime(Attributes.atime));
|
||||
if (Attributes.permissions and S_IFMT) = S_IFLNK then
|
||||
begin
|
||||
// Follow the link to detect if the target is a directory, but keep
|
||||
// the symlink's own mtime and size for sync comparisons.
|
||||
LinkAttrs:= Attributes;
|
||||
if libssh2_sftp_stat(FSFTPSession, PAnsiChar(FindRec.Path + AFileName), @Attributes) = 0 then
|
||||
begin
|
||||
if (Attributes.permissions and S_IFMT) = S_IFDIR then
|
||||
|
|
@ -432,8 +503,16 @@ begin
|
|||
FindData.nFileSizeLow:= 0;
|
||||
FindData.nFileSizeHigh:= 0;
|
||||
FindData.dwFileAttributes:= FindData.dwFileAttributes or FILE_ATTRIBUTE_REPARSE_POINT;
|
||||
end
|
||||
else
|
||||
begin
|
||||
// Restore the symlink's own size (= byte length of link target string).
|
||||
FindData.nFileSizeLow:= Int64Rec(LinkAttrs.filesize).Lo;
|
||||
FindData.nFileSizeHigh:= Int64Rec(LinkAttrs.filesize).Hi;
|
||||
end;
|
||||
end;
|
||||
FindData.ftLastWriteTime:= TWfxFileTime(UnixFileTimeToWinTime(LinkAttrs.mtime));
|
||||
FindData.ftLastAccessTime:= TWfxFileTime(UnixFileTimeToWinTime(LinkAttrs.atime));
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
|
|
|||
|
|
@ -137,7 +137,7 @@ begin
|
|||
TreeBuilder := TFileSystemTreeBuilder.Create(@AskQuestion, @CheckOperationState);
|
||||
try
|
||||
ElevateAction:= dupError;
|
||||
TreeBuilder.SymLinkOption:= fsooslFollow;
|
||||
TreeBuilder.SymLinkOption:= fsooslDontFollow;
|
||||
TreeBuilder.BuildFromFiles(SourceFiles);
|
||||
FSourceFilesTree := TreeBuilder.ReleaseTree;
|
||||
FStatistics.TotalFiles := TreeBuilder.FilesCount;
|
||||
|
|
|
|||
|
|
@ -369,7 +369,10 @@ begin
|
|||
Result := ProcessDirectory(aSubNode, AbsoluteTargetFileName)
|
||||
else
|
||||
Result := ProcessFile(aSubNode, AbsoluteTargetFileName);
|
||||
end;
|
||||
end
|
||||
else
|
||||
// Link not followed — pass it to ProcessFile to handle (e.g. SFTP symlink creation).
|
||||
Result := ProcessFile(aNode, AbsoluteTargetFileName);
|
||||
end;
|
||||
|
||||
function TWfxPluginOperationHelper.ProcessFile(aNode: TFileTreeNode;
|
||||
|
|
|
|||
|
|
@ -236,6 +236,7 @@ implementation
|
|||
|
||||
uses
|
||||
fMain, uDebug, fDiffer, fSyncDirsPerformDlg, uGlobs, LCLType, LazUTF8, LazFileUtils,
|
||||
uOSForms,
|
||||
uFileSystemFileSource, uFileSourceOperationOptions, DCDateTimeUtils, SyncObjs,
|
||||
uDCUtils, uFileSourceUtil, uFileSourceOperationTypes, uShowForm, uAdministrator,
|
||||
uOSUtils, uLng, uMasks, Math, uClipboard, IntegerList, fMaskInputDlg, uSearchTemplate,
|
||||
|
|
@ -289,13 +290,22 @@ type
|
|||
end;
|
||||
|
||||
procedure ShowSyncDirsDlg(FileView1, FileView2: TFileView);
|
||||
var
|
||||
Dlg: TfrmSyncDirsDlg;
|
||||
begin
|
||||
if not Assigned(FileView1) then
|
||||
raise Exception.Create('ShowSyncDirsDlg: FileView1=nil');
|
||||
if not Assigned(FileView2) then
|
||||
raise Exception.Create('ShowSyncDirsDlg: FileView2=nil');
|
||||
with TfrmSyncDirsDlg.Create(Application, FileView1, FileView2) do
|
||||
Show;
|
||||
Dlg := TfrmSyncDirsDlg.Create(Application, FileView1, FileView2);
|
||||
{ Center on the same monitor as the main DC window. }
|
||||
if Assigned(frmMain) then
|
||||
with GetFrmMainMonitor do
|
||||
Dlg.SetBounds(
|
||||
Left + (Width - Dlg.Width) div 2,
|
||||
Top + (Height - Dlg.Height) div 2,
|
||||
Dlg.Width, Dlg.Height);
|
||||
Dlg.Show;
|
||||
end;
|
||||
|
||||
{ TDrawGrid }
|
||||
|
|
@ -465,7 +475,12 @@ begin
|
|||
end;
|
||||
if R.FAction = srsUnknown then
|
||||
begin
|
||||
R.FAction := R.FState;
|
||||
// Mirror asymmetric logic from TFileSyncRec.Recalc:
|
||||
// in asymmetric mode srsNotEq means left wins → srsCopyRight.
|
||||
if chkAsymmetric.Checked and (R.FState = srsNotEq) then
|
||||
R.FAction := srsCopyRight
|
||||
else
|
||||
R.FAction := R.FState;
|
||||
end;
|
||||
except
|
||||
on E: Exception do
|
||||
|
|
@ -543,6 +558,38 @@ end;
|
|||
procedure TFileSyncRec.UpdateState(ignoreDate: Boolean);
|
||||
var
|
||||
FileTimeDiff: Integer;
|
||||
|
||||
function AreEquivalentLinks: Boolean;
|
||||
var
|
||||
LeftTarget, RightTarget: String;
|
||||
LeftResolved, RightResolved: String;
|
||||
begin
|
||||
Result := False;
|
||||
|
||||
if not (FFileL.IsLink and FFileR.IsLink) then Exit;
|
||||
|
||||
LeftTarget := FFileL.LinkProperty.LinkTo;
|
||||
RightTarget := FFileR.LinkProperty.LinkTo;
|
||||
|
||||
// Fast path: identical link text means semantically identical link.
|
||||
if LeftTarget = RightTarget then Exit(True);
|
||||
|
||||
if (LeftTarget = EmptyStr) or (RightTarget = EmptyStr) then
|
||||
begin
|
||||
// One side doesn't expose the link target (e.g. WFX/SFTP plugin).
|
||||
// SFTP file size for a symlink equals the byte length of its target
|
||||
// string, so equal sizes strongly imply equal targets. This is a
|
||||
// best-effort check for the copy-then-verify use case.
|
||||
Result := FFileL.Size = FFileR.Size;
|
||||
Exit;
|
||||
end;
|
||||
|
||||
// Also accept different textual forms that resolve to the same target.
|
||||
LeftResolved := GetAbsoluteFileName(FFileL.Path, LeftTarget);
|
||||
RightResolved := GetAbsoluteFileName(FFileR.Path, RightTarget);
|
||||
|
||||
Result := mbCompareFileNames(LeftResolved, RightResolved);
|
||||
end;
|
||||
begin
|
||||
FState := srsNotEq;
|
||||
if Assigned(FFileR) and not Assigned(FFileL) then
|
||||
|
|
@ -552,7 +599,8 @@ begin
|
|||
FState := srsCopyRight
|
||||
else begin
|
||||
FileTimeDiff := FileTimeCompare(FFileL.ModificationTime, FFileR.ModificationTime, FForm.FNtfsShift);
|
||||
if ((FileTimeDiff = 0) or ignoreDate) and (FFileL.Size = FFileR.Size) then
|
||||
if (((FileTimeDiff = 0) or ignoreDate) and (FFileL.Size = FFileR.Size))
|
||||
or AreEquivalentLinks then
|
||||
FState := srsEqual
|
||||
else
|
||||
if not ignoreDate then
|
||||
|
|
@ -562,11 +610,18 @@ begin
|
|||
if FileTimeDiff < 0 then
|
||||
FState := srsCopyLeft;
|
||||
end;
|
||||
if FForm.chkAsymmetric.Checked and (FState = srsCopyLeft) then
|
||||
FAction := srsDoNothing
|
||||
else begin
|
||||
if FForm.chkAsymmetric.Checked then
|
||||
begin
|
||||
// In asymmetric/mirror mode left is unconditionally authoritative.
|
||||
// srsCopyLeft means right is newer — left still wins (overwrite right).
|
||||
// srsNotEq means ambiguous diff (e.g. content check) — left still wins.
|
||||
if FState in [srsCopyLeft, srsNotEq] then
|
||||
FAction := srsCopyRight
|
||||
else
|
||||
FAction := FState;
|
||||
end
|
||||
else
|
||||
FAction := FState;
|
||||
end;
|
||||
end;
|
||||
|
||||
{ TfrmSyncDirsDlg }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue