package com.jbirdvegas.mgerrit.tasks;
/*
* Copyright (C) 2013 Android Open Kang Project (AOKP)
* Author: Evan Conway (P4R4N01D), 2013
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import android.content.Context;
import android.content.Intent;
import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.Volley;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.jbirdvegas.mgerrit.message.ErrorDuringConnection;
import com.jbirdvegas.mgerrit.message.Finished;
import com.jbirdvegas.mgerrit.message.StartingRequest;
import com.jbirdvegas.mgerrit.objects.GerritURL;
import de.greenrobot.event.EventBus;
/**
* Base class that the GerritService expects as a contract to synchronise data from the
* server into the database. Comes with a decent dose of Generics
* @param <T> The intermediary class where the JSON data is deserialized
*/
abstract class SyncProcessor<T> {
protected final Context mContext;
private final EventBus mEventBus;
private GerritURL mCurrentUrl;
private ResponseHandler mResponseHandler;
private final Intent mIntent;
private static Gson gson;
static {
GsonBuilder gsonBuilder = new GsonBuilder();
Deserializers.addDeserializers(gsonBuilder);
gson = gsonBuilder.create();
}
/**
* Alternate constructor where the url is not dynamic and can be determined from the
* current Gerrit instance.
*
* Note: subclasses using this constructor MUST override fetchData or setUrl
* @param context Context for network access
* @param intent The original intent to GerritService that started initiated
* this SyncProcessor.
*/
SyncProcessor(Context context, Intent intent) {
this(context, intent, null);
}
/**
* Standard constructor to create a SyncProcessor
* @param context Context for network access
* @param intent The original intent to GerritService that started initiated
* this SyncProcessor.
* @param url The Gerrit URL from which to retrieve the data from
*/
SyncProcessor(Context context, Intent intent, GerritURL url) {
this.mContext = context;
this.mCurrentUrl = url;
this.mIntent = intent;
this.mEventBus = EventBus.getDefault();
}
protected Context getContext() { return mContext; }
protected GerritURL getUrl() { return mCurrentUrl; }
protected void setUrl(GerritURL url) { mCurrentUrl = url; }
// Helper method to extract the relevant query portion of the URL
protected String getQuery() {
return getUrl().getQuery();
}
// Helper method to return the change status
private String getStatus() {
if (mCurrentUrl == null) return null;
else return mCurrentUrl.getStatus();
}
public Intent getIntent() { return mIntent; }
/**
* Inserts data into the database
* @param data A collection of the deserialized data ready for insertion
*/
abstract int insert(T data);
/**
* @return Whether it is necessary to contact the server
*/
abstract boolean isSyncRequired(Context context);
/**
* @return T.class (the class of T). This is used for Volley Gson requests
*/
abstract Class<T> getType();
/**
* Do some additional work after the data has been processed.
* @param data The data that was just processed passed here for convenience
*/
void doPostProcess(T data) {
// Default to doing nothing - subclasses can override this
}
/**
* @param data A collection of the deserialized data
* @return The number of items contained in data
*/
abstract int count(T data);
/**
* Sends a request to the Gerrit server for the url set in the constructor
* SyncProcessor(Context, GerritURL). The default simply calls
* fetchData(String, RequestQueue).
*/
protected void fetchData(RequestQueue queue) {
fetchData(mCurrentUrl.toString(), queue);
}
/**
* Send a request to the Gerrit server. Expects the response to be in
* Json format.
* @param url The URL of the request
* @param queue An instance of the Volley request queue.
*/
protected void fetchData(final String url, RequestQueue queue) {
GsonRequest request = new GsonRequest<>(url, gson, getType(), 5,
getListener(url), new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError volleyError) {
mEventBus.post(new ErrorDuringConnection(mIntent, url, getStatus(), volleyError));
}
});
fetchData(url, request, queue);
}
protected void fetchData(final String url, Request<T> request, RequestQueue queue) {
if (queue == null) queue = Volley.newRequestQueue(getContext());
mEventBus.post(new StartingRequest(mIntent, url, getStatus()));
queue.add(request);
}
protected boolean isInSyncInterval(long syncInterval, long lastSync) {
if (lastSync == 0) return false; // Always sync if this is the first time
long timeNow = System.currentTimeMillis();
return ((timeNow - lastSync) < syncInterval);
}
public void cancelOperation() {
if (mResponseHandler == null) return;
mResponseHandler.interrupt();
mResponseHandler = null;
}
protected Response.Listener<T> getListener(final String url) {
return new Response.Listener<T>() {
@Override
public void onResponse(T data) {
/* Offload all the work to a separate thread so database activity is not
* done on the main thread. */
mResponseHandler = new ResponseHandler(data, url);
mResponseHandler.start();
}
};
}
class ResponseHandler extends Thread {
private final T mData;
private final String mUrl;
ResponseHandler(T data, String url) {
this.mData = data;
this.mUrl = url;
}
@Override
public void run() {
int numItems = 0;
// Order is important here, as we need to insert the data first
if (mData != null && count(mData) > 0) {
numItems = insert(mData);
}
EventBus.getDefault().post(new Finished(mIntent, mUrl, getStatus(), numItems));
if (mData != null) doPostProcess(mData);
GerritService.finishedRequest(mCurrentUrl);
// This thread has finished so the parent activity should no longer need it
mResponseHandler = null;
}
}
}