## File Transfers

RCF has built-in support for transferring files and directories. While smaller files can be transferred easily in a single call (for example using `RCF::ByteBuffer` arguments), this technique fails once the size of the file exceeds the maximum message size of the connection.

To reliably transfer files of arbitrary size, the files need to be split into chunks and transmitted in separate calls, with the recipient reassembling the chunks to form the destination file. RCF automates this chunking logic in a secure and efficient way, using the `RCF::FileUpload` and `RCF::FileDownload` classes.

### File uploads

RCF clients can upload files or directories to a server by including a `FileUpload` object as a remote call parameter:

    RCF_BEGIN(I_HelloWorld, "I_HelloWorld")
        RCF_METHOD_V1(void, Print, const std::string &)
        RCF_METHOD_V1(void, UploadFiles, RCF::FileUpload)
    RCF_END(I_HelloWorld)

    RcfClient<I_HelloWorld> client( RCF::TcpEndpoint(50001) );
    RCF::FileUpload fileUpload("C:\\FileToUpload.zip");
    client.UploadFiles(fileUpload);

The serialization implementation of the `FileUpload` class takes care of transferring the nominated files to the server. On the server-side, the servant object that the remote call is dispatched to, uses the `FileUpload` object to locate the newly uploaded files on the local system:

    class HelloWorldImpl
    {
    public:
        void Print(const std::string & s)
        {
            std::cout << "I_HelloWorld service: " << s << std::endl;
        }

        void UploadFiles(RCF::FileUpload fileUpload)
        {
            // Where are the uploaded files.
            std::string uploadPath = fileUpload.getLocalPath();

            // Which files have been uploaded.
            RCF::FileManifest & manifest = fileUpload.getManifest();
        }
    };

If the connection to the server is lost during the transfer, the upload can be resumed by reusing the same `FileUpload` object:

    // Call UploadFiles with the same arguments again, to resume an interrupted upload.
    client.UploadFiles(fileUpload);

To monitor the progress of the upload, use `ClientStub::setFileProgressCallback()`:

    void onFileTransferProgress(const RCF::FileTransferProgress & progress)
    {
        std::cout << "Total bytes to transfer: " << progress.mBytesTotalToTransfer << std::endl;
        std::cout << "Bytes transferred so far: " << progress.mBytesTransferredSoFar << std::endl;
        std::cout << "Server-side bandwidth limit (if any): " << progress.mServerLimitBps << std::endl;
    }

    client.getClientStub().setFileProgressCallback(&onFileTransferProgress);

To encrypt the file upload, set the transport protocol of the `RcfClient<>` object, before commencing the upload. For instance, to use NTLM:

    client.getClientStub().setTransportProtocol(RCF::Tp_Ntlm);

On the server, the default upload location is a subdirectory of the current working directory. You can use `RcfServer::setFileUploadDirectory()` to set a different location for file uploads:

    server.setFileUploadDirectory("C:\\RCF-Uploads");

Once files are uploaded to the server, the lifetime of the uploaded files becomes the responsibility of the server application.

The server can also monitor the progress of all file uploads, as well as cancel file uploads:

    void onServerFileUpload(RCF::RcfSession & session, const RCF::FileUploadInfo & uploadInfo)
    {
        // Which files are being uploaded.
        const RCF::FileManifest& manifest = uploadInfo.mManifest;

        // How far has the upload progressed.
        boost::uint32_t currentFile = uploadInfo.mCurrentFile;
        boost::uint64_t currentFilePos = uploadInfo.mCurrentPos;

        // To cancel the upload, throw an exception.
        // ...
    }

    server.setOnFileUploadProgress(&onServerFileUpload);

### File downloads

File downloads are implemented in RCF in a similary way to file uploads. RCF clients can download files or directories from a server by including a `FileDownload` object as a remote call parameter:

    RCF_BEGIN(I_HelloWorld, "I_HelloWorld")
        RCF_METHOD_V1(void, Print, const std::string &)
        RCF_METHOD_V1(void, DownloadFiles, RCF::FileDownload)
    RCF_END(I_HelloWorld)

    RcfClient<I_HelloWorld> client( RCF::TcpEndpoint(50001) );

    RCF::FileDownload fileDownload;
    client.DownloadFiles(fileDownload);

    std::string localFilePath = fileDownload.getLocalPath();
    RCF::FileManifest & fileManifest = fileDownload.getManifest();

The servant object that the remote call is dispatched to, uses the `FileDownload` object to set the files that will be downloaded to the client:

    class HelloWorldImpl
    {
    public:
        void Print(const std::string & s)
        {
            std::cout << "I_HelloWorld service: " << s << std::endl;
        }

        void DownloadFiles(RCF::FileDownload fileDownload)
        {
            fileDownload = RCF::FileDownload("C:\\FileToDownload.zip");
        }
    };

Interrupted downloads can be resumed by specifying the same download location as for the previous download attempt:

    // Either reuse the FileDownload object from the previous call:
    client.DownloadFiles(fileDownload);

    // , or call FileDownload::setDownloadToPath() on a new FileDownload:
    RCF::FileDownload secondFileDownload;
    std::string previousDownloadToPath = fileDownload.getDownloadToPath();
    secondFileDownload.setDownloadToPath(previousDownloadToPath);
    client.DownloadFiles(secondFileDownload);

To monitor the progess of a file download, use `ClientStub::setFileProgressCallback()`:

    void onFileTransferProgress(const RCF::FileTransferProgress & progress)
    {
        std::cout << "Total bytes to transfer: " << progress.mBytesTotalToTransfer << std::endl;
        std::cout << "Bytes transferred so far: " << progress.mBytesTransferredSoFar << std::endl;
        std::cout << "Server-side bandwidth limit (if any): " << progress.mServerLimitBps << std::endl;
    }

    client.getClientStub().setFileProgressCallback(&onFileTransferProgress);

To encrypt the download, set an appropriate transport protocol on the `RcfClient<>` object:

    client.getClientStub().setTransportProtocol(RCF::Tp_Ntlm);

Similarly to file uploads, the server can monitor and optionally cancel any file downloads that are in progress:

    void onServerFileDownload(RCF::RcfSession & session, const RCF::FileDownloadInfo & uploadInfo)
    {
        // Which files are being downloaded.
        const RCF::FileManifest& manifest = uploadInfo.mManifest;

        // How far has the download progressed.
        boost::uint32_t currentFile = uploadInfo.mCurrentFile;
        boost::uint64_t currentFilePos = uploadInfo.mCurrentPos;

        // To cancel the download, throw an exception.
        // ...
    }

    server.setOnFileDownloadProgress(&onServerFileDownload);

### Bandwidth limits

RCF file transfers will automatically run as fast as the network allows them to run. However, in some cases you may want to limit bandwidth usage and run file transfers at a restricted pace. RCF allows you set the maximum bandwidth consumed by all file uploads, or all file downloads, on a server:

    RCF::RcfServer server( RCF::TcpEndpoint(50001) );

    // 1 Mbps upload limit.
    boost::uint32_t serverUploadLimitMbps = 1;

    // Convert to bytes/second.
    boost::uint32_t serverUploadLimitBps = serverUploadLimitMbps*1000*1000/8;
    server.setFileUploadBandwidthLimit(serverUploadLimitBps);

    // 5 Mbps upload limit.
    boost::uint32_t serverDownloadLimitMbps = 5;

    // Convert to bytes/second.
    boost::uint32_t serverDownloadLimitBps = serverDownloadLimitMbps*1000*1000/8;
    server.setFileUploadBandwidthLimit(serverDownloadLimitBps);

In this example, if three clients are uploading files simultaneously, the total bandwidth consumed by all three clients will not be allowed to exceed 1 Mbps. Similarly, if there are three clients downloading files simultaneously, the total bandwidth consumed by the downloads will not be allowed to exceed 5 Mbps.

RCF also supports setting bandwidth limits on a more granular level, with application-defined subsets of clients sharing a particular bandwidth quota. For example, on a server connected to multiple networks with varying bandwidth characteristics, you may want to limit file transfer bandwidth based on which network the file transfer is taking place on. To do this, use `RcfServer::setFileUploadCustomBandwidthLimit()`/`RcfServer::setFileDownloadCustomBandwidthLimit()` to provide a callback function for the `RcfServer` to call, whenever a file transfer commences. From the callback funtion you can assign a relevant bandwidth quota.

For example, imagine we want to implement the following bandwidth limits:

1. 1 Mbps upload bandwidth for TCP connections from `192.168.*.*`.
2. 56 Kbps upload bandwidth for TCP connections from `15.146.*.*`.
3. Unlimited upload bandwidth for all other connections.

We'll need three separate `RCF::BandwidthQuota` objects for the three bandwidth quotas described above, and then we'll use `RcfServer::setFileUploadCustomBandwidthLimit()` to assign bandwidth quotas based on the IP address of the client:

    // 1 Mbps quota bucket.
    RCF::BandwidthQuotaPtr quota_1_Mbps( new RCF::BandwidthQuota(1*1000*1000/8) );

    // 56 Kbps quota bucket.
    RCF::BandwidthQuotaPtr quota_56_Kbps( new RCF::BandwidthQuota(56*1000/8) );

    // Unlimited quota bucket.
    RCF::BandwidthQuotaPtr quota_unlimited( new RCF::BandwidthQuota(0) );

    RCF::BandwidthQuotaPtr uploadBandwidthQuotaCb(RCF::RcfSession & session)
    {
        // Use clients IP address to determine which quota to allocate from.

        const RCF::RemoteAddress & clientAddr = session.getClientAddress();
        const RCF::IpAddress & clientIpAddr = dynamic_cast<const RCF::IpAddress &>(clientAddr);
        if ( clientIpAddr.matches( RCF::IpAddress("192.168.0.0", 16) ) )
        {
            return quota_1_Mbps;
        }
        else if ( clientIpAddr.matches( RCF::IpAddress("15.146.0.0", 16) ) )
        {
            return quota_56_Kbps;
        }
        else
        {
            return quota_unlimited;
        }
    }

    // Assign a custom file upload bandwidth limit.
    server.setFileUploadCustomBandwidthLimit(&uploadBandwidthQuotaCb);