`robot.Rws.File` gives access to the file system of the controller: browse it, download and upload files, and create, rename, copy or delete files and directories. Nothing has to be shared or mounted on the PC.

Paths are the ones used on the controller. The environment variables of the controller are accepted where a path is expected, `$home` and `$temp` for example, and they behave as directories. `robot.Rws.Controller.GetEnvironmentVariable` gives the real path behind such a name.

## Browse the file system

`ListDirectory` returns a `DirectoryListing` with the files, the subdirectories and, at the root, the storage devices of the controller. The complete content is always returned, however many entries the directory holds.

**C# : FileList**
```csharp
using UnderAutomation.ABB;
using UnderAutomation.ABB.Rws.Data;

public class FileList
{
    static void Main()
    {
        AbbController robot = new AbbController();
        robot.Connect("192.168.0.1");

        /**/
        // The root lists the storage devices of the controller
        DirectoryListing root = robot.Rws.File.ListDirectory("/");

        foreach (DeviceItem device in root.Devices)
        {
            Console.WriteLine($"{device.Name} ({device.DeviceType}) {device.FreeSpace}/{device.TotalSpace} bytes");
        }

        // A directory lists its files and its subdirectories
        DirectoryListing listing = robot.Rws.File.ListDirectory("$home");
        Console.WriteLine($"{listing.DirectoryCount} directories, {listing.FileCount} files");

        foreach (DirectoryItem directory in listing.Directories)
        {
            Console.WriteLine($"[DIR] {directory.Name}");
        }

        foreach (FileItem file in listing.Files)
        {
            Console.WriteLine($"{file.Name} {file.Size} bytes, modified {file.ModificationDate}");
        }
        /**/

        robot.Disconnect();
    }
}
```

Pass `"/"` or null to list the root. The root is where the storage devices are, with their free and total space. A `DeviceItem` has a `DeviceType`: `Fixed` for an internal disk, `Removable` for a USB key, `RamDisk`, `Remote` for a network storage, `Unknown` otherwise.

`FileCount`, `DirectoryCount`, `DeviceCount` and `TotalCount` are computed by the SDK, they save a null check on the three arrays.

## One class per kind of item

`FileItem`, `DirectoryItem` and `DeviceItem` all derive from `FileSystemItem`, which carries the name and the dates. There is no flag saying what an item is, the type itself says it.

**C# : FileSystemItems**
```csharp
using UnderAutomation.ABB;
using UnderAutomation.ABB.Rws.Data;

public class FileSystemItems
{
    static void Main()
    {
        AbbController robot = new AbbController();
        robot.Connect("192.168.0.1");

        DirectoryListing listing = robot.Rws.File.ListDirectory("/");

        /**/
        // The three arrays hold subclasses of the same base class
        List<FileSystemItem> items = new List<FileSystemItem>();
        items.AddRange(listing.Directories);
        items.AddRange(listing.Files);
        items.AddRange(listing.Devices);

        foreach (FileSystemItem item in items)
        {
            // Name, CreationDate and ModificationDate come from the base class
            Console.Write($"{item.Name} {item.ModificationDate} ");

            // What the item really is, is found with a type test, not with a flag
            if (item is FileItem file)
            {
                Console.WriteLine($"file of {file.Size} bytes");
            }
            else if (item is DirectoryItem directory)
            {
                Console.WriteLine($"directory, read only: {directory.IsReadOnly}");
            }
            else if (item is DeviceItem device)
            {
                Console.WriteLine($"device of type {device.DeviceType}, {device.FreeSpace} bytes free");
            }
        }
        /**/

        robot.Disconnect();
    }
}
```

`CreationDate` and `ModificationDate` are nullable, the controller does not always report them. `Size` is in bytes and only exists on a file.

## Download a file

Four methods read a file, they differ only by what you get back.

| Method                                  | Returns                                   |
| --------------------------------------- | ----------------------------------------- |
| `GetFileAsText(path, encoding)`         | `string`, UTF-8 when no encoding is given |
| `GetFileAsBytes(path)`                  | `byte[]`                                  |
| `GetFileToDestination(path, localPath)` | Nothing, the file is written on the PC    |
| `GetFileAsReadonlyStream(path)`         | A readable `Stream`                       |

**C# : FileDownload**
```csharp
using System.Text;
using UnderAutomation.ABB;

public class FileDownload
{
    static void Main()
    {
        AbbController robot = new AbbController();
        robot.Connect("192.168.0.1");

        /**/
        // A text file, UTF-8 by default
        string text = robot.Rws.File.GetFileAsText("$home/notes.txt");

        // Another encoding when the file is not UTF-8
        string latin = robot.Rws.File.GetFileAsText("$home/notes.txt", Encoding.GetEncoding("iso-8859-1"));

        // A binary file
        byte[] bytes = robot.Rws.File.GetFileAsBytes("$home/backup.zip");

        // Straight to a file of the PC
        robot.Rws.File.GetFileToDestination("$home/backup.zip", @"C:\temp\backup.zip");
        /**/

        /**/
        // As a stream, to copy the content without keeping a second copy of it
        using (Stream source = robot.Rws.File.GetFileAsReadonlyStream("$home/backup.zip"))
        using (FileStream destination = System.IO.File.Create(@"C:\temp\backup.zip"))
        {
            source.CopyTo(destination);
        }
        /**/

        robot.Disconnect();
    }
}
```

Use `GetFileAsText` for a RAPID module, a configuration file or a log. Use the bytes or the stream for a backup archive or any binary content. The synchronous `GetFileAsReadonlyStream` buffers the whole content in memory first, for compatibility with .NET Framework 3.5 and 4.0. `GetFileAsReadonlyStreamAsync` reads from the network as you read the stream, which is the one to use for a large file.

## Upload a file

The four upload methods mirror the downloads. An existing file is replaced.

**C# : FileUpload**
```csharp
using UnderAutomation.ABB;

public class FileUpload
{
    static void Main()
    {
        AbbController robot = new AbbController();
        robot.Connect("192.168.0.1");

        /**/
        // From a string, UTF-8 by default
        robot.Rws.File.UploadFileFromText("$home/notes.txt", "Written by the SDK");

        // From raw bytes
        robot.Rws.File.UploadFileFromBytes("$home/data.bin", new byte[] { 1, 2, 3, 4 });

        // From a file of the PC
        robot.Rws.File.UploadFileFromPath("$home/MyModule.mod", @"C:\temp\MyModule.mod");

        // From a stream, for a large file
        using (FileStream source = System.IO.File.OpenRead(@"C:\temp\MyModule.mod"))
        {
            robot.Rws.File.UploadFileFromStream("$home/MyModule.mod", source);
        }
        /**/

        robot.Disconnect();
    }
}
```

`UploadFileFromText` writes UTF-8 by default, pass an `Encoding` for another one. `UploadFileFromStream` sends the content as it reads it, so a large file does not have to be loaded in memory.

Create the destination directory first with `CreateDirectory` when it does not exist yet.

## Create, rename, copy and delete

**C# : FileManage**
```csharp
using UnderAutomation.ABB;

public class FileManage
{
    static void Main()
    {
        AbbController robot = new AbbController();
        robot.Connect("192.168.0.1");

        /**/
        // Directories. The new name can be nested, the missing levels are created.
        robot.Rws.File.CreateDirectory("$home", "MyApp/Logs");
        robot.Rws.File.RenameDirectory("$home/MyApp/Logs", "Archive");
        robot.Rws.File.CopyDirectory("$home/MyApp/Archive", "Archive2", true);
        robot.Rws.File.DeleteDirectory("$home/MyApp/Archive2");
        /**/

        /**/
        // Files. The third argument of the copy overwrites an existing target.
        robot.Rws.File.RenameFile("$home/notes.txt", "notes-old.txt");
        robot.Rws.File.CopyFile("$home/notes-old.txt", "notes-backup.txt", true);
        robot.Rws.File.DeleteFile("$home/notes-old.txt");
        /**/

        robot.Disconnect();
    }
}
```

A few points to know:

- `CreateDirectory` takes the parent directory and the new name. The name can be nested, and the missing levels are created.
- The new name of a rename or a copy is relative to the directory the item is in. An absolute path on the controller is also accepted.
- The copy methods take an `overwrite` argument. With `false`, the controller refuses to replace an existing target.
- `DeleteDirectory` deletes the directory and its content. There is no recycle bin on the controller, a deleted file is gone.

The controller protects part of its file system. A write in a read only location fails with an `RwsException`, and `IsReadOnly` on the item tells you before you try. The user account also needs the matching UAS grant.

## Around the file system

Other services write files on the controller and leave them for this one to read:

- [Controller](/abb/documentation/rws-controller) writes a backup in a folder of the controller. Browse that folder and download its files, as shown in [Backup & restore a controller](/abb/documentation/backup-restore-controller).
- [Event log](/abb/documentation/rws-elog) writes the whole log to one file with `SaveInSystemDumpFormat`.
- [RAPID modules](/abb/documentation/rws-rapid-modules) loads a module from a path on the controller, so a module written from the PC is uploaded first and loaded afterwards.

Nothing here needs the [mastership](/abb/documentation/rws-mastership), the file system is not one of its domains. Loading into RAPID what you uploaded does need it.

## API reference