Android bindings to Storj V3 libuplink.
- Android 5.0 Lollipop or newer
Add the Gradle dependency to the build.gradle
file of the app module:
dependencies {
implementation 'io.storj:uplink-android:1.0.0-rc.1'
}
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
New Access Grant
can be requested from satellite with Satellite Address, API Key and passphrase.
String satelliteAddress = "[email protected]:7777";
String serializedApiKey = "13Yqft7v...";
String passphrase = "super secret passphrase";
Uplink uplink = new Uplink();
Access access = uplink.requestAccessWithPassphrase(satelliteAddress, serializedApiKey, passphrase);
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/"));
Access Grant
can be serialized to string for easy sharing.
Access access = ...;
String serializedAccess = access.serialize();
If received a serialized Access Grant
as a string, it can be parsed to Access
object.
String serializedAccess = "13GRuHAW...";
Access access = Access.parse(serializedAccess);
Access access = ...;
Uplink uplink = new Uplink();
try (Project project = uplink.openProject(access)) {
project.createBucket("my-bucket");
}
Access access = ...;
Uplink uplink = new Uplink();
try (Project project = uplink.openProject(access)) {
BucketInfo info = project.statBucket("my-bucket"));
}
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.
}
}
Access access = ...;
Uplink uplink = new Uplink();
try (Project project = uplink.openProject(access)) {
BucketInfo info = project.deleteBucket("my-bucket"));
}
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);
}
}
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);
}
}
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);
}
}
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);
}
}
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 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.
}
}
Access access = ...;
Uplink uplink = new Uplink();
try (Project project = uplink.openProject(access)) {
project.deleteObject("my-bucket", "key/to/my/object"));
}
Sharing content on the Storj network is achieved by sharing the following pieces of information to the receiving party:
- A serialized shared
Access
Grant to access the shared content. - The bucket name containing the shared content.
- 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);
}
}