package au.com.newint.newinternationalist;
import android.os.AsyncTask;
import android.util.Log;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.output.NullOutputStream;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.concurrent.RejectedExecutionException;
/**
* Created by pix on 27/02/15.
*/
public abstract class CacheStreamFactory {
private final String name;
protected final CacheStreamFactory fallback;
//public PreloadTask preloadTask;
final HashMap<String,PreloadTask> preloadTasks;
CacheStreamFactory(CacheStreamFactory fallback, String name) {
Helpers.debugLog("CacheStreamFactory", "creating factory of type " + name + ", fallback is " + ((fallback != null) ? "not" : "") + " null");
//preloadTask = new PreloadTask();
this.preloadTasks = new HashMap<>();
this.fallback = fallback;
this.name = name;
}
public String toString() {
return "CacheStreamFactory[]";
}
interface CachePreloadCallback {
void onLoad(byte[] payload);
void onLoadBackground(byte [] payload);
}
class PreloadParameters {
Object lock;
CachePreloadCallback callback;
String startingAt;
String stoppingAt;
PreloadParameters(Object lock, CachePreloadCallback callback, String startingAt, String stoppingAt) {
this.lock = lock;
this.callback = callback;
this.stoppingAt = stoppingAt;
this.startingAt = startingAt;
}
}
class PreloadReturn {
CachePreloadCallback callback;
byte[] payload;
PreloadReturn(CachePreloadCallback callback, byte[] payload) {
this.callback = callback;
this.payload = payload;
}
}
class PreloadTask extends AsyncTask<PreloadParameters,Integer,PreloadReturn> {
final ArrayList<CachePreloadCallback> callbacks;
boolean callbacksProcessed;
PreloadTask() {
super();
callbacksProcessed = false;
callbacks = new ArrayList<>();
}
@Override
protected PreloadReturn doInBackground(PreloadParameters... params) {
//CachePreloadCallback callback = null;
String startingAt = null;
String stoppingAt = null;
Object lock;
if (params.length > 0) {
lock = params[0].lock;
} else {
Log.e("CacheStreamFactory","params.length <= 0");
return new PreloadReturn(null,null);
}
synchronized (lock) {
//callback = params[0].callback;
startingAt = params[0].startingAt;
stoppingAt = params[0].stoppingAt;
InputStream inputStream = createInputStream(startingAt, stoppingAt);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
if (inputStream == null) {
Log.e("CacheStreamFactory","createInputStream returned null");
return new PreloadReturn(null,null);
}
try {
IOUtils.copy(inputStream, byteArrayOutputStream);
inputStream.close();
} catch (IOException e) {
//e.printStackTrace();
}
byte[] payload = byteArrayOutputStream.toByteArray();
synchronized(CacheStreamFactory.this.preloadTasks) {
// remove ourselves from the list before executing callbacks
CacheStreamFactory.this.preloadTasks.remove(this);
}
for(CachePreloadCallback callback : this.callbacks) {
callback.onLoadBackground(payload);
}
return new PreloadReturn(null,payload);
}
}
@Override
protected void onPostExecute(PreloadReturn params) {
super.onPostExecute(params);
Helpers.debugLog("CacheStreamFactory", CacheStreamFactory.this+"->preload()->onPostExecute("+((params==null)?"null":"not-null")+")");
synchronized(CacheStreamFactory.this.preloadTasks) {
// remove ourselves from the list before executing callbacks
CacheStreamFactory.this.preloadTasks.remove(this);
}
//CachePreloadCallback callback = params.callback;
// on network failure params.payload will be null
byte[] payload = params.payload;
//Helpers.debugLog("CacheStreamFactory", CacheStreamFactory.this+"->preload()->onPostExecute("+((callback==null)?"null":"not-null")+")");
//if (callback != null) {
for(CachePreloadCallback callback : this.callbacks) {
callback.onLoad(payload);
}
//}
}
}
void cancel(boolean mayInterruptIfRunning) {
Helpers.debugLog("CacheStreamFactory", "cancelling callbacks");
synchronized (preloadTasks) {
for(PreloadTask preloadTask : preloadTasks.values()) {
preloadTask.callbacks.clear();
}
}
}
void preload(CachePreloadCallback callback) {
preload(null, null, callback);
}
void preload(String startingAt, String stoppingAt, CachePreloadCallback callback) {
Helpers.debugLog("CacheStreamFactory", this+"->preload(...,"+startingAt+","+stoppingAt+")");
//do we already have a preload task matching these start/stop parameters?
synchronized (preloadTasks) {
PreloadTask preloadTask = preloadTasks.get(startingAt + ":" + stoppingAt);
if (preloadTask != null) {
// yes we do
// assume callbacks have not been called yet as it is still in the map
preloadTask.callbacks.add(callback);
} else {
// no we don't
// create new preload task
preloadTask = new PreloadTask();
// and add the callback
preloadTask.callbacks.add(callback);
//preloadTask.execute(new PreloadParameters(this,callback,startingAt,stoppingAt));
// not sure how much of this is necessary now that we are handling multiple callbacks (listeners)
try {
preloadTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, new PreloadParameters(this, null, startingAt, stoppingAt));
} catch (RejectedExecutionException e) {
// FIXME: Fix multiple blocked threads from filling the pool
Log.e("CacheStreamFactory", "Too many threads... sobbing quietly and then ignoring your ridiculous request.");
}
}
}
}
InputStream createInputStream() {
return createInputStream(null,null);
}
InputStream createInputStream(String startingAt, String stoppingAt) {
Helpers.debugLog("CacheStreamFactory",this+"->createInputStream("+startingAt+","+stoppingAt+")");
synchronized (this) {
Helpers.debugLog("CacheStreamFactory","unblocked!");
if (stoppingAt != null && stoppingAt.equals(name)) {
Log.e("CacheStreamFactory", "stoppingAt hit, returning null");
return null;
}
if (startingAt == null || startingAt.equals(name)) {
InputStream cis = createCacheInputStream();
if (cis != null) {
Helpers.debugLog("CacheStreamFactory", this + ": cis!=null");
return cis;
} else {
return wrappedFallbackStream(null, stoppingAt);
}
}
return wrappedFallbackStream(startingAt, stoppingAt);
}
}
// try separating the cache stream generation from the public input stream generation
protected abstract InputStream createCacheInputStream();
protected abstract OutputStream createCacheOutputStream();
protected abstract void invalidateCache();
public void invalidate() {
invalidateCache();
if (fallback!=null) {
fallback.invalidate();
}
}
private InputStream wrappedFallbackStream(String startingAt, String stoppingAt) {
Helpers.debugLog("CacheStreamFactory", this+"->wrappedFallbackStream("+startingAt+", "+stoppingAt+")");
if (fallback==null) {
return null;
}
//final InputStream fallbackInputStream = new BufferedInputStream(fallback.createInputStream(startingAt, stoppingAt));
//final OutputStream cacheOutputStream = new BufferedOutputStream(createCacheOutputStream());
final InputStream fallbackInputStream = new BufferedInputStream(fallback.createInputStream(startingAt, stoppingAt));
final OutputStream cacheOutputStream = new BufferedOutputStream(createCacheOutputStream());
InputStream writeThroughStream = new InputStream() {
@Override
public int read() throws IOException {
//Helpers.debugLog("writeThroughStream","read() called");
int b = -1;
try {
b = fallbackInputStream.read();
if(b>=0) {
cacheOutputStream.write(b);
//this breaks buffering but is sometimes used for testing
//cacheOutputStream.flush();
} else {
Log.e("writeThroughStream","fallbackInputStream.read() got "+b);
}
}
catch (IOException e) {
Log.e("writeThroughStream", "a wrapped stream got an exception");
e.printStackTrace();
}
return b;
}
@Override
public long skip(long n) throws IOException {
long skipped;
Helpers.debugLog("writeThroughStream","skip("+n+") called");
for(skipped = 0;skipped < n; skipped++) {
try {
read();
}
catch (IOException e) {
Log.e("writeThroughStream", "skip: exception during read");
e.printStackTrace();
}
}
return skipped;
}
@Override
public void close() throws IOException {
fallbackInputStream.close();
cacheOutputStream.close();
}
};
return writeThroughStream;
}
byte[] read() {
return read(null,null);
}
// convenience method to mimick old ByteCache
byte[] read(String startingAt, String stoppingAt) {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
try {
InputStream inputStream = this.createInputStream(startingAt, stoppingAt);
long c = IOUtils.copy(inputStream, byteArrayOutputStream);
inputStream.close();
Helpers.debugLog("CacheStreamFactory", this+": IOUtils.copy processed "+c+" bytes");
return byteArrayOutputStream.toByteArray();
} catch (IOException e) {
Log.e("CacheStreamFactory", this+": IOException while reading stream to byte array");
}
return null;
}
}