From 7576e2a47db57bb6a3880c77ea327c0775b926be Mon Sep 17 00:00:00 2001 From: Felix Ableitner Date: Thu, 15 May 2014 14:54:21 +0200 Subject: [PATCH] Improved polling for web gui (fixes #7). --- .../syncthingandroid/WebGuiActivity.java | 49 +++++----- .../service/SyncthingService.java | 89 +++++++++++++++++++ 2 files changed, 109 insertions(+), 29 deletions(-) diff --git a/src/main/java/com/nutomic/syncthingandroid/WebGuiActivity.java b/src/main/java/com/nutomic/syncthingandroid/WebGuiActivity.java index 1982fe90..001f65b3 100644 --- a/src/main/java/com/nutomic/syncthingandroid/WebGuiActivity.java +++ b/src/main/java/com/nutomic/syncthingandroid/WebGuiActivity.java @@ -8,7 +8,6 @@ import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.os.Bundle; -import android.os.Handler; import android.os.IBinder; import android.util.Log; import android.view.Menu; @@ -30,7 +29,7 @@ import java.io.InputStream; /** * Holds a WebView that shows the web ui of the local syncthing instance. */ -public class WebGuiActivity extends Activity { +public class WebGuiActivity extends Activity implements SyncthingService.OnWebGuiAvailableListener { private static final String TAG = "WebGuiActivity"; @@ -54,6 +53,7 @@ public class WebGuiActivity extends Activity { public void onServiceConnected(ComponentName className, IBinder service) { SyncthingServiceBinder binder = (SyncthingServiceBinder) service; mSyncthingService = binder.getService(); + mSyncthingService.registerOnWebGuiAvailableListener(WebGuiActivity.this); } public void onServiceDisconnected(ComponentName className) { @@ -62,32 +62,14 @@ public class WebGuiActivity extends Activity { }; /** - * Retries loading every second until the web UI becomes available. + * Hides the loading screen and shows the WebView once it is fully loaded. */ private WebViewClient mWebViewClient = new WebViewClient() { - private int mError = 0; - - @Override - public void onReceivedError(WebView view, int errorCode, String description, - String failingUrl) { - mError = errorCode; - new Handler().postDelayed(new Runnable() { - @Override - public void run() { - mError = 0; - mWebView.loadUrl(SyncthingService.SYNCTHING_URL); - } - }, 1000); - - } - @Override public void onPageFinished(WebView view, String url) { - if (mError == 0) { - mWebView.setVisibility(View.VISIBLE); - mLoadingView.setVisibility(View.GONE); - } + mWebView.setVisibility(View.VISIBLE); + mLoadingView.setVisibility(View.GONE); } }; @@ -100,11 +82,6 @@ public class WebGuiActivity extends Activity { @SuppressLint("SetJavaScriptEnabled") public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - getApplicationContext().startService( - new Intent(this, SyncthingService.class)); - getApplicationContext().bindService( - new Intent(this, SyncthingService.class), - mSyncthingServiceConnection, Context.BIND_AUTO_CREATE); setContentView(R.layout.main); @@ -115,7 +92,6 @@ public class WebGuiActivity extends Activity { mWebView = (WebView) findViewById(R.id.webview); mWebView.getSettings().setJavaScriptEnabled(true); mWebView.setWebViewClient(mWebViewClient); - mWebView.loadUrl(SyncthingService.SYNCTHING_URL); // Handle first start. File config = new File(getApplicationInfo().dataDir, CONFIG_FILE); @@ -130,8 +106,23 @@ public class WebGuiActivity extends Activity { .setNeutralButton(android.R.string.ok, null) .show(); } + + getApplicationContext().startService( + new Intent(this, SyncthingService.class)); + getApplicationContext().bindService( + new Intent(this, SyncthingService.class), + mSyncthingServiceConnection, Context.BIND_AUTO_CREATE); } + /** + * Loads and shows WebView, hides loading view. + */ + @Override + public void onWebGuiAvailable() { + mWebView.loadUrl(SyncthingService.SYNCTHING_URL); + } + + @Override public void onDestroy() { super.onDestroy(); diff --git a/src/main/java/com/nutomic/syncthingandroid/service/SyncthingService.java b/src/main/java/com/nutomic/syncthingandroid/service/SyncthingService.java index 52255a31..cba6caf6 100644 --- a/src/main/java/com/nutomic/syncthingandroid/service/SyncthingService.java +++ b/src/main/java/com/nutomic/syncthingandroid/service/SyncthingService.java @@ -4,6 +4,7 @@ import android.app.Notification; import android.app.PendingIntent; import android.app.Service; import android.content.Intent; +import android.os.AsyncTask; import android.os.IBinder; import android.support.v4.app.NotificationCompat; import android.util.Log; @@ -11,10 +12,21 @@ import android.util.Log; import com.nutomic.syncthingandroid.R; import com.nutomic.syncthingandroid.WebGuiActivity; +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.HttpStatus; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpHead; +import org.apache.http.conn.HttpHostConnectException; +import org.apache.http.impl.client.DefaultHttpClient; + import java.io.BufferedReader; import java.io.DataOutputStream; import java.io.IOException; +import java.io.InputStream; import java.io.InputStreamReader; +import java.util.LinkedList; /** * Holds the native syncthing instance and provides an API to access it. @@ -35,8 +47,26 @@ public class SyncthingService extends Service { */ public static final String SYNCTHING_URL = "http://127.0.0.1:8080"; + /** + * Interval in ms, at which connections to the web gui are performed on first start + * to find out if it's online. + */ + private static final long WEB_GUI_POLL_INTERVAL = 100; + private final SyncthingServiceBinder mBinder = new SyncthingServiceBinder(this); + /** + * Callback for when the Syncthing web interface becomes first available after service start. + */ + public interface OnWebGuiAvailableListener { + public void onWebGuiAvailable(); + } + + private LinkedList mOnWebGuiAvailableListeners = + new LinkedList(); + + private boolean mIsWebGuiAvailable = false; + @Override public int onStartCommand(Intent intent, int flags, int startId) { return START_STICKY; @@ -100,6 +130,48 @@ public class SyncthingService extends Service { } } } + + /** + * Polls SYNCTHING_URL until it returns HTTP status OK, then calls all listeners + * in mOnWebGuiAvailableListeners and clears it. + */ + private class PollWebGuiAvailableTask extends AsyncTask { + @Override + protected Void doInBackground(Void... voids) { + int status = 0; + HttpClient httpclient = new DefaultHttpClient(); + HttpHead head = new HttpHead(SYNCTHING_URL); + do { + try { + Thread.sleep(WEB_GUI_POLL_INTERVAL); + HttpResponse response = httpclient.execute(head); + // NOTE: status is not really needed, as HttpHostConnectException is thrown + // earlier. + status = response.getStatusLine().getStatusCode(); + } + catch (HttpHostConnectException e) { + // We catch this in every call, as long as the service is not online, + // so we ignore and continue. + } + catch (IOException e) { + Log.d(TAG, "Failed to poll for web interface", e); + } + catch (InterruptedException e) { + Log.d(TAG, "Failed to poll for web interface", e); + } + } while(status != HttpStatus.SC_OK); + return null; + } + + @Override + protected void onPostExecute(Void aVoid) { + mIsWebGuiAvailable = true; + for (OnWebGuiAvailableListener listener : mOnWebGuiAvailableListeners) { + listener.onWebGuiAvailable(); + } + mOnWebGuiAvailableListeners.clear(); + } + } /** * Creates notification, starts native binary. @@ -118,6 +190,8 @@ public class SyncthingService extends Service { startForeground(NOTIFICATION_ID, n); new Thread(new NativeSyncthingRunnable()).start(); + + new PollWebGuiAvailableTask().execute(); } @Override @@ -134,4 +208,19 @@ public class SyncthingService extends Service { new PostTask().execute(PostTask.URI_SHUTDOWN); } + /** + * Register a listener for the web gui becoming available.. + * + * If the web gui is already available, listener will be called immediately. + * Listeners are unregistered automatically after being called. + */ + public void registerOnWebGuiAvailableListener(OnWebGuiAvailableListener listener) { + if (mIsWebGuiAvailable) { + listener.onWebGuiAvailable(); + } + else { + mOnWebGuiAvailableListeners.addLast(listener); + } + } + }