package com.droidmapper.util;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.media.ExifInterface;
import android.os.Environment;
import android.provider.MediaStore;
import android.util.Log;
import com.dropbox.client2.DropboxAPI;
import com.dropbox.client2.android.AndroidAuthSession;
import com.dropbox.client2.exception.DropboxException;
import com.droidmapper.R;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Date;
import java.util.Vector;
/**
* This is a utility class that provides a way to its clients to buffer taken photos in a queue, and
* to upload them to Dropbox one by one.<br>
* <b>Note:</b> Currently this thread, after receiving stop command, stops immediately dropping all
* remaining queued tasks. If this is unwanted, because the app might/will lose a few photos, this
* class should be modified to first finish queued tasks and then exit.
*/
public class DropboxUploaderThread extends Thread {
private static final String TAG = DropboxUploaderThread.class.getName();
private volatile boolean halt;
private final DropboxAPI<AndroidAuthSession> dropboxApi;
private final Vector<String> queue;
private final float photoScale;
private final File tempDir;
private final Object lock;
/**
* Default constructor. It creates an instance of this class using the photo scale supplied as
* parameter.
*
* @param photoScale A float value between 0 and 1, that represents to how much of the original
* size the photo should be scaled.
* @param dropboxApi A pointer to the DropboxAPI instance that should be used to upload photos
* to Dropbox.
*/
public DropboxUploaderThread(float photoScale, DropboxAPI<AndroidAuthSession> dropboxApi) {
if (dropboxApi == null) {
throw new NullPointerException("DropboxAPI param can't be null.");
}
if (photoScale <= 0F || photoScale > 1F) {
throw new IllegalArgumentException("Param photoScale can't be equal or less than 0(zero).");
}
this.photoScale = photoScale;
this.dropboxApi = dropboxApi;
// Create queue that will buffer taken photos:
queue = new Vector<String>();
// Create this thread's lock(used for synchronization):
lock = new Object();
// A flag that we use to signal this thread to stop itself:
halt = false;
// Construct path to a temp file:
File picsDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
tempDir = new File(picsDir, "temp");
if (!tempDir.exists()) {
tempDir.mkdirs();
}
}
/**
* In a background thread scale the queued photos and upload them to the Dropbox.
*/
@Override
public void run() {
Log.d(TAG, "run() :: Start");
while (!halt) {
if (queue.isEmpty()) {
// If there are no images in the queue, sleep:
synchronized (lock) {
if (!halt) {
try {
lock.wait();
} catch (InterruptedException e) {
}
}
}
} else {
// Get new photo to upload:
String job = queue.remove(0);
Log.d(TAG, "run() :: Scaling to " + photoScale + " and uploading " + job);
try {
if (photoScale == 1F) {
// The photo shouldn't be scaled, we will upload it in full resolution:
uploadFile(job);
} else {
// Scale the photo first:
BitmapFactory.Options bfOptions = new BitmapFactory.Options();
bfOptions.inSampleSize = (int) (1F / photoScale); // Note that decoder uses a final value based on powers of 2, any other value will be rounded down to the nearest power of 2.
Bitmap scaled = BitmapFactory.decodeFile(job, bfOptions);
// Save the scaled photo to a temp file:
File jobFile = new File(job);
File tempFile = new File(tempDir, jobFile.getName());
if (!tempFile.exists()) {
try {
tempFile.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
FileOutputStream fos = null;
try {
fos = new FileOutputStream(tempFile);
scaled.compress(Bitmap.CompressFormat.JPEG, 100, fos);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
// Copy-paste exif tags:
Util.copyExifTags(job, tempFile.getPath(), scaled.getWidth(), scaled.getHeight());
// Then upload it:
uploadFile(tempFile.getPath());
// And delete the temp file:
tempFile.delete();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (DropboxException e) {
e.printStackTrace();
}
/*
* Note that currently the application doesn't care if upload succeeds of fails.
* I reckon that one frame more or less is not a problem. In case that it is an
* imperative for all captured frames to be uploaded to the Dropbox this method
* should be modified to repeat retry failed uploads. In this case it would also be
* a good idea to move this thread into a Service.
*/
}
}
Log.d(TAG, "run() :: Stop");
}
/**
* Add a new photo to the queue to be uploaded to Dropbox..
*
* @param path Path to the photo on local storage.
*/
public void queuePhoto(String path) {
Log.d(TAG, "queuePhoto() :: Already in queue " + queue.size());
// Add the new photo to the queue:
queue.add(path);
// Notify the thread about this(it might be sleeping):
synchronized (lock) {
lock.notifyAll();
}
}
/**
* Stop this thread.
*/
public void halt() {
Log.d(TAG, "halt()");
// Set the stop flag:
halt = true;
// Notify the thread about this(it might be sleeping):
synchronized (lock) {
lock.notifyAll();
}
}
/**
* A helper method that uploads a file whose path is passed as parameter to Dropbox.
*
* @param path Path to the file that should be uploaded to Dropbox.
* @throws FileNotFoundException If {@code file} does not exist.
* @throws DropboxException If a Dropbox related exception occurs.
*/
private void uploadFile(String path) throws FileNotFoundException, DropboxException {
File file = new File(path);
FileInputStream inputStream = null;
try {
inputStream = new FileInputStream(file);
DropboxAPI.Entry response = dropboxApi.putFile('/' + file.getName(), inputStream, file.length(), null, null);
Log.i(TAG, "uploadFile() :: The uploaded file's rev is: " + response.rev);
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}