package com.oreilly.demo.android.pa.finchvideo.provider;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.provider.BaseColumns;
import android.util.Log;
import com.finchframework.finch.Finch;
import com.finchframework.finch.rest.RESTfulContentProvider;
import com.finchframework.finch.rest.ResponseHandler;
import com.oreilly.demo.android.pa.finchvideo.FinchVideoDemo;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
/**
* Parses YouTube entity data and and inserts it into the finch video content
* provider.
*/
public class YouTubeHandler implements ResponseHandler {
public static final String MEDIA = "media";
public static final String GROUP = "group";
public static final String DESCRIPTION = "description";
public static final String THUMBNAIL = "thumbnail";
public static final String TITLE = "title";
public static final String CONTENT = "content";
public static final String WIDTH = "width";
public static final String HEIGHT = "height";
public static final String YT = "yt";
public static final String DURATION = "duration";
public static final String FORMAT = "format";
public static final String URI = "uri";
public static final String THUMB_URI = "thumb_uri";
public static final String MOBILE_FORMAT = "1";
public static final String ENTRY = "entry";
public static final String ID = "id";
private static final String FLUSH_TIME = "5 minutes";
private RESTfulContentProvider mFinchVideoProvider;
private String mQueryText;
private boolean isEntry;
public YouTubeHandler(RESTfulContentProvider restfulProvider,
String queryText)
{
mFinchVideoProvider = restfulProvider;
mQueryText = queryText;
}
/*
* Handles the response from the YouTube gdata server, which is in the form
* of an RSS feed containing references to YouTube videos.
*/
@Override
public void handleResponse(HttpResponse response, Uri uri) {
try {
int newCount = parseYoutubeEntity(response.getEntity());
// only flush old state now that new state has arrived
if (newCount > 0) {
deleteOld();
}
} catch (IOException e) {
// use the exception to avoid clearing old state, if we can not
// get new state. This way we leave the application with some
// data to work with in absence of network connectivity.
// we could retry the request for data in the hope that the network
// might return.
}
}
private void deleteOld() {
// delete any old elements, not just ones that match the current query.
Cursor old = null;
try {
SQLiteDatabase db = mFinchVideoProvider.getDatabase();
old = db.query(FinchVideo.Videos.VIDEO, null,
"video." + FinchVideo.Videos.TIMESTAMP +
" < strftime('%s', 'now', '-" + FLUSH_TIME + "')",
null, null, null, null);
int c = old.getCount();
if (old.getCount() > 0) {
StringBuilder sb = new StringBuilder();
boolean next;
if (old.moveToNext()) {
do {
String ID = old.getString(FinchVideo.ID_COLUMN);
sb.append(BaseColumns._ID);
sb.append(" = ");
sb.append(ID);
// get rid of associated cached thumb files
mFinchVideoProvider.deleteFile(ID);
next = old.moveToNext();
if (next) {
sb.append(" OR ");
}
} while (next);
}
String where = sb.toString();
db.delete(FinchVideo.Videos.VIDEO, where, null);
Log.d(Finch.LOG_TAG, "flushed old query results: " + c);
}
} finally {
if (old != null) {
old.close();
}
}
}
private int parseYoutubeEntity(HttpEntity entity) throws IOException {
InputStream youTubeContent = entity.getContent();
InputStreamReader inputReader = new InputStreamReader(youTubeContent);
int inserted = 0;
try {
XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
factory.setNamespaceAware(false);
XmlPullParser xpp = factory.newPullParser();
xpp.setInput(inputReader);
int eventType = xpp.getEventType();
String startName = null;
ContentValues mediaEntry = null;
// iterative pull parsing is a useful way to extract data from
// streams, since we dont have to hold the DOM model in memory
// during the parsing step.
while (eventType != XmlPullParser.END_DOCUMENT) {
if (eventType == XmlPullParser.START_DOCUMENT) {
} else if (eventType == XmlPullParser.END_DOCUMENT) {
} else if (eventType == XmlPullParser.START_TAG) {
startName = xpp.getName();
if ((startName != null)) {
if ((ENTRY).equals(startName)) {
mediaEntry = new ContentValues();
mediaEntry.put(FinchVideo.Videos.QUERY_TEXT_NAME,
mQueryText);
}
if ((MEDIA + ":" + CONTENT).equals(startName)) {
int c = xpp.getAttributeCount();
String mediaUri = null;
boolean isMobileFormat = false;
for (int i = 0; i < c; i++) {
String attrName = xpp.getAttributeName(i);
String attrValue = xpp.getAttributeValue(i);
if ((attrName != null) &&
URI.equals(attrName))
{
mediaUri = attrValue;
}
if ((attrName != null) && (YT + ":" + FORMAT).
equals(MOBILE_FORMAT))
{
isMobileFormat = true;
}
}
if (isMobileFormat && (mediaUri != null)) {
mediaEntry.put(URI, mediaUri);
}
}
if ((MEDIA + ":" + THUMBNAIL).equals(startName)) {
int c = xpp.getAttributeCount();
for (int i = 0; i < c; i++) {
String attrName = xpp.getAttributeName(i);
String attrValue = xpp.getAttributeValue(i);
if (attrName != null) {
if ("url".equals(attrName)) {
mediaEntry.put(
FinchVideo.Videos.
THUMB_URI_NAME,
attrValue);
} else if (WIDTH.equals(attrName))
{
mediaEntry.put(
FinchVideo.Videos.
THUMB_WIDTH_NAME,
attrValue);
} else if (HEIGHT.equals(attrName))
{
mediaEntry.put(
FinchVideo.Videos.
THUMB_HEIGHT_NAME,
attrValue);
}
}
}
}
if (ENTRY.equals(startName)) {
isEntry = true;
}
}
} else if(eventType == XmlPullParser.END_TAG) {
String endName = xpp.getName();
if (endName != null) {
if (ENTRY.equals(endName)) {
isEntry = false;
} else if (endName.equals(MEDIA + ":" + GROUP)) {
// insert the complete media group
inserted++;
// Directly invoke insert on the finch video
// provider, without using content resolver. We
// would not want the content provider to sync this
// data back to itself.
SQLiteDatabase db =
mFinchVideoProvider.getDatabase();
String mediaID = (String) mediaEntry.get(
FinchVideo.Videos.MEDIA_ID_NAME);
// insert thumb uri
String thumbContentUri =
FinchVideo.Videos.THUMB_URI + "/" + mediaID;
mediaEntry.put(FinchVideo.Videos.
THUMB_CONTENT_URI_NAME,
thumbContentUri);
String cacheFileName =
mFinchVideoProvider.getCacheName(mediaID);
mediaEntry.put(FinchVideo.Videos._DATA,
cacheFileName);
Uri providerUri = mFinchVideoProvider.
insert(FinchVideo.Videos.CONTENT_URI,
mediaEntry, db);
if (providerUri != null) {
String thumbUri = (String) mediaEntry.
get(FinchVideo.Videos.THUMB_URI_NAME);
// We might consider lazily downloading the
// image so that it was only downloaded on
// viewing. Downloading more aggressively,
// could also improve performance.
mFinchVideoProvider.
cacheUri2File(String.valueOf(mediaID),
thumbUri);
}
}
}
} else if (eventType == XmlPullParser.TEXT) {
// newline can turn into an extra text event
String text = xpp.getText();
if (text != null) {
text = text.trim();
if ((startName != null) && (!"".equals(text))){
if (ID.equals(startName) && isEntry) {
int lastSlash = text.lastIndexOf("/");
String entryId =
text.substring(lastSlash + 1);
mediaEntry.put(FinchVideo.Videos.MEDIA_ID_NAME,
entryId);
} else if ((MEDIA + ":" + TITLE).
equals(startName)) {
mediaEntry.put(TITLE, text);
} else if ((MEDIA + ":" +
DESCRIPTION).equals(startName))
{
mediaEntry.put(DESCRIPTION, text);
}
}
}
}
eventType = xpp.next();
}
// an alternate notification scheme, might be to notify only after
// all entries have been inserted.
} catch (XmlPullParserException e) {
Log.d(FinchVideoDemo.LOG_TAG,
"could not parse video feed", e);
} catch (IOException e) {
Log.d(FinchVideoDemo.LOG_TAG,
"could not process video stream", e);
}
return inserted;
}
}