In my app, I need to upload multiple files (1 sqlite db file and multiple image files) for backup purpose to user's google drive.
I am using android google drive api, but not sure, how to do back to back file uploads and then later on downloads like this.
The db file obviously comes from /data/data//databases kind of directory whereas images are stored in pictures directory. I need to grab all of these one by one and upload to drive.
Also, I have seen that if a given file (with the same title) already exists, even then, a new file with the same title is created on drive (obviously has diff DriveId but same title). I would like to check, if the file exists and only upload if it doesn't, else skip that file.
Please help.. I have been trying to refer the android demos on github by google, but have been only able to do bits and pieces using that.
File title is not unique in Drive API. However you can save the IDs of your newly created files in your app's local storage, so you can check against the IDs in the Drive side when you want to upload the file again.
You can use the CreateFileActvity.java from the Google Drive Demo GitHub page. It will return a file ID after you create a file successfully, so you can store the ID in your local storage.
Sample code from CreateFileActivity.java:
final private ResultCallback<DriveFileResult> fileCallback = new
ResultCallback<DriveFileResult>() {
#Override
public void onResult(DriveFileResult result) {
if (!result.getStatus().isSuccess()) {
showMessage("Error while trying to create the file");
return;
}
showMessage("Created a file with content: " + result.getDriveFile().getDriveId());
}
};
Just in case somebody is looking how to upload multiple files to Drive, here is solution that worked for me:
for(String fileName: fileNameArrayList){backupImage(fileName);}
private void backupImage(String fileName) {
Drive.DriveApi.newDriveContents(mGoogleApiClient).setResultCallback(
new BackupImagesContentsCallback(mContext, mGoogleApiClient, fileName));
}
Backup callback:
public class BackupImagesContentsCallback implements ResultCallback<DriveApi.DriveContentsResult> {
#Override
public void onResult(#NonNull DriveApi.DriveContentsResult driveContentsResult) {
if (!driveContentsResult.getStatus().isSuccess()) {
Log.v(TAG, "Error while trying to backup images");
return;
}
MetadataChangeSet changeSet = new MetadataChangeSet.Builder()
.setTitle(mFileName) // Google Drive File name
.setMimeType("image/jpeg")
.setStarred(true).build();
Drive.DriveApi.getAppFolder(mGoogleApiClient)
.createFile(mGoogleApiClient, changeSet, driveContentsResult.getDriveContents())
.setResultCallback(backupImageFileCallback);
}
final private ResultCallback<DriveFolder.DriveFileResult> backupImageFileCallback = new ResultCallback<DriveFolder.DriveFileResult>() {
#Override
public void onResult(#NonNull DriveFolder.DriveFileResult result) {
if (!result.getStatus().isSuccess()) {
Log.v(TAG, "Error while trying to backup images");
return;
}
DriveFile mImageFile;
mImageFile = result.getDriveFile();
mImageId = result.getDriveFile().getDriveId();
mImageFile.open(mGoogleApiClient, DriveFile.MODE_WRITE_ONLY, (bytesDownloaded, bytesExpected) -> {
}).setResultCallback(backupImagesContentsOpenedCallback);
}
};
final private ResultCallback<DriveApi.DriveContentsResult> backupImagesContentsOpenedCallback =
new ResultCallback<DriveApi.DriveContentsResult>() {
#Override
public void onResult(#NonNull DriveApi.DriveContentsResult result) {
if (!result.getStatus().isSuccess()) {
return;
}
DriveContents contents = result.getDriveContents();
BufferedOutputStream bos = new BufferedOutputStream(contents.getOutputStream());
byte[] buffer = new byte[1024];
int n;
File imageDirectory = new File(mContext.getFilesDir(),
Constants.IMAGE_DIRECTORY_NAME);
try {
FileInputStream is = new FileInputStream(new File(imageDirectory,
mFileName));
BufferedInputStream bis = new BufferedInputStream(is);
while ((n = bis.read(buffer)) > 0) {
bos.write(buffer, 0, n);
}
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
contents.commit(mGoogleApiClient, null);
}
};
}
This is not perfect solution, just a working code.
Related
I am trying to upload files from the internal storage to a user's Google Drive through the API, to back up user data. What I have done so far works on occasion, but it often creates two or three copies of the files as well and I have no idea why.
As far as I know there is no way to upload entire folders and their contents at once, so instead I first create an empty backup folder, then iterate through the user data and copy the files into the folder one by one.
Note: for now I am using the root folder of the Google Drive instead of the App folder that is dedicated for this purpose. This makes it easier to see the files that are created. When everything works, I'll swap the root folder with the app folder.
Creating a new backup first starts with looking for existing backup folders and deleting them if there are any:
private void createNewBackup(Context context, GoogleSignInAccount googleSignInAccount) {
// Create query to search for existing backup folder
Query query = new Query.Builder()
.addFilter(Filters.eq(SearchableField.TITLE, "Backup"))
.build();
Drive.getDriveResourceClient(context, googleSignInAccount).query(query)
// if a backup folder is found, delete it:
.addOnSuccessListener(new OnSuccessListener<MetadataBuffer>() {
#Override
public void onSuccess(MetadataBuffer metadata) {
// there are never 2 backup folders, so it is always the first set of metadata, hence get(0).
DriveFolder backupfolder = metadata.get(0).getDriveId().asDriveFolder();
Drive.getDriveResourceClient(context, googleSignInAccount).delete(backupfolder)
// when the folder is deleted, create a new backup folder
.addOnSuccessListener(new OnSuccessListener<Void>() {
#Override
public void onSuccess(Void aVoid) {
createBackupFolder(context, googleSignInAccount);
}
});
}
})
// if no backup folder is found, create a new backup folder
.addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
createBackupFolder(context, googleSignInAccount);
}
});
}
When the old backup folder is deleted, this is the code that creates the new backup folder
public void createBackupFolder(Context context, GoogleSignInAccount googleSignInAccount) {
// Get the root folder of the drive:
Drive.getDriveResourceClient(context, googleSignInAccount).getRootFolder().addOnSuccessListener(new OnSuccessListener<DriveFolder>() {
#Override
public void onSuccess(DriveFolder driveFolder) {
// create backup folder in root folder:
MetadataChangeSet changeSet = new MetadataChangeSet.Builder()
.setTitle("Backup")
.setMimeType(DriveFolder.MIME_TYPE)
.setStarred(true)
.build();
Log.d("Test", "Creating backup folder");
Drive.getDriveResourceClient(context, googleSignInAccount).createFolder(driveFolder, changeSet)
.addOnSuccessListener(new OnSuccessListener<DriveFolder>() {
#Override
public void onSuccess(DriveFolder backupFolder) {
Log.d("Test", "Created backup folder");
writeDataToBackupFolder(context, googleSignInAccount, backupFolder);
}
})
// if the folder couldn't be created:
.addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Log.d("Test", "failed to create backup folder");
}
});
}
});}
And then finally the files in the subfolders "premium" and "shifts" are copied to the backup folder on the drive with the writeDataToBackupFolder method:
public void writeDataToBackupFolder(Context context, GoogleSignInAccount googleSignInAccount, DriveFolder backupFolder) {
// iterate over the files in the subfolders
File[] subfolders = new File(context.getFilesDir().getPath()).listFiles();
for (int i = 0; i < subfolders.length; i++) {
if (subfolders[i].getName().equals("premium") || subfolders[i].getName().equals("shifts")) {
File[] filesInSubfolder = new File(subfolders[i].getPath()).listFiles();
for (int j = 0; j < filesInSubfolder.length; j++) {
// for every file, get its contents and write them to a file and upload it to the drive
String fileName = subfolders[i].getName() + "/" + filesInSubfolder[j].getName();
List<String> content = readFromFile(context, fileName);
Drive.getDriveResourceClient(context, googleSignInAccount).createContents().addOnSuccessListener(new OnSuccessListener<DriveContents>() {
#Override
public void onSuccess(DriveContents driveContents) {
OutputStream outputStream = driveContents.getOutputStream();
Writer writer = new OutputStreamWriter(outputStream);
try {
for (int k = 0; k < content.size(); k++) {
writer.write(content.get(k));
writer.write("\n");
}
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
MetadataChangeSet changeSet = new MetadataChangeSet.Builder()
.setTitle(fileName)
.setMimeType("text/plain")
.setStarred(true)
.build();
Log.d("Test", "Creating file "+fileName);
Drive.getDriveResourceClient(context, googleSignInAccount).createFile(backupFolder, changeSet, driveContents)
.addOnSuccessListener(new OnSuccessListener<DriveFile>() {
#Override
public void onSuccess(DriveFile driveFile) {
Log.d("Test", "Created file "+fileName);
}
})
.addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Log.d("Test", "Failed to create "+fileName);
}
});
}
});
}
}
}}
It deletes the old backup folder and creates the new backup folder just fine and it always copies the files to the Google Drive. They are all text files, but sometimes (more often than not) duplicates are created. For example, the folder 'premium' has files 1.txt, 2.txt, 3.txt, etc., but on the drive they would appear as premium/1.txt, premium/1.txt, premium/2.txt, premium/3.txt, premium/3.txt, and I have no idea why. Which files become duplicates is random and can change everytime I call the createNewBackup method. As you can see in the code, I register created files in the log, but no duplicates show up there, only on the Drive. Is it a problem with the Google Drive API? Am I calling everything too quickly? How could I stop this from happening?
Never mind. Even though the duplicates problem has not been solved yet, it does not really pose a problem, because when I restore a backup, all the user files are overwritten, so it does not matter if that happens once or twice extra.
I am used to opening my files in my apps using the next code:
public void openFile(#NonNull String uri) {
checkNotNull(uri);
File file = new File(uri);
String dataType = null;
if (ContentTypeUtils.isPdf(uri)) dataType = "application/pdf";
else if (ContentTypeUtils.isImage(uri)) dataType = "image/*";
if (file.exists() && dataType != null) {
Intent target = new Intent(Intent.ACTION_VIEW);
target.setDataAndType(Uri.fromFile(file), dataType);
target.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
Intent intent = Intent.createChooser(target, "Open file");
try {
startActivity(intent);
} catch (ActivityNotFoundException e) {
e.printStackTrace();
Log.e(TAG, "There is a problem when opening the file :(");
}
} else {
Toast.makeText(getContext(), "Invalido", Toast.LENGTH_LONG).show();
}
}
I had always used static files so this was enough, but now I am using the Google Drive SDK for Android. I possess the driveId of the file I want to open but the problem is I cannot find a clean way to open the file contents I obtain by doing this:
Drive.DriveApi.fetchDriveId(mGoogleApiClient, documentFile.getDriveId())
.setResultCallback(driveIdResult -> {
PendingResult<DriveApi.DriveContentsResult> open =
driveIdResult.getDriveId().asDriveFile().open(
mGoogleApiClient,
DriveFile.MODE_READ_ONLY,
null);
open.setResultCallback(result -> {
DriveContents contents = result.getDriveContents();
InputStream inputStream = contents.getInputStream();
// I know I can get the input stream, and use it to write a new file.
});
});
So the only thing that comes to my mind is creating a static route to create a file every time I have to open it, and erasing it every time I have to open a new file.
What I have understood up until now is that the Google Drive API for Android already saves an instance of the file so what I have in mind sounds unnecessary, I would like to know if there is a better way to achieve this. Is there a way I can open the file and do something similar to what I do with the Intent.ACTION_VIEW in a cleaner way?
Thanks in advance.
Well since it seems this will not be answered I will post what I did. All I did was create a temp file where I put my contents to be read. I still don't know if it was the best choice so this question will still be opened for a better answer.
open.setResultCallback(result -> {
DriveContents contents = result.getDriveContents();
InputStream inputStream = contents.getInputStream();
writeTempFile(inputStream);
});
And here the implementation of the `writeTempFile`:
private synchronized File writeTempFile(#NonNull InputStream inputStream) {
checkNotNull(inputStream);
File filePath = new File(mActivity.getFilesDir(), "TempFiles");
if (!filePath.exists()) filePath.mkdirs();
File file = new File(filePath, TEMP_FILE);
try {
OutputStream outputStream = new FileOutputStream(file);
IOUtils.copyLarge(inputStream, outputStream);
IOUtils.closeQuietly(inputStream);
IOUtils.closeQuietly(outputStream);
} catch (IOException e) {
e.printStackTrace();
}
return file;
}
I'm able to upload database to Drive using the following post.
Drive API - Download/upload sql database
But I'm not able to access it directly offline without using app.
Aim: Use the db file further in different application so I want it to be in a usable format whenever I download the content directly from google drive.
I am using MODE_WRITE_ONLY to upload the file to drive from within app
mfile.open(api, DriveFile.MODE_WRITE_ONLY, new DriveFile.DownloadProgressListener()
And mime type as this String mimeType = MimeTypeMap.getSingleton().getExtensionFromMimeType("db");
My db size is 44kb when I access from external sd card on phone, however it shows 40kb when I see on drive. Please suggest what can I do to make it readable so that I can directly open it in an sqlite browser because when I open it shows "File not recognized".
Do I have to make changes in the WRITE only part or mime type for db file. Please suggest what could be the problem.
Since I've successfully tested an SQLite file upload to GooDrive, I can post a piece of code that does it:
Let's assume, there is a SQLite file on your android device:
java.io.File dbFile = Context.getDatabasePath([YOUR_DB_NAME])
Then you can call this method:
upload("temp.db", dbFile, "application/x-sqlite3")
com.google.android.gms.common.api.GoogleApiClient GAC;
///...
void upload(final String titl, final File file, final String mime) {
if (GAC != null && GAC.isConnected() && titl != null && file != null) try {
Drive.DriveApi.newDriveContents(GAC).setResultCallback(new ResultCallback<DriveContentsResult>() {
#Override
public void onResult(#NonNull DriveContentsResult contRslt) {
if (contRslt.getStatus().isSuccess()){
DriveContents cont = contRslt.getDriveContents();
if (cont != null && file2Os(cont.getOutputStream(), file)) {
MetadataChangeSet meta = new Builder().setTitle(titl).setMimeType(mime).build();
Drive.DriveApi.getRootFolder(GAC).createFile(GAC, meta, cont).setResultCallback(
new ResultCallback<DriveFileResult>() {
#Override
public void onResult(#NonNull DriveFileResult fileRslt) {
if (fileRslt.getStatus().isSuccess()) {
// fileRslt.getDriveFile(); BINGO !!!
}
}
}
);
}
}
}
});
} catch (Exception e) { e.printStackTrace(); }
}
static boolean file2Os(OutputStream os, File file) {
boolean bOK = false;
InputStream is = null;
if (file != null && os != null) try {
byte[] buf = new byte[4096];
is = new FileInputStream(file);
int c;
while ((c = is.read(buf, 0, buf.length)) > 0)
os.write(buf, 0, c);
bOK = true;
} catch (Exception e) {e.printStackTrace();}
finally {
try {
os.flush(); os.close();
if (is != null )is.close();
} catch (Exception e) {e.printStackTrace();}
}
return bOK;
}
To create a "temp.db" SQLite file in the root of your GooDrive.
You can certainly supply a different parent folder (instead of Drive.DriveApi.getRootFolder(GAC)) if you need to place your file in a different location.
I want to use Google Drive as server to store app data. So, I have written all the data to a file and then upload that file to Drive.
Code:
try {
// outputStream.write(bitmapStream.toByteArray());
outputStream.write(text.getBytes());
}
catch (IOException e1) {
Log.i(TAG, "Unable to write file contents.");
}
MetadataChangeSet metadataChangeSet = new MetadataChangeSet.Builder()
.setMimeType("text/txt").setTitle("testfile.txt").build();
IntentSender intentSender = Drive.DriveApi
.newCreateFileActivityBuilder()
.setInitialMetadata(metadataChangeSet)
.setInitialDriveContents(result.getDriveContents())
.build(mGoogleApiClient);
try {
startIntentSenderForResult(intentSender, REQUEST_CODE_CREATOR, null, 0, 0, 0);
}
catch (SendIntentException e) {
Log.i(TAG, "Failed to launch file chooser.");
}
Is it possible that whenever the device is connected to internet, the app data will get synchronized with data available on Google Drive?
I read about Google Drive API, but was unable to understand:
1) how will the synchronization happen?
2) can we synchronize with the file stored in App folder of Google Drive?
3) do I need to write the file to Google Drive or I'll have to save the file to some container and Google will update itself when connected to internet(as in ios)?
Please guide me.
EDIT
Before creating a file in drive, I have done a check.
Query query = new Query.Builder()
.addFilter(Filters.eq(SearchableField.MIME_TYPE, "text/plain"))
.addFilter(Filters.eq(SearchableField.TITLE, "appdata.txt")).build();
Drive.DriveApi.query(getGoogleApiClient(), query).setResultCallback(
metadataCallback);
final private ResultCallback<MetadataBufferResult> metadataCallback = new ResultCallback<MetadataBufferResult>() {
#Override
public void onResult(MetadataBufferResult result) {
metadata = result.getMetadataBuffer();
for (int i = 0; i < metadata.getCount(); i++) {
DriveFile file = Drive.DriveApi.getFile(getGoogleApiClient(),
metadata.get(i).getDriveId());
file.trash(getGoogleApiClient());
}
Is it a wrong way to proceed?
newCreateFileActivityBuilder() will start an activity to let the user choose a location in their Drive to create the file.
If you want to use the App folder you'll need to follow the instructions in https://developers.google.com/drive/android/appfolder
I spent the last 8 hours to find a way to upload a file from the internal memory of my android phone to a google drive account.
I'm very frustrated right now. The official examples hosted at GitHub, explain how to take a photo and upload it on the drive. My problem is different, I already have a file on my device and I'd like to upload it on the drive.
Here is my last try (this is inspired by various examples found on the web):
public class MainActivity extends ActionBarActivity {
private GoogleAccountCredential credential;
private static Drive service;
int ACCOUNT_REQUEST = 1;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
credential = GoogleAccountCredential.usingOAuth2(this, DriveScopes.DRIVE);
startActivityForResult(credential.newChooseAccountIntent(), ACCOUNT_REQUEST);
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
protected void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
if (resultCode == RESULT_OK && data != null && data.getExtras() != null) {
String accountName = data.getStringExtra(AccountManager.KEY_ACCOUNT_NAME);
if (accountName != null) {
credential.setSelectedAccountName(accountName);
Drive.Builder builder = new Drive.Builder(AndroidHttp.newCompatibleTransport(), new GsonFactory(), credential);
builder.setApplicationName("Test Application");
service =builder.build();
uploadTheFile();
}
}
}
public void uploadTheFile() {
String filePath = "storage/sdcard0/file.txt";
String fileName = "File";
File body = new File();
body.setTitle(fileName);
body.setDescription(fileName);
body.setMimeType("text/plain");
java.io.File fileContent = new java.io.File(filePath);
FileContent mediaContent = new FileContent("text/plain", fileContent);
try {
File file = service.files().insert(body, mediaContent).execute();
} catch (IOException e) {
e.printStackTrace();
}
}
I can select the account where to upload the file (file.txt), but then it crash with:
java.lang.IllegalStateException: calling this from your main thread can lead to deadlock
I've tried to give as many info as I can, if you need further info, don't exitate to ask! Thanks !
EDIT: Thanks to greenapps the deadlock problem is fixed, but I still can't upload my file: where should I put my client id and client secret ?? Thanks !
String filePath = "storage/sdcard0/file.txt";
change that to
String filePath = "/storage/sdcard0/file.txt";
And before uploading check if that file exists.
File file = new File (filePath);
if ( ! file.exists ) )
{
message that file does not exist
return false;
}
Your IllegalStateException can be solved by putting uploadFile() in an AsyncTask
If you can get the photo upload "quick starter" demonstration code working (no small task in itself), try modifying that code to copy your file (instead of the photo) to Drive's outputStream as shown in the following code which worked for me. (I'm not a serious Java programmer so I wouldn't be surprised if there's a better way to copy a file to an outputStream.)
public void onResult(ContentsResult result) {
// Get an output stream for the contents.
OutputStream outputStream = result.getContents().getOutputStream();
// Write specified local file to Google Drive output stream.
byte[] buf = new byte[1024];
int nbyteWritten = 0;
int nbyte;
try {
DataInputStream dis = new DataInputStream(new FileInputStream(file));
DataOutputStream dos = new DataOutputStream(outputStream);
while ((nbyte = dis.read(buf)) > 0) {
dos.write(buf, 0, nbyte);
nbyteWritten += nbyte;
}
dis.close();
dos.close();
} catch (IOException e) {
Log.i(TAG, "Unable to write file contents.");
}
This should to a lot easier using the Android-specific API. See create files. It handles all the authorization and uploading for you. Just copy the contents from your local file on disk into the OutputStream provided by the new contents object.