Skip to content

Commit

Permalink
In File and Dropbox synchronizers, add options to export to GPX, or T…
Browse files Browse the repository at this point in the history
…CX, or both.

Ensure at least one option is selected.
Remove usage of File's format field AUTH_CONFIG, use the ACCOUNT.FORMAT field present in all synchronizers.
Migrate DB to copy format from AUTH_CONFIG to ACCOUNT.FORMAT, and delete it from AUTH_CONFIG.
  • Loading branch information
opoto committed Jan 26, 2020
1 parent 0039c5b commit c7ff8cf
Show file tree
Hide file tree
Showing 12 changed files with 471 additions and 146 deletions.
20 changes: 0 additions & 20 deletions app/res/layout/filepermission.xml
Original file line number Diff line number Diff line change
Expand Up @@ -55,24 +55,4 @@

</TableRow>

<TableRow
android:layout_width="wrap_content"
android:layout_height="wrap_content">

<CheckBox
android:id="@+id/tcxformat"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="8dp"
android:text="TCX"
tools:ignore="HardcodedText"/>

<CheckBox
android:id="@+id/gpxformat"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="GPX"
tools:ignore="HardcodedText"/>
</TableRow>

</LinearLayout>
36 changes: 27 additions & 9 deletions app/src/main/org/runnerup/db/DBHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@

package org.runnerup.db;

import android.annotation.TargetApi;
import android.support.v7.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.ContentValues;
Expand All @@ -27,9 +26,10 @@
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.os.AsyncTask;
import android.os.Build;
import android.util.Log;

import org.json.JSONException;
import org.json.JSONObject;
import org.runnerup.R;
import org.runnerup.common.util.Constants;
import org.runnerup.db.entities.DBEntity;
Expand All @@ -41,7 +41,6 @@
import org.runnerup.export.FunBeatSynchronizer;
import org.runnerup.export.GarminSynchronizer;
import org.runnerup.export.GoogleFitSynchronizer;
import org.runnerup.export.GooglePlusSynchronizer;
import org.runnerup.export.JoggSESynchronizer;
import org.runnerup.export.MapMyRunSynchronizer;
import org.runnerup.export.NikePlusSynchronizer;
Expand All @@ -53,7 +52,9 @@
import org.runnerup.export.RuntasticSynchronizer;
import org.runnerup.export.StravaSynchronizer;
import org.runnerup.util.FileUtil;
import org.runnerup.workout.FileFormats;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
Expand Down Expand Up @@ -133,7 +134,7 @@ public class DBHelper extends SQLiteOpenHelper implements
+ (DB.ACCOUNT.NAME + " text not null, ")
+ (DB.ACCOUNT.DESCRIPTION + " text, ") //DBVERSION update: remove
+ (DB.ACCOUNT.URL + " text, ") //DBVERSION update: remove
+ (DB.ACCOUNT.FORMAT + " text not null, ") //DBVERSION update: remove
+ (DB.ACCOUNT.FORMAT + " text not null, ") // tcx/gpx, for file uploads
+ (DB.ACCOUNT.FLAGS + " integer not null default " + DB.ACCOUNT.DEFAULT_FLAGS + ", ") //Mostly not used but dynamic changes could be stored here
+ (DB.ACCOUNT.ENABLED + " integer not null default 1,") //Account is not hidden/disabled
+ (DB.ACCOUNT.AUTH_METHOD + " text not null, ") //DBVERSION update: remove
Expand Down Expand Up @@ -391,7 +392,7 @@ private static void echoDo(SQLiteDatabase arg0, String str) {
}

private void migrateFileSynchronizerInfo(SQLiteDatabase arg0) {
//Migrate storage of parameters, FORMAT is removed
// Migrate storage of parameters
String from[] = { "_id", DB.ACCOUNT.FORMAT, DB.ACCOUNT.AUTH_CONFIG };
String args[] = { FileSynchronizer.NAME };
Cursor c = arg0.query(DB.ACCOUNT.TABLE, from,
Expand All @@ -402,14 +403,31 @@ private void migrateFileSynchronizerInfo(SQLiteDatabase arg0) {
if (c.moveToFirst()) {
ContentValues tmp = DBHelper.get(c);
//URL was stored in AUTH_CONFIG previously, FORMAT migrated too
String oldUrl = tmp.getAsString(DB.ACCOUNT.AUTH_CONFIG);
String oldAuthConfig = tmp.getAsString(DB.ACCOUNT.AUTH_CONFIG);
//DBVERSION update, not needed in onUpgrade()
if (oldUrl.startsWith("/")) {
tmp.put(DB.ACCOUNT.URL, oldUrl);
if (oldAuthConfig.startsWith("/")) {
tmp.put(DB.ACCOUNT.URL, oldAuthConfig);
String authConfig = FileSynchronizer.contentValuesToAuthConfig(tmp);
tmp = new ContentValues();
tmp.put(DB.ACCOUNT.AUTH_CONFIG, authConfig);
tmp.put(DB.ACCOUNT.FORMAT, FileFormats.DEFAULT_FORMATS.toString());
arg0.update(DB.ACCOUNT.TABLE, tmp, DB.ACCOUNT.NAME + " = ?", args);
} else {
try {
// Check if AUTH_CONFIG contains deprecated FORMAT field
JSONObject authcfg = new JSONObject(oldAuthConfig);
String format = authcfg.optString(DB.ACCOUNT.FORMAT, null);
if (format != null) {
// Move deprecated FORMAT field in AUTH_CONFIG to ACCOUNT.FORMAT
authcfg.put(DB.ACCOUNT.FORMAT, null);
tmp = new ContentValues();
tmp.put(DB.ACCOUNT.AUTH_CONFIG, authcfg.toString());
tmp.put(DB.ACCOUNT.FORMAT, format);
arg0.update(DB.ACCOUNT.TABLE, tmp, DB.ACCOUNT.NAME + " = ?", args);
}
} catch (JSONException e) {
Log.w("DBHelper", "Failed to parse File auth config", e);
}
}
}
c.close();
Expand Down Expand Up @@ -486,8 +504,8 @@ private static void insertAccount(SQLiteDatabase arg0, String name, int enabled,
if (flags >= 0) {
arg1.put(DB.ACCOUNT.FLAGS, flags);
}
arg1.put(DB.ACCOUNT.FORMAT, FileFormats.DEFAULT_FORMATS.toString());
//DBVERSION update, must provide dummy data
arg1.put(DB.ACCOUNT.FORMAT, "tcx");
arg1.put(DB.ACCOUNT.AUTH_METHOD, "dummy");

//SQLite has no UPSERT command. Optimize for no change.
Expand Down
188 changes: 108 additions & 80 deletions app/src/main/org/runnerup/export/DropboxSynchronizer.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package org.runnerup.export;
import android.app.Activity;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
Expand All @@ -28,17 +29,17 @@
import org.runnerup.R;
import org.runnerup.common.util.Constants;
import org.runnerup.common.util.Constants.DB;
import org.runnerup.db.PathSimplifier;
import org.runnerup.export.format.GPX;
import org.runnerup.export.format.TCX;
import org.runnerup.export.oauth2client.OAuth2Activity;
import org.runnerup.export.oauth2client.OAuth2Server;
import org.runnerup.export.util.SyncHelper;
import org.runnerup.workout.FileFormats;
import org.runnerup.workout.Sport;

import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.StringWriter;
import java.net.HttpURLConnection;
Expand All @@ -59,11 +60,16 @@ public class DropboxSynchronizer extends DefaultSynchronizer implements OAuth2Se

private long id = 0;
private String access_token = null;
private FileFormats mFormat;
private PathSimplifier simplifier = null;

DropboxSynchronizer() {
DropboxSynchronizer(Context context) {
if (ENABLED == 0) {
Log.w(NAME, "No client id configured in this build");
}
this.simplifier = PathSimplifier.isEnabledForExportGpx(context) ?
new PathSimplifier(context, true) :
null;
}

@Override
Expand Down Expand Up @@ -129,6 +135,7 @@ public void init(ContentValues config) {
String authConfig = config.getAsString(DB.ACCOUNT.AUTH_CONFIG);
if (authConfig != null) {
try {
mFormat = new FileFormats(config.getAsString(DB.ACCOUNT.FORMAT));
JSONObject tmp = new JSONObject(authConfig);
parseAuthData(tmp);
} catch (Exception e) {
Expand Down Expand Up @@ -216,6 +223,73 @@ private String getDesc(SQLiteDatabase db, final long mID) {
return desc;
}

// upload a single file
private Status uploadFile(SQLiteDatabase db, final long mID, Sport sport,
StringWriter writer, String fileExt)
throws IOException, JSONException {

Status s;

// Upload to default directory /Apps/RunnerUp
String file = String.format(Locale.getDefault(), "/RunnerUp_%s_%04d_%s.%s",
android.os.Build.MODEL.replaceAll("\\s","_"), mID, sport.TapiriikType(),
fileExt);

HttpURLConnection conn = (HttpURLConnection) new URL(UPLOAD_URL).openConnection();
conn.setDoOutput(true);
conn.setRequestMethod(RequestMethod.POST.name());
conn.addRequestProperty("Content-Type", "application/octet-stream");
conn.setRequestProperty("Authorization", "Bearer " + access_token);
JSONObject parameters = new JSONObject();
try {
parameters.put("path", file);
parameters.put("mode", "add");
parameters.put("autorename", true);
} catch (JSONException e) {
e.printStackTrace();
return Status.ERROR;
}
conn.addRequestProperty("Dropbox-API-Arg", parameters.toString());
OutputStream out = new BufferedOutputStream(conn.getOutputStream());
out.write(writer.getBuffer().toString().getBytes());
out.flush();
out.close();

int responseCode = conn.getResponseCode();
String amsg = conn.getResponseMessage();
Log.v(getName(), "code: " + responseCode + ", amsg: " + amsg+" ");

JSONObject obj = SyncHelper.parse(conn, getName());

if (obj != null && responseCode >= HttpURLConnection.HTTP_OK && responseCode < HttpURLConnection.HTTP_MULT_CHOICE) {
s = Status.OK;
s.activityId = mID;
if (obj.has("id")) {
// Note: duplicate will not set activity_id
s.externalId = noNullStr(obj.getString("id"));
if (s.externalId != null) {
s.externalIdStatus = ExternalIdStatus.OK;
}
}
return s;
}
String error = obj != null && obj.has("error") ?
noNullStr(obj.getString("error")) :
"";
Log.e(getName(),"Error uploading, code: " +
responseCode + ", amsg: " + amsg + " " + error + ", json: " + (obj == null ? "" : obj));
if (responseCode == HttpURLConnection.HTTP_UNAUTHORIZED) {
// token no longer valid
access_token = null;
s = Status.NEED_AUTH;
s.authMethod = AuthMethod.OAUTH2;
} else {
s = Status.ERROR;
}

return s;
}

@Override
public Status upload(SQLiteDatabase db, final long mID) {
Status s = connect();
Expand All @@ -225,96 +299,50 @@ public Status upload(SQLiteDatabase db, final long mID) {

Sport sport = Sport.RUNNING;
try {
String[] columns = {
Constants.DB.ACTIVITY.SPORT
};
Cursor c = null;
try {
c = db.query(Constants.DB.ACTIVITY.TABLE, columns, "_id = " + mID,
null, null, null, null);
if (c.moveToFirst()) {
sport = Sport.valueOf(c.getInt(0));
}
} finally {
if (c != null) {
c.close();
}
}
// Upload to default directory /Apps/RunnerUp
String file = String.format(Locale.getDefault(), "/RunnerUp_%s_%04d_%s.tcx",
android.os.Build.MODEL.replaceAll("\\s","_"), mID, sport.TapiriikType());

StringWriter writer = new StringWriter();
TCX tcx = new TCX(db);
tcx.export(mID, writer);

HttpURLConnection conn = (HttpURLConnection) new URL(UPLOAD_URL).openConnection();
conn.setDoOutput(true);
conn.setRequestMethod(RequestMethod.POST.name());
conn.addRequestProperty("Content-Type", "application/octet-stream");
conn.setRequestProperty("Authorization", "Bearer " + access_token);
JSONObject parameters = new JSONObject();
String[] columns = { Constants.DB.ACTIVITY.SPORT };
Cursor c = null;
try {
parameters.put("path", file);
parameters.put("mode", "add");
parameters.put("autorename", true);
} catch (JSONException e) {
e.printStackTrace();
return Status.ERROR;
}
conn.addRequestProperty("Dropbox-API-Arg", parameters.toString());
OutputStream out = new BufferedOutputStream(conn.getOutputStream());
out.write(writer.getBuffer().toString().getBytes());
out.flush();
out.close();

int responseCode = conn.getResponseCode();
String amsg = conn.getResponseMessage();
Log.v(getName(), "code: " + responseCode + ", amsg: " + amsg+" ");

JSONObject obj = SyncHelper.parse(conn, getName());

if (obj != null && responseCode >= HttpURLConnection.HTTP_OK && responseCode < HttpURLConnection.HTTP_MULT_CHOICE) {
s = Status.OK;
s.activityId = mID;
if (obj.has("id")) {
// Note: duplicate will not set activity_id
s.externalId = noNullStr(obj.getString("id"));
if (s.externalId != null) {
s.externalIdStatus = ExternalIdStatus.OK;
}
c = db.query(Constants.DB.ACTIVITY.TABLE, columns, "_id = " + mID,
null, null, null, null);
if (c.moveToFirst()) {
sport = Sport.valueOf(c.getInt(0));
}
} finally {
if (c != null) {
c.close();
}
return s;
}
String error = obj != null && obj.has("error") ?
noNullStr(obj.getString("error")) :
"";
Log.e(getName(),"Error uploading, code: " +
responseCode + ", amsg: " + amsg + " " + error + ", json: " + (obj == null ? "" : obj));
if (responseCode == HttpURLConnection.HTTP_UNAUTHORIZED) {
// token no longer valid
access_token = null;
s = Status.NEED_AUTH;
s.authMethod = AuthMethod.OAUTH2;

StringWriter writer = new StringWriter();
if (mFormat.contains(FileFormats.TCX)) {
TCX tcx = new TCX(db);
tcx.export(mID, writer);
s = uploadFile(db, mID, sport, writer, FileFormats.TCX.getValue());
}
if (s == Status.OK && mFormat.contains(FileFormats.GPX)) {
GPX gpx = new GPX(db, true, true, simplifier);
gpx.export(mID, writer);
s = uploadFile(db, mID, sport, writer, FileFormats.GPX.getValue());
}
s = Status.ERROR;
return s;

} catch (IOException e) {
s = Status.ERROR;
s.ex = e;
} catch (JSONException e) {
} catch (Exception e) {
Log.e(getName(),"Error uploading, exception: ", e);
s = Status.ERROR;
s.ex = e;
}

s.ex.printStackTrace();
return s;
}

@Override
public boolean checkSupport(Feature f) {
return f == Feature.UPLOAD;
switch (f) {
case UPLOAD:
case FILE_FORMAT:
return true;
default:
return false;
}
}
}

Loading

0 comments on commit c7ff8cf

Please sign in to comment.