Skip to content

Commit

Permalink
Perform auto backups of all books on daily basis (with sync service) -
Browse files Browse the repository at this point in the history
…closes codinguser#565

Use Storage Access Framework for selecting default backup location - fixes codinguser#646

If a default backup location is set, all backups will be performed to that specified file.
Otherwise, the default locations on the SD card will be used
  • Loading branch information
codinguser committed Apr 20, 2017
1 parent ec673cc commit 55c8f18
Show file tree
Hide file tree
Showing 9 changed files with 241 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@
import org.gnucash.android.ui.settings.PreferenceActivity;
import org.gnucash.android.util.TimestampHelper;

import java.util.ArrayList;
import java.util.List;

/**
* Database adapter for creating/modifying book entries
*/
Expand Down Expand Up @@ -161,6 +164,17 @@ public boolean isActive(String bookUID){
}
}

public @NonNull List<String> getAllBookUIDs(){
List<String> bookUIDs = new ArrayList<>();
try (Cursor cursor = mDb.query(true, mTableName, new String[]{BookEntry.COLUMN_UID},
null, null, null, null, null, null)) {
while (cursor.moveToNext()) {
bookUIDs.add(cursor.getString(cursor.getColumnIndexOrThrow(BookEntry.COLUMN_UID)));
}
}

return bookUIDs;
}

/**
* Return the name of the currently active book.
Expand Down
53 changes: 53 additions & 0 deletions app/src/main/java/org/gnucash/android/export/ExportAsyncTask.java
Original file line number Diff line number Diff line change
Expand Up @@ -261,11 +261,42 @@ private void moveToTarget() throws Exporter.ExporterException {
moveExportToSDCard();
break;

case URI:
moveExportToUri();
break;

default:
throw new Exporter.ExporterException(mExportParams, "Invalid target");
}
}

/**
* Move the exported files to a specified URI.
* This URI could be a Storage Access Framework file
* @throws Exporter.ExporterException
*/
private void moveExportToUri() throws Exporter.ExporterException {
Uri exportUri = Uri.parse(mExportParams.getExportLocation());
if (exportUri == null){
Log.w(TAG, "No URI found for export destination");
return;
}

//we only support exporting to a single file
String exportedFile = mExportedFiles.get(0);
try {
moveFile(exportedFile, mContext.getContentResolver().openOutputStream(exportUri));
} catch (IOException e) {
e.printStackTrace();
Log.e(TAG, "Error moving export file to: " + exportUri);
Crashlytics.logException(e);
}
}

/**
* Move the exported files to a GnuCash folder on Google Drive
* @throws Exporter.ExporterException
*/
private void moveExportToGoogleDrive() throws Exporter.ExporterException {
Log.i(TAG, "Moving exported file to Google Drive");
final GoogleApiClient googleApiClient = BackupPreferenceFragment.getGoogleApiClient(GnuCashApplication.getAppContext());
Expand Down Expand Up @@ -514,6 +545,28 @@ public void moveFile(String src, String dst) throws IOException {
srcFile.delete();
}

/**
* Move file from a location on disk to an outputstream.
* The outputstream could be for a URI in the Storage Access Framework
* @param src Input file (usually newly exported file)
* @param outputStream Output stream to write to
* @throws IOException if error occurred while moving the file
*/
public void moveFile(@NonNull String src, @NonNull OutputStream outputStream) throws IOException {
byte[] buffer = new byte[1024];
int read;
try (FileInputStream inputStream = new FileInputStream(src)) {
while ((read = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, read);
}
} finally {
outputStream.flush();
outputStream.close();
}
Log.i(TAG, "Deleting temp export file: " + src);
new File(src).delete();
}

private void reportSuccess() {
String targetLocation;
switch (mExportParams.getExportTarget()){
Expand Down
40 changes: 35 additions & 5 deletions app/src/main/java/org/gnucash/android/export/ExportParams.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

package org.gnucash.android.export;

import android.net.Uri;

import org.gnucash.android.ui.export.ExportFormFragment;
import org.gnucash.android.util.TimestampHelper;

Expand All @@ -35,7 +37,7 @@ public class ExportParams {
* Options for the destination of the exported transctions file.
* It could be stored on the {@link #SD_CARD} or exported through another program via {@link #SHARING}
*/
public enum ExportTarget {SD_CARD, SHARING, DROPBOX, GOOGLE_DRIVE, OWNCLOUD }
public enum ExportTarget {SD_CARD, SHARING, DROPBOX, GOOGLE_DRIVE, OWNCLOUD, URI}

/**
* Format to use for the exported transactions
Expand All @@ -59,6 +61,12 @@ public enum ExportTarget {SD_CARD, SHARING, DROPBOX, GOOGLE_DRIVE, OWNCLOUD }
*/
private ExportTarget mExportTarget = ExportTarget.SHARING;

/**
* Location to save the file name being exported.
* This is typically a Uri and used for {@link ExportTarget#URI} target
*/
private String mExportLocation;

/**
* Creates a new set of paramters and specifies the export format
* @param format Format to use when exporting the transactions
Expand Down Expand Up @@ -132,10 +140,28 @@ public void setExportTarget(ExportTarget mExportTarget) {
this.mExportTarget = mExportTarget;
}

/**
* Return the location where the file should be exported to.
* When used with {@link ExportTarget#URI}, the returned value will be a URI which can be parsed
* with {@link Uri#parse(String)}
* @return String representing export file destination.
*/
public String getExportLocation(){
return mExportLocation;
}

/**
* Set the location where to export the file
* @param exportLocation Destination of the export
*/
public void setExportLocation(String exportLocation){
mExportLocation = exportLocation;
}

@Override
public String toString() {
return "Export all transactions created since " + TimestampHelper.getUtcStringFromTimestamp(mExportStartTime) + " UTC"
+ " as "+ mExportFormat.name() + " to " + mExportTarget.name();
+ " as "+ mExportFormat.name() + " to " + mExportTarget.name() + (mExportLocation != null ? " (" + mExportLocation +")" : "");
}

/**
Expand All @@ -146,9 +172,11 @@ public String toString() {
public String toCsv(){
String separator = ";";

return mExportFormat.name() + separator + mExportTarget.name() + separator
return mExportFormat.name() + separator
+ mExportTarget.name() + separator
+ TimestampHelper.getUtcStringFromTimestamp(mExportStartTime) + separator
+ Boolean.toString(mDeleteTransactionsAfterExport);
+ Boolean.toString(mDeleteTransactionsAfterExport) + separator
+ (mExportLocation != null ? mExportLocation : "");
}

/**
Expand All @@ -162,7 +190,9 @@ public static ExportParams parseCsv(String csvParams){
params.setExportTarget(ExportTarget.valueOf(tokens[1]));
params.setExportStartTime(TimestampHelper.getTimestampFromUtcString(tokens[2]));
params.setDeleteTransactionsAfterExport(Boolean.parseBoolean(tokens[3]));

if (tokens.length == 5){
params.setExportLocation(tokens[4]);
}
return params;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@

import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.util.Log;

import com.crashlytics.android.Crashlytics;

import org.gnucash.android.app.GnuCashApplication;
import org.gnucash.android.db.DatabaseSchema;
import org.gnucash.android.db.adapter.BooksDbAdapter;
import org.gnucash.android.db.adapter.CommoditiesDbAdapter;
Expand All @@ -43,20 +45,21 @@
import org.gnucash.android.model.Recurrence;
import org.gnucash.android.model.ScheduledAction;
import org.gnucash.android.model.TransactionType;
import org.gnucash.android.util.BookUtils;
import org.gnucash.android.util.TimestampHelper;
import org.xmlpull.v1.XmlPullParserFactory;
import org.xmlpull.v1.XmlSerializer;

import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.math.BigDecimal;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Currency;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
Expand Down Expand Up @@ -910,10 +913,26 @@ public String getExportMimeType(){
* @return {@code true} if backup was successful, {@code false} otherwise
*/
public static boolean createBackup(){
return createBackup(BooksDbAdapter.getInstance().getActiveBookUID());
}

/**
* Create a backup of the book in the default backup location
* @param bookUID Unique ID of the book
* @return {@code true} if backup was successful, {@code false} otherwise
*/
public static boolean createBackup(String bookUID){
OutputStream outputStream;
try {
String bookUID = BooksDbAdapter.getInstance().getActiveBookUID();
FileOutputStream fileOutputStream = new FileOutputStream(getBackupFilePath(bookUID));
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream);
String backupFile = BookUtils.getBookBackupFileUri(bookUID);
if (backupFile != null){
outputStream = GnuCashApplication.getAppContext().getContentResolver().openOutputStream(Uri.parse(backupFile));
} else { //no Uri set by user, use default location on SD card
backupFile = getBackupFilePath(bookUID);
outputStream = new FileOutputStream(backupFile);
}

BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outputStream);
GZIPOutputStream gzipOutputStream = new GZIPOutputStream(bufferedOutputStream);
OutputStreamWriter writer = new OutputStreamWriter(gzipOutputStream);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@

import android.app.IntentService;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.os.PowerManager;
import android.support.annotation.VisibleForTesting;
import android.util.Log;
Expand All @@ -36,16 +38,23 @@
import org.gnucash.android.db.adapter.SplitsDbAdapter;
import org.gnucash.android.db.adapter.TransactionsDbAdapter;
import org.gnucash.android.export.ExportAsyncTask;
import org.gnucash.android.export.ExportFormat;
import org.gnucash.android.export.ExportParams;
import org.gnucash.android.export.xml.GncXmlExporter;
import org.gnucash.android.model.Book;
import org.gnucash.android.model.ScheduledAction;
import org.gnucash.android.model.Transaction;
import org.gnucash.android.util.BookUtils;

import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.zip.GZIPOutputStream;

/**
* Service for running scheduled events.
Expand All @@ -70,10 +79,11 @@ protected void onHandleIntent(Intent intent) {
PowerManager.WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, LOG_TAG);
wakeLock.acquire();

autoBackup(); //First run automatic backup of all books before doing anything else
try {
BooksDbAdapter booksDbAdapter = BooksDbAdapter.getInstance();
List<Book> books = booksDbAdapter.getAllRecords();
for (Book book : books) {
for (Book book : books) { //// TODO: 20.04.2017 Retrieve only the book UIDs with new method
DatabaseHelper dbHelper = new DatabaseHelper(GnuCashApplication.getAppContext(), book.getUID());
SQLiteDatabase db = dbHelper.getWritableDatabase();
RecurrenceDbAdapter recurrenceDbAdapter = new RecurrenceDbAdapter(db);
Expand Down Expand Up @@ -181,6 +191,11 @@ private static int executeBackup(ScheduledAction scheduledAction, SQLiteDatabase
return 1;
}

/**
* Check if a scheduled action is due for execution
* @param scheduledAction Scheduled action
* @return {@code true} if execution is due, {@code false} otherwise
*/
private static boolean shouldExecuteScheduledBackup(ScheduledAction scheduledAction) {
long now = System.currentTimeMillis();
long endTime = scheduledAction.getEndTime();
Expand Down Expand Up @@ -244,4 +259,34 @@ private static int executeTransactions(ScheduledAction scheduledAction, SQLiteDa
scheduledAction.setExecutionCount(previousExecutionCount);
return executionCount;
}

/**
* Perform an automatic backup of all books in the database.
* This method is run everytime the service is executed
*/
private static void autoBackup(){
BooksDbAdapter booksDbAdapter = BooksDbAdapter.getInstance();
List<String> bookUIDs = booksDbAdapter.getAllBookUIDs();
Context context = GnuCashApplication.getAppContext();

for (String bookUID : bookUIDs) {
String backupFile = BookUtils.getBookBackupFileUri(bookUID);
if (backupFile == null){
GncXmlExporter.createBackup();
continue;
}

try (BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(context.getContentResolver().openOutputStream(Uri.parse(backupFile)))){
GZIPOutputStream gzipOutputStream = new GZIPOutputStream(bufferedOutputStream);
OutputStreamWriter writer = new OutputStreamWriter(gzipOutputStream);
ExportParams params = new ExportParams(ExportFormat.XML);
new GncXmlExporter(params).generateExport(writer);
writer.close();
} catch (IOException ex) {
Log.e(LOG_TAG, "Auto backup failed for book " + bookUID);
ex.printStackTrace();
Crashlytics.logException(ex);
}
}
}
}
Loading

0 comments on commit 55c8f18

Please sign in to comment.