.. _cloud-integration-vtfx-page:

######################################################
Ceetron Cloud: Add a Send-To-Cloud Button to Your App
######################################################

.. image:: ../../../images/cloud.png
    :height: 493
    
With the VTFx Component it is easy to add cloud export support to your application. With a single click of a button the 
users can share the 3D model they are currently showing in your app with the rest of the world. The model can be shown 
in any modern web browser without installing any software or plug-in.

To add a Send-To-Cloud button to your app, please follow the description below:

**1: Get the upload ID from the User**

The user needs to provide his/her unique upload ID to upload the file.. The user will need an account on 
https://cloud.ceetron.com, so you need to send the user either to the https://cloud.ceetron.com/signup page or 
https://cloud.ceetron.com/login page. From the "My Account" page the user can press the button "Show and copy NN's 
Upload ID" to get the upload ID. You should only ask for this once, and then store it somewhere safe in your app.

The Upload ID is a GUID, e.g.: 765ba418-58ea-4c0a-afd1-fb4ff8f410ee

**2: Export the Model to a VTFx File**

Next, we need to create the VTFx file for upload. See :ref:`tutorials-page` for documentation on how to do that. You 
should export to a temporary file, which should be deleted after upload.

**3: Upload the File to Ceetron Cloud**

Use the following REST API call to upload the file:

API: %https://cloud.ceetron.com/api/v1/models

Params:

-   uploadId: The users' Upload ID. See 1.
-   uploadApp: The name of your app.
-   name: The name of the model. This will become the default name in the My Models page of the user on Ceetron Cloud.

Example:

::

    %https://cloud.ceetron.com/api/v1/models?uploadId=765ba418-58ea-4c0a-afd1-fb4ff8f410ee&uploadApp=My%20App&name=Demo%20Model

.. note::
    The uploadApp and name parameters needs to be percent encoded to form a valid URI

The upload must be done with a "multipart/form-data" HTTPS POST, where the VTFx is sent as a file with the name "vtfx".

The header should be:

::

    Content-Disposition: form-data; name=\"vtfx\"; filename=\"upload.vtfx\"
    Content-Type: application/octet-stream

The Send-To-Cloud feature is implemented in the Demo App (Qt and .NET) distributed with |ProductName|.
Download a free evaluation version of |ProductName| to test this feature and see an example implementation
in both Qt and .NET. 

**4: Present the Link to the User**

.. image:: ../../../images/cloud_viewer.png
    :height: 374

Finally, you should present the unique URL to the user so he/she can share it. You might also present a link to 
https://cloud.ceetron.com/profile so the user can see the newly uploaded file.


**Security Note**

1: SSL
You should use SSL when uploading if possible. This will ensure that the communication between your app and the server 
is encrypted and that all the data is protected.

2: Upload ID
If you choose to store the upload ID in your app (in the registry/settings file), you should encrypt it and not store it 
in clear-text.

********
Examples
********

.. |img| image:: ../../../images/vtfx/cloud_example.png
   :width: 150
   :target: examples/cloud-example.html

+-----------------------+------------------------------------------------------------------------------------+
| |img|                 | :ref:`cloud-example`     |br|                                                      |
|                       | An example showing how to upload a VTFx file to Ceetron Cloud (using Qt).          |
+-----------------------+------------------------------------------------------------------------------------+

.. note::
    For the Qt version you will need a version of Qt compiled with SSL support and the OpenSSL DLLs available on the 
    system for this to work. The .NET version should work without any problems.

**Qt (4.x and Later) Example**

.. code-block:: cpp

    QHttpMultiPart *multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
    multiPart->setBoundary("boundary_Ceetron89906a4aJn1kvlAG52SFG2AD");

    QHttpPart vtfxPart;
    vtfxPart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/octet-stream"));
    vtfxPart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"vtfx\" filename=\"upload.vtfx\""));

    QFile *file = new QFile(tempFilename);
    if (!file->open(QIODevice::ReadOnly))
    {
        QMessageBox::critical(this, "Upload error", QString("Error opening temporary file %1").arg(tempFilename), QMessageBox::Ok);
        return;
    }


    vtfxPart.setBodyDevice(file);

    file->setParent(multiPart); // we cannot delete the file now, so delete it with the multiPart
    multiPart->append(vtfxPart);

    QString uploadApp = QUrl::toPercentEncoding("Envision Demo App Qt");

    QFileInfo fi(m_fileName);
    QString caseName = QUrl::toPercentEncoding(fi.fileName());

    QString urlString = QString("https://cloud.ceetron.com/api/v1/models?uploadId=%1&uploadApp=%2&name=%3").arg(uploadId).arg(uploadApp).arg(caseName);
    
    QUrl url(urlString);
    QNetworkRequest request(url);

    QNetworkAccessManager* manager = new QNetworkAccessManager(this);
    QNetworkReply* reply = manager->post(request, multiPart);
    multiPart->setParent(reply);


**C#/.NET Example**

.. code-block:: cs
    
        string uploadApp = Uri.EscapeDataString("Demo App .NET");
        string caseName = Uri.EscapeDataString("Demo app case");
        string urlString = String.Format("https://cloud.ceetron.com/api/v1/models?uploadId={0}&uploadApp={1}&name={2}", uploadId, uploadApp, caseName);

        string boundary = "boundary_Ceetron89906a4aJn1kvlAG52SFG2AD";
        byte[] boundarybytes = System.Text.Encoding.ASCII.GetBytes("\r\n--" + boundary + "\r\n");

        HttpWebRequest wr = (HttpWebRequest)WebRequest.Create(urlString);
        wr.ContentType = "multipart/form-data; boundary=" + boundary;
        wr.Method = "POST";
        wr.KeepAlive = true;
        wr.Credentials = System.Net.CredentialCache.DefaultCredentials;

        Stream rs = wr.GetRequestStream();
        rs.Write(boundarybytes, 0, boundarybytes.Length);

        string header = "Content-Disposition: form-data; name=\"vtfx\"; filename=\"upload.vtfx\"\r\nContent-Type: application/octet-stream\r\n\r\n";
        byte[] headerbytes = System.Text.Encoding.UTF8.GetBytes(header);
        rs.Write(headerbytes, 0, headerbytes.Length);

        FileStream fileStream = new FileStream(filename, FileMode.Open, FileAccess.Read);
        byte[] buffer = new byte[4096];
        int bytesRead = 0;
        while ((bytesRead = fileStream.Read(buffer, 0, buffer.Length)) != 0)
        {
            rs.Write(buffer, 0, bytesRead);
        }
        fileStream.Close();
        File.Delete(filename);

        byte[] trailer = System.Text.Encoding.ASCII.GetBytes("\r\n--" + boundary + "--\r\n");
        rs.Write(trailer, 0, trailer.Length);
        rs.Close();

        HttpWebResponse wresp = null;
        try
        {
            wresp = (HttpWebResponse)wr.GetResponse();
            Stream stream = wresp.GetResponseStream();
            StreamReader reader = new StreamReader(stream);
            string responseText = reader.ReadToEnd();

            string modelKey = parseTopLevelJsonValue(responseText, "modelKey");

            string htmlResponse = "";
            HttpStatusCode httpStatusCode = wresp.StatusCode;

            if (httpStatusCode == HttpStatusCode.OK)
            {
                string viewerUrl = "https://cloud.ceetron.com/v/" + modelKey;
                htmlResponse = "<h1>Upload Successful</h1><p>Click <a href=\"" + viewerUrl + "\">here<a> to see your uploaded model.<p>The link to the model is:</p><pre>" + viewerUrl + "</pre>";
            }
            else
            {
                // Note: Will probably not get here, as GetResponse() seems to throw if status is not 200
                htmlResponse = string.Format("<h1>Upload Failed</h1><p>httpStatusCode:{0}", httpStatusCode);
            }

            UploadResultDialog dlg = new UploadResultDialog(htmlResponse);
            dlg.ShowDialog();
        }
        catch (Exception ex)
        {
            string htmlResponse = "<h1>Error uploading to Ceetron Cloud</h1><p>" + ex.Message + "</p>";

            if (ex is WebException)
            {
                WebException webEx = (WebException)ex;

                wresp = (HttpWebResponse)webEx.Response;
                Stream stream = wresp.GetResponseStream();
                StreamReader reader = new StreamReader(stream);
                string responseText = reader.ReadToEnd();

                string failReason = parseTopLevelJsonValue(responseText, "message");
                string apiErrorCode = parseTopLevelJsonValue(responseText, "apiErrorCode");

                if (myKey != null && apiErrorCode == "ERR_ILLEGAL_UPLOAD_ID")
                {
                    // Upload ID is invalid, delete from the registry
                    myKey.Close();
                    Registry.CurrentUser.DeleteSubKey(@"Software\Ceetron\dNetDemoApp\Settings\");
                }

                htmlResponse = string.Format("<h1>Upload Failed</h1><p>{0}</p><p>{1}</p><p>apiErrorCode: {2}</p>", failReason, ex.Message, apiErrorCode);
            }

            UploadResultDialog dlg = new UploadResultDialog(htmlResponse);
            dlg.ShowDialog();

            if (wresp != null)
            {
                wresp.Close();
                wresp = null;
            }
        }
        finally
        {
            wr = null;
            Cursor.Current = Cursors.Default;
        }

**MFC (win32) Example**

.. code-block:: cpp
    
    CString urlString;
    urlString.Format("api/v1/models?uploadId=%s&uploadApp=%s&name=%s", ToPercentEncoding(sUploadID).ShortChar(), ToPercentEncoding(sUploadApp).ShortChar(), ToPercentEncoding(sCaseName).ShortChar());

    CInternetSession session("sendFile");
    CHttpConnection *connection = session.GetHttpConnection(ceetronCloudHost, INTERNET_FLAG_SECURE, ceetronCloudPort);
    if (!connection) 
    {
        *psErrorMsg = "Error connecting to Ceetron cloud";
        return VT_FALSE;
    }

    CHttpFile* pHTTP = connection->OpenRequest(CHttpConnection::HTTP_VERB_POST, urlString, NULL, 1, NULL, NULL, INTERNET_FLAG_SECURE);
    if (!pHTTP) 
    {
        *psErrorMsg = "Error OpenRequest to Ceetron cloud";
        return VT_FALSE;
    }

    CString boundary        = _T("boundary_GLview89906a4aJn1kvlAG52SFG2AD");       
    CString endline         = "\r\n";
    CString start_delim     = "--" + boundary + endline;
    CString cont_disp_str   = "Content-Disposition: form-data; ";
    CString stop_delim      = endline + "--" + boundary + "--" + endline;

    CString vtfx_str = start_delim + cont_disp_str + "name=" + "\"vtfx\""+"; filename=\"upload.vtfx\""+endline+"Content-Type: application/octet-stream"+endline+endline;
    DWORD dwTotalRequestLength = vtfx_str.GetLength() + stop_delim.GetLength() + (DWORD)vtfxFileToUpload.GetLength();

    CString strRequestHeaders = CString("Content-Type: multipart/form-data; boundary=") + boundary + endline;
    pHTTP->AddRequestHeaders(strRequestHeaders);
    pHTTP->SendRequestEx(dwTotalRequestLength, HSR_SYNC | HSR_INITIATE);

    //Write out the headers and the form variables
    pHTTP->Write((LPCSTR)vtfx_str, vtfx_str.GetLength());

    //upload the file.

    DWORD dwReadLength = -1;
    DWORD dwChunkLength = 64 * 1024; 
    void* pBuffer = malloc(dwChunkLength);

    //int length = vtfxFileToUpload.GetLength(); //used to calculate percentage complete.
    while (0 != dwReadLength)
    {
        dwReadLength = vtfxFileToUpload.Read(pBuffer, dwChunkLength);
        if (0 != dwReadLength)
        {
            pHTTP->Write(pBuffer, dwReadLength);
        }
    }

    free(pBuffer);

    vtfxFileToUpload.Close();

    //Finish the upload.
    pHTTP->Write((LPCSTR)stop_delim, stop_delim.GetLength());
    pHTTP->EndRequest(HSR_SYNC);

    //get the response from the server.
    LPSTR szResponse;
    CString strResponse;
    DWORD dwResponseLength = (DWORD)pHTTP->GetLength();
    while (0 != dwResponseLength )
    {
        szResponse = (LPSTR)malloc(dwResponseLength + 1);
        szResponse[dwResponseLength] = '\0';
        pHTTP->Read(szResponse, dwResponseLength);
        strResponse += szResponse;
        free(szResponse);
        dwResponseLength = (DWORD)pHTTP->GetLength();
    }
