/uplink-android

Storj network Android library

Primary LanguageJavaMIT LicenseMIT

android-libuplink

Javadocs

Android bindings to Storj V3 libuplink.

Requirements

  • Android 5.0 Lollipop or newer

Setup

Add the Gradle dependency to the build.gradle file of the app module:

dependencies {
    implementation 'io.storj:uplink-android:1.0.0-rc.1'
}

Usage

Access Grant

The Access Grant contains all information required to access resources on the Storj network:

  • Satellite Address where metadata is stored
  • API Key for accessing a project on the satellite
  • Encryption Access for decrypting the content

Creating new access grant from passphrase

New Access Grant can be requested from satellite with Satellite Address, API Key and passphrase.

String satelliteAddress = "12EayRS2V1kEsWESU9QMRseFhdxYxKicsiFmxrsLZHeLUtdps3S@us1.storj.io:7777";
String serializedApiKey = "13Yqft7v...";
String passphrase = "super secret passphrase";

Uplink uplink = new Uplink();
Access access = uplink.requestAccessWithPassphrase(satelliteAddress, serializedApiKey, passphrase);

Sharing access grant

Access Grant can be shared in terms of allowed operations and access to specific buckets and objects.

Permission permission = new Permission.Builder().allowDownload().allowList().build();
access.share(permission, new SharePrefix("my-bucket", "pictures/birthday/"));

Serializing access grant to string

Access Grant can be serialized to string for easy sharing.

Access access = ...;
String serializedAccess = access.serialize(); 

Parsing serialized access grant from string

If received a serialized Access Grant as a string, it can be parsed to Access object.

String serializedAccess = "13GRuHAW...";
Access access = Access.parse(serializedAccess);

Buckets

Creating new bucket

Access access = ...;
Uplink uplink = new Uplink();
try (Project project = uplink.openProject(access)) {
    project.createBucket("my-bucket");
}

Getting info about a bucket

Access access = ...;
Uplink uplink = new Uplink();
try (Project project = uplink.openProject(access)) {
    BucketInfo info = project.statBucket("my-bucket"));
}

Listing buckets

Access access = ...;
Uplink uplink = new Uplink();
try (Project project = uplink.openProject(access);
        BucketIterator iterator = project.listBuckets()) {
    for (BucketInfo info : iterator) {
        // Do something for each bucket.
    }
}

Deleting a bucket

Access access = ...;
Uplink uplink = new Uplink();
try (Project project = uplink.openProject(access)) {
    BucketInfo info = project.deleteBucket("my-bucket"));
}

Objects

Downloading an object

Below is the easiest way for downloading a complete object to a local file.

Access access = ...;
Uplink uplink = new Uplink();
try (Project project = uplink.openProject(access);
        InputStream in = project.downloadObject("my-bucket", "key/to/my/object");
        OutputStream out = new FileOutputStream("path/to/local/file")) {
    byte[] buffer = new byte[8 * 1024];
    int bytesRead;
    while ((bytesRead = in.read(buffer)) != -1) {
        out.write(buffer, 0, bytesRead);
    }
}

Downloading a range of an object

If only a portion of the object should be downloaded, this can be specified with download options. The example below will download only 4 KiB from the object, starting at 1 KiB offset.

Access access = ...;
Uplink uplink = new Uplink();
ObjectDownloadOption[] options = new ObjectDownloadOption[]{
        ObjectDownloadOption.offset(1024),
        ObjectDownloadOption.length(4096)
};
try (Project project = uplink.openProject(access);
        InputStream in = project.downloadObject("my-bucket", "key/to/my/object",
                options);
        OutputStream out = new FileOutputStream("path/to/local/file")) {
    byte[] buffer = new byte[8 * 1024];
    int bytesRead;
    while ((bytesRead = in.read(buffer)) != -1) {
        out.write(buffer, 0, bytesRead);
    }
}

Downloading an object with progress monitoring and cancellation

If progress monitoring and/or cancellation is important, the client can take advantage of the ObjectInputStream class.

As all operations in the Storj Java API are blocking, the client should use some means for asynchronous processing - like the AsyncTask from the Android platform.

The example below shows how to download with progress monitoring and cancellation using the AsyncTask:

public class DownloadTask extends AsyncTask<Void, Long, Throwable> {

    private Access mAccess;
    private String mBucket;
    private ObjectInfo mInfo;
    private File mLocalFile;

    private int mNotificationId;
    private long mDownloadedBytes;
    private long mLastNotifiedTime;

    DownloadTask(Access access, String bucket, ObjectInfo info, File localFile) {
        mAccess = access;
        mBucket = bucket;
        mInfo = info;
        mLocalFile = localFile;
    }

    @Override
    protected Exception doInBackground(Void... params) {
        try (Project project = new Uplink().openProject(mAccess);
             ObjectInputStream in = project.downloadObject(mBucket, mInfo.getKey());
             OutputStream out = new FileOutputStream(mLocalFile)) {
            byte[] buffer = new byte[128 * 1024];
            int len;
            while ((len = in.read(buffer)) != -1) {
                if (isCancelled()) {
                    // exiting the try-with-resource block aborts the download process
                    return null;
                }
                out.write(buffer, 0, len);
                if (isCancelled()) {
                    // exiting the try-with-resource block aborts the download process
                    return null;
                }
                publishProgress((long) len);
            }
        } catch (StorjException | IOException e) {
            return e;
        }
    }

    @Override
    protected void onProgressUpdate(Long... params) {
        long increment = params[0];
        mDownloadedBytes += increment;

        long now = System.currentTimeMillis();

        // Calculate the progress in percents.
        long size = mFile.getSystemMetadata().getContentLength();
        int progress = (size == 0) ? 100 : (int) ((mDownloadedBytes * 100) / size);

        // Check if 1 second elapsed since last notification or progress is at 100%.
        if (progress == 100 || mLastNotifiedTime == 0 || now > mLastNotifiedTime + 1150) {
            // Place your code here to update the GUI with the new progress.
            mLastNotifiedTime = now;
        }
    }

    @Override
    protected void onPostExecute(Throwable t) {
        if (t != null) {
            String errorMessage = t.getMessage();
            // The download failed.
            // Place your code here to update the GUI with the error message.
            return;
        }

        // The download is successful.
        // Place your code here to update the GUI.
    }

    protected void onCancelled(Throwable t) {
        // The download was cancelled.
        // Place your code here to update the GUI.
    }

    /**
     * Call this method to cancel the download.
     */
    void cancel() {
        this.cancel(false);
    }
}

Uploading new object

Below is the easiest way for uploading new object.

Note the importance of specifying the location of the temp directory using the UplinkOption.tempDir() option. This is where the file being uploaded will be first encrypted before actually uploaded into pieces to the Storj network. For Android, it is recommended to set the temp directory to the application's cache directory.

Access access = ...;
String tempDir = ...;
Uplink uplink = new Uplink(UplinkOption.tempDir(tempDir));
try (Project project = uplink.openProject(access);
        OutputStream out = project.uploadObject("my-bucket", "key/to/my/object");
        InputStream in = new FileInputStream("path/to/local/file")) {
    byte[] buffer = new byte[8 * 1024];
    int bytesRead;
    while ((bytesRead = in.read(buffer)) != -1) {
        out.write(buffer, 0, bytesRead);
    }
}

Uploading new object with progress monitoring and cancellation

If progress monitoring and/or cancellation is important, the client can take advantage of the ObjectOutputStream class.

As all operations in the Storj Java API are blocking, the client should use some means for asynchronous processing - like the AsyncTask from the Android platform.

The example below shows how to upload with progress monitoring and cancellation using the AsyncTask.

Note the importance of specifying the location of the temp directory using the UplinkOption.tempDir() option. This is where the file being uploaded will be first encrypted before actually uploaded into pieces to the Storj network. For Android, it is recommended to set the temp directory to the application's cache directory.

public class UploadTask extends AsyncTask<Void, Long, Throwable> {

    private Access mAccess;
    private String mBucket;
    private String mObjectKey;
    private File mFile;
    private String mTempDir;

    private long mFileSize;
    private long mUploadedBytes;
    private long mLastNotifiedTime;

    UploadTask(Access access, String bucket, String objectKey, File file, String tempDir) {
        mAccess = scope;
        mBucket = bucket;
        mObjectKey = objectKey;
        mFile = file;
        mTempDir = tempDir;
        mFileSize = mFile.length();
    }

    @Override
    protected Exception doInBackground(Void... params) {
        Uplink uplink = new Uplink(UplinkOption.tempDir(mTempDir));
        try (Project project = uplink.openProject(mAccess);
             InputStream in = new FileInputStream(mFilePath);
             ObjectOutputStream out = project.uploadObject(mBucket, mObjectKey)) {
            byte[] buffer = new byte[128 * 1024];
            int len;
            while ((len = in.read(buffer)) != -1) {
                if (isCancelled()) {
                    // exiting the try-with-resource block without commit aborts the upload process
                    return null;
                }
                out.write(buffer, 0, len);
                if (isCancelled()) {
                    // exiting the try-with-resource block without commit aborts the upload process
                    return null;
                }
                publishProgress((long) len);
            }
            out.commit();
        } catch (StorjException | IOException e) {
            // exiting the try-with-resource block without commit aborts the upload process
            return e;
        }

        return null;
    }

    @Override
    protected void onProgressUpdate(Long... params) {
        long increment = params[0];
        mUploadedBytes += increment;

        long now = System.currentTimeMillis();

        // Calculate the progress in percents.
        int progress = (mFileSize == 0) ? 100 : (int) ((mUploadedBytes * 100) / mFileSize);

        // check if 1 second elapsed since last notification or progress is at 100%
        if (progress == 100 || mLastNotifiedTime == 0 || now > mLastNotifiedTime + 1150) {
            // Place your code here to update the GUI with the new progress.
            mLastNotifiedTime = now;
        }
    }

    @Override
    protected void onPostExecute(Throwable t) {
        if (t != null) {
            String errorMessage = t.getMessage();
            // The upload failed.
            // Place your code here to update the GUI with the error message.
            return;
        }

        // The upload is successful.
        // Place your code here to update the GUI.
    }

    protected void onCancelled(Throwable t) {
        // The upload was cancelled.
        // Place your code here to update the GUI.
    }

    /**
     * Call this method to cancel the upload.
     */
    void cancel() {
        this.cancel(false);
    }
}

Listing objects

Listing the content of a bucket, non-recursive:

Access access = ...;
Uplink uplink = new Uplink();
try (Project project = uplink.openProject(access)) {
    Iterable<ObjectInfo> objects = bucket.listObjects();
    for (ObjectInfo object : objects) {
        // Do something for each object.
    }
}

Listing all content under specific prefix, recursive:

Access access = ...;
Uplink uplink = new Uplink();
try (Project project = uplink.openProject(access);
        ObjectIterator objects = project.listObjects("my-bucket",
                ObjectListOption.prefix("some/key/prefix"), ObjectListOption.recursive())) {
    for (ObjectInfo object : objects) {
        // Do something for each object.
    }
}

Deleting an object

Access access = ...;
Uplink uplink = new Uplink();
try (Project project = uplink.openProject(access)) {
    project.deleteObject("my-bucket", "key/to/my/object"));
}

Sharing content

Sharing content on the Storj network is achieved by sharing the following pieces of information to the receiving party:

  1. A serialized shared Access Grant to access the shared content.
  2. The bucket name containing the shared content.
  3. The object key or prefix to the share content.

Below is an example for uploading a file and preparing the restricted Access Grant:

Access access = ...;
String tempDir = ...;
Uplink uplink = new Uplink(UplinkOption.tempDir(tempDir));
try (Project project = uplink.openProject(access);
        OutputStream out = project.uploadObject("my-bucket", "key/to/my/object");
        InputStream in = new FileInputStream("path/to/local/file")) {
    byte[] buffer = new byte[8 * 1024];
    int bytesRead;
    while ((bytesRead = in.read(buffer)) != -1) {
        out.write(buffer, 0, bytesRead);
    }
}

// Share a read-only access grant to "my-bucket/key/to/my" prefix.
// It is possible to share the access grant only to "my-bucket/key/to/my/file" object too.
Scope sharedAccess = access.share(
        new Permission.Builder().allowDownload().allowList().build(),
        new SharePrefix("my-bucket", "key/to/my"));

// Serialize the access grant to, so it can be easily sent to the receiving party.
String serializedShare = sharedAccess.serialize();

The receiving party can download the shared file using the following example:

// Info received by the other party
String serializedAccess = "13GRuHAW...";
String bucketName = "my-bucket";
String objectKey = "key/to/my/object";

Access access = Access.parse(serializedAccess);
try (Project project = uplink.openProject(access);
        InputStream in = project.downloadObject(bucketName, objectKey);
        OutputStream out = new FileOutputStream("path/to/local/file")) {
    byte[] buffer = new byte[8 * 1024];
    int bytesRead;
    while ((bytesRead = in.read(buffer)) != -1) {
        out.write(buffer, 0, bytesRead);
    }
}