This page explains how the Staubli SDK accesses the files of a CS8 or CS9 controller, real or emulated by Staubli Robotics Suite: the connection, the paths and the errors. The file client `controller.File` uploads, downloads, lists and manages the files, and sends complete VAL 3 applications.

## Real or emulated controller

The same methods work on both, with the same paths. Only the address changes.

### Real controller

The file client uses the FTP server of the controller. Give its IP address, and the user and the password of the FTP server.

**C# : FilesConnect**
```csharp
using UnderAutomation.Staubli;

public class FilesConnect
{
    static void Main()
    {
        /**/
        var parameters = new ConnectionParameters("192.168.0.254");

        // The file client is disabled by default
        parameters.File.Enable = true;

        // User and password of the FTP server of the controller
        parameters.File.User = "default";
        parameters.File.Password = "default";

        var controller = new StaubliController();
        controller.Connect(parameters);

        // False with a real controller: the files go through FTP
        Console.WriteLine(controller.File.IsSimulated);
        /**/

        controller.Disconnect();
    }
}
```

**Python : FilesConnect**
```python
from underautomation.staubli.staubli_controller import StaubliController
from underautomation.staubli.connection_parameters import ConnectionParameters

##
parameters = ConnectionParameters("192.168.0.254")

# The file client is disabled by default
parameters.file.enable = True

# User and password of the FTP server of the controller
parameters.file.user = "default"
parameters.file.password = "default"

controller = StaubliController()
controller.connect(parameters)

# False with a real controller: the files go through FTP
print(controller.file.is_simulated)
##

controller.disconnect()
```

### Emulated controller

The emulator of Staubli Robotics Suite (SRS) has no FTP server. It keeps the files of the emulated controller in the folder of its `.controller` file, with the same tree as a real controller (`usr`, `log`). Give the path of the `.controller` file as address:

- **Local path** (`C:\...\MyCell\Controller1\Controller1.controller`): the emulator runs on this PC. The SOAP client connects to `127.0.0.1`.
- **UNC path** (`\\SRS-PC\share\...\Controller1\Controller1.controller`): the emulator runs on another PC. The SOAP client connects to this PC, and the files go through the Windows share.

A path that is not a `.controller` file is refused. The SOAP port of the emulated controller is read from its configuration: see [Test with the Staubli Robotics Suite emulator](/staubli/documentation/simulator).

**C# : FilesConnectSimulator**
```csharp
using UnderAutomation.Staubli;

public class FilesConnectSimulator
{
    static void Main()
    {
        /**/
        // Controller emulated by Staubli Robotics Suite on this PC: give its .controller file.
        // The SOAP client connects to 127.0.0.1, the file client uses the folder of the .controller file.
        var parameters = new ConnectionParameters(@"C:\SRS\MyCell\Controller1\Controller1.controller");

        // Emulator on another PC: give a UNC path. The SOAP client connects to this PC.
        // var parameters = new ConnectionParameters(@"\\SRS-PC\SRS\MyCell\Controller1\Controller1.controller");

        parameters.File.Enable = true;

        var controller = new StaubliController();
        controller.Connect(parameters);

        // True: the files are read and written in the folder of the .controller file
        Console.WriteLine(controller.File.IsSimulated);
        Console.WriteLine(controller.File.ControllerFolder);
        /**/

        controller.Disconnect();
    }
}
```

**Python : FilesConnectSimulator**
```python
from underautomation.staubli.staubli_controller import StaubliController
from underautomation.staubli.connection_parameters import ConnectionParameters

##
# Controller emulated by Staubli Robotics Suite on this PC: give its .controller file.
# The SOAP client connects to 127.0.0.1, the file client uses the folder of the .controller file.
parameters = ConnectionParameters(r"C:\SRS\MyCell\Controller1\Controller1.controller")

# Emulator on another PC: give a UNC path. The SOAP client connects to this PC.
# parameters = ConnectionParameters(r"\\SRS-PC\SRS\MyCell\Controller1\Controller1.controller")

parameters.file.enable = True

controller = StaubliController()
controller.connect(parameters)

# True: the files are read and written in the folder of the .controller file
print(controller.file.is_simulated)
print(controller.file.controller_folder)
##

controller.disconnect()
```

`IsSimulated` tells which mode is used. With an emulated controller, `ControllerFile` gives the full path of the `.controller` file and `ControllerFolder` the folder of the files, and the user and the password are not used.

## Connection parameters

| Parameter        | Default     | Meaning                                                       |
| ---------------- | ----------- | ------------------------------------------------------------- |
| `File.Enable`    | `false`     | Connect the file client                                       |
| `File.User`      | `"default"` | User of the FTP server of the controller                      |
| `File.Password`  | `"default"` | Password of this user                                         |
| `File.Port`      | `21`        | Port of the FTP server (`FileConnectParameters.DEFAULT_PORT`) |
| `File.TimeoutMs` | `30000`     | Timeout of the FTP connection and of the transfers, in ms     |

Set `Soap.Enable` to `false` to use the file client alone. The SOAP parameters are on the page [Connect to your robot](/staubli/documentation/connect).

## Paths on the controller

The paths are the paths of the controller, with `/` as separator: `/usr/usrapp/myApp/myApp.pjx`. A path that does not start with `/` is relative to the root of the controller. With an emulated controller, the root is the folder of the `.controller` file: a path cannot go outside of it.

| Folder              | Content                                                                                   |
| ------------------- | ----------------------------------------------------------------------------------------- |
| `/usr/usrapp`       | The VAL 3 applications, one sub-folder per application (`FileClientBase.USER_APP_FOLDER`) |
| `/usr/usrapp/myApp` | The files of the application `myApp`: `myApp.pjx`, its programs and its data              |

The project path `Disk://myApp/myApp.pjx` of the SOAP methods (`LoadProject`, `StartApplication`) is the file `/usr/usrapp/myApp/myApp.pjx`.

## Standalone file client

`FileClient` connects without `StaubliController`. Its address is an IP or the path of a `.controller` file, as above.

**C# : FilesStandalone**
```csharp
using UnderAutomation.Staubli.Files;

public class FilesStandalone
{
    static void Main()
    {
        /**/
        var files = new FileClient();

        // Real controller: IP, FTP user and password (port 21 by default)
        files.Connect("192.168.0.254", "default", "default");

        // Or the .controller file of a controller emulated by Staubli Robotics Suite (the user and the password are not used)
        // files.Connect(@"C:\SRS\MyCell\Controller1\Controller1.controller", null, null);

        foreach (FileItem item in files.GetListing("/usr/usrapp"))
            Console.WriteLine(item.Name);

        files.Disconnect();
        /**/
    }
}
```

**Python : FilesStandalone**
```python
from underautomation.staubli.files.file_client import FileClient

##
files = FileClient()

# Real controller: IP, FTP user and password (port 21 by default)
files.connect("192.168.0.254", "default", "default")

# Or the .controller file of a controller emulated by Staubli Robotics Suite (the user and the password are not used)
# files.connect(r"C:\SRS\MyCell\Controller1\Controller1.controller", None, None)

for item in files.get_listing("/usr/usrapp"):
    print(item.name)

files.disconnect()
##
```

## Errors

**C# : FilesErrors**
```csharp
using UnderAutomation.Staubli;
using UnderAutomation.Staubli.Files;

public class FilesErrors
{
    static void Main()
    {
        var parameters = new ConnectionParameters("192.168.0.254");
        parameters.File.Enable = true;
        var controller = new StaubliController();

        /**/
        try
        {
            controller.Connect(parameters);
            byte[] content = controller.File.DownloadBytesFromController("/usr/usrapp/myApp/myApp.pjx");
        }
        catch (FileException ex)
        {
            // Connection refused, file not found, or operation refused by the controller
            Console.WriteLine(ex.Message);

            // FTP reply of the controller, 0 and null when there is none
            Console.WriteLine($"{ex.RemotePath} {ex.ReplyCode} {ex.ReplyMessage}");
        }
        catch (DirectoryNotFoundException ex)
        {
            // The address is a folder that does not exist
            Console.WriteLine(ex.Message);
        }
        /**/

        controller.Disconnect();
    }
}
```

**Python : FilesErrors**
```python
from underautomation.staubli.staubli_controller import StaubliController
from underautomation.staubli.connection_parameters import ConnectionParameters
from UnderAutomation.Staubli.Files import FileException
from System.IO import DirectoryNotFoundException

parameters = ConnectionParameters("192.168.0.254")
parameters.file.enable = True
controller = StaubliController()

##
# The exceptions come from the .NET runtime, so their members keep their original names
try:
    controller.connect(parameters)
    content = controller.file.download_bytes_from_controller("/usr/usrapp/myApp/myApp.pjx")
except FileException as ex:
    # Connection refused, file not found, or operation refused by the controller
    print(ex.Message)

    # FTP reply of the controller, 0 and None when there is none
    print(ex.RemotePath, ex.ReplyCode, ex.ReplyMessage)
except DirectoryNotFoundException as ex:
    # The address is a folder that does not exist
    print(ex.Message)
##

controller.disconnect()
```

| Exception                    | When                                                                                        |
| ---------------------------- | ------------------------------------------------------------------------------------------- |
| `FileException`              | The FTP connection failed, the file does not exist, or the controller refused the operation |
| `ArgumentException`          | The address is a path but not a `.controller` file, or a path goes outside of its folder    |
| `FileNotFoundException`      | The `.controller` file does not exist                                                       |
| `InvalidOperationException`  | A method is called before the connection, or after `Disconnect`                             |

When the FTP connection fails, the message of `FileException` also reminds that an emulated controller needs the path of its `.controller` file. `ReplyCode` and `ReplyMessage` give the reply of the FTP server of the controller, when there is one. The errors of the local files of your PC (for example a local file that does not exist) are not converted.

## Reference

**Methods of FileClientBase**
```csharp
// Creates a folder on the controller, with its parent folders when they do not exist. Nothing is done when the folder exists.
void CreateDirectory(string path);

// Deletes a folder of the controller and all its content
void DeleteDirectory(string path);

// Deletes a file of the controller
void DeleteFile(string path);

// Checks if a folder exists on the controller
bool DirectoryExists(string path);

// Disconnects the client
void Disconnect();

// Downloads a file of the controller and returns its content
byte[] DownloadBytesFromController(string remotePath, OnProgressDelegate progress = null);

// Downloads a file of the controller to a local file. The local file is replaced when it exists, and its folder is created when it does not exist.
void DownloadFileFromController(string localPath, string remotePath, OnProgressDelegate progress = null);

// Downloads a file of the controller and writes its content to a stream
void DownloadStreamFromController(Stream stream, string remotePath, OnProgressDelegate progress = null);

// Checks if a file exists on the controller
bool FileExists(string path);

// Gets information about a file or a folder of the controller
FileItem GetFileInfo(string path);

// Lists the files and folders of a folder of the controller
FileItem[] GetListing(string path);

// Renames or moves a file or a folder of the controller
void Rename(string path, string newPath);

// Uploads a complete VAL 3 application to the controller. The local folder of the application, named as the application and with its project file inside (for example C:\MyApps\myApp\myApp.pjx), is copied with its sub-folders to "/usr/usrapp/myApp" (FileClientBase.USER_APP_FOLDER). When the application already exists on the controller, its folder is deleted first: the files that are not in the local folder are removed. Stop and unload the application before (robot.Soap.StopAndUnloadAll()), then load it after (robot.Soap.LoadProject("Disk://myApp/myApp.pjx")).
string UploadApplicationToController(string localAppFolder, OnProgressDelegate progress = null);

// Uploads data as a file to the controller. The file of the controller is replaced when it exists.
void UploadBytesToController(byte[] data, string remotePath, bool createRemoteDir = false, OnProgressDelegate progress = null);

// Uploads a local file to the controller. The file of the controller is replaced when it exists.
void UploadFileToController(string localPath, string remotePath, bool createRemoteDir = false, OnProgressDelegate progress = null);

// Uploads the content of a stream as a file to the controller, from the current position of the stream to its end. The file of the controller is replaced when it exists.
void UploadStreamToController(Stream stream, string remotePath, bool createRemoteDir = false, OnProgressDelegate progress = null);
```

Every method also exists in an asynchronous version, with the same name followed by `Async` and an optional `CancellationToken`.

**Members of Files.Internal.FileClientBase**
```csharp
public abstract class FileClientBase {
    // Full path of the .controller file of the controller emulated by Staubli Robotics Suite. Null with a real controller.
    public string ControllerFile { get; }

    // Full path of the folder of the .controller file: root of the files of the emulated controller. Null with a real controller.
    public string ControllerFolder { get; }

    // Creates a folder on the controller, with its parent folders when they do not exist. Nothing is done when the folder exists.
    public void CreateDirectory(string path)

    // Creates a folder on the controller, with its parent folders when they do not exist (asynchronous). Nothing is done when the folder exists.
    public Task CreateDirectoryAsync(string path, CancellationToken cancellationToken = default)

    // Deletes a folder of the controller and all its content
    public void DeleteDirectory(string path)

    // Deletes a folder of the controller and all its content (asynchronous)
    public Task DeleteDirectoryAsync(string path, CancellationToken cancellationToken = default)

    // Deletes a file of the controller
    public void DeleteFile(string path)

    // Deletes a file of the controller (asynchronous)
    public Task DeleteFileAsync(string path, CancellationToken cancellationToken = default)

    // Checks if a folder exists on the controller
    public bool DirectoryExists(string path)

    // Checks if a folder exists on the controller (asynchronous)
    public Task<bool> DirectoryExistsAsync(string path, CancellationToken cancellationToken = default)

    // Disconnects the client
    public void Disconnect()

    // Downloads a file of the controller and returns its content
    public byte[] DownloadBytesFromController(string remotePath, OnProgressDelegate progress = null)

    // Downloads a file of the controller and returns its content (asynchronous)
    public Task<byte[]> DownloadBytesFromControllerAsync(string remotePath, OnProgressDelegate progress = null, CancellationToken cancellationToken = default)

    // Downloads a file of the controller to a local file. The local file is replaced when it exists, and its folder is created when it does not exist.
    public void DownloadFileFromController(string localPath, string remotePath, OnProgressDelegate progress = null)

    // Downloads a file of the controller to a local file (asynchronous). The local file is replaced when it exists, and its folder is created when it does not exist.
    public Task DownloadFileFromControllerAsync(string localPath, string remotePath, OnProgressDelegate progress = null, CancellationToken cancellationToken = default)

    // Downloads a file of the controller and writes its content to a stream
    public void DownloadStreamFromController(Stream stream, string remotePath, OnProgressDelegate progress = null)

    // Downloads a file of the controller and writes its content to a stream (asynchronous)
    public Task DownloadStreamFromControllerAsync(Stream stream, string remotePath, OnProgressDelegate progress = null, CancellationToken cancellationToken = default)

    // True when the client is connected
    public bool Enabled { get; }

    // Checks if a file exists on the controller
    public bool FileExists(string path)

    // Checks if a file exists on the controller (asynchronous)
    public Task<bool> FileExistsAsync(string path, CancellationToken cancellationToken = default)

    // Gets information about a file or a folder of the controller
    public FileItem GetFileInfo(string path)

    // Gets information about a file or a folder of the controller (asynchronous)
    public Task<FileItem> GetFileInfoAsync(string path, CancellationToken cancellationToken = default)

    // Lists the files and folders of a folder of the controller
    public FileItem[] GetListing(string path)

    // Lists the files and folders of a folder of the controller (asynchronous)
    public Task<FileItem[]> GetListingAsync(string path, CancellationToken cancellationToken = default)

    // IP or host name of the controller. Null with an emulated controller.
    public string Ip { get; }

    // True when the files are accessed in the folder of the .controller file of a controller emulated by Staubli Robotics Suite,
    // false when they are accessed through FTP
    public bool IsSimulated { get; }

    // Port of the FTP server of the controller. 0 with an emulated controller.
    public int Port { get; }

    // Renames or moves a file or a folder of the controller
    public void Rename(string path, string newPath)

    // Renames or moves a file or a folder of the controller (asynchronous)
    public Task RenameAsync(string path, string newPath, CancellationToken cancellationToken = default)

    // Folder of the VAL 3 applications on the controller. Each application is in a sub-folder named as the application
    // (for example "/usr/usrapp/myApp/myApp.pjx"). The project path "Disk://myApp/myApp.pjx" of robot.Soap.LoadProject(...) is this file.
    public const string USER_APP_FOLDER = "/usr/usrapp"

    // Uploads a complete VAL 3 application to the controller. The local folder of the application, named as the application and with its
    // project file inside (for example C:\MyApps\myApp\myApp.pjx), is copied with its sub-folders to "/usr/usrapp/myApp" (<xref href="UnderAutomation.Staubli.Files.Internal.FileClientBase.USER_APP_FOLDER" data-throw-if-not-resolved="false"></xref>).
    // When the application already exists on the controller, its folder is deleted first: the files that are not in the local folder are removed.
    // Stop and unload the application before (robot.Soap.StopAndUnloadAll()), then load it after (robot.Soap.LoadProject("Disk://myApp/myApp.pjx")).
    public string UploadApplicationToController(string localAppFolder, OnProgressDelegate progress = null)

    // Uploads a complete VAL 3 application to the controller (asynchronous). The local folder of the application, named as the application and with its
    // project file inside (for example C:\MyApps\myApp\myApp.pjx), is copied with its sub-folders to "/usr/usrapp/myApp" (<xref href="UnderAutomation.Staubli.Files.Internal.FileClientBase.USER_APP_FOLDER" data-throw-if-not-resolved="false"></xref>).
    // When the application already exists on the controller, its folder is deleted first: the files that are not in the local folder are removed.
    // Stop and unload the application before (robot.Soap.StopAndUnloadAll()), then load it after (robot.Soap.LoadProject("Disk://myApp/myApp.pjx")).
    public Task<string> UploadApplicationToControllerAsync(string localAppFolder, OnProgressDelegate progress = null, CancellationToken cancellationToken = default)

    // Uploads data as a file to the controller. The file of the controller is replaced when it exists.
    public void UploadBytesToController(byte[] data, string remotePath, bool createRemoteDir = false, OnProgressDelegate progress = null)

    // Uploads data as a file to the controller (asynchronous). The file of the controller is replaced when it exists.
    public Task UploadBytesToControllerAsync(byte[] data, string remotePath, bool createRemoteDir = false, OnProgressDelegate progress = null, CancellationToken cancellationToken = default)

    // Uploads a local file to the controller. The file of the controller is replaced when it exists.
    public void UploadFileToController(string localPath, string remotePath, bool createRemoteDir = false, OnProgressDelegate progress = null)

    // Uploads a local file to the controller (asynchronous). The file of the controller is replaced when it exists.
    public Task UploadFileToControllerAsync(string localPath, string remotePath, bool createRemoteDir = false, OnProgressDelegate progress = null, CancellationToken cancellationToken = default)

    // Uploads the content of a stream as a file to the controller, from the current position of the stream to its end. The file of the controller is replaced when it exists.
    public void UploadStreamToController(Stream stream, string remotePath, bool createRemoteDir = false, OnProgressDelegate progress = null)

    // Uploads the content of a stream as a file to the controller, from the current position of the stream to its end (asynchronous). The file of the controller is replaced when it exists.
    public Task UploadStreamToControllerAsync(Stream stream, string remotePath, bool createRemoteDir = false, OnProgressDelegate progress = null, CancellationToken cancellationToken = default)
}
```

**Members of Common.FileConnectParameters**
```csharp
public class FileConnectParameters : FileConnectParametersBase {
    public FileConnectParameters()

    // Default port of the FTP server
    public const int DEFAULT_PORT = 21

    // Default timeout of the FTP connection and of the transfers, in milliseconds
    public const int DEFAULT_TIMEOUT_MS = 30000

    // Should use this service (default: false)
    public bool Enable { get; set; }
}
```

**Members of Files.Internal.FileConnectParametersBase**
```csharp
public class FileConnectParametersBase {
    public FileConnectParametersBase()

    // Password of the user (default: default). Not used with a controller emulated by Staubli Robotics Suite.
    public string Password { get; set; }

    // Port of the FTP server of the controller (default: 21)
    public int Port { get; set; }

    // Timeout of the FTP connection and of the transfers, in milliseconds (default: 30000)
    public int TimeoutMs { get; set; }

    // User of the FTP server of the controller (default: default). Not used with a controller emulated by Staubli Robotics Suite.
    public string User { get; set; }
}
```

**Members of Files.FileException**
```csharp
public class FileException : Exception, ISerializable {
    // Path of the file or folder on the controller concerned by the operation. Null when the operation has no path.
    public string RemotePath { get; }

    // FTP reply code returned by the controller (for example 550). 0 when the controller did not reply, and with an emulated controller.
    public int ReplyCode { get; }

    // Reply text returned by the controller. Null when the controller did not reply, and with an emulated controller.
    public string ReplyMessage { get; }
}
```