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

../../../_images/cloud.png

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 Tutorials 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 to Ceetron:

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 CEETRON Envision. Download a free evaluation version of CEETRON Envision to test this feature and see an example implementation in both Qt and .NET.

4: Present the Link to the User

../../../_images/cloud_viewer.png

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

Send to Cloud
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

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

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

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();
}