package org.gscript.data.library;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Properties;
import org.gscript.R;
import org.gscript.view.LibraryPropertiesView;
import com.google.api.client.extensions.android.http.AndroidHttp;
import com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential;
import com.google.api.client.googleapis.extensions.android.gms.auth.UserRecoverableAuthIOException;
import com.google.api.client.http.GenericUrl;
import com.google.api.client.http.HttpResponse;
import com.google.api.client.json.gson.GsonFactory;
import com.google.api.services.drive.Drive;
import com.google.api.services.drive.Drive.Files;
import com.google.api.services.drive.DriveScopes;
import com.google.api.services.drive.model.File;
import com.google.api.services.drive.model.FileList;
import android.accounts.AccountManager;
import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Handler;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
@LibraryAttribute(title = "Google Drive", description = "Google Drive", version = 0, view = GoogleDriveLibrary.GoogleDriveLibraryView.class)
public class GoogleDriveLibrary extends Library {
static final String LOG_TAG = "GoogleDriveLibrary";
static final String ACCOUNT_PROPERTY = "account";
static final String SCRIPT_EXTENSIONS = "sh";
static final String TEXT_PLAIN_MIMETYPE = "text/plain";
static final long REFRESH_REPEAT_DELAY = 5000;
private Drive mDriveService;
private GoogleAccountCredential mCredential;
HashMap<String, LibraryItem> mCachedItems = new HashMap<String, LibraryItem>();
String mAccount;
Handler mSyncHandler;
volatile boolean mSyncStop;
volatile long mSyncScheduled = 0;
volatile long mSyncTime = 0;
@Override
public void onCreate(Context context, Properties properties) {
mCredential = GoogleAccountCredential.usingOAuth2(context,
DriveScopes.DRIVE_READONLY);
mAccount = properties.getProperty(ACCOUNT_PROPERTY);
mCredential.setSelectedAccountName(mAccount);
mDriveService = getDriveService(mCredential);
restoreCache();
mSyncThread.start();
}
@Override
public void onDestroy() {
mSyncStop = true;
}
@Override
public ArrayList<LibraryItem> onQuery(String path, int flags) {
/* check if we have a refresh flag and not repeating refreshes */
if ((flags & Library.FLAG_MANUAL_REFRESH) != 0
&& ((System.currentTimeMillis() - mSyncTime) > REFRESH_REPEAT_DELAY)) {
mSyncScheduled = 0;
}
ArrayList<LibraryItem> items = new ArrayList<LibraryItem>();
Uri pathUri = Uri.parse(path);
synchronized (mCachedItems) {
if (pathUri.getPathSegments().size() > 1) {
/* try to get single file */
final LibraryItem item = mCachedItems.get(path);
if (item != null)
items.add(item);
} else {
/* return all files */
items.addAll(mCachedItems.values());
}
}
return items;
}
Thread mSyncThread = new Thread("GoogleDriveSync " + this.getId()) {
@Override
public void run() {
while (!mSyncStop) {
long currentTime = System.currentTimeMillis();
if (currentTime > mSyncScheduled) {
/* start sync */
boolean success = true;
Log.d(LOG_TAG, "GoogleDriveSync started");
List<File> result = new ArrayList<File>();
try {
Files.List request = mDriveService
.files()
.list()
.setQ("mimeType = '" + TEXT_PLAIN_MIMETYPE
+ "'");
do {
FileList files = request.execute();
result.addAll(files.getItems());
request.setPageToken(files.getNextPageToken());
} while (request.getPageToken() != null
&& request.getPageToken().length() > 0);
} catch (Exception ex) {
success = false;
}
if (success) {
boolean itemsUpdated = false;
final ArrayList<LibraryItem> items = new ArrayList<LibraryItem>();
for (File file : result) {
if (SCRIPT_EXTENSIONS.equals(file
.getFileExtension())) {
String itemPath = String.format("/%s/%s",
file.getId(), file.getTitle());
/* get current cached item for path */
LibraryItem item = mCachedItems.get(itemPath);
if (item == null) {
/* create new item */
item = new LibraryItem(
GoogleDriveLibrary.this,
LibraryItem.TYPE_SCRIPT, itemPath);
}
if (!item.hasContent()
|| item.getModifiedDate() != file
.getModifiedDate().getValue()) {
/*
* either no content or the file has been
* modified so download content
*/
try {
Log.d(LOG_TAG, "fetching content...");
HttpResponse resp = mDriveService
.getRequestFactory()
.buildGetRequest(
new GenericUrl(
file.getDownloadUrl()))
.execute();
InputStream is = resp.getContent();
StringBuilder sb = new StringBuilder();
BufferedReader reader = new BufferedReader(
new InputStreamReader(is));
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
sb.append('\n');
}
item.setContent(sb.toString());
item.setModifiedDate(file
.getModifiedDate().getValue());
} catch (IOException e) {
}
itemsUpdated = true;
}
items.add(item);
}
}
/* insert synced items to cache */
synchronized (mCachedItems) {
int prevSize = mCachedItems.size();
mCachedItems.clear();
int count = items.size();
for (int i = 0; i < count; ++i) {
final LibraryItem item = items.get(i);
mCachedItems.put(item.path, item);
}
Log.d(LOG_TAG, "successfully synced " + count
+ " items...");
/* notify that we have new content */
if (mCachedItems.size() != prevSize || itemsUpdated) {
GoogleDriveLibrary.this.notifyChange();
/* serialize data */
saveCache(items);
} else {
Log.d(LOG_TAG,
"cache still valid no need to notify");
}
}
/* resync in 60 minutes */
mSyncScheduled = System.currentTimeMillis()
+ (1000 * 60 * 60);
mSyncTime = System.currentTimeMillis();
} else {
Log.d(LOG_TAG, "failed to sync items...");
/* retry in 30 minutes */
mSyncScheduled = System.currentTimeMillis()
+ (1000 * 60 * 30);
}
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
}
}
};
public void saveCache(ArrayList<LibraryItem> items) {
java.io.File file = new java.io.File(this.getContext().getCacheDir(),
String.format("library-%d.cache", this.getId()));
if (LibraryItem.serializeItems(items, file.getPath())) {
Log.d(LOG_TAG, "succesfully saved library cache..");
}
}
public void restoreCache() {
ArrayList<LibraryItem> deserializedItems = new ArrayList<LibraryItem>();
java.io.File file = new java.io.File(this.getContext().getCacheDir(),
String.format("library-%d.cache", this.getId()));
if (LibraryItem.deserializeItems(deserializedItems, file.getPath())) {
for (LibraryItem item : deserializedItems) {
mCachedItems.put(item.path, item);
Log.d(LOG_TAG, String.format(
"restored cached item path: %s moddate: %d", item.path,
item.moddate));
}
Log.d(LOG_TAG, "succesfully restored library cache.. "
+ mCachedItems.size() + " items restored");
}
}
public static class GoogleDriveLibraryView extends LibraryPropertiesView {
static final int REQUEST_ACCOUNT_PICKER = 1;
static final int REQUEST_AUTHORIZATION = 2;
private Drive mService;
private GoogleAccountCredential mCredential;
Activity mActivity;
String mAccount;
boolean mValid;
public GoogleDriveLibraryView(Context context, Properties properties,
LibraryPropertiesListener listener) {
super(context, properties, listener);
final LayoutInflater inflater = LayoutInflater.from(context);
View view = inflater.inflate(R.layout.library_googledrive, null,
false);
this.addView(view, LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT);
Button buttonChooseAccount = (Button) findViewById(R.id.buttonChooseAccount);
buttonChooseAccount.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
mActivity = (Activity) GoogleDriveLibraryView.this
.getContext();
mCredential = GoogleAccountCredential.usingOAuth2(
mActivity, DriveScopes.DRIVE_READONLY);
try {
mActivity.startActivityForResult(
mCredential.newChooseAccountIntent(),
REQUEST_ACCOUNT_PICKER);
} catch (ActivityNotFoundException e) {
}
}
});
mAccount = getProperties().getProperty(ACCOUNT_PROPERTY);
notifyPropertiesChanged(hasValidProperties());
}
@Override
public void onActivityResult(final int requestCode,
final int resultCode, final Intent data) {
switch (requestCode) {
case REQUEST_ACCOUNT_PICKER:
if (resultCode == Activity.RESULT_OK && data != null
&& data.getExtras() != null) {
mAccount = data
.getStringExtra(AccountManager.KEY_ACCOUNT_NAME);
if (mAccount != null) {
getProperties().setProperty(ACCOUNT_PROPERTY, mAccount);
mCredential.setSelectedAccountName(mAccount);
mService = getDriveService(mCredential);
new TestAuthorizationTask().execute();
}
}
break;
case REQUEST_AUTHORIZATION:
new TestAuthorizationTask().execute(false);
break;
}
}
@Override
public boolean hasValidProperties() {
return (mAccount != null && mAccount.length() > 0);
}
private class TestAuthorizationTask extends
AsyncTask<Boolean, Void, Void> {
@Override
protected Void doInBackground(Boolean... params) {
boolean first = (params.length > 0) ? params[0] : true;
/* test auth by trying to get one file */
try {
List<File> result = new ArrayList<File>();
Files.List request = mService.files().list()
.setQ("mimeType = '" + TEXT_PLAIN_MIMETYPE + "'")
.setMaxResults(1);
try {
FileList files = request.execute();
result.addAll(files.getItems());
Log.d(LOG_TAG, "authorization succeeded");
} catch (UserRecoverableAuthIOException e) {
if (mActivity != null && first) {
Log.d(LOG_TAG, "authorization failed");
mActivity.startActivityForResult(e.getIntent(),
REQUEST_AUTHORIZATION);
} else {
Toast.makeText(getContext(),
"Authentication failed", Toast.LENGTH_LONG)
.show();
}
} catch (IOException e) {
Log.d(LOG_TAG, "IOException");
}
} catch (IOException ex) {
Log.d(LOG_TAG, "Exception");
}
return null;
}
@Override
protected void onPostExecute(Void result) {
notifyPropertiesChanged(hasValidProperties());
}
}
}
private static Drive getDriveService(GoogleAccountCredential credential) {
return new Drive.Builder(AndroidHttp.newCompatibleTransport(),
new GsonFactory(), credential).build();
}
}