Skip to content

Commit

Permalink
Refactored share logic to show a notification when an item is added t…
Browse files Browse the repository at this point in the history
…o the playlist (#473)

- added method to HostConnection that returns a Future object instead of
  taking a callback.
- added logic for handling null Handlers in HostConnection methods
- added method to HostManager to run a function that takes a HostConnection
  in a background thread where the Future API results above can be
  synchronously composed.
- replaced chain of callbacks in RemoteActivity with a sequence of
  future gets in OpenSharedUrl.
  • Loading branch information
monzee authored and SyncedSynapse committed Jan 26, 2018
1 parent 5f734bb commit 1e9160c
Show file tree
Hide file tree
Showing 5 changed files with 385 additions and 130 deletions.
54 changes: 45 additions & 9 deletions app/src/main/java/org/xbmc/kore/host/HostManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,32 +22,27 @@
import android.net.Uri;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.util.Base64;

import com.squareup.okhttp.Interceptor;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.Response;
import com.squareup.picasso.OkHttpDownloader;
import com.squareup.picasso.Picasso;

import org.xbmc.kore.BuildConfig;
import org.xbmc.kore.Settings;
import org.xbmc.kore.jsonrpc.ApiCallback;
import org.xbmc.kore.jsonrpc.HostConnection;
import org.xbmc.kore.jsonrpc.method.Application;
import org.xbmc.kore.jsonrpc.method.System;
import org.xbmc.kore.jsonrpc.type.ApplicationType;
import org.xbmc.kore.provider.MediaContract;
import org.xbmc.kore.utils.BasicAuthUrlConnectionDownloader;
import org.xbmc.kore.utils.LogUtils;
import org.xbmc.kore.utils.NetUtils;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

/**
* Manages XBMC Hosts
Expand All @@ -58,6 +53,21 @@
public class HostManager {
private static final String TAG = LogUtils.makeLogTag(HostManager.class);

/**
* A block of code that is run in the background thread and receives a
* reference to the current host.
*
* @see #withCurrentHost(Session)
*/
public interface Session<T> {
T using(HostConnection host) throws Exception;
}

/**
* Provides the thread where all sessions are run.
*/
private static final ExecutorService SESSIONS = Executors.newSingleThreadExecutor();

// Singleton instance
private static volatile HostManager instance = null;

Expand Down Expand Up @@ -111,6 +121,32 @@ public static HostManager getInstance(Context context) {
return instance;
}

/**
* Runs a session block.
* <p>
* This method provides a context for awaiting {@link org.xbmc.kore.jsonrpc.ApiFuture
* future} objects returned by callback-less remote method invocations. This
* enables a more natural style of doing a sequence of remote calls instead
* of nesting or chaining callbacks.
*
* @param session The function to run
* @param <T> The type of the value returned by the session
* @return a future wrapping the value returned (or exception thrown) by the
* session; null when there's no current host.
*/
public <T> Future<T> withCurrentHost(final Session<T> session) {
final HostConnection conn = getConnection();
if (conn != null) {
return SESSIONS.submit(new Callable<T>() {
@Override
public T call() throws Exception {
return session.using(conn);
}
});
}
return null;
}

/**
* Returns the current host list
* @return Host list
Expand Down
108 changes: 108 additions & 0 deletions app/src/main/java/org/xbmc/kore/jsonrpc/ApiFuture.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package org.xbmc.kore.jsonrpc;

import android.support.annotation.NonNull;

import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

/**
* A Java future wrapping the result of a Kodi remote method call.
* <p>
* Instantiable only through {@link HostConnection#execute(ApiMethod)}.
*
* @param <T> The type of the result of the remote method call.
*/
class ApiFuture<T> implements Future<T> {
private enum Status { WAITING, OK, ERROR, CANCELLED }
private final Object lock = new Object();
private Status status = Status.WAITING;
private T ok;
private Throwable error;

static <T> Future<T> from(HostConnection host, ApiMethod<T> method) {
final ApiFuture<T> future = new ApiFuture<>();
host.execute(method, new ApiCallback<T>() {
@Override
public void onSuccess(T result) {
synchronized (future.lock) {
future.ok = result;
future.status = Status.OK;
future.lock.notifyAll();
}
}

@Override
public void onError(int errorCode, String description) {
synchronized (future.lock) {
future.error = new ApiException(errorCode, description);
future.status = Status.ERROR;
future.lock.notifyAll();
}
}
}, null);
return future;
}

private ApiFuture() {}

@Override
public T get() throws InterruptedException, ExecutionException {
try {
return get(0, TimeUnit.MILLISECONDS);
} catch (TimeoutException e) {
throw new IllegalStateException("impossible");
}
}

@Override
public T get(long timeout, @NonNull TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException
{
boolean timed = timeout > 0;
long remaining = unit.toNanos(timeout);
while (true) synchronized (lock) {
switch (status) {
case OK: return ok;
case ERROR: throw new ExecutionException(error);
case CANCELLED: throw new CancellationException();
case WAITING:
if (timed && remaining <= 0) {
throw new TimeoutException();
}
if (!timed) {
lock.wait();
} else {
long start = System.nanoTime();
TimeUnit.NANOSECONDS.timedWait(lock, remaining);
remaining -= System.nanoTime() - start;
}
}
}
}

@Override
public boolean cancel(boolean b) {
if (status != Status.WAITING) {
return false;
}
synchronized (lock) {
status = Status.CANCELLED;
lock.notifyAll();
return true;
}
}

@Override
public boolean isCancelled() {
return status == Status.CANCELLED;
}

@Override
public boolean isDone() {
return status != Status.WAITING;
}

}
Loading

0 comments on commit 1e9160c

Please sign in to comment.