Welcome!

Welcome to the official BlackBerry Support Community Forums.

This is your resource to discuss support topics with your peers, and learn from each other.

inside custom component

Java Development

Download large files using the BlackBerry Mobile Data System

by BlackBerry Development Advisor on ‎02-17-2010 11:57 AM - edited on ‎11-15-2011 10:43 AM by BlackBerry Development Advisor (Retired) (23,061 Views)

Summary

 

This article applies to the following:

  • BlackBerry® Enterprise Server software version 4.1 Service Pack 2 (4.1.2) and later
  • BlackBerry® Device Software 4.2 and later

Details

 

Background

 

The size of files that can be downloaded to the BlackBerry smartphone using the BlackBerry® Mobile Data System (BlackBerry MDS) is limited due to storage availability on the BlackBerry smartphone and the cost of transferring data wirelessly. The BlackBerry MDS uses the Multipurpose Internet Mail Extensions (MIME) type reported by the web server to determine which file limit should be applied. This file limit may also include any additional overhead from the web server, and will apply both before and after transcoding is done by the BlackBerry MDS. BlackBerry MDS provides a response code of 413 if the file size is too big. For standard Hypertext Transfer Protocol (HTTP) file downloading, as is done in the following example, there is no additional overhead or transcoding. For more information on BlackBerry MDS file settings and response code 413, see this article.

 

HTTP Range request header


If it is supported by the server, the Range header can be used to make individual requests for parts of a file. By keeping these file range requests below the limit of an individual file, larger files can be downloaded in parts and recombined on the BlackBerry smartphone. The file does not have to be separated into chunks on the server using this approach. The following example uses the Range header to request a file in several parts.

 

Note: The use of response code 206 for partial content and downloading the file correctly is based entirely on calculating the correct byte range to request. The same approach could be used to resume downloading interrupted transfers.

 

private class DownloadCombiner extends Thread {
    private String remoteName;
    private String localName;
    private int connection;
    private int chunksize;
    public DownloadCombiner(String remoteName, 
        String localName, int connection, int chunksize) {
        this.remoteName = remoteName;
        this.localName = localName;
        this.connection = connection;
        this.chunksize = chunksize;
    }
    public void run() {
        try {
            int chunkIndex = 0;
            int totalSize = 0;

            /*
             * File connection
             */
            FileConnection file =(FileConnection)Connector.open(localName);
            if (!file.exists()) {
                file.create();
            }
            file.setWritable(true);
            OutputStream out = file.openOutputStream();

            /*
             * HTTP Connections
             */
            String currentFile = remoteName + connectionType();
            log("Full URL: " + currentFile);
            HttpConnection conn;
            InputStream in;
            int rangeStart = 0;
            int rangeEnd = 0;
            while (true) {
                log("Opening Chunk: " + chunkIndex);
                conn = (HttpConnection) Connector.open(currentFile, 
                        Connector.READ_WRITE, true);
                rangeStart = chunkIndex * chunksize;
                rangeEnd = rangeStart + chunksize - 1;
                log("Requesting Range: " + rangeStart + 
                     "-" + rangeEnd);
                conn.setRequestProperty("Range", "bytes=" +
                     rangeStart + "-" + rangeEnd);
                int responseCode = conn.getResponseCode();
                if (responseCode != 200 && responseCode != 206)
                {
                    log("Response Code = " + conn.getResponseCode());
                    break;
                }
                log("Retreived Range: " + conn.getHeaderField("Content-Range"));
                in = conn.openInputStream();
                int length = -1;
                byte[] readBlock = new byte[256];
                int fileSize = 0;
                while ((length = in.read(readBlock)) != -1) {
                    out.write(readBlock, 0, length);
                    fileSize += length;
                    Thread.yield(); // Try not to get cut off
                }
                totalSize += fileSize;
                log("Chunk Downloaded: " + fileSize + " Bytes");
                chunkIndex++; // index (range) increase
                in.close();
                conn.close();
                in = null;
                conn = null;
                /*
                 * Pause to allow connections to close and other Threads
                 * to run.
                 */
                 Thread.sleep(1000);
            }
            log("Full file downloaded: " + totalSize + " Bytes");
            out.close();
            file.close();
            log("Wrote file to local storage");
            } catch (Exception e) {
                 log(e.toString());
            }
        }
    private String connectionType() {
        switch (connection) {
            case 1:
                return ";deviceside=false";
            case 2:
                return ";deviceside=true;interface=wifi";
            default:
                return ";deviceside=true";
        }
    }
}

 

 

File splitting

 

A large file can be downloaded to the BlackBerry smartphone by splitting the file into multiple parts and then downloading each part to the microSD card, where the parts can be recombined into the original file.

 

The following code segment describes a scheme for splitting a file into defined part sizes. The file parts are labeled with the original filename and a part ID.

 

 

public void splitFile() throws Exception {
BufferedInputStream input = new BufferedInputStream(new FileInputStream(_file));
byte[] readBlock = new byte[_chunksize];
int chunkFileId = 0;
int length = -1;
while ((length = input.read(readBlock)) != -1) {
String chunkFileName = _file.getName() + "." + chunkFileId++;
BufferedOutputStream output = new BufferedOutputStream(new FileOutputStream(chunkFileName));
output.write(readBlock, 0, length);
output.flush();
output.close();
System.out.println("Wrote Chunk file " + chunkFileName + " of " + length + " Bytes");
}
System.out.println("Completed Splitting " + _file.getName());
}


 

File downloading

 

Once the file has been split into a series of parts, each part can be downloaded in order and written out to one file on the microSD card.

 

Note: The response code is used to determine whether the part exists. If the part does not exist, the downloading process is completed.

 

 

private class DownloadCombiner extends Thread {

private String remoteName;
private String localName;
private int connection;

public DownloadCombiner(String remoteName, String localName) {
this.remoteName = remoteName;
this.localName = localName;
}

public void run() {
try {
int chunkIndex = 0;
int totalSize = 0;
FileConnection file = (FileConnection) Connector.open(localName);
if (!file.exists()) {
file.create();
}
file.setWritable(true);
OutputStream out = file.openOutputStream();
HttpConnection conn;
InputStream in;
while (true) {
System.out.println("Opening Chunk: " + chunkIndex);
String currentFile = remoteName + "." + chunkIndex++;
conn = (HttpConnection) Connector.open(currentFile);
if (conn.getResponseCode() != 200) {
System.out.println("Response Code = " +
conn.getResponseCode());
break;
}
in = conn.openInputStream();
int length = -1;
byte[] readBlock = new byte[256];
int fileSize = 0;
while ((length = in.read(readBlock)) != -1) {
out.write(readBlock, 0, length);
fileSize += length;
}
totalSize += fileSize;
System.out.println("Chunk Downloaded: " + fileSize + " Bytes");
in.close();
conn.close();
in = null;
conn = null;
Thread.yield(); // allow other threads time
}
System.out.println("Full file downloaded: " + totalSize + " Bytes");
out.close();
file.close();
System.out.println("Wrote file to local storage");
} catch (Exception e) {
System.err.println(e.toString());
}
}
}

 

 

Contributors
Comments
by Developer on ‎11-15-2011 06:32 AM

Please note that in the first code sample an extra closing curly bracket is needed after file.create(); 

by Administrator on ‎11-15-2011 10:22 AM

Thanks for catching this.  I'll get this fixed ASAP.