package monakhv.android.samlib.data; import android.content.Context; import android.os.AsyncTask; import android.os.Bundle; import android.util.Log; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.api.GoogleApiClient; import com.google.android.gms.drive.*; import com.google.android.gms.drive.query.Filters; import com.google.android.gms.drive.query.Query; import com.google.android.gms.drive.query.SearchableField; import monakhv.android.samlib.R; import java.io.*; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; /* * Copyright 2014 Dmitry Monakhov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * 4/17/14. * * * Based on * https://github.com/googledrive/android-demos/blob/master/src/com/google/android/gms/drive/sample/demo/ApiClientAsyncTask.java * */ public abstract class ApiClientAsyncTask<Params, Progress, Result> extends AsyncTask<Params, Progress, Result> { private static final String DEBUG_TAG = "ApiClientAsyncTask"; protected static final long TIMEOUT_SEC = 20; private static final int BUF_SIZE = 4096; private static final int RETRY_SYNC = 10; private static final String MimeType = "application/octet-stream"; private final GoogleApiClient mClient; private final Context context; private final String account; private String errorMsg; public ApiClientAsyncTask(Context context, String account) { this.context = context; this.account = account; GoogleApiClient.Builder builder = new GoogleApiClient.Builder(context) .addApi(Drive.API) .setAccountName(account) .addScope(Drive.SCOPE_FILE) .addScope(Drive.SCOPE_APPFOLDER); mClient = builder.build(); } @Override protected Result doInBackground(Params... params) { final CountDownLatch latch = new CountDownLatch(1); mClient.registerConnectionCallbacks(new GoogleApiClient.ConnectionCallbacks() { @Override public void onConnected(Bundle bundle) { latch.countDown(); } @Override public void onConnectionSuspended(int i) { } }); mClient.registerConnectionFailedListener(new GoogleApiClient.OnConnectionFailedListener() { @Override public void onConnectionFailed(ConnectionResult connectionResult) { onConnectionFailedTask(connectionResult); latch.countDown(); } }); mClient.connect(); try { latch.await(); } catch (InterruptedException ex) { return null; } if (!mClient.isConnected()) { return null; } try { return doInBackgroundConnected(params); } finally { mClient.disconnect(); } } protected GoogleApiClient getGoogleApiClient() { return mClient; } protected abstract Result doInBackgroundConnected(Params... params); public abstract void onConnectionFailedTask(ConnectionResult connectionResult); /** * Making request to make full re sync */ protected void reSync() { Drive.DriveApi.requestSync(getGoogleApiClient()) .await(TIMEOUT_SEC, TimeUnit.SECONDS); } /** * get default drive folder to store data * * @return Folder */ protected DriveFolder getFolder() { //return Drive.DriveApi.getAppFolder(getGoogleApiClient()); return Drive.DriveApi.getRootFolder(getGoogleApiClient()); } /** * Search file by name * * @return file or null if not found or Error */ protected List<DriveFile> getFile(String fileName) { List<DriveFile> files = new ArrayList<>(); DriveFolder folder = getFolder(); Query query = new Query.Builder().addFilter(Filters.eq(SearchableField.TITLE, fileName)).build(); DriveApi.MetadataBufferResult res = folder.queryChildren(getGoogleApiClient(), query).await(TIMEOUT_SEC, TimeUnit.SECONDS); if (!res.getStatus().isSuccess()) { setError("Error Search File"); return null; } MetadataBuffer mdSet = res.getMetadataBuffer(); int count = mdSet.getCount(); Log.d(DEBUG_TAG, "Found " + count + " files for " + account); int i = 0; while (i < count) { Metadata data = mdSet.get(i); DriveFile file = Drive.DriveApi.getFile(getGoogleApiClient(), data.getDriveId()); Log.d(DEBUG_TAG, i + " " + data.getModifiedDate()); Log.d(DEBUG_TAG, i + " " + data.getModifiedByMeDate()); Log.d(DEBUG_TAG, i + " " + data.getCreatedDate()); if (data.getDriveId().getResourceId() != null && !data.isTrashed()) { Log.d(DEBUG_TAG, "add file " + i); files.add(file); } ++i; } return files; } /** * Waiting for full synchronization of the file when ModifiedDate=ModifiedByMeDate * * @param file file is required to synchronize * @return true if the file is synchronized */ protected boolean makeSync(DriveFile file) { DriveResource.MetadataResult metadataResult; int i = 0; while (i < RETRY_SYNC) { reSync(); int is = i + 1; Log.d(DEBUG_TAG, "Retry number: " + i + " sleep " + is + " second"); try { TimeUnit.SECONDS.sleep(i + 1); } catch (InterruptedException ex1) { Log.e(DEBUG_TAG, "Sleep interrupted: ", ex1); setError(R.string.res_export_google_interrupt); return false; } metadataResult = file.getMetadata(getGoogleApiClient()).await(TIMEOUT_SEC, TimeUnit.SECONDS); if (!metadataResult.getStatus().isSuccess()) { setError("Error re-read file"); return false; } Date mDate = metadataResult.getMetadata().getModifiedDate(); Date mMeDate = metadataResult.getMetadata().getModifiedByMeDate(); Date cDate = metadataResult.getMetadata().getCreatedDate(); Log.d(DEBUG_TAG, i + " " + mDate); Log.d(DEBUG_TAG, i + " " + mMeDate); Log.d(DEBUG_TAG, i + " " + cDate); if (mDate.equals(mMeDate)) { return true; } ++i; } setError(R.string.res_export_google_delayed); return false; } /** * Blocking method to write data to existing file * * @param dataBase file to read from * @param file file to write to * @return result status */ protected boolean writeFile(final File dataBase, final DriveFile file) { Date date = Calendar.getInstance().getTime(); Log.d(DEBUG_TAG, "Current: " + date); DriveApi.DriveContentsResult result = file.open(getGoogleApiClient(), DriveFile.MODE_WRITE_ONLY, null) .await(TIMEOUT_SEC, TimeUnit.SECONDS); if (!result.getStatus().isSuccess()) { setError("Error Writing to file"); return false; } DriveContents contents = result.getDriveContents(); OutputStream output = contents.getOutputStream(); if (!writeDataToFile(dataBase, output)) { return false; } com.google.android.gms.common.api.Status status = contents.commit(getGoogleApiClient(), null).await(TIMEOUT_SEC, TimeUnit.SECONDS); if (!status.isSuccess()) { setError("Error Commit file"); return false; } return makeSync(file); } private boolean writeDataToFile(File file, OutputStream output) { try { FileInputStream input = new FileInputStream(file); byte buffer[] = new byte[BUF_SIZE]; int bytesRead; while ((bytesRead = input.read(buffer)) != -1) { output.write(buffer, 0, bytesRead); } } catch (Exception e) { setError("Error Writing to file"); return false; } return true; } /** * Read data base from the file * * @param file the to read from * @param dataBase data base to restore * @return result status */ protected boolean readFile(final DriveFile file, File dataBase) { DriveApi.DriveContentsResult result = file.open(getGoogleApiClient(), DriveFile.MODE_READ_ONLY, null) .await(TIMEOUT_SEC, TimeUnit.SECONDS); if (!result.getStatus().isSuccess()) { setError("Error Reading file"); return false; } DriveContents contents = result.getDriveContents(); InputStream input = contents.getInputStream(); try { FileOutputStream output = new FileOutputStream(dataBase); byte buffer[] = new byte[BUF_SIZE]; int bytesRead; while ((bytesRead = input.read(buffer)) != -1) { output.write(buffer, 0, bytesRead); } } catch (Exception ex) { setError("Error Reading to file"); return false; } contents.discard(getGoogleApiClient()); return true; } /** * Blocking method to Add new file data to Google Drive */ protected boolean createFile(File dataBase, String fileName) { DriveApi.DriveContentsResult contentsResult = Drive.DriveApi.newDriveContents(getGoogleApiClient()).await(TIMEOUT_SEC, TimeUnit.SECONDS); if (!contentsResult.getStatus().isSuccess()) { setError(R.string.res_export_google_bad); return false; } MetadataChangeSet changeSet = new MetadataChangeSet.Builder() .setTitle(fileName) .setMimeType(MimeType) .setStarred(true).build(); DriveContents originalContents = contentsResult.getDriveContents(); OutputStream os = originalContents.getOutputStream(); //write data to file if (!writeDataToFile(dataBase, os)) { return false; } // create a file DriveFolder.DriveFileResult fileResult = getFolder() .createFile(getGoogleApiClient(), changeSet, originalContents) .await(TIMEOUT_SEC, TimeUnit.SECONDS); if (!fileResult.getStatus().isSuccess()) { setError("Error saving to file"); return false; } //reread file DriveResource.MetadataResult metadataResult = fileResult.getDriveFile() .getMetadata(getGoogleApiClient()) .await(TIMEOUT_SEC, TimeUnit.SECONDS); if (!metadataResult.getStatus().isSuccess()) { setError("Error test writing"); return false; } DriveFile file = Drive.DriveApi.getFile(getGoogleApiClient(), metadataResult.getMetadata().getDriveId()); return makeSync(file); } public void setError(String message) { errorMsg = message; } public void setError(int res) { setError(context.getString(res)); } public String getErrorMsg() { return errorMsg; } }