Using Google Drive to backup and restore SQLite Database

21,212

Solution 1

Both APIs used to access Google Drive deal with a binary content. So the only thing you have to do is to upload your binary DB file, give it a proper MIME type and a NAME (title).
The selection of API depends on you, GDAA behaves like a 'local' entity with uploads / downloads handled by Google Play Services, REST Api is more low-level, giving you more control, but you have to take care of networking issues (wifi on/off, etc), i.e. you usually have to build a sync service to do so. With GDAA it is done for you by GooPlaySvcs. But I digress.
I can point you to this GitHub demo, fairly recent (GooPlaySvcs 7.00.+), I use to test different REST / GDAA issues. The MainActivity is a bit complicated by the fact that it allows for switching between different Google accounts, but if you get through these hurdles, you can use either REST or GDAA CRUD wrappers.

Take look at this line. The byte[] buffer contains binary JPEG data and it goes with "image/jpeg" mime type (and a time-based name). The only thing you have to do if is load your DB file into a byte[] buffer using a construct like this:

  private static final int BUF_SZ = 4096;

  static byte[] file2Bytes(File file) {
    if (file != null) try {
       return is2Bytes(new FileInputStream(file));
    } catch (Exception ignore) {}
   return null;
  }

  static byte[] is2Bytes(InputStream is) {
    byte[] buf = null;
    BufferedInputStream bufIS = null;
    if (is != null) try {
      ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream();
      bufIS = new BufferedInputStream(is);
      buf = new byte[BUF_SZ];
      int cnt;
      while ((cnt = bufIS.read(buf)) >= 0) {
        byteBuffer.write(buf, 0, cnt);
      }
      buf = byteBuffer.size() > 0 ? byteBuffer.toByteArray() : null;
    } catch (Exception e) {le(e);}
    finally {
      try {
        if (bufIS != null) bufIS.close();
      } catch (Exception e) {le(e);}
    }
    return buf;
  }

I don't remember the MIME type for SQLite DB now, but I am sure it can be done since I was doing exactly that once (the code is gone now, unfortunately). And I remember I could actually access and modify the SQLite DB 'up in the cloud' using some web app.

Good Luck

UPDATE:
After I wrote the rant above I looked at the demo you're talking about. If you have it working, the easiest way is actually to plug your DB file right here, set the correct MIME and you're good to go. Take you pick.
And to address your 'deprecated' issue. GDAA is still being developed and the quickstart is over a year old. That's the world we live in :-)

Solution 2

You need to replace the deprecated code with:
contents.commit(api, null);

See https://developer.android.com/reference/com/google/android/gms/drive/DriveContents.html

Share:
21,212

Related videos on Youtube

Cytus
Author by

Cytus

Updated on July 09, 2022

Comments

  • Cytus
    Cytus almost 2 years

    I've managed to create a backup of my database on an SD card and restore from there but realized that the purpose of my backup is to ensure the safety of the data and in this case if the physical device itself is damaged, lost, or spontaneously combusts so will the backup on the SD card. So having the backup in the same place as the original in this case, quite frankly defeats the purpose of having a backup.

    So I thought of using Google Drive as a safer place to keep the db file, that and it's free. I've taken a peek into Google's quickstart demo which I got working just fine. But I still have no idea how to get this done for my case.

    I've found some code to fiddle with but it's still using some deprecated methods and so far I've only managed to run it when omitting the deprecated area but it only creates a blank binary file in my Google Drive so I think that deprecated area is where it actually uploads the DB backup content. If anyone could help out that would be greatly appreciated.

    I'll leave it down below in case anyone can use it to explain things to me better. I've also marked the deprecated method below, it's near the end.

    public class ExpectoPatronum extends Activity implements ConnectionCallbacks, OnConnectionFailedListener {
    
    private static final String TAG = "MainActivity";
    private GoogleApiClient api;
    private boolean mResolvingError = false;
    private DriveFile mfile;
    private static final int DIALOG_ERROR_CODE =100;
    private static final String DATABASE_NAME = "demodb";
    private static final String GOOGLE_DRIVE_FILE_NAME = "sqlite_db_backup";
    
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    
        // Create the Drive API instance
        api = new GoogleApiClient.Builder(this).addApi(Drive.API).addScope(Drive.SCOPE_FILE).
                addConnectionCallbacks(this).addOnConnectionFailedListener(this).build();
    }
    
    @Override
    public void onStart() {
        super.onStart();
        if(!mResolvingError) {
            api.connect(); // Connect the client to Google Drive
        }
    }
    
    @Override
    public void onStop() {
        super.onStop();
        api.disconnect(); // Disconnect the client from Google Drive
    }
    
    @Override
    public void onConnectionFailed(ConnectionResult result) {
        Log.v(TAG, "Connection failed");
        if(mResolvingError) { // If already in resolution state, just return.
            return;
        } else if(result.hasResolution()) { // Error can be resolved by starting an intent with user interaction
            mResolvingError = true;
            try {
                result.startResolutionForResult(this, DIALOG_ERROR_CODE);
            } catch (SendIntentException e) {
                e.printStackTrace();
            }
        } else { // Error cannot be resolved. Display Error Dialog stating the reason if possible.
            ErrorDialogFragment fragment = new ErrorDialogFragment();
            Bundle args = new Bundle();
            args.putInt("error", result.getErrorCode());
            fragment.setArguments(args);
            fragment.show(getFragmentManager(), "errordialog");
        }
    }
    
    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        if(requestCode == DIALOG_ERROR_CODE) {
            mResolvingError = false;
            if(resultCode == RESULT_OK) { // Error was resolved, now connect to the client if not done so.
                if(!api.isConnecting() && !api.isConnected()) {
                    api.connect();
                }
            }
        }
    }
    
    @Override
    public void onConnected(Bundle connectionHint) {
        Log.v(TAG, "Connected successfully");
    
        /* Connection to Google Drive established. Now request for Contents instance, which can be used to provide file contents.
           The callback is registered for the same. */
        Drive.DriveApi.newDriveContents(api).setResultCallback(contentsCallback);
    }
    
    final private ResultCallback<DriveApi.DriveContentsResult> contentsCallback = new ResultCallback<DriveApi.DriveContentsResult>() {
    
        @Override
        public void onResult(DriveApi.DriveContentsResult result) {
            if (!result.getStatus().isSuccess()) {
                Log.v(TAG, "Error while trying to create new file contents");
                return;
            }
    
            String mimeType = MimeTypeMap.getSingleton().getExtensionFromMimeType("db");
            MetadataChangeSet changeSet = new MetadataChangeSet.Builder()
                    .setTitle(GOOGLE_DRIVE_FILE_NAME) // Google Drive File name
                    .setMimeType(mimeType)
                    .setStarred(true).build();
            // create a file on root folder
            Drive.DriveApi.getRootFolder(api)
                    .createFile(api, changeSet, result.getDriveContents())
                    .setResultCallback(fileCallback);
        }
    
    };
    
    final private ResultCallback<DriveFileResult> fileCallback = new ResultCallback<DriveFileResult>() {
    
        @Override
        public void onResult(DriveFileResult result) {
            if (!result.getStatus().isSuccess()) {
                Log.v(TAG, "Error while trying to create the file");
                return;
            }
            mfile = result.getDriveFile();
            mfile.open(api, DriveFile.MODE_WRITE_ONLY, null).setResultCallback(contentsOpenedCallback);
        }
    };
    
    final private ResultCallback<DriveApi.DriveContentsResult> contentsOpenedCallback = new ResultCallback<DriveApi.DriveContentsResult>() {
    
        @Override
        public void onResult(DriveApi.DriveContentsResult result) {
    
            if (!result.getStatus().isSuccess()) {
                Log.v(TAG, "Error opening file");
                return;
            }
    
            try {
                FileInputStream is = new FileInputStream(getDbPath());
                BufferedInputStream in = new BufferedInputStream(is);
                byte[] buffer = new byte[8 * 1024];
                DriveContents content = result.getDriveContents();
                BufferedOutputStream out = new BufferedOutputStream(content.getOutputStream());
                int n = 0;
                while( ( n = in.read(buffer) ) > 0 ) {
                    out.write(buffer, 0, n);
                }
    
                in.close();
    commitAndCloseContents is DEPRECATED -->/**mfile.commitAndCloseContents(api, content).setResultCallback(new ResultCallback<Status>() {   
                    @Override
                    public void onResult(Status result) {
                        // Handle the response status
                    }
                });**/
            } catch (FileNotFoundException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    
    };
    
    private File getDbPath() {
        return this.getDatabasePath(DATABASE_NAME);
    }
    
    @Override
    public void onConnectionSuspended(int cause) {
        // TODO Auto-generated method stub
        Log.v(TAG, "Connection suspended");
    
    }
    
    public void onDialogDismissed() {
        mResolvingError = false;
    }
    
    public static class ErrorDialogFragment extends DialogFragment {
        public ErrorDialogFragment() {}
    
        public Dialog onCreateDialog(Bundle savedInstanceState) {
            int errorCode = this.getArguments().getInt("error");
            return GooglePlayServicesUtil.getErrorDialog(errorCode, this.getActivity(), DIALOG_ERROR_CODE);
        }
    
        public void onDismiss(DialogInterface dialog) {
            ((ExpectoPatronum) getActivity()).onDialogDismissed();
        }
    }
    }
    
    • pinoyyid
      pinoyyid about 9 years
      Make sure you understand the difference between the Drive rest API and GDAA. For your use case, gdaa is the best way to go, so you should ignore any quick start tutorial that isn't gdaa. As an aside, posting that much code into a question isn't very useful.
    • Jose19589
      Jose19589 over 7 years
  • Cytus
    Cytus about 9 years
    Thanks for the link and explanations, they've cleared up a few of my questions. Decided to go with your suggestion, just working on removing the "take picture" part and replacing it with something else. I tried working on the other one but so far still getting an empty binary. I'll just get to that some other time. Right now I'll just work on restoring the db from the drive file, you wouldn't happen to have any pointers for that would you ?
  • seanpj
    seanpj about 9 years
    The core you should focus on is here: github.com/seanpjanson/GDAADemo/blob/master/src/main/java/co‌​m/…. Modify the method so it fits your environment and step through checking the 'buf' (notice it's buf.length). In the end, you should see the file in GooDrive and it's length should the same. Try to download it to your desktop a compare it with the original from your Android device. GDAA does not care what kind of file you're shipping, it just moves the bytes up, creates an object and attaches metadata (title, mime, description,...)
  • seanpj
    seanpj over 7 years
    Sorry, I have retired a year ago.
  • shadygoneinsane
    shadygoneinsane about 7 years
    @seanpj Link seem broken !! Could you please update !
  • seanpj
    seanpj about 7 years
    @shadygoneinsane. Unfortunately I have retired over a year ago. The only code that may help (and is still alive) can be found here: github.com/seanpjanson/GDAADemo and here github.com/seanpjanson/RESTDemo. But it may be outdated, GooUniverse is changing quickly. Good luck