Browse Source

Loading and parsing of RSS feeds and showing loading status in the UI.

pull/11/head
Eric Kok 11 years ago
parent
commit
23021d81c7
  1. 2
      core/res/layout-w600dp/activity_torrents.xml
  2. 26
      core/res/layout-w900dp/activity_rssfeeds.xml
  3. 2
      core/res/layout-w900dp/activity_torrents.xml
  4. 1
      core/res/layout/activity_rssfeeds.xml
  5. 1
      core/res/layout/activity_rssitems.xml
  6. 149
      core/src/org/ifies/android/sax/Channel.java
  7. 146
      core/src/org/ifies/android/sax/Item.java
  8. 237
      core/src/org/ifies/android/sax/RssParser.java
  9. 88
      core/src/org/transdroid/core/app/settings/ApplicationSettings.java
  10. 33
      core/src/org/transdroid/core/app/settings/RssfeedSetting.java
  11. 6
      core/src/org/transdroid/core/gui/TorrentsActivity.java
  12. 20
      core/src/org/transdroid/core/gui/navigation/NavigationHelper.java
  13. 60
      core/src/org/transdroid/core/gui/rss/RssfeedLoader.java
  14. 31
      core/src/org/transdroid/core/gui/rss/RssfeedView.java
  15. 74
      core/src/org/transdroid/core/gui/rss/RssfeedsActivity.java
  16. 23
      core/src/org/transdroid/core/gui/rss/RssfeedsAdapter.java
  17. 36
      core/src/org/transdroid/core/gui/rss/RssfeedsFragment.java
  18. 8
      core/src/org/transdroid/core/gui/rss/RssitemView.java
  19. 26
      core/src/org/transdroid/core/gui/rss/RssitemsActivity.java
  20. 19
      core/src/org/transdroid/core/gui/rss/RssitemsAdapter.java
  21. 23
      core/src/org/transdroid/core/gui/rss/RssitemsFragment.java
  22. 153
      core/src/org/transdroid/core/rssparser/Channel.java
  23. 2
      core/src/org/transdroid/core/rssparser/HttpHelper.java
  24. 157
      core/src/org/transdroid/core/rssparser/Item.java
  25. 235
      core/src/org/transdroid/core/rssparser/RssParser.java

2
core/res/layout-w600dp/activity_torrents.xml

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- This layout is for phones in landscape and 7" tablets in portrait and shows torrents and filters. -->
<!-- This layout is for 7" and 10" tablets in portrait and shows torrents and filters. -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"

26
core/res/layout-w900dp/activity_rssfeeds.xml

@ -0,0 +1,26 @@ @@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:baselineAligned="false"
tools:context=".RssfeedsActivity" >
<fragment
android:id="@+id/rssfeeds_list"
android:layout_width="0dip"
android:layout_height="match_parent"
android:layout_weight="1"
class="org.transdroid.core.gui.rss.RssFeedsFragment_"
tools:layout="@layout/fragment_rssfeeds" />
<fragment
android:id="@+id/rssitems_list"
android:layout_width="0dip"
android:layout_height="match_parent"
android:layout_weight="1"
class="org.transdroid.core.gui.rss.RssItemsFragment_"
tools:layout="@layout/fragment_rssitems" />
</LinearLayout>

2
core/res/layout-w900dp/activity_torrents.xml

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- This layout is for 7" tablets in landscape and 10" tablets and shows torrents, filters and details. -->
<!-- This layout is for 7" and 10" tablets in landscape shows torrents, filters and details. -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"

1
core/res/layout/activity_rssfeeds.xml

@ -1,5 +1,4 @@ @@ -1,5 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- This layout is for phones in portrait and shows only the torrents list. -->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"

1
core/res/layout/activity_rssitems.xml

@ -1,5 +1,4 @@ @@ -1,5 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- This layout is for phones in portrait and shows only the torrents list. -->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"

149
core/src/org/ifies/android/sax/Channel.java

@ -1,149 +0,0 @@ @@ -1,149 +0,0 @@
/*
* Taken from the 'Learning Android' project,;
* released as Public Domain software at
* http://github.com/digitalspaghetti/learning-android
*/
package org.ifies.android.sax;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import android.os.Parcel;
import android.os.Parcelable;
public class Channel implements Parcelable {
public Channel() {
setCategories(new ArrayList<String>());
setItems(new ArrayList<Item>());
}
public void setId(int id) {
m_Id = id;
}
public int getId() {
return m_Id;
}
public void setTitle(String title) {
m_Title = title;
}
public String getTitle() {
return m_Title;
}
public void setLink(String link) {
m_Link = link;
}
public String getLink() {
return m_Link;
}
public void setDescription(String description) {
m_Description = description;
}
public String getDescription() {
return m_Description;
}
public void setPubDate(Date date) {
m_PubDate = date;
}
public Date getPubDate() {
return m_PubDate;
}
public void setLastBuildDate(long lastBuildDate) {
m_LastBuildDate = lastBuildDate;
}
public long getLastBuildDate() {
return m_LastBuildDate;
}
public void setCategories(List<String> categories) {
m_Categories = categories;
}
public void addCategory(String category) {
m_Categories.add(category);
}
public List<String> getCategories() {
return m_Categories;
}
public void setItems(List<Item> items) {
m_Items = items;
}
public void addItem(Item item) {
m_Items.add(item);
}
public List<Item> getItems() {
return m_Items;
}
public void setImage(String image) {
m_Image = image;
}
public String getImage() {
return m_Image;
}
private int m_Id;
private String m_Title;
private String m_Link;
private String m_Description;
private Date m_PubDate;
private long m_LastBuildDate;
private List<String> m_Categories;
private List<Item> m_Items;
private String m_Image;
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeInt(m_Id);
out.writeString(m_Title);
out.writeString(m_Link);
out.writeString(m_Description);
out.writeLong(m_PubDate == null? -1: m_PubDate.getTime());
out.writeLong(m_LastBuildDate);
out.writeStringList(m_Categories);
out.writeTypedList(m_Items);
out.writeString(m_Image);
}
public static final Parcelable.Creator<Channel> CREATOR = new Parcelable.Creator<Channel>() {
public Channel createFromParcel(Parcel in) {
return new Channel(in);
}
public Channel[] newArray(int size) {
return new Channel[size];
}
};
private Channel(Parcel in) {
m_Id = in.readInt();
m_Title = in.readString();
m_Link = in.readString();
m_Description = in.readString();
long pubDate = in.readLong();
m_PubDate = pubDate == -1? null: new Date(pubDate);
m_LastBuildDate = in.readLong();
m_Categories = new ArrayList<String>();
in.readTypedList(m_Items, Item.CREATOR);
in.readStringList(m_Categories);
m_Image = in.readString();
}
}

146
core/src/org/ifies/android/sax/Item.java

@ -1,146 +0,0 @@ @@ -1,146 +0,0 @@
/*
* Taken from the 'Learning Android' project,;
* released as Public Domain software at
* http://github.com/digitalspaghetti/learning-android
*/
package org.ifies.android.sax;
import java.util.Date;
import android.os.Parcel;
import android.os.Parcelable;
public class Item implements Comparable<Item>, Parcelable {
public void setId(int id) {
this._id = id;
}
public int getId() {
return _id;
}
public void setTitle(String title) {
this.title = title;
}
public String getTitle() {
return this.title;
}
public void setDescription(String description) {
this.description = description;
}
public String getDescription() {
return this.description;
}
public void setLink(String link) {
this.link = link;
}
public String getLink() {
return this.link;
}
public void setPubdate(Date pubdate) {
this.pubDate = pubdate;
}
public Date getPubdate() {
return this.pubDate;
}
public void setEnclosureUrl(String enclosureUrl) {
this.enclosureUrl = enclosureUrl;
}
public void setEnclosureLength(long enclosureLength) {
this.enclosureLength = enclosureLength;
}
public void setEnclosureType(String enclosureType) {
this.enclosureType = enclosureType;
}
public String getEnclosureUrl() {
return this.enclosureUrl;
}
public String getEnclosureType() {
return this.enclosureType;
}
public long getEnclosureLength() {
return this.enclosureLength;
}
private int _id;
private String title;
private String link;
private String description;
private Date pubDate;
private String enclosureUrl;
private String enclosureType;
private long enclosureLength;
/**
* Returns 'the' item link, which preferably is the enclosure url, but otherwise the link (or null if that is empty too)
* @return A single link url to be used
*/
public String getTheLink() {
if (this.getEnclosureUrl() != null) {
return this.getEnclosureUrl();
} else {
return this.getLink();
}
}
/**
* CompareTo is used to compare (and sort) item based on their publication dates
*/
@Override
public int compareTo(Item another) {
if (another == null || this.pubDate == null || another.getPubdate() == null) {
return 0;
}
return this.pubDate.compareTo(another.getPubdate());
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeInt(_id);
out.writeString(title);
out.writeString(link);
out.writeString(description);
out.writeLong(pubDate == null? -1: pubDate.getTime());
out.writeString(enclosureUrl);
out.writeString(enclosureType);
out.writeLong(enclosureLength);
}
public static final Parcelable.Creator<Item> CREATOR = new Parcelable.Creator<Item>() {
public Item createFromParcel(Parcel in) {
return new Item(in);
}
public Item[] newArray(int size) {
return new Item[size];
}
};
private Item(Parcel in) {
_id = in.readInt();
title = in.readString();
link = in.readString();
description = in.readString();
long pubDateIn = in.readLong();
pubDate = pubDateIn == -1? null: new Date(pubDateIn);
enclosureUrl = in.readString();
enclosureType = in.readString();
enclosureLength = in.readLong();
}
}

237
core/src/org/ifies/android/sax/RssParser.java

@ -1,237 +0,0 @@ @@ -1,237 +0,0 @@
/*
* Taken from the 'Learning Android' project,;
* released as Public Domain software at
* http://github.com/digitalspaghetti/learning-android
* and modified heavily for Transdroid
*/
package org.ifies.android.sax;
import java.io.IOException;
import java.util.Date;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
public class RssParser extends DefaultHandler
{
/**
* The constructor for the RSS Parser
* @param url
*/
public RssParser(String url) {
this.urlString = url;
this.text = new StringBuilder();
}
/**
* Returns the feed as a RssFeed, which is a ListArray
* @return RssFeed rssFeed
*/
public Channel getChannel() {
return (this.channel);
}
public void parse() throws ParserConfigurationException, SAXException, IOException {
DefaultHttpClient httpclient = initialise();
HttpResponse result = httpclient.execute(new HttpGet(urlString));
//FileInputStream urlInputStream = new FileInputStream("/sdcard/rsstest2.txt");
SAXParserFactory spf = SAXParserFactory.newInstance();
if (spf != null) {
SAXParser sp = spf.newSAXParser();
sp.parse(result.getEntity().getContent(), this);
}
}
/**
* Instantiates an HTTP client that can be used for all requests.
* @param connectionTimeout The connection timeout in milliseconds
* @throws DaemonException On conflicting or missing settings
*/
protected DefaultHttpClient initialise() {
SchemeRegistry registry = new SchemeRegistry();
registry.register(new Scheme("http", new PlainSocketFactory(), 80));
HttpParams httpparams = new BasicHttpParams();
HttpConnectionParams.setConnectionTimeout(httpparams, 5000);
HttpConnectionParams.setSoTimeout(httpparams, 5000);
DefaultHttpClient httpclient = new DefaultHttpClient(new ThreadSafeClientConnManager(httpparams, registry), httpparams);
httpclient.addRequestInterceptor(HttpHelper.gzipRequestInterceptor);
httpclient.addResponseInterceptor(HttpHelper.gzipResponseInterceptor);
return httpclient;
}
/**
* By default creates a standard Item (with title, description and links), which
* may to overriden to add more data.
* @return A possibly decorated Item instance
*/
protected Item createNewItem() {
return new Item();
}
public void startElement(String uri, String localName, String qName, Attributes attributes) {
/** First lets check for the channel */
if (localName.equalsIgnoreCase("channel")) {
this.channel = new Channel();
}
/** Now lets check for an item */
if (localName.equalsIgnoreCase("item") && (this.channel != null)) {
this.item = createNewItem();
this.channel.addItem(this.item);
}
/** Now lets check for an image */
if (localName.equalsIgnoreCase("image") && (this.channel != null)) {
this.imgStatus = true;
}
/** Checking for a enclosure */
if (localName.equalsIgnoreCase("enclosure")) {
/** Lets check we are in an item */
if (this.item != null && attributes != null && attributes.getLength() > 0) {
if (attributes.getValue("url") != null) {
this.item.setEnclosureUrl(parseLink(attributes.getValue("url")));
}
if (attributes.getValue("type") != null) {
this.item.setEnclosureType(attributes.getValue("type"));
}
if (attributes.getValue("length") != null) {
this.item.setEnclosureLength(Long.parseLong(attributes.getValue("length")));
}
}
}
}
/**
* This is where we actually parse for the elements contents
*/
@SuppressWarnings("deprecation")
public void endElement(String uri, String localName, String qName) {
/** Check we have an RSS Feed */
if (this.channel == null) {
return;
}
/** Check are at the end of an item */
if (localName.equalsIgnoreCase("item")) {
this.item = null;
}
/** Check we are at the end of an image */
if (localName.equalsIgnoreCase("image"))
this.imgStatus = false;
/** Now we need to parse which title we are in */
if (localName.equalsIgnoreCase("title"))
{
/** We are an item, so we set the item title */
if (this.item != null){
this.item.setTitle(this.text.toString().trim());
/** We are in an image */
} else {
this.channel.setTitle(this.text.toString().trim());
}
}
/** Now we are checking for a link */
if (localName.equalsIgnoreCase("link")) {
/** Check we are in an item **/
if (this.item != null) {
this.item.setLink(parseLink(this.text.toString()));
/** Check we are in an image */
} else if (this.imgStatus) {
this.channel.setImage(parseLink(this.text.toString()));
/** Check we are in a channel */
} else {
this.channel.setLink(parseLink(this.text.toString()));
}
}
/** Checking for a description */
if (localName.equalsIgnoreCase("description")) {
/** Lets check we are in an item */
if (this.item != null) {
this.item.setDescription(this.text.toString().trim());
/** Lets check we are in the channel */
} else {
this.channel.setDescription(this.text.toString().trim());
}
}
/** Checking for a pubdate */
if (localName.equalsIgnoreCase("pubDate")) {
/** Lets check we are in an item */
if (this.item != null) {
try {
this.item.setPubdate(new Date(Date.parse(this.text.toString().trim())));
} catch (Exception e) {
// Date is malformed (not parsable by Date.parse)
}
/** Lets check we are in the channel */
} else {
try {
this.channel.setPubDate(new Date(Date.parse(this.text.toString().trim())));
} catch (Exception e) {
// Date is malformed (not parsable by Date.parse)
}
}
}
/** Check for the category */
if (localName.equalsIgnoreCase("category") && (this.item != null)) {
this.channel.addCategory(this.text.toString().trim());
}
addAdditionalData(localName, this.item, this.text.toString());
this.text.setLength(0);
}
/**
* May be overridden to add additional data from tags that are not standard in RSS.
* Not used by this default RSS style parser.
* @param localName The tag name
* @param item The Item we are currently parsing
* @param text The new text content
*/
protected void addAdditionalData(String localName, Item item, String text) { }
public void characters(char[] ch, int start, int length) {
this.text.append(ch, start, length);
}
private String parseLink(String string) {
return string.trim();
}
private String urlString;
private Channel channel;
private StringBuilder text;
private Item item;
private boolean imgStatus;
}

88
core/src/org/transdroid/core/app/settings/ApplicationSettings.java

@ -1,6 +1,7 @@ @@ -1,6 +1,7 @@
package org.transdroid.core.app.settings;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.androidannotations.annotations.EBean;
@ -64,40 +65,30 @@ public class ApplicationSettings { @@ -64,40 +65,30 @@ public class ApplicationSettings {
Daemon type = Daemon.fromCode(prefs.getString("server_type_" + order, null));
boolean ssl = prefs.getBoolean("server_sslenabled_" + order, false);
String defaultPort = Integer.toString(Daemon.getDefaultPortNumber(type, ssl));
return new ServerSetting(order,
prefs.getString("server_name_" + order, null),
type,
prefs.getString("server_address_" + order, null),
prefs.getString("server_localaddress_" + order, null),
prefs.getString("server_localnetwork_" + order, null),
Integer.parseInt(prefs.getString("server_port_" + order, defaultPort)),
ssl,
prefs.getBoolean("server_ssltrustall_" + order, false),
prefs.getString("server_ssltrustkey_" + order, null),
prefs.getString("server_folder_" + order, null),
prefs.getBoolean("server_useauth_" + order, true),
prefs.getString("server_user_" + order, null),
prefs.getString("server_pass_" + order, null),
prefs.getString("server_extrapass_" + order, null),
OS.fromCode(prefs.getString("server_os_" + order, "type_linux")),
prefs.getString("server_downloaddir_" + order, null),
prefs.getString("server_ftpurl_" + order, null),
prefs.getString("server_ftppass_" + order, null),
Integer.parseInt(prefs.getString("server_timeout_"+ order, "8")),
prefs.getBoolean("server_alarmfinished_" + order, true),
prefs.getBoolean("server_alarmnew_" + order, false), false);
return new ServerSetting(order, prefs.getString("server_name_" + order, null), type, prefs.getString(
"server_address_" + order, null), prefs.getString("server_localaddress_" + order, null),
prefs.getString("server_localnetwork_" + order, null), Integer.parseInt(prefs.getString("server_port_"
+ order, defaultPort)), ssl, prefs.getBoolean("server_ssltrustall_" + order, false),
prefs.getString("server_ssltrustkey_" + order, null), prefs.getString("server_folder_" + order, null),
prefs.getBoolean("server_useauth_" + order, true), prefs.getString("server_user_" + order, null),
prefs.getString("server_pass_" + order, null), prefs.getString("server_extrapass_" + order, null),
OS.fromCode(prefs.getString("server_os_" + order, "type_linux")), prefs.getString("server_downloaddir_"
+ order, null), prefs.getString("server_ftpurl_" + order, null), prefs.getString(
"server_ftppass_" + order, null), Integer.parseInt(prefs.getString("server_timeout_" + order,
"8")), prefs.getBoolean("server_alarmfinished_" + order, true), prefs.getBoolean(
"server_alarmnew_" + order, false), false);
// @formatter:on
}
/**
* Removes all settings related to a configured server. Since servers are ordered, the order of the remaining
* Removes all settings related to a configured server. Since servers are ordered, the order of the remaining
* servers will be updated accordingly.
* @param order The identifying order number/key of the settings to remove
*/
public void removeServerSettings(int order) {
if (prefs.getString("server_type_" + order, null) == null)
return; // The settings that were requested to be removed do not exist
// Copy all settings higher than the supplied order number to the previous spot
Editor edit = prefs.edit();
int max = getMaxServer();
@ -148,7 +139,7 @@ public class ApplicationSettings { @@ -148,7 +139,7 @@ public class ApplicationSettings {
edit.remove("server_alarmfinished_" + max);
edit.remove("server_alarmfinished_" + max);
edit.commit();
}
/**
@ -228,9 +219,8 @@ public class ApplicationSettings { @@ -228,9 +219,8 @@ public class ApplicationSettings {
*/
public WebsearchSetting getWebsearchSetting(int order) {
// @formatter:off
return new WebsearchSetting(order,
prefs.getString("websearch_name_" + order, null),
prefs.getString("websearch_baseurl_" + order, null));
return new WebsearchSetting(order, prefs.getString("websearch_name_" + order, null), prefs.getString(
"websearch_baseurl_" + order, null));
// @formatter:on
}
@ -242,7 +232,7 @@ public class ApplicationSettings { @@ -242,7 +232,7 @@ public class ApplicationSettings {
public void removeWebsearchSettings(int order) {
if (prefs.getString("websearch_baseurl_" + order, null) == null)
return; // The settings that were requested to be removed do not exist
// Copy all settings higher than the supplied order number to the previous spot
Editor edit = prefs.edit();
int max = getMaxWebsearch();
@ -255,7 +245,7 @@ public class ApplicationSettings { @@ -255,7 +245,7 @@ public class ApplicationSettings {
edit.remove("websearch_name_" + max);
edit.remove("websearch_baseurl_" + max);
edit.commit();
}
/**
@ -288,11 +278,10 @@ public class ApplicationSettings { @@ -288,11 +278,10 @@ public class ApplicationSettings {
*/
public RssfeedSetting getRssfeedSetting(int order) {
// @formatter:off
return new RssfeedSetting(order,
prefs.getString("rssfeed_name_" + order, null),
prefs.getString("rssfeed_url_" + order, null),
prefs.getBoolean("rssfeed_reqauth_" + order, false),
prefs.getString("rssfeed_lastnew_" + order, null));
long lastViewed = prefs.getLong("rssfeed_lastviewed_" + order, -1);
return new RssfeedSetting(order, prefs.getString("rssfeed_name_" + order, null), prefs.getString("rssfeed_url_"
+ order, null), prefs.getBoolean("rssfeed_reqauth_" + order, false), lastViewed == -1L ? null
: new Date(lastViewed));
// @formatter:on
}
@ -304,7 +293,7 @@ public class ApplicationSettings { @@ -304,7 +293,7 @@ public class ApplicationSettings {
public void removeRssfeedSettings(int order) {
if (prefs.getString("rssfeed_url_" + order, null) == null)
return; // The settings that were requested to be removed do not exist
// Copy all settings higher than the supplied order number to the previous spot
Editor edit = prefs.edit();
int max = getMaxRssfeed();
@ -312,16 +301,30 @@ public class ApplicationSettings { @@ -312,16 +301,30 @@ public class ApplicationSettings {
edit.putString("rssfeed_name_" + i, prefs.getString("rssfeed_name_" + (i + 1), null));
edit.putString("rssfeed_url_" + i, prefs.getString("rssfeed_url_" + (i + 1), null));
edit.putBoolean("rssfeed_reqauth_" + i, prefs.getBoolean("rssfeed_reqauth_" + (i + 1), false));
edit.putString("rssfeed_lastnew_" + i, prefs.getString("rssfeed_lastnew_" + (i + 1), null));
edit.putLong("rssfeed_lastviewed_" + i, prefs.getLong("rssfeed_lastviewed_" + (i + 1), -1));
}
// Remove the last settings, of which we are now sure are no longer required
edit.remove("rssfeed_name_" + max);
edit.remove("rssfeed_url_" + max);
edit.remove("rssfeed_reqauth_" + max);
edit.remove("rssfeed_lastnew_" + max);
edit.remove("rssfeed_lastviewed_" + max);
edit.commit();
}
/**
* Registers for some RSS feed (as identified by its order numbe/key) the last date and time that it was viewed by
* the user. This is used to determine which items in an RSS feed are 'new'. Warning: any previously retrieved
* {@link RssfeedSetting} object is now no longer in sync, as this will not automatically be updated in the object.
* Use {@link #getRssfeedSetting(int)} to get fresh data.
* @param order The identifying order number/key of the settings of te RSS feed that was viewed
* @param lastViewed The date and time that the feed was last viewed; typically now
*/
public void setRssfeedLastViewer(int order, Date lastViewed) {
if (prefs.getString("rssfeed_url_" + order, null) == null)
return; // The settings that were requested to be removed do not exist
prefs.edit().putLong("rssfeed_lastviewed_" + order, lastViewed.getTime()).commit();
}
/**
@ -335,12 +338,13 @@ public class ApplicationSettings { @@ -335,12 +338,13 @@ public class ApplicationSettings {
}
/**
* Returns the sort order property that the user last used. Use together with {@link #getLastUsedSortDescending()} to
* get the full last used sort settings.
* Returns the sort order property that the user last used. Use together with {@link #getLastUsedSortDescending()}
* to get the full last used sort settings.
* @return The last used sort order enumeration value
*/
public TorrentsSortBy getLastUsedSortOrder() {
return TorrentsSortBy.getStatus(prefs.getInt("system_lastusedsortorder", TorrentsSortBy.Alphanumeric.getCode()));
return TorrentsSortBy
.getStatus(prefs.getInt("system_lastusedsortorder", TorrentsSortBy.Alphanumeric.getCode()));
}
/**

33
core/src/org/transdroid/core/app/settings/RssfeedSetting.java

@ -1,5 +1,7 @@ @@ -1,5 +1,7 @@
package org.transdroid.core.app.settings;
import java.util.Date;
import org.transdroid.core.gui.lists.SimpleListItem;
import android.net.Uri;
@ -12,19 +14,19 @@ import android.text.TextUtils; @@ -12,19 +14,19 @@ import android.text.TextUtils;
public class RssfeedSetting implements SimpleListItem {
private static final String DEFAULT_NAME = "Default";
private final int order;
private final String name;
private final String url;
private final boolean requiresAuth;
private String lastNew;
private Date lastViewed;
public RssfeedSetting(int order, String name, String baseUrl, boolean needsAuth, String lastNew) {
public RssfeedSetting(int order, String name, String baseUrl, boolean needsAuth, Date lastViewed) {
this.order = order;
this.name = name;
this.url = baseUrl;
this.requiresAuth = needsAuth;
this.lastNew = lastNew;
this.lastViewed = lastViewed;
}
public int getOrder() {
@ -37,7 +39,7 @@ public class RssfeedSetting implements SimpleListItem { @@ -37,7 +39,7 @@ public class RssfeedSetting implements SimpleListItem {
return name;
if (!TextUtils.isEmpty(url)) {
String host = Uri.parse(url).getHost();
return host == null? DEFAULT_NAME: host;
return host == null ? DEFAULT_NAME : host;
}
return DEFAULT_NAME;
}
@ -51,20 +53,13 @@ public class RssfeedSetting implements SimpleListItem { @@ -51,20 +53,13 @@ public class RssfeedSetting implements SimpleListItem {
}
/**
* Returns the URL of the item that was the newest last time we checked this feed
* Returns the URL of the item that was the newest last time we checked this feed. Note that this is NOT updated
* automatically after the settings were loaded from {@link ApplicationSettings}; instead the settings have to be
* manually loaded again using {@link ApplicationSettings#getRssfeedSetting(int)}.
* @return The last new item's URL as URL-encoded string
*/
public String getLastNew() {
// TODO: Persist this into Preferences
return this.lastNew;
}
/**
* Record the URL of what is now the last item we retrieved
* @param lastNew The URL of the last new item as URL-encoded string
*/
public void setLastNew(String lastNew) {
this.lastNew = lastNew;
public Date getLastViewed() {
return this.lastViewed;
}
/**
@ -74,7 +69,7 @@ public class RssfeedSetting implements SimpleListItem { @@ -74,7 +69,7 @@ public class RssfeedSetting implements SimpleListItem {
public String getHumanReadableIdentifier() {
String host = Uri.parse(url).getHost();
String path = Uri.parse(url).getPath();
return (host == null? null: host + (path == null? "": path));
return (host == null ? null : host + (path == null ? "" : path));
}
}

6
core/src/org/transdroid/core/gui/TorrentsActivity.java

@ -25,6 +25,7 @@ import org.transdroid.core.gui.lists.LocalTorrent; @@ -25,6 +25,7 @@ import org.transdroid.core.gui.lists.LocalTorrent;
import org.transdroid.core.gui.lists.SimpleListItem;
import org.transdroid.core.gui.log.*;
import org.transdroid.core.gui.navigation.*;
import org.transdroid.core.gui.rss.RssfeedsActivity_;
import org.transdroid.core.gui.search.BarcodeHelper;
import org.transdroid.core.gui.search.FilePickerHelper;
import org.transdroid.core.gui.search.UrlEntryDialog;
@ -474,6 +475,11 @@ public class TorrentsActivity extends SherlockFragmentActivity implements OnNavi @@ -474,6 +475,11 @@ public class TorrentsActivity extends SherlockFragmentActivity implements OnNavi
updateTurtleMode(false);
}
@OptionsItem(resName = "action_rss")
protected void openRss() {
RssfeedsActivity_.intent(this).start();
}
@OptionsItem(resName = "action_settings")
protected void openSettings() {
MainSettingsActivity_.intent(this).start();

20
core/src/org/transdroid/core/gui/navigation/NavigationHelper.java

@ -54,14 +54,22 @@ public class NavigationHelper { @@ -54,14 +54,22 @@ public class NavigationHelper {
*/
public ImageLoader getImageCache() {
if (imageCache == null) {
// @formatter:off
imageCache = ImageLoader.getInstance();
Builder imageCacheBuilder = new Builder(context).defaultDisplayImageOptions(
new DisplayImageOptions.Builder().cacheInMemory().cacheOnDisc()
.imageScaleType(ImageScaleType.IN_SAMPLE_INT).build()).memoryCache(
new UsingFreqLimitedMemoryCache(1024 * 1024));
imageCacheBuilder.discCache(new FileCountLimitedDiscCache(context.getCacheDir(),
new Md5FileNameGenerator(), 25));
Builder imageCacheBuilder = new Builder(context)
.defaultDisplayImageOptions(
new DisplayImageOptions.Builder()
.cacheInMemory()
.cacheOnDisc()
.imageScaleType(ImageScaleType.IN_SAMPLE_INT)
.showImageForEmptyUri(R.drawable.ic_launcher)
.build())
.memoryCache(
new UsingFreqLimitedMemoryCache(1024 * 1024))
.discCache(
new FileCountLimitedDiscCache(context.getCacheDir(), new Md5FileNameGenerator(), 25));
imageCache.init(imageCacheBuilder.build());
// @formatter:on
}
return imageCache;
}

60
core/src/org/transdroid/core/gui/rss/RssfeedLoader.java

@ -0,0 +1,60 @@ @@ -0,0 +1,60 @@
package org.transdroid.core.gui.rss;
import org.transdroid.core.app.settings.RssfeedSetting;
import org.transdroid.core.rssparser.Channel;
import org.transdroid.core.rssparser.Item;
/**
* A container class that holds RSS feed settings and, after they have been retrieved, the contents as {@link Channel},
* the number of new items and an indication of a connection error.
* @author Eric Kok
*/
public class RssfeedLoader {
private final RssfeedSetting setting;
private Channel channel = null;
private int newCount = -1;
private boolean hasError = false;
public RssfeedLoader(RssfeedSetting setting) {
this.setting = setting;
}
public void update(Channel channel, boolean hasError) {
this.channel = channel;
this.hasError = hasError;
if (channel == null || hasError) {
hasError = true;
newCount = -1;
return;
}
// Count the number of new items, based on the date that this RSS feed was last viewed by the user
newCount = 0;
for (Item item : channel.getItems()) {
if (item.getPubdate() == null || setting.getLastViewed() == null
|| item.getPubdate().after(setting.getLastViewed())) {
newCount++;
item.setIsNew(true);
} else {
item.setIsNew(true);
}
}
}
public Channel getChannel() {
return channel;
}
public RssfeedSetting getSetting() {
return setting;
}
public int getNewCount() {
return newCount;
}
public boolean hasError() {
return hasError ;
}
}

31
core/src/org/transdroid/core/gui/rss/RssfeedView.java

@ -1,11 +1,11 @@ @@ -1,11 +1,11 @@
package org.transdroid.core.gui.rss;
import org.androidannotations.annotations.Background;
import org.androidannotations.annotations.Bean;
import org.androidannotations.annotations.EViewGroup;
import org.androidannotations.annotations.ViewById;
import org.transdroid.core.app.settings.RssfeedSetting;
import org.transdroid.core.gui.navigation.NavigationHelper;
import org.transdroid.core.rssparser.Channel;
import android.content.Context;
import android.view.View;
@ -39,25 +39,28 @@ public class RssfeedView extends LinearLayout { @@ -39,25 +39,28 @@ public class RssfeedView extends LinearLayout {
super(context);
}
public void bind(RssfeedSetting rssfeed) {
public void bind(RssfeedLoader rssfeedLoader) {
nameText.setText(rssfeed.getName());
faviconImage.setImageDrawable(null);
loadingProgress.setVisibility(View.VISIBLE);
newcountText.setVisibility(View.VISIBLE);
// Show the RSS feed name and either a loading indicator or the number of new items
nameText.setText(rssfeedLoader.getSetting().getName());
if (rssfeedLoader.hasError() || rssfeedLoader.getChannel() != null) {
loadingProgress.setVisibility(View.GONE);
newcountText.setVisibility(View.VISIBLE);
newcountText.setText(rssfeedLoader.hasError()? "?": Integer.toString(rssfeedLoader.getNewCount()));
} else {
loadingProgress.setVisibility(View.VISIBLE);
newcountText.setVisibility(View.GONE);
}
// Load the RSS feed site' favicon
// Clear and then asynchronously load the RSS feed site' favicon
// Uses the g.etfv.co service to resolve the favicon of any feed URL
navigationHelper.getImageCache().displayImage(String.format(GETFVO_URL, rssfeed), faviconImage);
// Refresh the number of new items in this feed
refreshNewCount();
faviconImage.setImageDrawable(null);
navigationHelper.getImageCache().displayImage(String.format(GETFVO_URL, rssfeedLoader), faviconImage);
}
@Background
protected void refreshNewCount() {
// TODO: Implement
public Channel getChannel() {
return null;
}
}

74
core/src/org/transdroid/core/gui/rss/RssfeedsActivity.java

@ -1,18 +1,31 @@ @@ -1,18 +1,31 @@
package org.transdroid.core.gui.rss;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.xml.parsers.ParserConfigurationException;
import org.androidannotations.annotations.Background;
import org.androidannotations.annotations.Bean;
import org.androidannotations.annotations.EActivity;
import org.androidannotations.annotations.FragmentById;
import org.androidannotations.annotations.UiThread;
import org.transdroid.core.app.settings.ApplicationSettings;
import org.transdroid.core.app.settings.RssfeedSetting;
import org.transdroid.core.rssparser.Channel;
import org.transdroid.core.rssparser.RssParser;
import org.xml.sax.SAXException;
import com.actionbarsherlock.app.SherlockFragmentActivity;
@EActivity(resName = "activity_rssfeeds")
public class RssfeedsActivity extends SherlockFragmentActivity {
// Settings
// Settings and local data
@Bean
protected ApplicationSettings applicationSettings;
protected List<RssfeedLoader> loaders;
// Contained feeds and items fragments
@FragmentById(resName = "rssfeeds_list")
@ -20,4 +33,63 @@ public class RssfeedsActivity extends SherlockFragmentActivity { @@ -20,4 +33,63 @@ public class RssfeedsActivity extends SherlockFragmentActivity {
@FragmentById(resName = "rssitems_list")
protected RssitemsFragment fragmentItems;
@Override
protected void onResume() {
super.onResume();
loaders = new ArrayList<RssfeedLoader>();
// For each RSS feed setting the user created, start a loader that retrieved the RSS feed (via a background
// thread) and, on success, determines the new items in the feed
for (RssfeedSetting setting : applicationSettings.getRssfeedSettings()) {
RssfeedLoader loader = new RssfeedLoader(setting);
loaders.add(loader);
loadRssfeed(loader);
}
fragmentFeeds.update(loaders);
}
/**
* Performs the loading of the RSS feed content and parsing of items, in a background thread.
* @param loader The RSS feed loader for which to retrieve the contents
*/
@Background
protected void loadRssfeed(RssfeedLoader loader) {
RssParser parser = new RssParser(loader.getSetting().getUrl());
try {
parser.parse();
handleRssfeedResult(loader, parser.getChannel(), true);
} catch (ParserConfigurationException e) {
handleRssfeedResult(loader, null, true);
} catch (SAXException e) {
handleRssfeedResult(loader, null, true);
} catch (IOException e) {
handleRssfeedResult(loader, null, true);
}
}
/**
* Stores the retrieved RSS feed content channel into the loader and updates the RSS feed in the feeds list
* fragment.
* @param loader The RSS feed loader that was executed
* @param channel The data that was retrieved, or null if it could not be parsed
* @param hasError True if a connection error occurred in the loading of the feed; false otherwise
*/
@UiThread
protected void handleRssfeedResult(RssfeedLoader loader, Channel channel, boolean hasError) {
loader.update(channel, hasError);
fragmentFeeds.notifyDataSetChanged();
}
/**
* Opens an RSS feed in the dedicated fragment (if there was space in the UI) or a new {@link RssitemsActivity}.
* @param loader The RSS feed loader (with settings and the loaded content channel) to show
*/
public void openRssfeed(RssfeedLoader loader) {
if (fragmentItems != null) {
fragmentItems.update(loader.getChannel());
} else {
RssitemsActivity_.intent(this).rssfeed(loader.getChannel()).start();
}
}
}

23
core/src/org/transdroid/core/gui/rss/RssfeedsAdapter.java

@ -5,6 +5,7 @@ import java.util.List; @@ -5,6 +5,7 @@ import java.util.List;
import org.androidannotations.annotations.EBean;
import org.androidannotations.annotations.RootContext;
import org.transdroid.core.app.settings.RssfeedSetting;
import org.transdroid.core.rssparser.Channel;
import android.content.Context;
import android.view.View;
@ -12,23 +13,23 @@ import android.view.ViewGroup; @@ -12,23 +13,23 @@ import android.view.ViewGroup;
import android.widget.BaseAdapter;
/**
* Adapter that contains a list of RSS feed settings.
* Adapter that contains a list of {@link RssfeedSetting}s, each with associated loaded RSS feed {@link Channel}.
* @author Eric Kok
*/
@EBean
public class RssfeedsAdapter extends BaseAdapter {
private List<RssfeedSetting> rssfeeds = null;
private List<RssfeedLoader> loaders = null;
@RootContext
protected Context context;
/**
* Allows updating the full internal list of feeds at once, replacing the old list
* @param newRssfeeds The new list of RSS feed settings objects
* Allows updating the full internal list of feed loaders at once, replacing the old list
* @param loaders The new list of RSS feed loader objects, which pair settings and a loaded channel
*/
public void update(List<RssfeedSetting> newRssfeeds) {
this.rssfeeds = newRssfeeds;
public void update(List<RssfeedLoader> loaders) {
this.loaders = loaders;
notifyDataSetChanged();
}
@ -39,16 +40,16 @@ public class RssfeedsAdapter extends BaseAdapter { @@ -39,16 +40,16 @@ public class RssfeedsAdapter extends BaseAdapter {
@Override
public int getCount() {
if (rssfeeds == null)
if (loaders == null)
return 0;
return rssfeeds.size();
return loaders.size();
}
@Override
public RssfeedSetting getItem(int position) {
if (rssfeeds == null)
public RssfeedLoader getItem(int position) {
if (loaders == null)
return null;
return rssfeeds.get(position);
return loaders.get(position);
}
@Override

36
core/src/org/transdroid/core/gui/rss/RssfeedsFragment.java

@ -1,12 +1,16 @@ @@ -1,12 +1,16 @@
package org.transdroid.core.gui.rss;
import java.util.List;
import org.androidannotations.annotations.AfterViews;
import org.androidannotations.annotations.Bean;
import org.androidannotations.annotations.EFragment;
import org.androidannotations.annotations.OptionsMenu;
import org.androidannotations.annotations.ViewById;
import org.transdroid.core.app.settings.ApplicationSettings;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.TextView;
import com.actionbarsherlock.app.SherlockFragment;
@ -20,24 +24,36 @@ import com.actionbarsherlock.view.SherlockListView; @@ -20,24 +24,36 @@ import com.actionbarsherlock.view.SherlockListView;
@OptionsMenu(resName = "fragment_rssfeeds")
public class RssfeedsFragment extends SherlockFragment {
// Settings
@Bean
protected ApplicationSettings applicationSettings;
@Bean
protected RssfeedsAdapter rssfeedsAdapter;
// Views
@ViewById(resName = "rssfeeds_list")
protected SherlockListView feedsList;
@Bean
protected RssfeedsAdapter rssfeedsAdapter;
@ViewById
protected TextView nosettingsText;
@AfterViews
protected void init() {
feedsList.setAdapter(rssfeedsAdapter);
rssfeedsAdapter.update(applicationSettings.getRssfeedSettings());
feedsList.setOnItemClickListener(onRssfeedSelected);
}
public void update(List<RssfeedLoader> loaders) {
rssfeedsAdapter.update(loaders);
}
private OnItemClickListener onRssfeedSelected = new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
((RssfeedsActivity)getActivity()).openRssfeed(rssfeedsAdapter.getItem(position));
}
};
/**
* Notifies the contained list of RSS feeds that the underlying data has been changed.
*/
public void notifyDataSetChanged() {
rssfeedsAdapter.notifyDataSetChanged();
}
}

8
core/src/org/transdroid/core/gui/rss/RssitemView.java

@ -1,10 +1,8 @@ @@ -1,10 +1,8 @@
package org.transdroid.core.gui.rss;
import java.util.Date;
import org.androidannotations.annotations.EViewGroup;
import org.androidannotations.annotations.ViewById;
import org.ifies.android.sax.Item;
import org.transdroid.core.rssparser.Item;
import android.content.Context;
import android.text.format.DateUtils;
@ -25,12 +23,12 @@ public class RssitemView extends RssitemStatusLayout { @@ -25,12 +23,12 @@ public class RssitemView extends RssitemStatusLayout {
super(context);
}
public void bind(Item rssitem, Date lastViewedItem) {
public void bind(Item rssitem) {
nameText.setText(rssitem.getTitle());
dateText.setText(DateUtils.getRelativeDateTimeString(getContext(), rssitem.getPubdate().getTime(),
DateUtils.SECOND_IN_MILLIS, DateUtils.WEEK_IN_MILLIS, DateUtils.FORMAT_ABBREV_MONTH));
setIsNew(rssitem.getPubdate().after(lastViewedItem));
setIsNew(rssitem.isNew());
}

26
core/src/org/transdroid/core/gui/rss/RssitemsActivity.java

@ -0,0 +1,26 @@ @@ -0,0 +1,26 @@
package org.transdroid.core.gui.rss;
import org.androidannotations.annotations.AfterViews;
import org.androidannotations.annotations.EActivity;
import org.androidannotations.annotations.Extra;
import org.androidannotations.annotations.FragmentById;
import org.transdroid.core.rssparser.Channel;
import com.actionbarsherlock.app.SherlockFragmentActivity;
@EActivity(resName = "activity_rssfeeds")
public class RssitemsActivity extends SherlockFragmentActivity {
@Extra
protected Channel rssfeed = null;
@FragmentById(resName = "rssitems_list")
protected RssitemsFragment fragmentItems;
@AfterViews
protected void init() {
// Get the intent extras and show them to the already loaded fragment
fragmentItems.update(rssfeed);
}
}

19
core/src/org/transdroid/core/gui/rss/RssitemsAdapter.java

@ -1,11 +1,9 @@ @@ -1,11 +1,9 @@
package org.transdroid.core.gui.rss;
import java.util.Date;
import org.androidannotations.annotations.EBean;
import org.androidannotations.annotations.RootContext;
import org.ifies.android.sax.Channel;
import org.ifies.android.sax.Item;
import org.transdroid.core.rssparser.Channel;
import org.transdroid.core.rssparser.Item;
import android.content.Context;
import android.view.View;
@ -20,7 +18,6 @@ import android.widget.BaseAdapter; @@ -20,7 +18,6 @@ import android.widget.BaseAdapter;
public class RssitemsAdapter extends BaseAdapter {
private Channel rssfeed = null;
private Date lastViewedItem;
@RootContext
protected Context context;
@ -34,16 +31,6 @@ public class RssitemsAdapter extends BaseAdapter { @@ -34,16 +31,6 @@ public class RssitemsAdapter extends BaseAdapter {
notifyDataSetChanged();
}
/**
* Registers the date that the user last viewed this feed. Any RSS items after this date will be visually marked as
* new.
* @param lastViewedItem The date after which RSS items should be marked as new
*/
public void setLastItemViewed(Date lastViewedItem) {
this.lastViewedItem = lastViewedItem;
notifyDataSetChanged();
}
@Override
public boolean hasStableIds() {
return true;
@ -76,7 +63,7 @@ public class RssitemsAdapter extends BaseAdapter { @@ -76,7 +63,7 @@ public class RssitemsAdapter extends BaseAdapter {
} else {
rssitemView = (RssitemView) convertView;
}
rssitemView.bind(getItem(position), lastViewedItem);
rssitemView.bind(getItem(position));
return rssitemView;
}

23
core/src/org/transdroid/core/gui/rss/RssitemsFragment.java

@ -1,14 +1,11 @@ @@ -1,14 +1,11 @@
package org.transdroid.core.gui.rss;
import java.util.Date;
import org.androidannotations.annotations.AfterViews;
import org.androidannotations.annotations.Bean;
import org.androidannotations.annotations.EFragment;
import org.androidannotations.annotations.FragmentArg;
import org.androidannotations.annotations.InstanceState;
import org.androidannotations.annotations.ViewById;
import org.ifies.android.sax.Channel;
import org.transdroid.core.rssparser.Channel;
import android.widget.TextView;
@ -22,12 +19,8 @@ import com.actionbarsherlock.view.SherlockListView; @@ -22,12 +19,8 @@ import com.actionbarsherlock.view.SherlockListView;
@EFragment(resName = "fragment_rssitems")
public class RssitemsFragment extends SherlockFragment {
@FragmentArg
@InstanceState
protected Channel rssfeed;
@FragmentArg
@InstanceState
protected Date lastViewedItem;
protected Channel rssfeed = null;
// Views
@ViewById(resName = "rssfeeds_list")
@ -39,11 +32,17 @@ public class RssitemsFragment extends SherlockFragment { @@ -39,11 +32,17 @@ public class RssitemsFragment extends SherlockFragment {
@AfterViews
protected void init() {
rssitemsList.setAdapter(rssitemsAdapter);
rssitemsAdapter.setLastItemViewed(lastViewedItem);
rssitemsAdapter.update(rssfeed);
update(rssfeed);
}
/**
* Update the shown RSS items in the list and the known last view date. This is automatically called when the
* fragment is instantiated by its build, but should be manually called if it was instantiated empty.
* @param rssfeed The RSS feed Channel object that was retrieved
*/
public void update(Channel rssfeed) {
rssitemsAdapter.update(rssfeed);
}
}

153
core/src/org/transdroid/core/rssparser/Channel.java

@ -0,0 +1,153 @@ @@ -0,0 +1,153 @@
/*
* Taken from the 'Learning Android' project, released as Public Domain software at
* http://github.com/digitalspaghetti/learning-android and modified heavily for Transdroid
*/
package org.transdroid.core.rssparser;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import android.os.Parcel;
import android.os.Parcelable;
public class Channel implements Parcelable {
private int id;
private String title;
private String link;
private String description;
private Date pubDate;
private long lastBuildDate;
private List<String> categories;
private List<Item> items;
private String image;
public Channel() {
this.categories = new ArrayList<String>();
this.items = new ArrayList<Item>();
}
public void setId(int id) {
this.id = id;
}
public int getId() {
return id;
}
public void setTitle(String title) {
this.title = title;
}
public String getTitle() {
return title;
}
public void setLink(String link) {
this.link = link;
}
public String getLink() {
return link;
}
public void setDescription(String description) {
this.description = description;
}
public String getDescription() {
return description;
}
public void setPubDate(Date date) {
this.pubDate = date;
}
public Date getPubDate() {
return pubDate;
}
public void setLastBuildDate(long lastBuildDate) {
this.lastBuildDate = lastBuildDate;
}
public long getLastBuildDate() {
return lastBuildDate;
}
public void setCategories(List<String> categories) {
this.categories = categories;
}
public void addCategory(String category) {
this.categories.add(category);
}
public List<String> getCategories() {
return categories;
}
public void setItems(List<Item> items) {
this.items = items;
}
public void addItem(Item item) {
this.items.add(item);
}
public List<Item> getItems() {
return items;
}
public void setImage(String image) {
this.image = image;
}
public String getImage() {
return image;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeInt(id);
out.writeString(title);
out.writeString(link);
out.writeString(description);
out.writeLong(pubDate == null ? -1 : pubDate.getTime());
out.writeLong(lastBuildDate);
out.writeStringList(categories);
out.writeTypedList(items);
out.writeString(image);
}
public static final Parcelable.Creator<Channel> CREATOR = new Parcelable.Creator<Channel>() {
public Channel createFromParcel(Parcel in) {
return new Channel(in);
}
public Channel[] newArray(int size) {
return new Channel[size];
}
};
private Channel(Parcel in) {
id = in.readInt();
title = in.readString();
link = in.readString();
description = in.readString();
long pubDateIn = in.readLong();
pubDate = pubDateIn == -1 ? null : new Date(pubDateIn);
lastBuildDate = in.readLong();
categories = new ArrayList<String>();
in.readTypedList(items, Item.CREATOR);
in.readStringList(categories);
image = in.readString();
}
}

2
core/src/org/ifies/android/sax/HttpHelper.java → core/src/org/transdroid/core/rssparser/HttpHelper.java

@ -16,7 +16,7 @@ @@ -16,7 +16,7 @@
* You should have received a copy of the GNU Lesser General Public
* License along with Transdroid. If not, see <http://www.gnu.org/licenses/>.
*/
package org.ifies.android.sax;
package org.transdroid.core.rssparser;
import java.io.BufferedReader;
import java.io.IOException;

157
core/src/org/transdroid/core/rssparser/Item.java

@ -0,0 +1,157 @@ @@ -0,0 +1,157 @@
/*
* Taken from the 'Learning Android' project, released as Public Domain software at
* http://github.com/digitalspaghetti/learning-android and modified heavily for Transdroid
*/
package org.transdroid.core.rssparser;
import java.util.Date;
import android.os.Parcel;
import android.os.Parcelable;
public class Item implements Parcelable {
private int id;
private String title;
private String link;
private String description;
private Date pubDate;
private String enclosureUrl;
private String enclosureType;
private long enclosureLength;
/**
* isNew is not parsed from the RSS feed but may be set using {@link #setIsNew(boolean)} manually
*/
private boolean isNew;
public void setId(int id) {
this.id = id;
}
public int getId() {
return id;
}
public void setTitle(String title) {
this.title = title;
}
public String getTitle() {
return this.title;
}
public void setDescription(String description) {
this.description = description;
}
public String getDescription() {
return this.description;
}
public void setLink(String link) {
this.link = link;
}
public String getLink() {
return this.link;
}
public void setPubdate(Date pubdate) {
this.pubDate = pubdate;
}
public Date getPubdate() {
return this.pubDate;
}
public void setEnclosureUrl(String enclosureUrl) {
this.enclosureUrl = enclosureUrl;
}
public void setEnclosureLength(long enclosureLength) {
this.enclosureLength = enclosureLength;
}
public void setEnclosureType(String enclosureType) {
this.enclosureType = enclosureType;
}
public String getEnclosureUrl() {
return this.enclosureUrl;
}
public String getEnclosureType() {
return this.enclosureType;
}
public long getEnclosureLength() {
return this.enclosureLength;
}
public void setIsNew(boolean isNew) {
this.isNew = isNew;
}
public boolean isNew() {
return isNew;
}
/**
* Returns 'the' item link, which preferably is the enclosure url, but otherwise the link (or null if that is empty
* too).
* @return A single link url to be used
*/
public String getTheLink() {
if (this.getEnclosureUrl() != null) {
return this.getEnclosureUrl();
} else {
return this.getLink();
}
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeInt(id);
out.writeString(title);
out.writeString(link);
out.writeString(description);
out.writeLong(pubDate == null ? -1 : pubDate.getTime());
out.writeString(enclosureUrl);
out.writeString(enclosureType);
out.writeLong(enclosureLength);
out.writeInt(isNew? 1: 0);
}
public static final Parcelable.Creator<Item> CREATOR = new Parcelable.Creator<Item>() {
public Item createFromParcel(Parcel in) {
return new Item(in);
}
public Item[] newArray(int size) {
return new Item[size];
}
};
public Item() {
}
private Item(Parcel in) {
id = in.readInt();
title = in.readString();
link = in.readString();
description = in.readString();
long pubDateIn = in.readLong();
pubDate = pubDateIn == -1 ? null : new Date(pubDateIn);
enclosureUrl = in.readString();
enclosureType = in.readString();
enclosureLength = in.readLong();
isNew = in.readInt() == 1? true: false;
}
}

235
core/src/org/transdroid/core/rssparser/RssParser.java

@ -0,0 +1,235 @@ @@ -0,0 +1,235 @@
/*
* Taken from the 'Learning Android' project, released as Public Domain software at
* http://github.com/digitalspaghetti/learning-android and modified heavily for Transdroid
*/
package org.transdroid.core.rssparser;
import java.io.IOException;
import java.util.Date;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
public class RssParser extends DefaultHandler {
private String urlString;
private Channel channel;
private StringBuilder text;
private Item item;
private boolean imageStatus;
/**
* The constructor for the RSS parser; call {@link #parse()} to synchronously create an HTTP connection and parse
* the RSS feed contents. The results can be retrieved with {@link #getChannel()}.
* @param url
*/
public RssParser(String url) {
this.urlString = url;
this.text = new StringBuilder();
}
/**
* Returns the loaded RSS feed as channel which contains the individual {@link Item}s
* @return A channel object that ocntains the feed details and individual items
*/
public Channel getChannel() {
return this.channel;
}
/**
* Initialises an HTTP connection, retrieves the content and parses the RSS feed as standard XML.
* @throws ParserConfigurationException Thrown if the SX parser is not working corectly
* @throws SAXException Thrown if the SAX parser can encounters non-standard XML content
* @throws IOException Thrown if the RSS feed content can not be retrieved, such as when no connection is available
*/
public void parse() throws ParserConfigurationException, SAXException, IOException {
DefaultHttpClient httpclient = initialise();
HttpResponse result = httpclient.execute(new HttpGet(urlString));
SAXParserFactory spf = SAXParserFactory.newInstance();
if (spf != null) {
SAXParser sp = spf.newSAXParser();
sp.parse(result.getEntity().getContent(), this);
}
}
private DefaultHttpClient initialise() {
SchemeRegistry registry = new SchemeRegistry();
registry.register(new Scheme("http", new PlainSocketFactory(), 80));
HttpParams httpparams = new BasicHttpParams();
HttpConnectionParams.setConnectionTimeout(httpparams, 5000);
HttpConnectionParams.setSoTimeout(httpparams, 5000);
DefaultHttpClient httpclient = new DefaultHttpClient(new ThreadSafeClientConnManager(httpparams, registry),
httpparams);
httpclient.addRequestInterceptor(HttpHelper.gzipRequestInterceptor);
httpclient.addResponseInterceptor(HttpHelper.gzipResponseInterceptor);
return httpclient;
}
/**
* By default creates a standard Item (with title, description and links), which may to overridden to add more data
* (i.e. custom tags that a feed may supply).
* @return A possibly decorated Item instance
*/
protected Item createNewItem() {
return new Item();
}
@Override
public final void startElement(String uri, String localName, String qName, Attributes attributes) {
/** First lets check for the channel */
if (localName.equalsIgnoreCase("channel")) {
this.channel = new Channel();
}
/** Now lets check for an item */
if (localName.equalsIgnoreCase("item") && (this.channel != null)) {
this.item = createNewItem();
this.channel.addItem(this.item);
}
/** Now lets check for an image */
if (localName.equalsIgnoreCase("image") && (this.channel != null)) {
this.imageStatus = true;
}
/** Checking for a enclosure */
if (localName.equalsIgnoreCase("enclosure")) {
/** Lets check we are in an item */
if (this.item != null && attributes != null && attributes.getLength() > 0) {
if (attributes.getValue("url") != null) {
this.item.setEnclosureUrl(attributes.getValue("url").trim());
}
if (attributes.getValue("type") != null) {
this.item.setEnclosureType(attributes.getValue("type"));
}
if (attributes.getValue("length") != null) {
this.item.setEnclosureLength(Long.parseLong(attributes.getValue("length")));
}
}
}
}
/**
* This is where we actually parse for the elements contents
*/
@SuppressWarnings("deprecation")
public final void endElement(String uri, String localName, String qName) {
/** Check we have an RSS Feed */
if (this.channel == null) {
return;
}
/** Check are at the end of an item */
if (localName.equalsIgnoreCase("item")) {
this.item = null;
}
/** Check we are at the end of an image */
if (localName.equalsIgnoreCase("image"))
this.imageStatus = false;
/** Now we need to parse which title we are in */
if (localName.equalsIgnoreCase("title")) {
/** We are an item, so we set the item title */
if (this.item != null) {
this.item.setTitle(this.text.toString().trim());
/** We are in an image */
} else {
this.channel.setTitle(this.text.toString().trim());
}
}
/** Now we are checking for a link */
if (localName.equalsIgnoreCase("link")) {
/** Check we are in an item **/
if (this.item != null) {
this.item.setLink(this.text.toString().trim());
/** Check we are in an image */
} else if (this.imageStatus) {
this.channel.setImage(this.text.toString().trim());
/** Check we are in a channel */
} else {
this.channel.setLink(this.text.toString().trim());
}
}
/** Checking for a description */
if (localName.equalsIgnoreCase("description")) {
/** Lets check we are in an item */
if (this.item != null) {
this.item.setDescription(this.text.toString().trim());
/** Lets check we are in the channel */
} else {
this.channel.setDescription(this.text.toString().trim());
}
}
/** Checking for a pubdate */
if (localName.equalsIgnoreCase("pubDate")) {
/** Lets check we are in an item */
if (this.item != null) {
try {
this.item.setPubdate(new Date(Date.parse(this.text.toString().trim())));
} catch (Exception e) {
// Date is malformed (not parsable by Date.parse)
}
/** Lets check we are in the channel */
} else {
try {
this.channel.setPubDate(new Date(Date.parse(this.text.toString().trim())));
} catch (Exception e) {
// Date is malformed (not parsable by Date.parse)
}
}
}
/** Check for the category */
if (localName.equalsIgnoreCase("category") && (this.item != null)) {
this.channel.addCategory(this.text.toString().trim());
}
addAdditionalData(localName, this.item, this.text.toString());
this.text.setLength(0);
}
/**
* May be overridden to add additional data from tags that are not standard in RSS. Not used by this default RSS
* style parser. Usually used in conjunction with {@link #createNewItem()}.
* @param localName The tag name
* @param item The Item we are currently parsing
* @param text The new text content
*/
protected void addAdditionalData(String localName, Item item, String text) {
}
@Override
public final void characters(char[] ch, int start, int length) {
this.text.append(ch, start, length);
}
}
Loading…
Cancel
Save