1
0
Fork 0
mirror of https://github.com/syncthing/syncthing-android.git synced 2024-11-27 06:41:15 +00:00

Simplified event handling code.

This commit is contained in:
Felix Ableitner 2016-01-23 17:55:04 +01:00
parent 3d09d43bd4
commit c656c45081
3 changed files with 48 additions and 151 deletions

View file

@ -3,22 +3,22 @@ package com.nutomic.syncthingandroid.syncthing;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.os.Bundle; import android.net.Uri;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.support.v4.content.LocalBroadcastManager;
import android.util.Log; import android.util.Log;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.File; import java.io.File;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
/** /**
* Run by the syncthing service to convert syncthing events into local broadcasts. * 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, public class EventProcessor implements SyncthingService.OnWebGuiAvailableListener, Runnable,
RestApi.OnReceiveEventListener { RestApi.OnReceiveEventListener {
@ -26,16 +26,16 @@ public class EventProcessor implements SyncthingService.OnWebGuiAvailableListene
private static final String TAG = "EventProcessor"; private static final String TAG = "EventProcessor";
private static final String PREF_LAST_SYNC_ID = "last_sync_id"; 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. * 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. * This intervall will not wake up the device to save battery power.
*/ */
public static final long EVENT_UPDATE_INTERVAL = TimeUnit.SECONDS.toMillis(15); 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 final Handler mMainThreadHandler = new Handler(Looper.getMainLooper());
private volatile long mLastEventId = 0; private volatile long mLastEventId = 0;
@ -43,43 +43,12 @@ public class EventProcessor implements SyncthingService.OnWebGuiAvailableListene
private final Context mContext; private final Context mContext;
private final RestApi mApi; private final RestApi mApi;
private final LocalBroadcastManager mLocalBM;
private final Map<String, String> 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) { public EventProcessor(Context context, RestApi api) {
mContext = context; mContext = context;
mApi = api; 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 @Override
public void run() { public void run() {
// Restore the last event id if the event processor may have been restartet. // 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. // 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() { mApi.getEvents(0, 1, new RestApi.OnReceiveEventListener() {
@Override @Override
public void onEvent(long id, String eventType, Bundle eventData) { public void onEvent(long id, String eventType, JSONObject data) throws JSONException {
} }
@Override @Override
@ -107,41 +77,25 @@ public class EventProcessor implements SyncthingService.OnWebGuiAvailableListene
} }
/** /**
* @see RestApi.OnReceiveEventListener * Performs the actual event handling.
*/ */
@Override @Override
public void onEvent(final long id, final String eventType, final Bundle eventData) { public void onEvent(long id, String type, JSONObject data) throws JSONException {
// If a folder item is contained within the event. Resolve the local path. switch (type) {
if (eventData.containsKey("folder")) { case "ItemFinished":
String folderPath = null; File updatedFile = new File(data.getString("folderpath"), data.getString("item"));
synchronized (mFolderToPath) { Log.i(TAG, "Notified media scanner about " + updatedFile.toString());
if (mFolderToPath.size() == 0) updateFolderMap(); mContext.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE,
folderPath = mFolderToPath.get(eventData.getString("folder")); Uri.fromFile(updatedFile)));
} break;
case "Ping":
if (folderPath != null) { // Ignored.
eventData.putString("_localFolderPath",folderPath); break;
default:
if (eventData.containsKey("item")) { Log.i(TAG, "Unhandled event " + type);
final File file = new File(new File(folderPath), eventData.getString("item"));
eventData.putString("_localItemPath", file.getPath());
}
}
} }
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 @Override
public void onDone(long id) { public void onDone(long id) {
if (mLastEventId < id) { if (mLastEventId < id) {
@ -162,15 +116,10 @@ public class EventProcessor implements SyncthingService.OnWebGuiAvailableListene
} }
} }
/**
* @see SyncthingService.OnWebGuiAvailableListener
*/
@Override @Override
public void onWebGuiAvailable() { public void onWebGuiAvailable() {
Log.d(TAG, "WebGUI available. Starting event processor."); Log.d(TAG, "WebGUI available. Starting event processor.");
updateFolderMap();
// Remove all pending callbacks and add a new one. This makes sure that only one // Remove all pending callbacks and add a new one. This makes sure that only one
// event poller is running at any given time. // event poller is running at any given time.
synchronized (mMainThreadHandler) { synchronized (mMainThreadHandler) {

View file

@ -7,7 +7,6 @@ import android.content.ClipboardManager;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Bundle;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import android.widget.Toast; import android.widget.Toast;
@ -25,9 +24,7 @@ import java.io.Serializable;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
@ -640,11 +637,14 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener,
public interface OnReceiveEventListener { public interface OnReceiveEventListener {
/** /**
* Called for each event. * 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 id ID of the event. Monotonously increasing.
* @param eventType Name of the event. (See Syncthing documentation) * @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. * 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. * Retrieves the events that have accumulated since the given event id.
*
* The OnReceiveEventListeners onEvent method is called for each event. * The OnReceiveEventListeners onEvent method is called for each event.
*/ */
public final void getEvents(final long sinceId, final long limit, final OnReceiveEventListener listener) { public final void getEvents(final long sinceId, final long limit, final OnReceiveEventListener listener) {
new GetTask(mHttpsCertPath) {
GetTask eventGetTask = new GetTask(mHttpsCertPath) {
@Override @Override
protected void onPostExecute(String s) { protected void onPostExecute(String s) {
if (s == null) if (s == null)
@ -723,56 +723,26 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener,
try { try {
JSONArray jsonEvents = new JSONArray(s); JSONArray jsonEvents = new JSONArray(s);
long lastId = 0; long lastId = 0;
for (int i = 0; i < jsonEvents.length(); i++) { 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); if (lastId < id)
final String eventType = json.getString("type").toLowerCase(Locale.US); lastId = id;
final long id = json.getLong("id");
Bundle dataBundle = null; JSONObject data = json.optJSONObject("data");
if (lastId < id) lastId = id; // Add folder path to data.
if (data != null && data.has("folder")) {
switch (eventType) { String folder = data.getString("folder");
// This special shortcut can be used if data only contains strings. String folderPath = getPathForFolder(folder);
// It just copies everything into a bundle. data.put("folderpath", folderPath);
case "itemfinished":
case "foldercompletion":
case "deviceconnected":
case "devicediscovered":
case "statechanged":
dataBundle = new Bundle();
JSONObject data = json.getJSONObject("data");
for (Iterator<String> 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"));
} }
if (dataBundle != null) listener.onEvent(id, eventType, dataBundle); listener.onEvent(id, type, data);
} }
listener.onDone(lastId); listener.onDone(lastId);
@ -781,9 +751,8 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener,
Log.w(TAG, "Failed to read events", e); Log.w(TAG, "Failed to read events", e);
} }
} }
}; }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, mUrl, GetTask.URI_EVENTS, mApiKey,
"since", String.valueOf(sinceId), "limit", String.valueOf(limit));
eventGetTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, mUrl, GetTask.URI_EVENTS, mApiKey, "since", String.valueOf(sinceId), "limit", String.valueOf(limit));
} }
/** /**

View file

@ -5,20 +5,17 @@ import android.app.AlertDialog;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.app.Service; import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.net.Uri;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Build; import android.os.Build;
import android.os.Environment; import android.os.Environment;
import android.os.IBinder; import android.os.IBinder;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.support.v4.app.NotificationCompat; import android.support.v4.app.NotificationCompat;
import android.support.v4.content.LocalBroadcastManager;
import android.util.Log; import android.util.Log;
import android.util.Pair; import android.util.Pair;
import android.widget.Toast; import android.widget.Toast;
@ -110,22 +107,6 @@ public class SyncthingService extends Service implements
private final SyncthingServiceBinder mBinder = new SyncthingServiceBinder(this); 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. * 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)); registerReceiver(mDeviceStateHolder, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
new StartupTask(sp.getString("gui_user",""), sp.getString("gui_password","")).execute(); new StartupTask(sp.getString("gui_user",""), sp.getString("gui_password","")).execute();
sp.registerOnSharedPreferenceChangeListener(this); 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(); shutdown();
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this); SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
sp.unregisterOnSharedPreferenceChangeListener(this); sp.unregisterOnSharedPreferenceChangeListener(this);
LocalBroadcastManager.getInstance(this).unregisterReceiver(mItemFinishedBroadcastReceiver);
} }
private void shutdown() { private void shutdown() {