Resource Manager

The Resource Manager provides functionality for storage and retrieval of binary data (anything that the service can copy as a ByteArray) such as text files and PDFs. Storage is not persistent: resources are only available as long as the application session remains active.

For other storage options, see application state and session storage.

The Resource Manager is primarily intended to facilitate the transfer service-side data to the client. To transfer small amounts of data from the client to the service, you can use command parameters. For larger amounts, it may be best to use a different tool or mechanism such as side loading using FTP/SCP or cloud storage.

The current storage limit is 2 GB per resource, due to how the retrieveObject method in the client APIs retrieves the resources.

Steps

Save the binary data as a resource

APIs:

ContentInfo | C++ | .Net | Java

Resources are expressed as ContentInfo objects.

Assuming that you have created a byte array in your service (using the native toolkit for your chosen programming language) and that you would now like to save it as a resource, simply instantiate a ContentInfo object, passing your byte array as a parameter.

You specify the mime type of your binary data in the constructor of this object.

In the snippets below, we are storing an image as a resource. First, we create the image based on the current view’s pixel bits and dimensions (defined elsewhere). The image is then converted to a JPEG before it is packed into a ContentInfo object.

C++

Image current(size.Width, size.Height, PixelFormat::Bgr24);;
ByteArray bytes = JpegEncoder::JpegCompress(current, 100);
ContentInfo MyBinaryResource("image/jpeg", bytes);

.Net

Image current = new Image(imageSize.Width, imageSize.Height, PIXEL_FORMAT);

if (current != null)
{
    using(MemoryStream stream = new MemoryStream())
    {
        current.Bitmap.Save(stream, ImageFormat.Jpeg);    
        stream.Flush();

        ContentInfo MyBinaryResource("image/jpeg", stream.ToArray()));
    }
}

Java

Image current = getImage(currentImage, clientSize.width, clientSize.height);

if (current != null)
{
    Dimension size =  new Dimension(current.getWidth(), current.getHeight());
    byte[] bytes = ImageProcessing.jpegEncode(current, size, 100);

    if (bytes != null)
    {
        ContentInfo MyBinaryResource = new ContentInfo ("image/jpeg", bytes));
    }
}

Store the resource on the service

APIs:

ResourceManager.store | C++ | .Net | Java

Once you created the ContentInfo object, you can use the store method to add this object to the Resource Manager's storage. This method will automatically assign the newly added resource a unique identifier (GUID key). This GUID is how the Resource Manager determines which clients have access to what files.

Here is what the code might look like, if you were storing the JPEG image ("MyBinaryResource") that we created in the earlier step:

C++

Guid key = MyApp::StateManager().ResourceManager().Store(MyBinaryResource);

.Net

Guid key = StateManager.Instance.ResourceManager.Store(MyBinaryResource);

Java

UUID key = StateManager.getInstance().getResourceManager().store(MyBinaryResource);

After the resource has been stored, you can make it available to the clients for retrieval by providing them with the resource's GUID. There are two ways of accomplishing this:

  • Write the resource GUID in application state, thereby making it available to all clients in a collaboration scenario. (For a refresher on how to write values to application state, click here).
  • Return the resource GUID in a command callback (assuming that the need to store the resource was triggered by a client-side command); this ensures that only the client who sent the command can retrieve it. (For a refresher on how to populate command callback responses, click here.)

You can use the store function to save the same data in several different formats. This would be useful, for instance, to handle operating system differences. Consider for example the case of a collaborative text editing application. When storing the file, the service would save it in two different formats, let’s say a Windows-targeted .docx file and a .pages file for iOS-based clients. Each file format would be assigned its own GUID key; the key for the .docx files would be sent to the Windows clients, and the key to the .pages would be sent to the iOS-based clients.

Updating and removing resources

The store method can be used to update an existing resource; in this case, you'd provide the resource's GUID in the method call, to indicate which resource should be overwritten.

To delete an existing resource, you would use the remove method, or the clear method to delete all stored resources in a single operation.


Retrieve the resource from the client

APIs:

WebClient.retrieveObject() | HTML5 | Android
PWWebClient.retrieveObject() | iOS

WebClient.getResourceURL() | HTML5 | Android

Assuming that your client has access to the resource's GUID (either having received it as a command callback or by reading it from application state), it can retrieve the resource in two ways:

  • It can use the retrieveObject method. In HTML5 and iOS clients, this method has a callback parameter, where you specify what the client must do once it has received the object.

    HTML5

    webClient.retrieveObject(key,
        function(obj, err) {
            // Do something useful
    }
    

    .iOS (Obj-C)

    [[PWFramework sharedInstance].client retrieveObject:resourceId onComplete:^(PWBinaryObject *callbackFunction, NSError *error)
    {
       // Do something useful
    }
    

    Android

    byte[] bytes = framework.getWebClient().retrieveObject(key).getObject();
    
  • It can use the getResourceURL method to construct a URL, then use this URL to access the resource directly (if using a native mobile client), or with a web browser (if using an HTML5 client).

    HTML5

    var resourceUrl = pureweb.getClient().getResourceUrl(key);
    window.prompt("Click here to view the saved resource" ,resourceUrl);
    

    .iOS

    The getResourceURL method is not currently available in the iOS API.

    Android

    TextView resourceUrl = (TextView)dialog.findViewById(R.id.resource_url);
    resourceUrl.setText("Resource URL: " + framework.getWebClient().getResourceUrl(key));
    

Example

Storing and retrieving screen captures

This example illustrates how you could use PureWeb's Resource Manager's functionality to implement a screen capture feature. It assumes that you are familiar with commands.

On the service

The first step is to register a command handler. In this example, the command is given the name "Screenshot" and the handling function is labeled "OnScreenshotRequested".

C++

StateManager::Instance()->CommandManager().AddUiHandler("Screenshot", Bind(this, &MyView::OnScreenshotRequested));

.Net

StateManager.Instance.CommandManager.AddUiHandler("Screenshot", OnScreenshotRequested);

Java

stateManager.getCommandManager().addUiHandler("Screenshot", new OnScreenshotRequested());

Then we define the command handler to capture the screen, store the image as a resource, and add the resource's GUID to the command response.

C++

void MyView::OnScreenshotRequested(CSI::Guid sessionId, CSI::Typeless typeless, CSI::Typeless& response)
{
    Image current;
    if (m_imageList.Count() > 0)
    {
        current = m_imageList[m_currentImage % m_imageList.Count()];
        ByteArray bytes = JpegEncoder::JpegCompress(current, 100);
        ContentInfo cinfo("image/jpeg", bytes);

        CSI::Guid key = StateManager::Instance()->ResourceManager().Store(cinfo);
        response["ResourceKey"] = key;
    }
    else
    {
        CSI_THROW(CSI::Exception, "Unable to generate image from empty image list.");
    }
}

.Net

private void OnScreenshotRequested(Guid sessionid, XElement command, XElement responses)
{
    Image current = null;


    if (!GenerateImages(m_useClientSize?m_clientSize:DefaultSize, out current))
    {
        if (current != null)
        {
            using(MemoryStream stream = new MemoryStream())
            {
                current.Bitmap.Save(stream, ImageFormat.Jpeg);    
                stream.Flush();

                Guid key = StateManager.Instance.ResourceManager.Store(new ContentInfo("image/jpeg", stream.ToArray()));
                responses.Add(new XElement("ResourceKey", key));
            }
        }
        else
        {
            throw new Exception("Unable to generate image for screenshot request.");
        }
    }
}

Java

private class OnScreenshotRequested implements CommandHandler
{
    public void invoke(UUID sessionId, Element command, Element responses)
    {
        Image current = getImage(currentImage, clientSize.width, clientSize.height);

        if (current != null)
        {
             Dimension size =  new Dimension(current.getWidth(), current.getHeight());
             byte[] bytes = ImageProcessing.jpegEncode(current, size, 100);

             if (bytes != null)
             {
                 UUID key = StateManager.getInstance().getResourceManager().store(new ContentInfo("image/jpeg", bytes));
                 responses.addContent(XmlUtility.createElement("ResourceKey", key.toString()));
             }
             else
             {
                 throw new RuntimeException("Unable to generate screenshot from current image.");
             }
         }
         else
         {
             throw new RuntimeException("Unable to generate image for screenshot request.");
         }
    }
}

On the client

We first create an interface element which, when selected, will fire the "Screenshot" command to the service.

The command provides a callback, which reads the resource's GUID from the command response, then retrieves the resource, displays the screenshot to the end user, and provides logic for error handling.

The callback uses both the retrieveObject and getResourceUrl methods, for illustration purposes, but in your own application you do not need to use both.

HTML5

// Create a button with an onClick function.
<button id="btnSaveScreen" onClick="ddxclient.onSaveScreenShotClicked();">Save Screenshot</button>

// Define the onClick function so that it queues the Screenshot command.
ddxclient.onSaveScreenShotClicked = function() {
    pureweb.getClient().queueCommand('Screenshot', null, ScreenshotCallback);
}

// Define the command callback so that it retrieves the screenshot and displays it to the end user
// Notice that within this command callback, the retrieveObject method has its own callback.
ScreenshotCallback = function (sender, args) {
    // Get the resource's key from the service-side response and store it in a variable
    var key = pureweb.xml.XmlUtility.getText({parent: args.getResponse(), childPath: '/ResourceKey'});
    var webClient = pureweb.getClient();
    // Get the resource's URL and store it in a variable.
    var resourceUrl = webClient.getResourceUrl(key);
    goog.dom.getElement('txtResourceUrl').value = resourceUrl;
    // call retrieveObject only if Blob is supported by the browser
    if (webClient.supportsRetrieveObject()) {
         webClient.retrieveObject(
            key,
            function(obj, err) {
                if (goog.isDefAndNotNull(obj)) {
                    goog.dom.getElement('screenshotImage').src = window.URL.createObjectURL(obj);
                    goog.style.showElement(goog.dom.getElement('screenshotDiv'), true);
                } else {
                    alert('Cannot retrieve screenshot: ' + err);
                }
            }
        );
    } else {
         goog.dom.getElement('screenshotImage').src = resourceUrl;
         goog.style.showElement(goog.dom.getElement('screenshotDiv'), true);
    }
}

iOS (Obj-C)

The getResourceUrl method is not available for iOS.

// Set up a button for capturing screenshots
@synthesize screenshotButton = _screenshotButton;
[_screenshotButton addTarget:self action:@selector(screenshotButtonTouchUpInside:) forControlEvents:UIControlEventTouchUpInside];

// Define the button's handler
- (void)screenshotButtonTouchUpInside
{
    [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
    
    // Queue the Screenshot command
    [[PWFramework sharedInstance].client queueCommand:@"Screenshot" onComplete:^(PWCommandResponseEventArgs *args) 
        // Define the command callback
        { 
            // // Get the resource's key from the service-side response and store it in a variable
            PWGuid *resourceId = [[args.response elementForName:@"ResourceKey"] getTextAsWithDefault:@encode(PWGuid) defaultValue:[PWGuid emptyGuid]];
            [self dismissViewControllerAnimated:YES completion:^(void)
                {
                    UIViewController *controller = [[UIViewController alloc] init];
                    UIImageView *view = [[UIImageView alloc] init];
                    controller.view = view;
                    
                    // Retrieve the screenshot and define the callback
                    [[PWFramework sharedInstance].client retrieveObject:resourceId onComplete:^(PWBinaryObject *screenShot, NSError *error)
                    {
                        [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];

                        if(!error)
                        {
                            [view setImage:[UIImage imageWithData:screenShot.object]];
                            
                            [self.navigationController pushViewController:controller animated:YES];
                        }
                    }];
                }];
        }];
}

Android

// Create a button which, when pressed, will fire the "Screenshot" command.
((Button)pgParentLayout.findViewById(R.id.save_screenshot)).setOnClickListener(new OnClickListener() {
    public void onClick(View v) {
        framework.getWebClient().queueCommand("Screenshot", new ScreenshotCommandResponseCallback());
    }
});

// Define the command callback function
private class ScreenshotCommandResponseCallback implements EventHandlerCommandResponseEventArgs {
    public void invoke(Object source, CommandResponseEventArgs args) {
        // Get the resource's key from the service-side response and store it in a variable
        final UUID key = XmlUtility.getTextAs(UUID.class, args.getResponse(), "ResourceKey");

        new Thread(new Runnable() {
            public void run() {
                 try {
                     // Call showScreenshotImageDialog, which contains the logic to retrieve the screenshot
                     showScreenshotImageDialog(key);
                 } catch (final IOException e) {
                     UiDispatcherUtil.beginInvoke(new Action() {
                         public void invoke() {
                             showExceptionDialog(e);
                         }
                     });
                 }
             }
         }).start();
    }
}

// The function that actually retrieves the resource and displays it to the end users
private void showScreenshotImageDialog(final UUID key) throws IOException {
    byte[] bitmapBytes = framework.getWebClient().retrieveObject(key).getObject();
    final Bitmap bitmap = BitmapFactory.decodeByteArray(bitmapBytes, 0, bitmapBytes.length);

    UiDispatcherUtil.invoke(new Action() {
        public void invoke() {
            final Dialog dialog = new Dialog(DDxActivity.this);
            dialog.setContentView(R.layout.screenshot);
            dialog.setTitle("Screenshot image");
            TextView resourceUrl = (TextView)dialog.findViewById(R.id.resource_url);
            resourceUrl.setText("Resource URL: " + framework.getWebClient().getResourceUrl(key));
            ImageView image = (ImageView)dialog.findViewById(R.id.screenshot);
            image.setImageBitmap(bitmap);

            Button okButton = (Button)dialog.findViewById(R.id.ok_button);
            okButton.setOnClickListener(new OnClickListener() {
                 public void onClick(View v) {
                     dialog.dismiss();
                 }
             });

             dialog.show();
         }
    });
}