Locking files while uploading / Upload to temporary file name

You may have an automated system monitoring a remote folder and you want to prevent it from accidentally picking a file that has not finished uploading yet. As majority of SFTP and FTP servers (WebDAV being an exception) do not support file locking, you need to prevent the automated system from picking the file otherwise.

Common workarounds are:

  1. Upload “done” file once an upload of data files finishes and have the automated system wait for the “done” file before processing the data files. This is easy solution, but won’t work in multi-user environment.
  2. Upload data files to temporary (“upload”) folder and move them atomically to target folder once the upload finishes.
  3. Upload data files to distinct temporary name, e.g. with .filepart extension, and rename them atomically once the upload finishes. Have the automated system ignore the .filepart files.

Advertisement

- A gross hack is to periodically check for file attributes (size and time) and consider the upload finished, if the attributes has not changed for some time interval.

Here we focus on the third approach (although the second is very similar, implementation-wise).

Using Transfer to temporary filename feature

With SFTP protocol, you can use Transfer to temporary filename feature to have WinSCP handle the rename automatically for you.

In scripting, use:

put -resumesupport=on d:\toupload\*.txt /home/martin/upload/

With WinSCP .NET assembly, use:

$transferOptions = New-Object WinSCP.TransferOptions
$transferOptions.ResumeSupport.State = [WinSCP.TransferResumeSupportState]::On
$session.PutFiles("d:\toupload\*.txt", "/home/martin/upload/", $False, $transferOptions).Check()

Moving/renaming uploaded files when upload finishes

If you need to use a different name pattern or a protocol different from SFTP or you want to take the second approach, you need to code the rename/move.

Advertisement

Following example shows implementation of the third approach using WinSCP .NET assembly in PowerShell:

param (
    $localPath = "c:\toupload\",
    $remotePath = "/home/user/upload/"
)
 
try
{
    # Load WinSCP .NET assembly
    Add-Type -Path "WinSCPnet.dll"
 
    # Setup session options
    $sessionOptions = New-Object WinSCP.SessionOptions -Property @{
        Protocol = [WinSCP.Protocol]::Sftp
        HostName = "example.com"
        UserName = "user"
        Password = "mypassword"
        SshHostKeyFingerprint = "ssh-rsa 2048 xxxxxxxxxxx..."
    }
 
    $session = New-Object WinSCP.Session
 
    try
    {
        # Connect
        $session.Open($sessionOptions)
 
        # Deliberately using an underscore instead of a dot,
        # as the dot has specific meaning in operation mask
        $suffix = "_filepart"
 
        $transferOptions = New-Object WinSCP.TransferOptions
        # Particularly with SFTP protocol, prevent additional .filepart suffix
        # from being added to uploaded files larger than 100 KB
        $transferOptions.ResumeSupport.State = [WinSCP.TransferResumeSupportState]::Off
 
        # Upload all .txt files with temporary "_filepart" suffix
        $transferResult =
            $session.PutFiles(($localPath + "*.txt"), ($remotePath + "*.*" + $suffix),
                $False, $transferOptions)
 
        # Throw on any error
        $transferResult.Check()
 
        # Rename uploaded files
        foreach ($transfer in $transferResult.Transfers)
        {
            # Remove suffix
            $finalName =
                $transfer.Destination.SubString(
                    0, $transfer.Destination.Length - $suffix.Length)
            Write-Host "Renaming uploaded file $($transfer.Destination) to $finalName"
            # Rename uploaded file to its final name
            $session.MoveFile($transfer.Destination, $finalName)
        }
    }
    finally
    {
        # Disconnect, clean up
        $session.Dispose()
    }
 
    exit 0
}
catch
{
    Write-Host "Error: $($_.Exception.Message)"
    exit 1
}

Advertisement

Further Reading

Last modified: by martin