From c656c4508133d64602bedf5bd041de51d4c0d861 Mon Sep 17 00:00:00 2001 From: Felix Ableitner Date: Sat, 23 Jan 2016 17:55:04 +0100 Subject: [PATCH] Simplified event handling code. --- .../syncthing/EventProcessor.java | 105 +++++------------- .../syncthingandroid/syncthing/RestApi.java | 73 ++++-------- .../syncthing/SyncthingService.java | 21 ---- 3 files changed, 48 insertions(+), 151 deletions(-) diff --git a/src/main/java/com/nutomic/syncthingandroid/syncthing/EventProcessor.java b/src/main/java/com/nutomic/syncthingandroid/syncthing/EventProcessor.java index 1f89c3af..9085b000 100644 --- a/src/main/java/com/nutomic/syncthingandroid/syncthing/EventProcessor.java +++ b/src/main/java/com/nutomic/syncthingandroid/syncthing/EventProcessor.java @@ -3,22 +3,22 @@ package com.nutomic.syncthingandroid.syncthing; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; -import android.os.Bundle; +import android.net.Uri; import android.os.Handler; import android.os.Looper; import android.preference.PreferenceManager; -import android.support.v4.content.LocalBroadcastManager; import android.util.Log; +import org.json.JSONException; +import org.json.JSONObject; + import java.io.File; -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; import java.util.concurrent.TimeUnit; /** * Run by the syncthing service to convert syncthing events into local broadcasts. - * It uses SyncthingService.GetEvents to read the pending events and wait for new events. + * + * It uses {@link RestApi#getEvents} to read the pending events and wait for new events. */ public class EventProcessor implements SyncthingService.OnWebGuiAvailableListener, Runnable, RestApi.OnReceiveEventListener { @@ -26,16 +26,16 @@ public class EventProcessor implements SyncthingService.OnWebGuiAvailableListene private static final String TAG = "EventProcessor"; private static final String PREF_LAST_SYNC_ID = "last_sync_id"; - private static final String EVENT_BASE_ACTION = "com.nutomic.syncthingandroid.event"; - /** * Minimum interval in seconds at which the events are polled from syncthing and processed. * This intervall will not wake up the device to save battery power. */ public static final long EVENT_UPDATE_INTERVAL = TimeUnit.SECONDS.toMillis(15); - // Use the MainThread for all callbacks and message handling - // or we have to track down nasty threading problems. + /** + * Use the MainThread for all callbacks and message handling + * or we have to track down nasty threading problems. + */ private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper()); private volatile long mLastEventId = 0; @@ -43,43 +43,12 @@ public class EventProcessor implements SyncthingService.OnWebGuiAvailableListene private final Context mContext; private final RestApi mApi; - private final LocalBroadcastManager mLocalBM; - private final Map mFolderToPath = new HashMap<>(); - /** - * Returns the action used by notification Intents fired for the given Syncthing event. - * @param eventName Name of the Syncthing event. - * @return Returns the full intent action used for local broadcasts. - */ - public static String getEventIntentAction(String eventName) { - return EVENT_BASE_ACTION + "." + eventName.toUpperCase(Locale.US); - } - - /** - * C'tor - * @param context Context of the service using this event processor. - * @param api Reference to the RestApi-Instance used for all API calls by this instance of the - * Event processor. - */ public EventProcessor(Context context, RestApi api) { mContext = context; mApi = api; - mLocalBM = LocalBroadcastManager.getInstance(mContext); } - private void updateFolderMap() - { - synchronized(mFolderToPath) { - mFolderToPath.clear(); - for (RestApi.Folder folder: mApi.getFolders()) { - mFolderToPath.put(folder.id, folder.path); - } - } - } - - /** - * @see Runnable - */ @Override public void run() { // Restore the last event id if the event processor may have been restartet. @@ -89,10 +58,11 @@ public class EventProcessor implements SyncthingService.OnWebGuiAvailableListene } // First check if the event number ran backwards. - // If that's the case we've to start at zero because syncthing was restartet. + // If that's the case we've to start at zero because syncthing was restarted. mApi.getEvents(0, 1, new RestApi.OnReceiveEventListener() { @Override - public void onEvent(long id, String eventType, Bundle eventData) { + public void onEvent(long id, String eventType, JSONObject data) throws JSONException { + } @Override @@ -107,41 +77,25 @@ public class EventProcessor implements SyncthingService.OnWebGuiAvailableListene } /** - * @see RestApi.OnReceiveEventListener + * Performs the actual event handling. */ @Override - public void onEvent(final long id, final String eventType, final Bundle eventData) { - // If a folder item is contained within the event. Resolve the local path. - if (eventData.containsKey("folder")) { - String folderPath = null; - synchronized (mFolderToPath) { - if (mFolderToPath.size() == 0) updateFolderMap(); - folderPath = mFolderToPath.get(eventData.getString("folder")); - } - - if (folderPath != null) { - eventData.putString("_localFolderPath",folderPath); - - if (eventData.containsKey("item")) { - final File file = new File(new File(folderPath), eventData.getString("item")); - - eventData.putString("_localItemPath", file.getPath()); - } - } + public void onEvent(long id, String type, JSONObject data) throws JSONException { + switch (type) { + case "ItemFinished": + File updatedFile = new File(data.getString("folderpath"), data.getString("item")); + Log.i(TAG, "Notified media scanner about " + updatedFile.toString()); + mContext.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, + Uri.fromFile(updatedFile))); + break; + case "Ping": + // Ignored. + break; + default: + Log.i(TAG, "Unhandled event " + type); } - - Intent broadcastIntent = - new Intent(EVENT_BASE_ACTION + "." + eventType.toUpperCase(Locale.US)); - broadcastIntent.putExtras(eventData); - mLocalBM.sendBroadcast(broadcastIntent); - - Log.d(TAG, "Sent local event broadcast " + broadcastIntent.getAction() + - " including " + eventType.length() + " extra data items."); } - /** - * @see RestApi.OnReceiveEventListener - */ @Override public void onDone(long id) { if (mLastEventId < id) { @@ -162,15 +116,10 @@ public class EventProcessor implements SyncthingService.OnWebGuiAvailableListene } } - /** - * @see SyncthingService.OnWebGuiAvailableListener - */ @Override public void onWebGuiAvailable() { Log.d(TAG, "WebGUI available. Starting event processor."); - updateFolderMap(); - // Remove all pending callbacks and add a new one. This makes sure that only one // event poller is running at any given time. synchronized (mMainThreadHandler) { diff --git a/src/main/java/com/nutomic/syncthingandroid/syncthing/RestApi.java b/src/main/java/com/nutomic/syncthingandroid/syncthing/RestApi.java index aa1c9b91..86affe67 100644 --- a/src/main/java/com/nutomic/syncthingandroid/syncthing/RestApi.java +++ b/src/main/java/com/nutomic/syncthingandroid/syncthing/RestApi.java @@ -7,7 +7,6 @@ import android.content.ClipboardManager; import android.content.Context; import android.content.Intent; import android.os.AsyncTask; -import android.os.Bundle; import android.text.TextUtils; import android.util.Log; import android.widget.Toast; @@ -25,9 +24,7 @@ import java.io.Serializable; import java.text.DecimalFormat; import java.util.ArrayList; import java.util.HashMap; -import java.util.Iterator; import java.util.List; -import java.util.Locale; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; @@ -640,11 +637,14 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener, public interface OnReceiveEventListener { /** * Called for each event. + * + * Events with a "folder" field in the data have an extra "folderpath" element added. + * * @param id ID of the event. Monotonously increasing. * @param eventType Name of the event. (See Syncthing documentation) - * @param eventData Bundle containing the data fields of the event as data elements. + * @param data Contains the data fields of the event. */ - void onEvent(final long id, final String eventType, final Bundle eventData); + void onEvent(long id, String eventType, JSONObject data) throws JSONException; /** * Called after all available events have been processed. @@ -711,11 +711,11 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener, /** * Retrieves the events that have accumulated since the given event id. + * * The OnReceiveEventListeners onEvent method is called for each event. */ public final void getEvents(final long sinceId, final long limit, final OnReceiveEventListener listener) { - - GetTask eventGetTask = new GetTask(mHttpsCertPath) { + new GetTask(mHttpsCertPath) { @Override protected void onPostExecute(String s) { if (s == null) @@ -723,56 +723,26 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener, try { JSONArray jsonEvents = new JSONArray(s); - long lastId = 0; for (int i = 0; i < jsonEvents.length(); i++) { + JSONObject json = jsonEvents.getJSONObject(i); + String type = json.getString("type"); + long id = json.getLong("id"); - final JSONObject json = jsonEvents.getJSONObject(i); - final String eventType = json.getString("type").toLowerCase(Locale.US); - final long id = json.getLong("id"); + if (lastId < id) + lastId = id; - Bundle dataBundle = null; + JSONObject data = json.optJSONObject("data"); - if (lastId < id) lastId = id; - - switch (eventType) { - // This special shortcut can be used if data only contains strings. - // It just copies everything into a bundle. - case "itemfinished": - case "foldercompletion": - case "deviceconnected": - case "devicediscovered": - case "statechanged": - dataBundle = new Bundle(); - JSONObject data = json.getJSONObject("data"); - - for (Iterator keyIterator = data.keys(); keyIterator.hasNext();) { - String key = keyIterator.next(); - dataBundle.putString(key, data.getString(key)); - } - - // If the event contains a folder keyword but no path synthesise - // a path keyword to ease processing of the event. - String folder = data.optString("folder", null); - if ((folder != null) && (data.optString("path", null) == null)) { - String folderPath = getPathForFolder(folder); - if (folderPath != null) { - dataBundle.putString("path", folderPath); - } - } - - break; - - // Ignored events. - case "ping": - break; - - default: - Log.d(TAG, "Unhandled event " + json.getString("type")); + // Add folder path to data. + if (data != null && data.has("folder")) { + String folder = data.getString("folder"); + String folderPath = getPathForFolder(folder); + data.put("folderpath", folderPath); } - if (dataBundle != null) listener.onEvent(id, eventType, dataBundle); + listener.onEvent(id, type, data); } listener.onDone(lastId); @@ -781,9 +751,8 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener, Log.w(TAG, "Failed to read events", e); } } - }; - - eventGetTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, mUrl, GetTask.URI_EVENTS, mApiKey, "since", String.valueOf(sinceId), "limit", String.valueOf(limit)); + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, mUrl, GetTask.URI_EVENTS, mApiKey, + "since", String.valueOf(sinceId), "limit", String.valueOf(limit)); } /** diff --git a/src/main/java/com/nutomic/syncthingandroid/syncthing/SyncthingService.java b/src/main/java/com/nutomic/syncthingandroid/syncthing/SyncthingService.java index 9205bb1c..0b7fa3cf 100644 --- a/src/main/java/com/nutomic/syncthingandroid/syncthing/SyncthingService.java +++ b/src/main/java/com/nutomic/syncthingandroid/syncthing/SyncthingService.java @@ -5,20 +5,17 @@ import android.app.AlertDialog; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; -import android.content.BroadcastReceiver; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; -import android.net.Uri; import android.os.AsyncTask; import android.os.Build; import android.os.Environment; import android.os.IBinder; import android.preference.PreferenceManager; import android.support.v4.app.NotificationCompat; -import android.support.v4.content.LocalBroadcastManager; import android.util.Log; import android.util.Pair; import android.widget.Toast; @@ -110,22 +107,6 @@ public class SyncthingService extends Service implements private final SyncthingServiceBinder mBinder = new SyncthingServiceBinder(this); - /** - * Processes the local broadcast message if an item was finished. - * Launches the media scanner to update the media library. - */ - private final BroadcastReceiver mItemFinishedBroadcastReceiver = new BroadcastReceiver() { - - @Override - public void onReceive(Context context, Intent intent) { - final File updatedFile = new File(intent.getStringExtra("_localItemPath")); - - Log.d(TAG, "Notified media scanner about " + updatedFile.toString()); - - context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(updatedFile))); - } - }; - /** * Callback for when the Syncthing web interface becomes first available after service start. */ @@ -357,7 +338,6 @@ public class SyncthingService extends Service implements registerReceiver(mDeviceStateHolder, new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); new StartupTask(sp.getString("gui_user",""), sp.getString("gui_password","")).execute(); sp.registerOnSharedPreferenceChangeListener(this); - LocalBroadcastManager.getInstance(this).registerReceiver(this.mItemFinishedBroadcastReceiver, new IntentFilter(EventProcessor.getEventIntentAction("itemfinished"))); } /** @@ -446,7 +426,6 @@ public class SyncthingService extends Service implements shutdown(); SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this); sp.unregisterOnSharedPreferenceChangeListener(this); - LocalBroadcastManager.getInstance(this).unregisterReceiver(mItemFinishedBroadcastReceiver); } private void shutdown() {