Commit bba1a4f3 authored by Alan McNatty's avatar Alan McNatty
Browse files

Backend API sync script now operational - greatly simplified thanks to richardm.

The MaharaProvider (ContentProvider) is working in basic mode .. we now have sync data in out db.
The sync data is currently being delete each timeand not treated kindly but that's to fix.

We can now move our attention to the UI.
parent 42988533
......@@ -35,7 +35,7 @@ safe_require('artefact', 'blog');
$json = array();
if (!get_config('allowmobileuploads')) {
jsonreplyfinal(array('fail' => 'Mobile uploads disabled'));
jsonreply(array('fail' => 'Mobile uploads disabled'));
}
$token = '';
......@@ -46,7 +46,7 @@ try {
catch (ParameterException $e) { }
if ($token == '') {
jsonreplyfinal(array('fail' => 'Auth token cannot be blank'));
jsonreply(array('fail' => 'Auth token cannot be blank'));
}
$username = '';
......@@ -56,7 +56,7 @@ try {
catch (ParameterException $e) { }
if ($username == '') {
jsonreplyfinal(array('fail' => 'Username cannot be blank'));
jsonreply(array('fail' => 'Username cannot be blank'));
}
$USER = new User();
......@@ -65,7 +65,7 @@ try {
$USER->find_by_mobileuploadtoken($token, $username);
}
catch (AuthUnknownUserException $e) {
jsonreplyfinal(array('fail' => 'Invalid user token'));
jsonreply(array('fail' => 'Invalid user token'));
}
// error_log(var_dump($USER));
......@@ -77,24 +77,29 @@ try {
}
catch (ParameterException $e) { }
$activity = get_records_sql_array('select n.id, n.subject, n.message
$activity_arr = get_records_sql_array('select n.id, n.subject, n.message
from {notification_internal_activity} n, {activity_type} a
where n.type=a.id and n.read=0 and '
. db_format_tsfield('n.ctime', '') . ' >= ? and n.usr= ? ',
array($lastsync + 0, $USER->id));
if ( $activity )
$json['activity'] = $activity;
if ( count($activity_arr) )
$json['activity'] = $activity_arr;
// OK - let's add tags
$tags_arr = array();
$tagsort = param_alpha('ts', null) != 'freq' ? 'alpha' : 'freq';
$tags = get_my_tags(null, false, $tagsort);
if ( $tags )
$json['tags'] = $tags;
foreach (get_my_tags(null, false, $tagsort) as $tag) {
$tags_arr[] = array("id" => $tag->tag, "tag" => $tag->tag);
}
if ( count($tags_arr) > 0 )
$json['tags'] = $tags_arr;
// OK - let's add journals
$blogs_arr = array();
$blogs = (object) array(
'offset' => param_integer('offset', 0),
......@@ -103,24 +108,32 @@ $blogs = (object) array(
list($blogs->count, $blogs->data) = ArtefactTypeBlog::get_blog_list($blogs->limit, $blogs->offset);
//foreach ( $blogs->data as $blog ) {
// unset($blog['deletefrom']); // just because it's large
//}
foreach ($blogs->data as $blog) {
if ( ! $blog->locked ) {
$blogs_arr[] = array("id" => $blog->id, "blog" => $blog->title);
}
}
if ( $blogs->data )
$json['blogs'] = $blogs->data;
if ( count($blogs_arr) > 0 )
$json['blogs'] = $blogs_arr;
// OK - let's add folders
$folders_arr = array();
$folders = ArtefactTypeFile::get_my_files_data(0, $USER->id, null, null, array("artefacttype" => array("folder")));
if ( $folders )
$json['folders'] = $folders;
foreach ($folders as $folder) {
if ( ! $folder->locked ) {
$folders_arr[] = array("id" => $folder->id, "folder" => $folder->title);
}
}
if ( count($folders_arr) > 0 )
$json['folders'] = $folders_arr;
// Here we need to create a new hash - update our own store of it and return it too the handset
jsonreplyfinal ( array("success" => $USER->refresh_mobileuploadtoken() ));
// Here we need to create a new hash - update our own store of it and return it to the handset
jsonreply( array("success" => $USER->refresh_mobileuploadtoken($token) ));
function jsonreplyfinal ( $arr ) {
function jsonreply( $arr ) {
global $json;
if ( $json )
$arr['sync'] = $json;
......
......@@ -136,7 +136,7 @@ catch (UploadException $e) {
}
// Here we need to create a new hash - update our own store of it and return it too the handset
JSONResponse ( "success", $USER->refresh_mobileuploadtoken() );
JSONResponse ( "success", $USER->refresh_mobileuploadtoken($token) );
function JSONResponse ( $key, $value ) {
header('Content-Type: application/json');
......
......@@ -9,7 +9,7 @@
<string name="options_menu_prefs">Preferences</string>
<string name="options_menu_deleteall">Delete All</string>
<string name="options_menu_uploadall">Upload All</string>
<string name="options_menu_viewsaved">Saved Artefacts</string>
<string name="options_menu_viewsaved">Saved Files</string>
<string name="context_menu_upload">Upload</string>
<string name="context_menu_view">View</string>
......@@ -18,12 +18,12 @@
<string name="pref_upload_url_key">upload.uri</string>
<string name="pref_upload_url_title">Upload URI</string>
<string name="pref_upload_url_summary">Enter the URL to post files to Mahara</string>
<string name="pref_upload_url_default">http://master.dev.mahara.org/artefact/file/mobileupload.php</string>
<string name="pref_upload_url_default">http://master.dev.mahara.org/api/mobile/upload.php</string>
<string name="pref_sync_url_key">sync.uri</string>
<string name="pref_sync_url_title">Sync URI</string>
<string name="pref_sync_url_summary">Enter the sync URL to Mahara</string>
<string name="pref_sync_url_default">http://master.dev.mahara.org/artefact/file/mobilesync.php</string>
<string name="pref_sync_url_default">http://master.dev.mahara.org/api/mobile/sync.php</string>
<string name="pref_auth_username_key">auth.username</string>
<string name="pref_auth_username_title">Username</string>
......@@ -33,9 +33,44 @@
<string name="pref_auth_token_title">Token</string>
<string name="pref_auth_token_summary">Enter your Mahara mobile token</string>
<string name="prefcat_advanced">Advanced</string>
<string name="pref_upload_journal_key">upload.journal</string>
<string name="pref_upload_journal_title">Journal</string>
<string name="pref_upload_journal_summary">Enter the Journal to post to</string>
<string name="pref_upload_journal_default">My Mobile Journal</string>
<string-array name="pref_upload_journal_entry">
<item>My Mobile Journal</item>
</string-array>
<string-array name="pref_upload_journal_entryValues">
<item>My Mobile Journal</item>
</string-array>
<string name="pref_upload_folder_key">upload.folder</string>
<string name="pref_upload_folder_title">Upload Folder</string>
<string name="pref_upload_folder_summary">Enter the (base) folder to upload your artefacts into</string>
<string name="pref_upload_folder_default">MobileUploads</string>
<string-array name="pref_upload_folder_entry">
<item>MobileUploads</item>
</string-array>
<string-array name="pref_upload_folder_entryValues">
<item>MobileUploads</item>
</string-array>
<string name="pref_upload_tags_key">upload.tags</string>
<string name="pref_upload_tags_title">Artefact Tags</string>
<string name="pref_upload_tags_summary">Enter the tags (space separated) to add to all uploaded artefacts</string>
<string name="pref_upload_tags_default">Android</string>
<string-array name="pref_upload_tags_entry">
<item>Android</item>
</string-array>
<string-array name="pref_upload_tags_entryValues">
<item>Android</item>
</string-array>
<string name="pref_upload_connection_key">upload.connection</string>
<string name="pref_upload_connection_title">Connection Type</string>
<string name="pref_upload_connection_summary">Restrict artefact upload to the following connection types (default is fall back to handset settings):</string>
<string name="pref_upload_connection_summary">Restrict upload to the following connection types (default is fall back to handset settings):</string>
<string-array name="pref_upload_connection_entry">
<item>Default</item>
<item>WiFi only</item>
......@@ -47,22 +82,6 @@
<item>mobile</item>
</string-array>
<string name="prefcat_advanced">Advanced</string>
<string name="pref_upload_folder_key">upload.folder</string>
<string name="pref_upload_folder_title">Upload Folder</string>
<string name="pref_upload_folder_summary">Enter the (base) folder to upload your artefacts into</string>
<string name="pref_upload_folder_default">MobileUploads</string>
<string name="pref_upload_tags_key">upload.tags</string>
<string name="pref_upload_tags_title">Artefact Tags</string>
<string name="pref_upload_tags_summary">Enter the tags (space separated) to add to all uploaded artefacts</string>
<string name="pref_upload_tags_default">Android</string>
<string name="pref_upload_view_key">upload.view</string>
<string name="pref_upload_view_title">Create Public View</string>
<string name="pref_upload_view_summary">Automatically create a public view for this folder?</string>
<string name="pref_menu_reset">Reset</string>
<string name="pref_menu_about">About</string>
<string name="pref_menu_scan">Scan</string>
......@@ -77,8 +96,8 @@
<string name="lbluploadoptions">Upload Settings</string>
<string name="artifact_retrieve_error">Error Retrieving Artefact Details</string>
<string name="artifactsettings">Artefact Settings</string>
<string name="artifact_retrieve_error">Error Retrieving File Details</string>
<string name="artifactsettings">Settings</string>
<string name="transferprogress">File Transfer Progress</string>
<string name="uploadstarting">Your Upload is Starting</string>
<string name="uploadfinished">Your Upload is Finished</string>
......@@ -86,7 +105,7 @@
<string name="savefailed">Save failed</string>
<string name="uploadsaved">Upload saved</string>
<string name="uploadnotavailable">Sorry you can only uploaded files as artefacts.</string>
<string name="uploadingartifact">Uploading Artefact</string>
<string name="uploadingartifact">Uploading ...</string>
<string name="uploading">Uploading</string>
<string name="uploadnoconnection">Sorry you don\'t have a working data connection to upload at this time.</string>
<string name="filetransfers">File Transfers</string>
......
......@@ -21,29 +21,45 @@
android:key="@string/pref_auth_token_key"
android:title="@string/pref_auth_token_title"
android:summary="@string/pref_auth_token_summary" />
<ListPreference
android:key="@string/pref_upload_connection_key"
android:title="@string/pref_upload_connection_title"
android:summary="@string/pref_upload_connection_summary"
android:defaultValue=""
android:entries="@array/pref_upload_connection_entry"
android:entryValues="@array/pref_upload_connection_entryValues"
android:dialogTitle="@string/pref_upload_connection_title" />
<PreferenceCategory
android:title="@string/prefcat_advanced">
<EditTextPreference
<ListPreference
android:key="@string/pref_upload_journal_key"
android:title="@string/pref_upload_journal_title"
android:summary="@string/pref_upload_journal_summary"
android:defaultValue="@string/pref_upload_journal_default"
android:entries="@array/pref_upload_journal_entry"
android:entryValues="@array/pref_upload_journal_entryValues"
android:dialogTitle="@string/pref_upload_journal_title" />
<ListPreference
android:key="@string/pref_upload_folder_key"
android:title="@string/pref_upload_folder_title"
android:summary="@string/pref_upload_folder_summary"
android:defaultValue="@string/pref_upload_folder_default" />
<EditTextPreference
android:defaultValue="@string/pref_upload_folder_default"
android:entries="@array/pref_upload_folder_entry"
android:entryValues="@array/pref_upload_folder_entryValues"
android:dialogTitle="@string/pref_upload_folder_title" />
<ListPreference
android:key="@string/pref_upload_tags_key"
android:title="@string/pref_upload_tags_title"
android:summary="@string/pref_upload_tags_summary"
android:defaultValue="@string/pref_upload_tags_default" />
android:defaultValue="@string/pref_upload_tags_default"
android:entries="@array/pref_upload_tags_entry"
android:entryValues="@array/pref_upload_tags_entryValues"
android:dialogTitle="@string/pref_upload_tags_title" />
<ListPreference
android:key="@string/pref_upload_connection_key"
android:title="@string/pref_upload_connection_title"
android:summary="@string/pref_upload_connection_summary"
android:defaultValue=""
android:entries="@array/pref_upload_connection_entry"
android:entryValues="@array/pref_upload_connection_entryValues"
android:dialogTitle="@string/pref_upload_connection_title" />
</PreferenceCategory>
</PreferenceScreen>
\ No newline at end of file
......@@ -50,6 +50,7 @@ public class GlobalResources {
public static final String ACCOUNT_TYPE = "nz.net.catalyst.MaharaDroid.account";
public static final String AUTHTOKEN_TYPE = "nz.net.catalyst.MaharaDroid.account";
public static final String CONTENT_URL = "nz.net.catalyst.MaharaDroid.provider";
public static final String SYNC_AUTHORITY = "nz.net.catalyst";
public static final int REGISTRATION_TIMEOUT = 30 * 1000; // ms
......
......@@ -23,13 +23,16 @@ package nz.net.catalyst.MaharaDroid;
import java.io.File;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
......@@ -37,6 +40,7 @@ import android.database.Cursor;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.os.RemoteException;
import android.preference.PreferenceManager;
import android.util.Log;
......@@ -50,8 +54,7 @@ public class Utils {
public static boolean canUpload(Context mContext) {
SharedPreferences mPrefs;
mPrefs = PreferenceManager.getDefaultSharedPreferences(mContext);
SharedPreferences mPrefs = PreferenceManager.getDefaultSharedPreferences(mContext);
boolean allowWiFi = false, allowMobile = false;
String mSetting = mPrefs.getString(mContext.getResources().getString(R.string.pref_upload_connection_key), "");
......@@ -166,7 +169,9 @@ public class Utils {
PendingIntent contentIntent = null;
// The PendingIntent to launch our activity if the user selects this notification
if ( intent != null ) {
if ( intent == null ) {
contentIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
} else {
contentIntent = PendingIntent.getActivity(mContext, 0, intent, 0);
}
if ( description == null ) {
......@@ -186,5 +191,111 @@ public class Utils {
mNM.cancel(id);
}
public static long processSyncResults(JSONObject result, ContentProviderClient myProvider, Context mContext) {
// TODO Auto-generated method stub
long numUpdates = 0;
try {
JSONObject syncObj = result.getJSONObject("sync");
//Log.i(TAG, syncObj.toString());
if ( syncObj.has("activity") ) {
JSONArray notArr = syncObj.getJSONArray("activity");
for (int i=0; i<notArr.length(); i++) {
Utils.showNotification(Integer.parseInt(notArr.getJSONObject(i).getString("id")),
notArr.getJSONObject(i).getString("subject"), notArr.getJSONObject(i).getString("message"),
null, mContext);
numUpdates++;
}
}
if ( syncObj.has("tags") ) {
long newItems = updateListPreferenceFromJSON(myProvider, syncObj.getJSONArray("tags"), "tag");
numUpdates += newItems;
}
if ( syncObj.has("blogs") ) {
long newItems = updateListPreferenceFromJSON(myProvider, syncObj.getJSONArray("blogs"), "blog");
numUpdates += newItems;
}
if ( syncObj.has("folders") ) {
long newItems = updateListPreferenceFromJSON(myProvider, syncObj.getJSONArray("folders"), "folder");
numUpdates += newItems;
}
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (RemoteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// Save last sync time
SharedPreferences mPrefs = PreferenceManager.getDefaultSharedPreferences(mContext);
mPrefs.edit()
.putLong("lastsync", System.currentTimeMillis()/1000)
.commit()
;
return numUpdates;
}
private static long updateListPreferenceFromJSON(ContentProviderClient myProvider, JSONArray jsonArray, String fieldName) throws JSONException, RemoteException {
int items = jsonArray.length();
ContentValues[] cv = new ContentValues[items];
Uri uri = Uri.parse(GlobalResources.CONTENT_URL + "/" + fieldName);
Log.i(TAG, jsonArray.toString());
for (int i=0; i<items; i++) {
String value = jsonArray.getJSONObject(i).getString(fieldName);
String id = jsonArray.getJSONObject(i).getString("id");
Log.i(TAG, "id: " + id + ", value: " + value);
//if ( myProvider.query(uri, null, null, null, null) != null ) {
if ( cv[i] == null )
cv[i] = new ContentValues();
cv[i].put("ID", id);
cv[i].put("VALUE", value);
//}
}
// TODO add a 'last_seen' column and delete any last_seen < this_sync
myProvider.delete(uri, null, null); // delete them all
myProvider.bulkInsert(uri, cv);
return items;
}
public static String getUploadURLPref(Context mContext) {
SharedPreferences mPrefs = PreferenceManager.getDefaultSharedPreferences(mContext);
return mPrefs.getString(mContext.getResources().getString(R.string.pref_upload_url_key), "");
}
public static Boolean getUploadCreateViewPref(Context mContext) {
SharedPreferences mPrefs = PreferenceManager.getDefaultSharedPreferences(mContext);
return mPrefs.getBoolean(mContext.getResources().getString(R.string.pref_upload_view_key), false);
}
public static String getUploadFolderPref(Context mContext) {
SharedPreferences mPrefs = PreferenceManager.getDefaultSharedPreferences(mContext);
return mPrefs.getString(mContext.getResources().getString(R.string.pref_upload_folder_key), "");
}
public static String getUploadAuthTokenPref(Context mContext) {
SharedPreferences mPrefs = PreferenceManager.getDefaultSharedPreferences(mContext);
return mPrefs.getString(mContext.getResources().getString(R.string.pref_auth_token_key), "");
}
public static String getUploadUsernamePref(Context mContext) {
SharedPreferences mPrefs = PreferenceManager.getDefaultSharedPreferences(mContext);
return mPrefs.getString(mContext.getResources().getString(R.string.pref_auth_username_key), "");
}
public static String getUploadTagsPref(String pref_tags, Context mContext) {
SharedPreferences mPrefs = PreferenceManager.getDefaultSharedPreferences(mContext);
String tags = ( pref_tags != null ) ? pref_tags.trim() : "" ;
return (mPrefs.getString(mContext.getResources().getString(R.string.pref_upload_tags_key), "") + " " + tags).trim();
}
}
......@@ -16,18 +16,13 @@
package nz.net.catalyst.MaharaDroid.authenticator;
import nz.net.catalyst.MaharaDroid.R;
import nz.net.catalyst.MaharaDroid.authenticator.AuthenticatorActivity;
import nz.net.catalyst.MaharaDroid.provider.MaharaProvider;
import nz.net.catalyst.MaharaDroid.ui.ArtefactExpandableListAdapterActivity;
import nz.net.catalyst.MaharaDroid.ui.EditPreferences;
import nz.net.catalyst.MaharaDroid.GlobalResources;
import nz.net.catalyst.MaharaDroid.LogConfig;
import nz.net.catalyst.MaharaDroid.Utils;
import android.accounts.Account;
import android.accounts.AccountAuthenticatorActivity;
import android.accounts.AccountManager;
import android.app.NotificationManager;
import android.content.ContentResolver;
import android.content.Intent;
import android.os.Bundle;
......@@ -46,12 +41,6 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity {
static final boolean VERBOSE = LogConfig.VERBOSE;
private AccountManager mAccountManager;
private NotificationManager mNM;
// Unique Identification Number for the Notification.
// We use it on Notification start, and to cancel it.
private int NOTIFICATION = R.string.login_authenticating;
/**
* If set we are just checking that the user knows their credentials; this
* doesn't cause the user's password to be changed on the device.
......@@ -87,65 +76,31 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity {
if ( DEBUG ) Log.d(TAG, "AuthenticatorActivity request new: " + mRequestNewAccount);
Utils.showNotification(NOTIFICATION, getText(R.string.login_authenticating), null, null, this);
MaharaAuthHandler.attemptAuth(mUsername, mHandler, AuthenticatorActivity.this);
finish();
}
/**
*
* Called when response is received from the server for authentication
* request. See onAuthenticationResult(). Sets the
* AccountAuthenticatorResult which is sent back to the caller. Also sets
* the authToken in AccountManager for this account.
*
* @param the confirmCredentials result.
*/
protected void finishLogin(String username, String authToken) {
if ( DEBUG ) Log.d(TAG, "finishLogin()");
final Account account = new Account(username, GlobalResources.ACCOUNT_TYPE);
if (mRequestNewAccount) {
mAccountManager.addAccountExplicitly(account, authToken, null);
// Set contacts sync for this account.
ContentResolver.setSyncAutomatically(account, MaharaProvider.AUTHORITY, true);
ContentResolver.setIsSyncable(account, MaharaProvider.AUTHORITY, 1);
} else {
mAccountManager.setPassword(account, authToken);
}
final Intent intent = new Intent();
intent.putExtra(AccountManager.KEY_ACCOUNT_NAME, username);
intent.putExtra(AccountManager.KEY_ACCOUNT_TYPE, GlobalResources.ACCOUNT_TYPE);
setAccountAuthenticatorResult(intent.getExtras());
setResult(RESULT_OK, intent);
finish();
}
/**
* Called when the authentication process completes (see attemptLogin()).
*/
public void onAuthenticationResult(String username, String authToken) {
if ( DEBUG ) Log.d(TAG, "onAuthenticationResult(" + authToken + ")");
// Hide the progress dialog
// If we have an auth token create the account
if (authToken != null) {
finishLogin(username, authToken);
Utils.showNotification(NOTIFICATION, getText(R.string.auth_result_success), null,
new Intent(this, ArtefactExpandableListAdapterActivity.class), this);
final Account account = new Account(username, GlobalResources.ACCOUNT_TYPE);
if (mRequestNewAccount) {
mAccountManager.addAccountExplicitly(account, null, null);
// Set contacts sync for this account.
ContentResolver.setSyncAutomatically(account, MaharaProvider.AUTHORITY, true);
ContentResolver.setIsSyncable(account, MaharaProvider.AUTHORITY, 1);
}
} else {
Log.e(TAG, "onAuthenticationResult: failed to authenticate");
// In this sample, we'll use the same text for the ticker and the expanded notification
Utils.showNotification(NOTIFICATION, getText(R.string.auth_result_fail_short),
getText(R.string.auth_result_fail_long),
new Intent(this, EditPreferences.class), this);
//Toast.makeText(this, getString(R.string.auth_result_fail), Toast.LENGTH_LONG).show();
final Intent intent = new Intent();
intent.putExtra(AccountManager.KEY_ACCOUNT_NAME, username);
intent.putExtra(AccountManager.KEY_ACCOUNT_TYPE, GlobalResources.ACCOUNT_TYPE);
setAccountAuthenticatorResult(intent.getExtras());
setResult(RESULT_OK, intent);
finish();
}
}
......
......@@ -20,8 +20,10 @@ public class MaharaAuthHandler {
// whether VERBOSE level logging is enabled
static final boolean VERBOSE = LogConfig.VERBOSE;
// private static HttpClient mHttpClient;
// Unique Identification Number for the Notification.
// We use it on Notification start, and to cancel it.
private static int NOTIFICATION = R.string.login_authenticating;
/**
* Executes the network requests on a separate thread.
*
......@@ -48,28 +50,39 @@ public class MaharaAuthHandler {
* password.
*
* @param handler The hander instance from the calling UI thread.
* @param context The context of the calling Activity.
* @param mContext The context of the calling Activity.
* @return boolean The boolean result indicating whether the user was
* successfully authenticated.
*/
public static boolean authenticate(String username, Handler handler, final Context context) {
public static boolean authenticate(String username, Handler handler, final Context mContext) {
// application preferences
SharedPreferences mPrefs = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences mPrefs = PreferenceManager.getDefaultSharedPreferences(mContext);
String authSyncURI = mPrefs.getString(context.getResources().getString(R.string.pref_sync_url_key).toString(),
context.getResources().getString(R.string.pref_sync_url_default).toString());
String authSyncURI = mPrefs.getString(mContext.getResources().getString(R.string.pref_sync_url_key).toString(),