diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 4c5848f9..8441bcbc 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -30,7 +30,7 @@ - + @@ -51,6 +51,8 @@ + + this.appInst.nodestackSize()) { - this.setResult(RESULT_DONTPOP); - finish(); - } + public void onResume() { refreshDisplay(); + + IntentFilter serviceFilter = new IntentFilter(SyncService.SYNC_UPDATE); + registerReceiver(syncReceiver, serviceFilter); + + super.onResume(); + } + + @Override + public void onPause() { + unregisterReceiver(this.syncReceiver); + super.onPause(); } /** @@ -96,6 +101,12 @@ public void onResume() { * data has been updated. */ private void refreshDisplay() { + // If this is the case, the parser/syncer has invalidated nodes + if (this.depth != 1 && this.depth > this.appInst.nodestackSize()) { + this.setResult(RESULT_DONTPOP); + finish(); + } + outlineAdapter.notifyDataSetChanged(); getListView().setSelection(lastSelection); } @@ -111,7 +122,7 @@ public boolean onCreateOptionsMenu(Menu menu) { public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.menu_sync: - runSynchronizer(); + runSync(); return true; case R.id.menu_settings: @@ -192,9 +203,19 @@ public void onListItemClick(ListView l, View v, int position, long id) { } } - public void showWizard() { + private void showWizard() { startActivity(new Intent(this, WizardActivity.class)); } + + private void runSync() { + this.syncDialog = new ProgressDialog(this); + this.syncDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); + this.syncDialog.setCancelable(false); + this.syncDialog.setMessage("Started synchronization"); + this.syncDialog.show(); + + startService(new Intent(this, SyncService.class)); + } private boolean runEditNewNodeActivity() { Intent intent = new Intent(this, NodeEditActivity.class); @@ -273,7 +294,47 @@ public void onActivityResult(int requestCode, int resultCode, Intent intent) { break; } } - + + + private void showMessage(String message) { + this.syncDialog.setMessage(message); + } + + private void stopSyncDialog() { + this.syncDialog.dismiss(); + this.refreshDisplay(); + } + + private void showProgressDialog(int number, int total) { + int progress = ((40 / total) * number); + // TODO Fix progress bar + this.syncDialog.setProgress(60 + progress + 1); + } + + private void showProgress(int total) { + this.syncDialog.setProgress(total); + } + + // TODO Add the handling of thrown error messages from synchronizer + public class SynchServiceReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + int num = intent.getIntExtra(Synchronizer.SYNC_FILES, 0); + int totalfiles = intent.getIntExtra(Synchronizer.SYNC_FILES_TOTAL, 0); + int progress = intent.getIntExtra(Synchronizer.SYNC_PROGRESS, 0); + String message = intent.getStringExtra(Synchronizer.SYNC_MESSAGE); + + if (intent.getBooleanExtra(Synchronizer.SYNC_DONE, false)) + stopSyncDialog(); + if (num > 0) + showProgressDialog(num, totalfiles); + if (message != null) + showMessage(message); + if (progress > 0) + showProgress(progress); + } + } + /** * This calls startActivityForResult() with Encryption.DECRYPT_MESSAGE. The * result is handled by onActivityResult() in this class, which calls a @@ -291,9 +352,10 @@ private void runDecryptAndExpandNode(Node node) { NodeEncryption.decrypt(this, rawData); } } - + /** - * This function is called with the results of {@link #runDecryptAndExpandNode}. + * This function is called with the results of + * {@link #runDecryptAndExpandNode}. */ private void parseEncryptedNode(Intent data, Node node) { OrgFileParser ofp = new OrgFileParser(getBaseContext(), appInst); @@ -301,52 +363,6 @@ private void parseEncryptedNode(Intent data, Node node) { String decryptedData = data .getStringExtra(NodeEncryption.EXTRA_DECRYPTED_MESSAGE); - ofp.parse(node, new BufferedReader(new StringReader( - decryptedData))); - } - - private void runSynchronizer() { - final SyncManager synchman = new SyncManager(this, this.appInst); - - if (!synchman.isConfigured()) { - Toast error = Toast.makeText((Context) this, - getString(R.string.error_synchronizer_not_configured), - Toast.LENGTH_LONG); - error.show(); - this.runShowSettings(); - return; - } - - Thread syncThread = new Thread() { - public void run() { - try { - syncError = null; - synchman.sync(); - } catch (IOException e) { - syncError = e; - } finally { - synchman.close(); - } - syncHandler.post(syncUpdateResults); - } - }; - syncThread.start(); - syncDialog = ProgressDialog.show(this, "", - getString(R.string.sync_wait), true); - } - - private final Runnable syncUpdateResults = new Runnable() { - public void run() { - postSynchronize(); - } - }; - - private void postSynchronize() { - syncDialog.dismiss(); - if (this.syncError != null) { - ErrorReporter.displayError(this, this.syncError.getMessage()); - } else { - this.onResume(); - } + ofp.parse(node, new BufferedReader(new StringReader(decryptedData))); } } diff --git a/src/com/matburt/mobileorg/Services/MobileOrgSyncService.java b/src/com/matburt/mobileorg/Services/SyncService.java similarity index 71% rename from src/com/matburt/mobileorg/Services/MobileOrgSyncService.java rename to src/com/matburt/mobileorg/Services/SyncService.java index 19e97310..09167750 100644 --- a/src/com/matburt/mobileorg/Services/MobileOrgSyncService.java +++ b/src/com/matburt/mobileorg/Services/SyncService.java @@ -6,7 +6,10 @@ import java.util.TimerTask; import com.matburt.mobileorg.Parsing.MobileOrgApplication; -import com.matburt.mobileorg.Synchronizers.SyncManager; +import com.matburt.mobileorg.Synchronizers.DropboxSynchronizer; +import com.matburt.mobileorg.Synchronizers.SDCardSynchronizer; +import com.matburt.mobileorg.Synchronizers.Synchronizer; +import com.matburt.mobileorg.Synchronizers.WebDAVSynchronizer; import android.app.Service; import android.content.Intent; @@ -15,36 +18,88 @@ import android.preference.PreferenceManager; import android.util.Log; -public class MobileOrgSyncService extends Service implements SharedPreferences.OnSharedPreferenceChangeListener{ +public class SyncService extends Service implements SharedPreferences.OnSharedPreferenceChangeListener{ + public static final String SYNC_UPDATE = "com.matburt.mobileorg.SyncService.action.SYNC_UPDATE"; + + private SharedPreferences appSettings; + private MobileOrgApplication appInst; + private Timer timer = new Timer(); private Date lastSyncDate; private Boolean timerScheduled = false; - private SharedPreferences appSettings; private static long kMinimalSyncInterval = 30000; - + @Override public void onCreate() { - super.onCreate(); - Log.d("MobileOrg", "Sync service created"); - + super.onCreate(); this.appSettings = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); this.appSettings.registerOnSharedPreferenceChangeListener(this); + this.appInst = (MobileOrgApplication) this.getApplication(); } - + + @Override + public void onStart(Intent intent, int startid) { + startTimer(); + } + @Override public void onDestroy() { stopTimer(); } - @Override - public IBinder onBind(Intent intent) { - return null; + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + runSynchronizer(); + return 0; + } + + private void runSynchronizer() { + String syncSource = appSettings.getString("syncSource", ""); + final Synchronizer synchronizer; + + if (syncSource.equals("webdav")) + synchronizer = new WebDAVSynchronizer(this, this.appInst); + else if (syncSource.equals("sdcard")) + synchronizer = new SDCardSynchronizer(this, this.appInst); + else if (syncSource.equals("dropbox")) + synchronizer = new DropboxSynchronizer(this, this.appInst); + else + return; // TODO Throw error + + if (!synchronizer.isConfigured()) + return; // TODO Throw error + + Thread syncThread = new Thread() { + public void run() { + try { + synchronizer.sync(); + } catch (IOException e) { + } finally { + synchronizer.close(); + } + } + }; + + syncThread.start(); + this.lastSyncDate = new Date(); } + + @Override - public void onStart(Intent intent, int startid) { - startTimer(); + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { + if(key.equals("doAutoSync")) { + if(sharedPreferences.getBoolean("doAutoSync", false) && !this.timerScheduled) { + startTimer(); + } else if(!sharedPreferences.getBoolean("doAutoSync", false) && this.timerScheduled) { + stopTimer(); + } + } else if(key.equals("autoSyncInterval")) { + stopTimer(); + startTimer(); + } } + private void startTimer() { if(!this.timerScheduled) { @@ -83,6 +138,13 @@ public void run() { } } + + @Override + public IBinder onBind(Intent intent) { + return null; + } + + private void stopTimer() { if(this.timer != null) { this.timer.cancel(); @@ -91,40 +153,4 @@ private void stopTimer() { } Log.d("MobileOrg", "Sync Service Unscheduled"); } - - @Override - public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { - if(key.equals("doAutoSync")) { - if(sharedPreferences.getBoolean("doAutoSync", false) && !this.timerScheduled) { - startTimer(); - } else if(!sharedPreferences.getBoolean("doAutoSync", false) && this.timerScheduled) { - stopTimer(); - } - } else if(key.equals("autoSyncInterval")) { - stopTimer(); - startTimer(); - } - } - - private void runSynchronizer() { - MobileOrgApplication appInst = (MobileOrgApplication) this.getApplication(); - final SyncManager syncman = new SyncManager(this, appInst); - - if (!syncman.isConfigured()) { - return; - } - - Thread syncThread = new Thread() { - public void run() { - try { - syncman.sync(); - } catch (IOException e) { - } finally { - syncman.close(); - } - } - }; - syncThread.start(); - this.lastSyncDate = new Date(); - } } diff --git a/src/com/matburt/mobileorg/Synchronizers/DropboxSynchronizer.java b/src/com/matburt/mobileorg/Synchronizers/DropboxSynchronizer.java index 5a1f757e..0fd227d7 100644 --- a/src/com/matburt/mobileorg/Synchronizers/DropboxSynchronizer.java +++ b/src/com/matburt/mobileorg/Synchronizers/DropboxSynchronizer.java @@ -15,6 +15,7 @@ import com.dropbox.client.DropboxAPI.Config; import com.dropbox.client.DropboxAPI.FileDownload; import com.matburt.mobileorg.R; +import com.matburt.mobileorg.Parsing.MobileOrgApplication; import com.matburt.mobileorg.Parsing.OrgFile; public class DropboxSynchronizer extends Synchronizer { @@ -25,8 +26,8 @@ public class DropboxSynchronizer extends Synchronizer { private DropboxAPI dropboxAPI = new DropboxAPI(); private com.dropbox.client.DropboxAPI.Config dropboxConfig; - DropboxSynchronizer(Context context) { - super(context); + public DropboxSynchronizer(Context context, MobileOrgApplication appInst) { + super(context, appInst); SharedPreferences sharedPreferences = PreferenceManager .getDefaultSharedPreferences(context); diff --git a/src/com/matburt/mobileorg/Synchronizers/SDCardSynchronizer.java b/src/com/matburt/mobileorg/Synchronizers/SDCardSynchronizer.java index 86ab26a4..387b8bd8 100644 --- a/src/com/matburt/mobileorg/Synchronizers/SDCardSynchronizer.java +++ b/src/com/matburt/mobileorg/Synchronizers/SDCardSynchronizer.java @@ -10,6 +10,7 @@ import android.content.Context; import android.preference.PreferenceManager; +import com.matburt.mobileorg.Parsing.MobileOrgApplication; import com.matburt.mobileorg.Parsing.OrgFile; public class SDCardSynchronizer extends Synchronizer { @@ -17,8 +18,8 @@ public class SDCardSynchronizer extends Synchronizer { private String remoteIndexPath; private String remotePath; - SDCardSynchronizer(Context context) { - super(context); + public SDCardSynchronizer(Context context, MobileOrgApplication appInst) { + super(context, appInst); this.remoteIndexPath = PreferenceManager.getDefaultSharedPreferences( context).getString("indexFilePath", ""); diff --git a/src/com/matburt/mobileorg/Synchronizers/SyncManager.java b/src/com/matburt/mobileorg/Synchronizers/SyncManager.java deleted file mode 100644 index 872529d7..00000000 --- a/src/com/matburt/mobileorg/Synchronizers/SyncManager.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.matburt.mobileorg.Synchronizers; - -import java.io.IOException; - -import com.matburt.mobileorg.Parsing.MobileOrgApplication; - -import android.content.Context; -import android.preference.PreferenceManager; - -/** - * This is a wrapper class for {@link Synchronizer}. It should be used instead of - * creating instances of {@link Synchronizer} directly. - */ -public class SyncManager { - final Synchronizer synchronizer; - - MobileOrgApplication appInst; - - public SyncManager(Context context, MobileOrgApplication appInst) { - String userSynchro = PreferenceManager.getDefaultSharedPreferences(context).getString("syncSource", ""); - if (userSynchro.equals("webdav")) { - synchronizer = new WebDAVSynchronizer(context); - } else if (userSynchro.equals("sdcard")) { - synchronizer = new SDCardSynchronizer(context); - } else if (userSynchro.equals("dropbox")) { - synchronizer = new DropboxSynchronizer(context); - } else - synchronizer = null; - - this.appInst = appInst; - } - - public boolean isConfigured() { - if(synchronizer == null) - return false; - - return synchronizer.isConfigured(); - } - - public boolean sync() throws IOException { - if (!isConfigured()) - return false; - - synchronizer.sync(appInst); - return true; - } - - public void close() { - synchronizer.close(); - } -} \ No newline at end of file diff --git a/src/com/matburt/mobileorg/Synchronizers/Synchronizer.java b/src/com/matburt/mobileorg/Synchronizers/Synchronizer.java index 42b9e4c5..0acae4f9 100644 --- a/src/com/matburt/mobileorg/Synchronizers/Synchronizer.java +++ b/src/com/matburt/mobileorg/Synchronizers/Synchronizer.java @@ -8,6 +8,7 @@ import java.util.regex.Pattern; import android.content.Context; +import android.content.Intent; import android.content.SharedPreferences; import android.content.res.Resources; import android.preference.PreferenceManager; @@ -16,6 +17,7 @@ import com.matburt.mobileorg.Parsing.MobileOrgApplication; import com.matburt.mobileorg.Parsing.OrgDatabase; import com.matburt.mobileorg.Parsing.OrgFile; +import com.matburt.mobileorg.Services.SyncService; /** * This class implements many of the operations that need to be done on @@ -26,6 +28,12 @@ * needed. */ abstract public class Synchronizer { + public final static String SYNC_PROGRESS = "sync_progress"; + public final static String SYNC_MESSAGE = "sync_message"; + public final static String SYNC_FILES = "sync_files"; + public final static String SYNC_FILES_TOTAL = "sync_total"; + public final static String SYNC_DONE = "sync_done"; + /** * Called before running the synchronizer to ensure that it's configuration * is in a valid state. @@ -49,30 +57,38 @@ abstract public class Synchronizer { protected SharedPreferences appSettings; protected Context context; protected Resources r; + private MobileOrgApplication appInst; - Synchronizer(Context context) { + Synchronizer(Context context, MobileOrgApplication appInst) { this.context = context; this.r = this.context.getResources(); this.appdb = new OrgDatabase((Context)context); this.appSettings = PreferenceManager.getDefaultSharedPreferences( context.getApplicationContext()); + this.appInst = appInst; + + announceSyncMessage("Connecting to service"); } - public void sync(MobileOrgApplication appInst) throws IOException { - push(OrgFile.CAPTURE_FILE, appInst); - pull(appInst); + public void sync() throws IOException { + announceSyncMessage("Uploading " + OrgFile.CAPTURE_FILE); + push(OrgFile.CAPTURE_FILE); + announceSyncProgress(20); + pull(); + announceSyncDone(); } /** * This method will fetch the local and the remote version of a file and * combine their content. This combined version is transfered to the remote. */ - protected void push(String filename, MobileOrgApplication appInst) throws IOException { + protected void push(String filename) throws IOException { OrgFile orgFile = new OrgFile(filename, context); String localContents = orgFile.read(); String remoteContent = OrgFile.read(getRemoteFile(filename)); - + announceSyncProgress(10); + if(localContents.equals("")) return; @@ -89,35 +105,76 @@ protected void push(String filename, MobileOrgApplication appInst) throws IOExce * host. Using those files, it determines the other files that need updating * and downloads them. */ - protected void pull(MobileOrgApplication appInst) throws IOException { + protected void pull() throws IOException { + announceSyncMessage("Downloading index file"); String remoteIndexContents = OrgFile.read(getRemoteFile("index.org")); + announceSyncProgress(40); ArrayList> todoLists = getTodos(remoteIndexContents); this.appdb.setTodoList(todoLists); ArrayList> priorityLists = getPriorities(remoteIndexContents); this.appdb.setPriorityList(priorityLists); - + + announceSyncMessage("Downloading checksum file"); String remoteChecksumContents = OrgFile.read(getRemoteFile("checksums.dat")); + announceSyncProgress(60); HashMap remoteChecksums = getChecksums(remoteChecksumContents); HashMap localChecksums = this.appdb.getChecksums(); HashMap fileChecksumMap = getOrgFilesFromMaster(remoteIndexContents); - for (String key : fileChecksumMap.keySet()) { - String filename = fileChecksumMap.get(key); - + ArrayList filesToGet = new ArrayList(); + + for (String key : fileChecksumMap.keySet()) { if (localChecksums.containsKey(key) && remoteChecksums.containsKey(key) && localChecksums.get(key).equals(remoteChecksums.get(key))) continue; - + + filesToGet.add(key); + } + + int i = 0; + for(String key: filesToGet) { + String filename = fileChecksumMap.get(key); + + i++; + announceSyncFile("Downloading " + filename, i, filesToGet.size()); + OrgFile orgfile = new OrgFile(filename, context); orgfile.fetch(getRemoteFile(filename)); appInst.addOrUpdateFile(filename, key, remoteChecksums.get(key)); - } + } + } + + + private void announceSyncProgress(int progress) { + Intent intent = new Intent(SyncService.SYNC_UPDATE); + intent.putExtra(SYNC_PROGRESS, progress); + this.context.sendBroadcast(intent); + } + + private void announceSyncFile(String message, int number, int total) { + Intent intent = new Intent(SyncService.SYNC_UPDATE); + intent.putExtra(SYNC_MESSAGE, message); + intent.putExtra(SYNC_FILES, number); + intent.putExtra(SYNC_FILES_TOTAL, total); + this.context.sendBroadcast(intent); + } + + private void announceSyncMessage(String message) { + Intent intent = new Intent(SyncService.SYNC_UPDATE); + intent.putExtra(SYNC_MESSAGE, message); + this.context.sendBroadcast(intent); + } + + private void announceSyncDone() { + Intent intent = new Intent(SyncService.SYNC_UPDATE); + intent.putExtra(SYNC_DONE, true); + this.context.sendBroadcast(intent); } private HashMap getOrgFilesFromMaster(String master) { diff --git a/src/com/matburt/mobileorg/Synchronizers/WebDAVSynchronizer.java b/src/com/matburt/mobileorg/Synchronizers/WebDAVSynchronizer.java index 038041ec..d56f71d7 100644 --- a/src/com/matburt/mobileorg/Synchronizers/WebDAVSynchronizer.java +++ b/src/com/matburt/mobileorg/Synchronizers/WebDAVSynchronizer.java @@ -32,6 +32,7 @@ import android.preference.PreferenceManager; import com.matburt.mobileorg.R; +import com.matburt.mobileorg.Parsing.MobileOrgApplication; import com.matburt.mobileorg.Parsing.OrgFile; public class WebDAVSynchronizer extends Synchronizer { @@ -42,8 +43,8 @@ public class WebDAVSynchronizer extends Synchronizer { private String username; private String password; - WebDAVSynchronizer(Context parentContext) { - super(parentContext); + public WebDAVSynchronizer(Context parentContext, MobileOrgApplication appInst) { + super(parentContext, appInst); SharedPreferences sharedPreferences = PreferenceManager .getDefaultSharedPreferences(context);