SFTP/SCP tabs left open for a long time stopped working and never
reconnected. Root causes:
- TScpSend.Connect overrides Connect and never ran TFTPSendEx.Connect,
the only place SO_KEEPALIVE was applied, so SSH sockets had no
keep-alive at all and idle links got dropped (NAT/firewall timeout).
- TScpSend.NetworkError required CanRead(0) AND last_errno<>0, so a
server-closed idle session (readable socket, errno still 0) was
reported healthy and never triggered a reconnect.
- On a detected error it only re-logged-in on the dead session object.
Changes:
- Factor keep-alive into TFTPSendEx.ApplyKeepAlive and apply it to both
the FTP control socket and the SSH socket. On Unix also tune
TCP_KEEPIDLE/INTVL/CNT (30/10/3) so idle connections are probed within
~1 min: keeps NAT mappings open and detects dead peers quickly.
- TScpSend.NetworkError now treats either condition (errno set or
unexpected readable socket) as a dead link.
- On a dead link FtpConnect discards the stale connection and reconnects
fresh, reusing the cached password or prompting again when none is
cached. A plain refresh then reconnects and re-lists the current
folder. Quick connections (no stored entry) report unable to reconnect.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* FIX: zip plugin - allow cancelling TAR archive packing mid-operation
TAbTarArchive.SaveArchive iterated all items without ever checking
the abort flag, making it impossible to cancel a TAR/TGZ/TBZ2 pack
operation until all files had been written.
Add a DoArchiveProgress call with abort check after each item in the
save loop, identical to the pattern already used in TAbZipArchive.SaveArchive
(abziptyp.pas) and in all extract loops (abarctyp.pas).
When the user cancels, EAbUserAbort is raised, caught in PackFilesW,
and the incomplete archive is deleted if it did not exist before packing.
* FIX: zip plugin - prevent crash on TAR abort inside GZip archive
When TAbUserAbort propagates out of TAbTarArchive.SaveArchive (e.g.
from the abort check added to the save loop), it exits the IsGzippedTar
branch of TAbGzipArchive.SaveArchive without calling SwapToGzip. The
outer finally block then compares FStream (which equals FTarStream,
possibly nil) with NewStream (FGzStream) and incorrectly frees
NewStream. The destructor later calls SwapToGzip (restoring FStream to
the already-freed FGzStream) and then frees FStream a second time,
causing an access violation.
Fix: compare FGzStream instead of FStream in the finally condition.
FGzStream always holds the original gzip stream reference and is never
changed by SwapToTar/SwapToGzip, so the NewStream ownership check is
correct regardless of which swap state the archive is in.
---------
Co-authored-by: heredie <heredie@localhost>