# tutorial_16.cpp

## C++ Tutorial Step 16: File transfers

In this example, we will write a simple SkypeKit-based file transfer tool. The program works as auto-accepting file receiver (if launched without target skypename and filename command-line arguments. With two more arguments: valid skypename and filename, the program will transmit the file to target and exit.

File transfers are initiated through Conversation::PostFiles method. This method does not directly return any Transfer objects. Instead, we will need to monitor conversations for messages where Message::P_TYPE equals POSTED_FILES. From these Message objects, we can then retrieve Transfer objects, with Message::GetTransfers.

- Conversation::OnMessage - for catching messages with P_TYPE == POSTED_FILES and retrieve Transfer objects
- Transfer::OnChange - for monitoring Transfer object state and progress changes.

Before we can proceed to file transfers, we will need to make some preparations.

Firstly, we will need to check all the incoming messages. For this we will need to keep a list of Conversation references. Let's add this list to our Skype class and as soon as we are logged in - populate it with GetConversationList.

    skype->GetConversationList(skype->allConvs);
    fetch(skype->allConvs);

To keep this list up-to-date during program execution, we will also need to override Skype::OnConversationListChange.

    void MySkype::OnConversationListChange (
        const ConversationRef & conversation,
        const Conversation::LIST_TYPE & type,
        const bool & added)
    {
        if ((added) && (type == MyConversation::ALL))
        {
            this->GetConversationList(this->allConvs);
            fetch(this->allConvs);
        };
    };

Another category of objects that we need to monitor is in-progress Transfer objects. As we cannot pre-fetch these objects as we did with Conversations, we will need to update this list in two places:

- MyConversation::OnMessage - if (Type == Message::POSTED_FILES), we will fetch the Transfer objects from the message and add them to a global reference list.
- in MyTransfer::OnChange - if the Transfer::P_STATUS gets set to a terminal value (CANCELLED, COMPLETED, FAILED, CANCELLED_BY_REMOTE) we can remove the Transfer reference from our global list and it will get neatly garbage collected.

Sending file

The code for sending file is in three places:

- Synchronous initiation of the transfer
- Retrieval of the Transfer object
- Asynchronous monitoring for transfer progress.

Firstly, in our initiation part will need to

- Retrieve a Conversation object by remote Participant skypename
- Post the file in the Conversation
- Wait until the transfer gets done, cancelled, or ends up failing.

    void MySkype::sendFile (SEString target, SEString fileName)
    {
        SEStringList targets;
        targets.append(target);

        MyConversation::Ref transferConv;

        GetConversationByParticipants(targets, transferConv, true, true);
        transferConv.fetch();

        SEFilenameList          fileList;
        TRANSFER_SENDFILE_ERROR errCode;
        SEFilename              errFile;

        fileList.append(fileName);

        if ( transferConv->PostFiles(fileList, "Sending file", errCode, errFile) )
        {
            printf("Error: cannot create file transfer.\n");
            isDoneSending = true;
        };

        while (!isDoneSending) {Delay(1);};
    };

Retrieval of the Transfer object goes into MyConversation::OnMessage, like this:

    void MyConversation::OnMessage (const MessageRef &message)
    {
        int messageType = message->GetProp(Message::P_TYPE).toInt();

        if (messageType == Message::POSTED_FILES)
        {
            MyTransfer::Refs transferList;
            message->GetTransfers(transferList);
            fetch(transferList);

            for (uint i=0; i < transferList.size(); i++)
            {
                globalTransferList->append(transferList[i]);
            };
        };
    };

And now that we have references to Transfer objects in globalTransferList, we can check for status updates:

    void MyTransfer::OnChange(int prop)
    {
        if (prop == Transfer::P_STATUS)
        {
            Transfer::STATUS transferStatus;
            GetPropStatus(transferStatus);

            if  (
                    (transferStatus == CANCELLED) ||
                    (transferStatus == COMPLETED) ||
                    (transferStatus == FAILED) ||
                    (transferStatus == CANCELLED_BY_REMOTE)
                )
            {
                printf("Removing Transfer from Conversation transfers list\n");
                if ( globalTransferList->contains(this->ref()) )
                {
                    globalTransferList->remove_val(this->ref());
                }

                if (globalTransferList->size() == 0)
                {
                    printf("All pending file transfers are complete.\nPress ENTER to quit.");
                    isDoneSending = true;
                };
            };
        };
    };

Upon reaching a terminal state, the global isDoneSending gets set and our MySkype::sendFile will drop back into main and the program will exit.

In addition to checking for terminal states, it would be nice to output some progress information, such as percentage of transfer completed and transfer speed. For this, let's add following at the end of MyTransfer::OnChange

    if (prop == Transfer::P_BYTESTRANSFERRED)
    {
        SEString transferProgressStr;
        this->GetPropBytestransferred(transferProgressStr);
        uint transferProgress = transferProgressStr.toUInt();

        SEString fileSizeStr;
        this->GetPropFilesize(fileSizeStr);

        float fileSize = fileSizeStr.toUInt();
        float progress = transferProgress / (fileSize / 100);

        uint transferRate;
        this->GetPropBytespersecond(transferRate);
        float transferRateInKb = transferRate / 1024;

        printf("Progress: %3.0f%% (%1.0f KB/s)\n", progress, transferRateInKb);
    };

The sending part is now complete. Let's see what needs to be done for incoming transfers.

As in case of incoming transfers, the entire process is entirely reactive - basically waiting for incoming POSTED_FILES messages, our "receiveFile" function ends up being somewhat ridiculous:

    void MySkype::receiveFile ()
    {
        int Ch;
        Ch = getchar();
    };

The one additional thing that does need to be done for incoming transfers is calling Transfer::Accept for them.

The obvious way of doing this would be to simple monitor Message::POSTED_FILES type messages in our MyConversation::OnMessage callback and accept all incoming file transfers from there. Sadly, this does not work reliably. The reason for this is Transfer status Transfer::PLACEHOLDER. Normally, incoming transfer objects appear in OnMessage callback with status NEW. Such transfers can be accepted immediately. However, occasionally the transfer object is not yet fully prepared (PLACEHOLDER is indicator of that) and in such cases the Transfer::Accept would fail.

The next obvious thing would be to monitor for Transfer::P_STATUS changes in our transfer class OnChange callback and on P_STATUS of an incoming transfer becoming NEW - accept it there. Sadly, this does not work reliably either. The reason for that is that in normal cases, the P_STATUS of a Transfer already is NEW by the time the object is created. Thus the status does not change anymore - and no property update happens. The PLACEHOLDER cases are exceptions.

The third obvious way is to check incoming transfers in both Conversation::OnMessage and Transfer::OnChange - most cases will be handled in OnMessage and the rest will get caught in Transfer::OnChange. This will work just fine.

As we are doing accepts from two places, it makes sense to have the necessary code piece as a separate method, like this:

    void MyTransfer::AutoAccept()
    {
      SEString fileName;
      SEString filePath;
      bool success;
      this->GetPropFilename(fileName);
      printf("Auto-accepting incoming file transfer: %s\n", (const char*)fileName);
      this->Accept(fileName, success);

      if (!success)
      {
        printf("Failed to accept incoming file transfer: %s\n", (const char*)fileName);
      };
    };

The next thing is to handle the normal case - in MyConversation::OnMessage and check each Transfer object for its P_TYPE. If the type is INCOMING, we can retrieve the (default) filename and call our AutoAccept. The MyConversation::OnMessage callback will end up looking like this:

    void MyConversation::OnMessage (const MessageRef &message)
    {
      int messageType = message->GetProp(Message::P_TYPE).toInt();

      if (messageType == Message::POSTED_FILES)
      {
        MyTransfer::Refs transferList;
        message->GetTransfers(transferList);
        fetch(transferList);

        // In case of incoming transfers, let's auto-accept them..
        Transfer::TYPE transferType;
        Transfer::STATUS transferStatus;

        for (uint i=0; i < transferList.size(); i++)
        {
          globalTransferList->append(transferList[i]);

          transferList[i]->GetPropType(transferType);
          transferList[i]->GetPropStatus(transferStatus);
          if ( (transferType == Transfer::INCOMING) && (transferStatus == Transfer::NEW) )
          {
            transferList[i]->AutoAccept();
          };
        };

        SEString bodyXml;
        message->GetPropBodyXml(bodyXml);
        printf("File transfer msg BodyXML:\n%s\n", (const char*)bodyXml);
      };
    };

For the cases when the initial transferStatus was Transfer::PLACEHOLDER, we will need to add the following piece to the MyTransfer::OnChange callback:

    Transfer::STATUS transferStatus;
    GetPropStatus(transferStatus);
    if (transferStatus == NEW)
    {
      Transfer::TYPE transferType;
      this->GetPropType(transferType);
      if (transferType == Transfer::INCOMING) this->AutoAccept();
    };

## Full code of this example

    /****************************************************************************

    Getting Started With SkypeKit. Tutorial Application, Step 16.

    In this example, we will write a simple SkypeKit-based file transfer tool.
    The program works as auto-accepting file receiver (if launched without target skypename
    and filename command-line arguments. With two more arguments: valid skypename and filename,
    the program will transmit the file to target and exit.

    **/

    #include "skype-embedded_2.h"
    #include "keypair.h"
    #include "tutorial_common.h"

    using namespace Sid;

    bool isDoneSending      = false;

    // For us to keep getting Transfer object property updates
    // we need to keep live references to these objects.
    TransferRefs * globalTransferList = new TransferRefs();

    SEString myAccountName;
    SEString myAccountPsw;
    SEString targetName = "";   // set from command line argument 3
    SEString fileName  = "";    // set from command line argument 4

    //----------------------------------------------------------------------------
    // Interface section

    class MyTransfer : public Transfer
    {
    public:
      typedef DRef<MyTransfer, Transfer> Ref;
      typedef DRefs<MyTransfer, Transfer> Refs;
      MyTransfer(unsigned int oid, SERootObject* root) : Transfer(oid, root) {};

      void OnChange(int prop);
    };

    class MyConversation : public Conversation
    {
    public:
      typedef DRef<MyConversation, Conversation> Ref;
      typedef DRefs<MyConversation, Conversation> Refs;

      MyConversation(unsigned int oid, SERootObject* root) : Conversation(oid, root) {};
      void OnMessage (const MessageRef &message);
    };

    class MySkype : public Skype
    {
    public:
      MySkype() : Skype() {}

      Account*        newAccount(int oid)       { return new MyAccount(oid, this); }
      Conversation*   newConversation(int oid)  { return new MyConversation(oid, this); }
      Transfer*       newTransfer(int oid)      { return new MyTransfer(oid, this); }

      MyConversation::Refs allConvs;

      void OnConversationListChange (
        const ConversationRef & conversation,
        const Conversation::LIST_TYPE & type,
        const bool & added);

      void sendFile (SEString target, SEString fileName);
      void receiveFile ();
    };

    MySkype* skype;

    //----------------------------------------------------------------------------
    // Implementation section

    void MyTransfer::OnChange(int prop)
    {
      /** Uncomment the following section to see ALL the Transfer class property changes
      SEString value;
      value = this->GetProp(prop);
      SEStringList dbg = this->getPropDebug(prop, value);
      printf("%s %s -> %s\n", (const char*)dbg[0], (const char*)dbg[1], (const char*)dbg[2]);
      **/

      if (prop == Transfer::P_STATUS)
      {
        // Transfer::P_STATUS debug output..
        Transfer::STATUS transferStatus;
        GetPropStatus(transferStatus);
        printf("%s\n", (const char*)tostring(transferStatus));

        // Are we done yet?
        if  (
            (transferStatus == CANCELLED) ||
            (transferStatus == COMPLETED) ||
            (transferStatus == FAILED) ||
            (transferStatus == CANCELLED_BY_REMOTE)
            )
        {
          printf("Removing Transfer from Conversation transfers list\n");
          if ( globalTransferList->contains(this->ref()) )

          {
            globalTransferList->remove_val(this->ref());
          }

          if (globalTransferList->size() == 0)
          {
            printf("All pending file transfers are complete.\nPress ENTER to quit.");
            isDoneSending = true;
          };
        };
      };

      // Transfer progress output.
      if (prop == Transfer::P_BYTESTRANSFERRED)
      {
        SEString transferProgressStr;
        this->GetPropBytestransferred(transferProgressStr);
        uint transferProgress = transferProgressStr.toUInt();

        SEString fileSizeStr;
        this->GetPropFilesize(fileSizeStr);

        // fileSize is float here, to avoid trouble with
        // files lessthan 100 bytes in size.
        float fileSize = fileSizeStr.toUInt();
        float progress = transferProgress / (fileSize / 100);

        uint transferRate;
        this->GetPropBytespersecond(transferRate);
        float transferRateInKb = transferRate / 1024;

        printf("Progress: %3.0f%% (%1.0f KB/s)\n", progress, transferRateInKb);
      };
    };

    void MyConversation::OnMessage (const MessageRef &message)
    {
      int messageType = message->GetProp(Message::P_TYPE).toInt();

      if (messageType == Message::POSTED_FILES)
      {
        MyTransfer::Refs transferList;
        message->GetTransfers(transferList);
        fetch(transferList);

        // In case of incoming transfers, let's auto-accept them..
        Transfer::TYPE transferType;
        for (uint i=0; i < transferList.size(); i++)
        {
          // To keep getting MyTransfer class events, we need to store the references
          globalTransferList->append(transferList[i]);

          transferList[i]->GetPropType(transferType);
          if (transferType == Transfer::INCOMING)
          {
            SEString fileName;
            SEString filePath;
            bool success;
            transferList[i]->GetPropFilename(fileName);

            // NB! Note that we do not specify file path here!
            // As the actual work is done in the runtime, by default all the
            // incoming files will be placed in runtime's local directory.
            // Also, if you specify a relative path, the path will be relative
            // to the location of the runtime.
            printf("Auto-accepting incoming file transfer: %s\n", (const char*)fileName);
            transferList[i]->Accept(fileName, success);
            if (!success)
            {
              printf("Failed to accept incoming file transfer: %s\n", (const char*)fileName);
            };
          };
        };

        SEString bodyXml;
        message->GetPropBodyXml(bodyXml);
        printf("File transfer msg BodyXML:\n%s\n", (const char*)bodyXml);
      };
    };

    // Keeping global conversation list up-to-date to
    // make sure we get all the incoming nessages

    void MySkype::OnConversationListChange (
      const ConversationRef & conversation,
      const Conversation::LIST_TYPE & type,
      const bool & added)
    {
      if ((added) && (type == Conversation::ALL_CONVERSATIONS))
      {
        this->GetConversationList(this->allConvs);
        fetch(this->allConvs);
      };
    };

    // Nothing to do here, just waiting for incoming transfers
    // Actual work is all in MyConversation::OnMessage
    void MySkype::receiveFile ()
    {
      getchar();
    };

    void MySkype::sendFile (SEString target, SEString fileName)
    {
      SEStringList targets;
      targets.append(target);

      MyConversation::Ref transferConv;

      GetConversationByParticipants(targets, transferConv, true, true);
      transferConv.fetch();

      SEFilenameList fileList;
      TRANSFER_SENDFILE_ERROR errCode;
      SEFilename errFile;

      fileList.append(fileName);

      if ( !transferConv->PostFiles(fileList, "Sending file", errCode, errFile) )
      {
        printf("Error: cannot create file transfer.\n");
        isDoneSending = true;
      };

      while (!isDoneSending) { Delay(1); };
    };

    //----------------------------------------------------------------------------
    // Main

    int main(int argc, char * argv[])
    {
      printf("*****************************************************************\n");
      printf(" SkypeKit Tutorial, Step 16. - File Transfers.\n");
      printf("*****************************************************************\n");

      if (argc < 3)
      {
        printf("usage: tutorial_16 <skypename> <password> [target] [filename]\n");
        return 0;
      };

      myAccountName = argv[1];
      myAccountPsw  = argv[2];

      if (argv[3])
      {
        targetName  = argv[3];
        if (argv[4]) { fileName  = argv[4]; };
      };

      printf("Creating skype ..\n");
      skype = new MySkype();

      printf("Submitting application token..\n");
      getKeyPair ();
      skype->init(keyBuf, inetAddr, portNum, "streamlog.txt");
      skype->start();

      printf("Retrieving account ..\n");
      MyAccount::Ref account;

      if (skype->GetAccount(myAccountName, account))
      {
        printf("Logging in..\n");
        account->LoginWithPassword(myAccountPsw, false, true);
        account->BlockWhileLoggingIn();
        printf("Loggin complete.\n");

        skype->GetConversationList(skype->allConvs);
        fetch(skype->allConvs);

        // if we had 3rd and 4th cmd-line arguments - we go into send mode
        if ((targetName != "") && (fileName != ""))
        {
          printf("Sending %s to %s\n", (const char*)fileName, (const char*)targetName);
          skype->sendFile(targetName, fileName);
        }
        else // wait for incoming file transfers
        {
          printf("Now accepting incoming file transfers..\n");
          printf("Press ENTER to quit.\n");
          skype->receiveFile();
        };

        printf("Logging out..\n");
        account->Logout(false);

        account->BlockWhileLoggingOut();
        printf("Logout complete.\n");
      }
      else
      {
          printf("Account does not exist\n");
      };

      printf("Cleaning up.\n");
      skype->stop();
      delete skype;
      printf("Done.\n");
      return 0;
    };

All Classes Files Functions Variables Typedefs Enumerations Enumerator Friends Defines

(c) Skype Technologies S.A. Confidential/Proprietary

Last updated: Fri Mar 16 2012