Skip to content

Commit

Permalink
Merge pull request #879 from opoto/upload_file_formats
Browse files Browse the repository at this point in the history
GPX/TCX file formats option for File and Dropbox Synchronizers
  • Loading branch information
gerhardol committed Jan 26, 2020
2 parents 0039c5b + c7ff8cf commit 011dfe2
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 011dfe2

Please sign in to comment.