Technology Blogs by SAP
Learn how to extend and personalize SAP applications. Follow the SAP technology blog for insights into SAP BTP, ABAP, SAP Analytics Cloud, SAP HANA, and more.
cancel
Showing results for 
Search instead for 
Did you mean: 

Earlier versions of the SMP SDK, supported OData related operations only for OData entities, feeds, properties, links, but not for downloading (GET) of resources such as media files. With the release of SDK SP 08, the SDK now supports media resource handling, for example creating and downloading of image files.  This blog walks you through the  relevant  APIs for this functionality, both for online and offline stores.

Let’s take a quick look at media element basics in Odata before delving into the details of the SDK. Any media element ( e.g. document, image or a video) consists of 2 related resources : the Media Link Entry (MLE) containing the structured data that describes the BLOB and the Media Resource (MR) that is the BLOB itself. For example, ‘ImageCollection’ can be an Odata feed whose entries  describe the structured data of the images  i.e. they are of type Media Link Entry and will link to the actual media resource.



In the service metadata document for service containing a feed such as the ImageCollection you will find:

<EntityType Name=“ImageCollection" m:HasStream="true">


The attribute  ‘HasStream’  used on an element to state that the Entity Type is describing a Media Link Entry in the associated OData service.


Consider a sample of an MLE entry below in an Odata feed.  The actual image (MR) is in the 'edit-media' link's href (ImageCollection('125')/$value). So, the MR is accessed through the MLE.


<entry>

<id>http://<server>:<port>/odata/ImageCollection('125')</id>

<title type="text" />

<updated>2015-08-26T17:26:34Z</updated>

<author>

<name />

</author>

<link rel="edit" title=“ImageCollection" href=“ImageCollection('125')" />

<link rel="edit-media" type="application/octet-stream" href=“ImageCollection('125')/$value" />

<category term="RMTSAMPLEFLIGHT.ImageCollection" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />

<content type="application/octet-stream" src=“ImageCollection('125')/$value" />

<m:properties>

<d:mediaid>125</d:mediaid>

<d:MEDIANAME>MEDIA_125</d:MEDIANAME>

<d:CURRCODE m:null="true" />

<d:URL m:null="true" />

</m:properties>

</entry>

Throughout the blog , we will interchangeably refer to the MLE as media entities and the MR as the media stream.

Retrieving Media Elements when operating an Online Store


Now let us take  look at how the SDK APIs to read such media elements, first with the online store. Consider  a call from our  application to read  ‘ImageCollection’. The read request is executed  and we retrieve the entries in the requestServerResponse: delegate method. But this is only the set of MLE entries and not the MR associated with each entry. To retrieve the media stream, the SDK introduces a new method (in SODataStoreAsync.h ) :


- (id<SODataDownloadMediaExecution>) scheduleMediaDownload:(NSURL*)url delegate:(id<SODataDownloadMediaDelegate>)delegate;


There are also delegate methods provided by SDK to handle the download ( in SODataDownloadMediaDelegate.h). These delegates are very similar to the SODataRequestDelegate methods.


Media Stream download request example:

  NSURL* mediaURL = [entity mediaLink]; // entity whose MR we need to retrieve

[odataStore scheduleMediaDownload:mediaURL delegate:dataController]; // dataController implements SODataDownloadMediaDelegates

Delegates implementation example for Media Stream download:

- (void) mediaDownloadServerResponse:(id<SODataDownloadMediaExecution>)requestExecution result:(id<SODataDownloadMediaResult>)result

{

    SODataDownloadMediaResultDefault* mediaRes = result;

    NSInputStream* stream = [mediaRes inputStream];

   

    NSMutableData* data = [[NSMutableData alloc] init];

    NSInteger len = 0;

    [stream open];

    while ([stream hasBytesAvailable]) {

        uint8_t buffer[25*1024];

        len = [(NSInputStream *)stream read:buffer maxLength:1024];

       

        if(len) {

            [data appendBytes: (const void *)buffer length:len];

        }

    }

    [stream close];

    UIImage *image = [[UIImage alloc] initWithData:data];

}

- (void) mediaDownloadFailed:(id<SODataDownloadMediaExecution>)requestExecution error:(NSError*)error

{

    NSLog(@" Media Download failed");

}

- (void) mediaDownloadStarted:(id<SODataDownloadMediaExecution>)requestExecution

{

    NSLog(@" Media Download started");

}

- (void) mediaDownloadCacheResponse:(id<SODataDownloadMediaExecution>)requestExecution result:(id<SODataDownloadMediaResult>)result

{

   NSLog(@" In Media Cache Response");

}

- (void) mediaDownloadFinished:(id<SODataDownloadMediaExecution>)requestExecution

{

    NSLog(@" Media Download finished");

}

Retrieving Media Elements when operating an Offline Store

When using an offline store, you can control which media elements to store in the offline store in one of the following ways:

  • In a defining request, specify the media resources to download.

- (void) addDefiningRequestWithName:(NSString*)name url:(NSString*) url retrieveStreams:(bool) retrieveStreams;

When you create a defining request, use the offline OData API to set a flag that triggers the download of media elements.When an offline store is opened for the first time, the media stream for all media entities that come from defining requests where 'retrieveStreams' is  true are downloaded to application on the device. Subsequently,when we want to access the stream belonging to an  entity (e.g to display it on a screen), we will continue to use sheduleMediaDownload: and the SODataDownloadMediaDelegates as with the online , but in this case the data will be retrieved from the local offline store in the device. When the app makes a refresh call ( the offline API used to  refresh data  with changes from the server), media streams from such defining requests are updated only if the media entity has changed.

  • Once a store is open, register a request to retrieve a particular media stream.


- (void) registerStreamRequestWithName:(NSString*)requestName resourcePath:(NSString*)resourcePath error:(NSError**) error; // register the stream

- (void) unregisterStreamRequestWithName:(NSString*)requestName error:(NSError**) error; // unregister the stream

For cases where the OData server has many media elements and it would be too much data to download it all to a device, we allow applications to select only specific media resources to download.  In this case, you would not set the 'retrieveStreams' to true in the defining requests, but would instead register a specific media stream request. Once a media resource request is registered, the first refresh call to the server  downloads the media stream, whether or not the associated media entity has changed. Subsequent refreshes update a media stream only if the associated media entity has been updated.

Here is a code snippet :

-(void)retrieveImage

{

[odataOfflineStore registerStreamRequestWithName:@"resourcereq" resourcePath:entity.resourcePath error:&err]; //The registered request must be the read link of the media entity (not the media stream).

[odataOfflineStore scheduleRefreshWithDelegate:offlineRefreshDelegate];

}

Create, Update and Delete operations on Media Resources

For a  POST operation, you provide the media data as the payload. This will create and return a new media entity that points to the data your provided in the payload.

-(void) addResource:(NSString *)imgName

{

      

    NSString* filePath = [[NSBundle mainBundle] pathForResource:imgName ofType:@"jpeg"]; //the image is from a file

    id<SODataPayload> imageToUpload = [[SODataUploadMediaDefault alloc] initWithFileAtPath:filePath contentType:@"image/jpeg"];

    SODataRequestParamSingleDefault *reqMedia = [[SODataRequestParamSingleDefault alloc] initWithMode:SODataRequestModeCreate

    resourcePath:[NSString stringWithFormat:@“ImageCollection"]];

    reqMedia.payload = imageToUpload;

    [odataStore scheduleRequest:reqMedia delegate:requestDelegate];

}

For updating the stream, you do a PUT against the edit_media metadata property of the media entity and provide the media resource as the payload.

- (void)updateResource: (NSString *)updateImageName{

   

   NSString* filePath = [[NSBundle mainBundle] pathForResource:updateImageName ofType:@"jpeg"];

    id<SODataPayload> imageToUpload = [[SODataUploadMediaDefault alloc] initWithFileAtPath:filePath contentType:@"image/jpeg"];

    SODataRequestParamSingleDefault *reqMedia = [[SODataRequestParamSingleDefault alloc] initWithMode:SODataRequestModeUpdate

   resourcePath:[NSString stringWithFormat:@"%@/$value", entity.resourcePath]];

    reqMedia.payload = imageToUpload;

    [odataStore scheduleRequest:reqMedia delegate:requestDelegate];

}

When you do a DELETE you are deleting both the media entity and the media data at the same time. i.e  The call below to delete the Entity ( MLE ) deletes the MR as well.

[odataStore scheduleDeleteEntity:entity delegate:requestDelegate options:nil];

Hope this was a short but useful introduction to media resource handling in the SMP SDK and gets you started with building iOS native apps with media support. Good Luck !

1 Comment