From 8fbd1ecf9ceec0523447346c1ae93e0cb612a96a Mon Sep 17 00:00:00 2001 From: Simon Rutishauser Date: Sun, 29 Sep 2013 17:08:26 +0200 Subject: [PATCH] this consolidates the code from pull request #253 (flattr queue and auto flattr) into a single commit, rebased onto the current development branch --- project.properties | 4 +- res/values/strings.xml | 17 +- res/xml/preferences.xml | 9 +- .../activity/MediaplayerActivity.java | 16 +- .../activity/PreferenceActivity.java | 3 + .../asynctask/FlattrClickWorker.java | 220 ++++++++++++++---- .../asynctask/FlattrStatusFetcher.java | 55 +++++ src/de/danoeh/antennapod/feed/Feed.java | 19 +- src/de/danoeh/antennapod/feed/FeedItem.java | 17 +- src/de/danoeh/antennapod/feed/FeedMedia.java | 38 ++- .../preferences/UserPreferences.java | 10 + .../danoeh/antennapod/storage/DBReader.java | 50 +++- src/de/danoeh/antennapod/storage/DBTasks.java | 8 + .../danoeh/antennapod/storage/DBWriter.java | 102 ++++++++ .../antennapod/storage/PodDBAdapter.java | 39 +++- .../antennapod/util/flattr/FlattrStatus.java | 67 ++++++ .../antennapod/util/flattr/FlattrThing.java | 7 + .../antennapod/util/flattr/FlattrUtils.java | 59 +++++ .../util/menuhandler/FeedItemMenuHandler.java | 8 +- .../util/menuhandler/FeedMenuHandler.java | 11 +- .../test/antennapod/storage/DBTasksTest.java | 6 +- .../test/antennapod/storage/DBWriterTest.java | 12 +- 22 files changed, 693 insertions(+), 84 deletions(-) create mode 100644 src/de/danoeh/antennapod/asynctask/FlattrStatusFetcher.java create mode 100644 src/de/danoeh/antennapod/util/flattr/FlattrStatus.java create mode 100644 src/de/danoeh/antennapod/util/flattr/FlattrThing.java diff --git a/project.properties b/project.properties index 75f295e31..d6cb674e4 100644 --- a/project.properties +++ b/project.properties @@ -10,6 +10,4 @@ # Project target. proguard.config=proguard.cfg target=android-18 -android.library.reference.1=submodules/ActionBarSherlock/library -android.library.reference.2=submodules/ViewPagerIndicator/library -android.library.reference.3=submodules/dslv/library + diff --git a/res/values/strings.xml b/res/values/strings.xml index 30590a719..86a7feef6 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -151,9 +151,20 @@ AntennaPod has no permission for this action. The reason for this could be that the access token of AntennaPod to your account has been revoked. You can either re-reauthenticate or visit the website of the thing instead. Access revoked You have successfully revoked AntennaPod\'s access token to your account. In order to complete the process, you have to remove this app from the list of approved applications in your account settings on the flattr website. - Successfully flattred this thing! - Flattring + + Flattred one thing! + Flattred %d things! + Flattred: %s. + Failed to flattr %d things! + Not flattred: %s. + Added thing to flattr queue! + Flattring %s + AntennaPod is flattring + AntennaPod has flattrd + AntennaPod flattr failed + Retrieving flattred things + Download Plugin Plugin Not Installed @@ -190,6 +201,8 @@ Support the development of AntennaPod by flattring it. Thanks! Revoke access Revoke the access permission to your flattr account for this app. + Automatic Flattr + Flattr episodes of which 80% have been played. Display only episodes Display only items which also have an episode. User Interface diff --git a/res/xml/preferences.xml b/res/xml/preferences.xml index 65507a346..0b2a3a264 100644 --- a/res/xml/preferences.xml +++ b/res/xml/preferences.xml @@ -80,6 +80,12 @@ + - - \ No newline at end of file + diff --git a/src/de/danoeh/antennapod/activity/MediaplayerActivity.java b/src/de/danoeh/antennapod/activity/MediaplayerActivity.java index 748a049a6..129134a47 100644 --- a/src/de/danoeh/antennapod/activity/MediaplayerActivity.java +++ b/src/de/danoeh/antennapod/activity/MediaplayerActivity.java @@ -21,14 +21,18 @@ import de.danoeh.antennapod.AppConfig; import de.danoeh.antennapod.R; import de.danoeh.antennapod.asynctask.FlattrClickWorker; import de.danoeh.antennapod.dialog.TimeDialog; +import de.danoeh.antennapod.feed.FeedItem; +import de.danoeh.antennapod.feed.FeedMedia; import de.danoeh.antennapod.preferences.UserPreferences; import de.danoeh.antennapod.service.PlaybackService; import de.danoeh.antennapod.util.Converter; import de.danoeh.antennapod.util.ShareUtils; import de.danoeh.antennapod.util.StorageUtils; +import de.danoeh.antennapod.util.flattr.FlattrStatus; import de.danoeh.antennapod.util.playback.MediaPlayerError; import de.danoeh.antennapod.util.playback.Playable; import de.danoeh.antennapod.util.playback.PlaybackController; +import de.danoeh.antennapod.storage.DBWriter; /** * Provides general features which are both needed for playing audio and video @@ -309,8 +313,16 @@ public abstract class MediaplayerActivity extends ActionBarActivity startActivity(new Intent(Intent.ACTION_VIEW, uri)); break; case R.id.support_item: - new FlattrClickWorker(this, media.getPaymentLink()) - .executeAsync(); + try { + FeedItem feedItem = ((FeedMedia) media).getItem(); + feedItem.getFlattrStatus().setFlattrQueue(); + + DBWriter.setFeedItem(this, feedItem); + new FlattrClickWorker(this).executeAsync(); + } + catch (ClassCastException e) { + Log.d(TAG, "Could not flattr item - most likely external media: " + e.toString()); + } break; case R.id.share_link_item: ShareUtils.shareLink(this, media.getWebsiteLink()); diff --git a/src/de/danoeh/antennapod/activity/PreferenceActivity.java b/src/de/danoeh/antennapod/activity/PreferenceActivity.java index e6fcf5306..beaade2b0 100644 --- a/src/de/danoeh/antennapod/activity/PreferenceActivity.java +++ b/src/de/danoeh/antennapod/activity/PreferenceActivity.java @@ -28,6 +28,7 @@ import de.danoeh.antennapod.dialog.GpodnetSetHostnameDialog; import de.danoeh.antennapod.dialog.VariableSpeedDialog; import de.danoeh.antennapod.preferences.GpodnetPreferences; import de.danoeh.antennapod.preferences.UserPreferences; +import de.danoeh.antennapod.util.flattr.FlattrStatus; import de.danoeh.antennapod.util.flattr.FlattrUtils; import java.io.File; @@ -44,6 +45,7 @@ public class PreferenceActivity extends android.preference.PreferenceActivity { private static final String PREF_FLATTR_THIS_APP = "prefFlattrThisApp"; private static final String PREF_FLATTR_AUTH = "pref_flattr_authenticate"; private static final String PREF_FLATTR_REVOKE = "prefRevokeAccess"; + private static final String PREF_AUTO_FLATTR = "pref_auto_flattr"; private static final String PREF_OPML_EXPORT = "prefOpmlExport"; private static final String PREF_ABOUT = "prefAbout"; private static final String PREF_CHOOSE_DATA_DIR = "prefChooseDataDir"; @@ -297,6 +299,7 @@ public class PreferenceActivity extends android.preference.PreferenceActivity { findPreference(PREF_FLATTR_AUTH).setEnabled(!hasFlattrToken); findPreference(PREF_FLATTR_REVOKE).setEnabled(hasFlattrToken); + findPreference(PREF_AUTO_FLATTR).setEnabled(hasFlattrToken); findPreference(UserPreferences.PREF_ENABLE_AUTODL_WIFI_FILTER) .setEnabled(UserPreferences.isEnableAutodownload()); diff --git a/src/de/danoeh/antennapod/asynctask/FlattrClickWorker.java b/src/de/danoeh/antennapod/asynctask/FlattrClickWorker.java index 975aa5efe..f97223ff5 100644 --- a/src/de/danoeh/antennapod/asynctask/FlattrClickWorker.java +++ b/src/de/danoeh/antennapod/asynctask/FlattrClickWorker.java @@ -1,107 +1,245 @@ package de.danoeh.antennapod.asynctask; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; + import org.shredzone.flattr4j.exception.FlattrException; +import org.shredzone.flattr4j.model.Flattr; import android.annotation.SuppressLint; +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; import android.app.ProgressDialog; import android.content.Context; +import android.content.Intent; +import android.graphics.BitmapFactory; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; import android.os.AsyncTask; +import android.support.v4.app.NotificationCompat; import android.util.Log; import android.widget.Toast; import de.danoeh.antennapod.AppConfig; import de.danoeh.antennapod.R; +import de.danoeh.antennapod.activity.FeedItemlistActivity; +import de.danoeh.antennapod.feed.Feed; +import de.danoeh.antennapod.feed.FeedItem; +import de.danoeh.antennapod.util.flattr.FlattrThing; import de.danoeh.antennapod.util.flattr.FlattrUtils; +import de.danoeh.antennapod.util.flattr.FlattrStatus; +import de.danoeh.antennapod.storage.DBReader; +import de.danoeh.antennapod.storage.DBWriter; /** Performs a click action in a background thread. */ -public class FlattrClickWorker extends AsyncTask { +public class FlattrClickWorker extends AsyncTask { protected static final String TAG = "FlattrClickWorker"; protected Context context; - protected String url; + + private final int NOTIFICATION_ID = 4; + protected String errorMsg; protected int exitCode; + protected ArrayList flattrd; + protected ArrayList flattr_failed; + + protected NotificationCompat.Builder notificationBuilder; + protected NotificationManager notificationManager; + protected ProgressDialog progDialog; - protected final static int SUCCESS = 0; + protected final static int EXIT_DEFAULT = 0; protected final static int NO_TOKEN = 1; - protected final static int FLATTR_ERROR = 2; + protected final static int ENQUEUED = 2; + protected final static int NO_THINGS = 3; + + private boolean enqueue_only = false; - public FlattrClickWorker(Context context, String url) { + public FlattrClickWorker(Context context, boolean enqueue_only) { + this(context); + this.enqueue_only = enqueue_only; + } + + public FlattrClickWorker(Context context) { super(); this.context = context; - this.url = url; - exitCode = SUCCESS; + exitCode = EXIT_DEFAULT; + + flattrd = new ArrayList(); + flattr_failed = new ArrayList(); + errorMsg = ""; } + /* only used in PreferencesActivity for flattring antennapod itself, + * can't really enqueue this thing + */ + public FlattrClickWorker(Context context, String url) { + Log.e(TAG, "Not implemented yet"); + } + protected void onNoAccessToken() { Log.w(TAG, "No access token was available"); - if (url.equals(FlattrUtils.APP_URL)) { - FlattrUtils.showNoTokenDialog(context, FlattrUtils.APP_LINK); - } else { - FlattrUtils.showNoTokenDialog(context, url); - } + FlattrUtils.showNoTokenDialog(context, ""); } protected void onFlattrError() { FlattrUtils.showErrorDialog(context, errorMsg); } - - protected void onSuccess() { - Toast toast = Toast.makeText(context.getApplicationContext(), - R.string.flattr_click_success, Toast.LENGTH_LONG); - toast.show(); + + protected void onFlattred() { + String notificationTitle = context.getString(R.string.flattrd_label); + String notificationText = "", notificationSubText = ""; + + // text for successfully flattred items + if (flattrd.size() == 1) + notificationText = String.format(context.getString(R.string.flattr_click_success)); + else if (flattrd.size() > 1) // flattred pending items from queue + notificationText = String.format(context.getString(R.string.flattr_click_success_count, flattrd.size())); + + if (flattrd.size() > 0) { + String acc = ""; + for (String s: flattrd) + acc += s + ", "; + acc = acc.substring(0, acc.length()-2); + + notificationSubText = String.format(context.getString(R.string.flattr_click_success_queue), acc); + } + + // add text for failures + if (flattr_failed.size() > 0) { + notificationTitle = context.getString(R.string.flattrd_failed_label); + notificationText = String.format(context.getString(R.string.flattr_click_failure_count), flattr_failed.size()) + + " " + notificationText; + + String acc = ""; + for (String s: flattr_failed) + acc += s + ", "; + acc = acc.substring(0, acc.length()-2); + + notificationSubText = String.format(context.getString(R.string.flattr_click_failure), acc) + + " " + notificationSubText; + } + + notificationBuilder = new NotificationCompat.Builder(context) // need new notificationBuilder and cancel/renotify to get rid of progress bar + .setContentTitle(notificationTitle) + .setContentText(notificationText) + .setSubText(notificationSubText) + .setTicker(notificationTitle) + .setSmallIcon(R.drawable.stat_notify_sync) + .setOngoing(false); + notificationManager.cancel(NOTIFICATION_ID); + notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build()); + } + + protected void onEnqueue() { + Toast.makeText(context.getApplicationContext(), + R.string.flattr_click_enqueued, + Toast.LENGTH_LONG) + .show(); } - protected void onSetupProgDialog() { - progDialog = new ProgressDialog(context); - progDialog.setMessage(context.getString(R.string.flattring_label)); - progDialog.setIndeterminate(true); - progDialog.setCancelable(false); - progDialog.show(); + protected void onSetupNotification() { + notificationBuilder = new NotificationCompat.Builder(context) + .setContentTitle(context.getString(R.string.flattring_label)) + .setAutoCancel(true) + .setSmallIcon(R.drawable.stat_notify_sync) + .setProgress(0, 0, true) + .setOngoing(true); + + notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); } @Override protected void onPostExecute(Void result) { if (AppConfig.DEBUG) Log.d(TAG, "Exit code was " + exitCode); - if (progDialog != null) { - progDialog.dismiss(); - } + switch (exitCode) { case NO_TOKEN: + notificationManager.cancel(NOTIFICATION_ID); onNoAccessToken(); break; - case FLATTR_ERROR: - onFlattrError(); + case ENQUEUED: + onEnqueue(); break; - case SUCCESS: - onSuccess(); + case EXIT_DEFAULT: + onFlattred(); + break; + case NO_THINGS: // FlattrClickWorker called automatically somewhere to empty flattr queue + notificationManager.cancel(NOTIFICATION_ID); break; } } @Override protected void onPreExecute() { - onSetupProgDialog(); + onSetupNotification(); } + private static boolean haveInternetAccess(Context context) { + ConnectivityManager cm = + (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + + NetworkInfo networkInfo = cm.getActiveNetworkInfo(); + return (networkInfo != null && networkInfo.isConnectedOrConnecting()); + } + @Override protected Void doInBackground(Void... params) { if (AppConfig.DEBUG) Log.d(TAG, "Starting background work"); - if (FlattrUtils.hasToken()) { - try { - FlattrUtils.clickUrl(context, url); - } catch (FlattrException e) { - e.printStackTrace(); - exitCode = FLATTR_ERROR; - errorMsg = e.getMessage(); - } - } else { + + exitCode = EXIT_DEFAULT; + + if (!FlattrUtils.hasToken()) { exitCode = NO_TOKEN; } + else if (DBReader.getFlattrQueueEmpty(context)) { + exitCode = NO_THINGS; + } + else if (!haveInternetAccess(context) || enqueue_only) { + exitCode = ENQUEUED; + } + else { + List flattrList = DBReader.getFlattrQueue(context); + Log.d(TAG, "flattrQueue processing list with " + flattrList.size() + " items."); + + flattrd.ensureCapacity(flattrList.size()); + + for (FlattrThing thing: flattrList) { + try { + Log.d(TAG, "flattrQueue processing " + thing.getTitle() + " " + thing.getPaymentLink()); + publishProgress(String.format(context.getString(R.string.flattring_thing), thing.getTitle())); + + thing.getFlattrStatus().setUnflattred(); // pop from queue to prevent unflattrable things from getting stuck in flattr queue infinitely + + FlattrUtils.clickUrl(context, thing.getPaymentLink()); + flattrd.add(thing.getTitle()); + + thing.getFlattrStatus().setFlattred(); + } + catch (FlattrException e) { + Log.d(TAG, "flattrQueue processing exception at item " + thing.getTitle() + " " + e.getMessage()); + flattr_failed.ensureCapacity(flattrList.size()); + flattr_failed.add(thing.getTitle()); + } + Log.d(TAG, "flattrQueue processing - going to write thing back to db with flattr_status " + Long.toString(thing.getFlattrStatus().toLong())); + DBWriter.setFlattredStatus(context, thing); + } + + } + return null; } - + + @Override + protected void onProgressUpdate(String... names) { + notificationBuilder.setContentText(names[0]); + notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build()); + } + @SuppressLint("NewApi") public void executeAsync() { FlattrUtils.hasToken(); diff --git a/src/de/danoeh/antennapod/asynctask/FlattrStatusFetcher.java b/src/de/danoeh/antennapod/asynctask/FlattrStatusFetcher.java new file mode 100644 index 000000000..00e91c399 --- /dev/null +++ b/src/de/danoeh/antennapod/asynctask/FlattrStatusFetcher.java @@ -0,0 +1,55 @@ +package de.danoeh.antennapod.asynctask; + +import java.util.List; + +import org.shredzone.flattr4j.exception.FlattrException; +import org.shredzone.flattr4j.model.Flattr; + +import android.util.Log; +import android.annotation.SuppressLint; +import android.content.Context; +import android.os.AsyncTask; +import de.danoeh.antennapod.AppConfig; +import de.danoeh.antennapod.util.flattr.FlattrUtils; +import de.danoeh.antennapod.storage.DBWriter; + +/** Fetch list of flattred things and flattr status in database in a background thread. */ + +public class FlattrStatusFetcher extends AsyncTask { + protected static final String TAG = "FlattrStatusFetcher"; + protected Context context; + + public FlattrStatusFetcher(Context context) { + super(); + this.context = context; + } + + @Override + protected Void doInBackground(Void... params) { + if (AppConfig.DEBUG) Log.d(TAG, "Starting background work: Retrieving Flattr status"); + + Thread.currentThread().setPriority(Thread.MIN_PRIORITY); + + try { + List flattredThings = FlattrUtils.retrieveFlattredThings(); + DBWriter.setFlattredStatus(context, flattredThings); + } + catch (FlattrException e) { + Log.d(TAG, "flattrQueue exception retrieving list with flattred items " + e.getMessage()); + } + + if (AppConfig.DEBUG) Log.d(TAG, "Finished background work: Retrieved Flattr status"); + + return null; + } + + @SuppressLint("NewApi") + public void executeAsync() { + FlattrUtils.hasToken(); + if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) { + executeOnExecutor(THREAD_POOL_EXECUTOR); + } else { + execute(); + } + } +} diff --git a/src/de/danoeh/antennapod/feed/Feed.java b/src/de/danoeh/antennapod/feed/Feed.java index 032930f83..6df7bacd8 100644 --- a/src/de/danoeh/antennapod/feed/Feed.java +++ b/src/de/danoeh/antennapod/feed/Feed.java @@ -7,13 +7,15 @@ import java.util.List; import de.danoeh.antennapod.preferences.UserPreferences; import de.danoeh.antennapod.util.EpisodeFilter; +import de.danoeh.antennapod.util.flattr.FlattrStatus; +import de.danoeh.antennapod.util.flattr.FlattrThing; /** * Data Object for a whole feed * * @author daniel */ -public class Feed extends FeedFile { +public class Feed extends FeedFile implements FlattrThing { public static final int FEEDFILETYPE_FEED = 0; public static final String TYPE_RSS2 = "rss"; public static final String TYPE_RSS091 = "rss"; @@ -40,6 +42,7 @@ public class Feed extends FeedFile { * Date of last refresh. */ private Date lastUpdate; + private FlattrStatus flattrStatus; private String paymentLink; /** * Feed type, for example RSS 2 or Atom @@ -51,7 +54,7 @@ public class Feed extends FeedFile { */ public Feed(long id, Date lastUpdate, String title, String link, String description, String paymentLink, String author, String language, String type, String feedIdentifier, FeedImage image, String fileUrl, - String downloadUrl, boolean downloaded) { + String downloadUrl, boolean downloaded, FlattrStatus status) { super(fileUrl, downloadUrl, downloaded); this.id = id; this.title = title; @@ -68,6 +71,7 @@ public class Feed extends FeedFile { this.type = type; this.feedIdentifier = feedIdentifier; this.image = image; + this.flattrStatus = status; items = new ArrayList(); } @@ -79,6 +83,7 @@ public class Feed extends FeedFile { super(); items = new ArrayList(); lastUpdate = new Date(); + this.flattrStatus = new FlattrStatus(); } /** @@ -88,6 +93,7 @@ public class Feed extends FeedFile { public Feed(String url, Date lastUpdate) { super(null, url, false); this.lastUpdate = (lastUpdate != null) ? (Date) lastUpdate.clone() : null; + this.flattrStatus = new FlattrStatus(); } /** @@ -97,6 +103,7 @@ public class Feed extends FeedFile { public Feed(String url, Date lastUpdate, String title) { this(url, lastUpdate); this.title = title; + this.flattrStatus = new FlattrStatus(); } /** @@ -334,6 +341,14 @@ public class Feed extends FeedFile { this.feedIdentifier = feedIdentifier; } + public void setFlattrStatus(FlattrStatus status) { + this.flattrStatus = status; + } + + public FlattrStatus getFlattrStatus() { + return flattrStatus; + } + public String getPaymentLink() { return paymentLink; } diff --git a/src/de/danoeh/antennapod/feed/FeedItem.java b/src/de/danoeh/antennapod/feed/FeedItem.java index a80460ece..37388fe44 100644 --- a/src/de/danoeh/antennapod/feed/FeedItem.java +++ b/src/de/danoeh/antennapod/feed/FeedItem.java @@ -10,6 +10,8 @@ import de.danoeh.antennapod.PodcastApp; import de.danoeh.antennapod.asynctask.ImageLoader; import de.danoeh.antennapod.storage.DBReader; import de.danoeh.antennapod.util.ShownotesProvider; +import de.danoeh.antennapod.util.flattr.FlattrStatus; +import de.danoeh.antennapod.util.flattr.FlattrThing; /** * Data Object for a XML message @@ -17,7 +19,7 @@ import de.danoeh.antennapod.util.ShownotesProvider; * @author daniel */ public class FeedItem extends FeedComponent implements - ImageLoader.ImageWorkerTaskResource, ShownotesProvider { + ImageLoader.ImageWorkerTaskResource, ShownotesProvider, FlattrThing { /** * The id/guid that can be found in the rss/atom feed. Might not be set. @@ -42,10 +44,12 @@ public class FeedItem extends FeedComponent implements private boolean read; private String paymentLink; + private FlattrStatus flattrStatus; private List chapters; public FeedItem() { this.read = true; + this.flattrStatus = new FlattrStatus(); } /** @@ -59,6 +63,7 @@ public class FeedItem extends FeedComponent implements this.pubDate = (pubDate != null) ? (Date) pubDate.clone() : null; this.read = read; this.feed = feed; + this.flattrStatus = new FlattrStatus(); } public void updateFromOther(FeedItem other) { @@ -195,7 +200,15 @@ public class FeedItem extends FeedComponent implements this.contentEncoded = contentEncoded; } - public String getPaymentLink() { + public void setFlattrStatus(FlattrStatus status) { + this.flattrStatus = status; + } + + public FlattrStatus getFlattrStatus() { + return flattrStatus; + } + + public String getPaymentLink() { return paymentLink; } diff --git a/src/de/danoeh/antennapod/feed/FeedMedia.java b/src/de/danoeh/antennapod/feed/FeedMedia.java index 492867983..be3af967f 100644 --- a/src/de/danoeh/antennapod/feed/FeedMedia.java +++ b/src/de/danoeh/antennapod/feed/FeedMedia.java @@ -10,14 +10,19 @@ import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; import android.os.Parcel; import android.os.Parcelable; +import android.util.Log; import de.danoeh.antennapod.PodcastApp; +import de.danoeh.antennapod.asynctask.FlattrClickWorker; import de.danoeh.antennapod.preferences.PlaybackPreferences; +import de.danoeh.antennapod.service.PlaybackService; import de.danoeh.antennapod.storage.DBReader; import de.danoeh.antennapod.storage.DBWriter; +import de.danoeh.antennapod.preferences.UserPreferences; import de.danoeh.antennapod.util.ChapterUtils; import de.danoeh.antennapod.util.playback.Playable; public class FeedMedia extends FeedFile implements Playable { + private static final String TAG = "FeedMedia"; public static final int FEEDFILETYPE_FEEDMEDIA = 2; public static final int PLAYABLE_TYPE_FEEDMEDIA = 1; @@ -27,6 +32,7 @@ public class FeedMedia extends FeedFile implements Playable { private int duration; private int position; // Current position in file + private int played_duration; // How many ms of this file have been played (for autoflattring) private long size; // File size in Byte private String mime_type; private volatile FeedItem item; @@ -45,12 +51,13 @@ public class FeedMedia extends FeedFile implements Playable { public FeedMedia(long id, FeedItem item, int duration, int position, long size, String mime_type, String file_url, String download_url, - boolean downloaded, Date playbackCompletionDate) { + boolean downloaded, Date playbackCompletionDate, int played_duration) { super(file_url, download_url, downloaded); this.id = id; this.item = item; this.duration = duration; this.position = position; + this.played_duration = played_duration; this.size = size; this.mime_type = mime_type; this.playbackCompletionDate = playbackCompletionDate == null @@ -137,12 +144,20 @@ public class FeedMedia extends FeedFile implements Playable { this.duration = duration; } - public int getPosition() { + public int getPlayedDuration() { + return played_duration; + } + + public int getPosition() { return position; } public void setPosition(int position) { - this.position = position; + final int WAITING_INTERVAL = 5000; + if (position > this.position) + played_duration += Math.min(position - this.position, 1.1*WAITING_INTERVAL); + + this.position = position; } public long getSize() { @@ -215,6 +230,7 @@ public class FeedMedia extends FeedFile implements Playable { dest.writeString(download_url); dest.writeByte((byte) ((downloaded) ? 1 : 0)); dest.writeLong((playbackCompletionDate != null) ? playbackCompletionDate.getTime() : 0); + dest.writeInt(played_duration); } @Override @@ -313,8 +329,18 @@ public class FeedMedia extends FeedFile implements Playable { @Override public void saveCurrentPosition(SharedPreferences pref, int newPosition) { - position = newPosition; - DBWriter.setFeedMediaPlaybackInformation(PodcastApp.getInstance(), this); + setPosition(newPosition); + + // Auto flattr + if (UserPreferences.isAutoFlattr() && item.getPaymentLink() != null && item.getFlattrStatus().getUnflattred() && (played_duration > 0.8*duration)) { + Log.d(TAG, "saveCurrentPosition: performing auto flattr since played duration " + Integer.toString(played_duration) + " is 80% of file duration " + Integer.toString(duration)); + item.getFlattrStatus().setFlattrQueue(); + + DBWriter.setFeedItem(PodcastApp.getInstance(), item); + new FlattrClickWorker(PodcastApp.getInstance(), true).executeAsync(); + } + + DBWriter.setFeedMediaPlaybackInformation(PodcastApp.getInstance(), this); } @Override @@ -358,7 +384,7 @@ public class FeedMedia extends FeedFile implements Playable { final long id = in.readLong(); final long itemID = in.readLong(); FeedMedia result = new FeedMedia(id, null, in.readInt(), in.readInt(), in.readLong(), in.readString(), in.readString(), - in.readString(), in.readByte() != 0, new Date(in.readLong())); + in.readString(), in.readByte() != 0, new Date(in.readLong()), in.readInt()); result.itemID = itemID; return result; } diff --git a/src/de/danoeh/antennapod/preferences/UserPreferences.java b/src/de/danoeh/antennapod/preferences/UserPreferences.java index d15f5ba53..538c6238e 100644 --- a/src/de/danoeh/antennapod/preferences/UserPreferences.java +++ b/src/de/danoeh/antennapod/preferences/UserPreferences.java @@ -40,6 +40,7 @@ public class UserPreferences implements public static final String PREF_MOBILE_UPDATE = "prefMobileUpdate"; public static final String PREF_DISPLAY_ONLY_EPISODES = "prefDisplayOnlyEpisodes"; public static final String PREF_AUTO_DELETE = "prefAutoDelete"; + public static final String PREF_AUTO_FLATTR = "pref_auto_flattr"; public static final String PREF_THEME = "prefTheme"; public static final String PREF_DATA_FOLDER = "prefDataFolder"; public static final String PREF_ENABLE_AUTODL = "prefEnableAutoDl"; @@ -62,6 +63,7 @@ public class UserPreferences implements private boolean allowMobileUpdate; private boolean displayOnlyEpisodes; private boolean autoDelete; + private boolean autoFlattr; private int theme; private boolean enableAutodownload; private boolean enableAutodownloadWifiFilter; @@ -110,6 +112,7 @@ public class UserPreferences implements allowMobileUpdate = sp.getBoolean(PREF_MOBILE_UPDATE, false); displayOnlyEpisodes = sp.getBoolean(PREF_DISPLAY_ONLY_EPISODES, false); autoDelete = sp.getBoolean(PREF_AUTO_DELETE, false); + autoFlattr = sp.getBoolean(PREF_AUTO_FLATTR, false); theme = readThemeValue(sp.getString(PREF_THEME, "0")); enableAutodownloadWifiFilter = sp.getBoolean( PREF_ENABLE_AUTODL_WIFI_FILTER, false); @@ -220,6 +223,11 @@ public class UserPreferences implements instanceAvailable(); return instance.autoDelete; } + + public static boolean isAutoFlattr() { + instanceAvailable(); + return instance.autoFlattr; + } public static int getTheme() { instanceAvailable(); @@ -288,6 +296,8 @@ public class UserPreferences implements } else if (key.equals(PREF_AUTO_DELETE)) { autoDelete = sp.getBoolean(PREF_AUTO_DELETE, false); + } else if (key.equals(PREF_AUTO_FLATTR)) { + autoFlattr = sp.getBoolean(PREF_AUTO_FLATTR, false); } else if (key.equals(PREF_DISPLAY_ONLY_EPISODES)) { displayOnlyEpisodes = sp.getBoolean(PREF_DISPLAY_ONLY_EPISODES, false); diff --git a/src/de/danoeh/antennapod/storage/DBReader.java b/src/de/danoeh/antennapod/storage/DBReader.java index a5a4c8cd4..404952039 100644 --- a/src/de/danoeh/antennapod/storage/DBReader.java +++ b/src/de/danoeh/antennapod/storage/DBReader.java @@ -4,6 +4,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.List; +import java.util.LinkedList; import android.content.Context; import android.database.Cursor; @@ -22,6 +23,8 @@ import de.danoeh.antennapod.service.download.*; import de.danoeh.antennapod.util.DownloadError; import de.danoeh.antennapod.util.comparator.DownloadStatusComparator; import de.danoeh.antennapod.util.comparator.FeedItemPubdateComparator; +import de.danoeh.antennapod.util.flattr.FlattrStatus; +import de.danoeh.antennapod.util.flattr.FlattrThing; /** * Provides methods for reading data from the AntennaPod database. @@ -216,6 +219,8 @@ public final class DBReader { .getInt(PodDBAdapter.IDX_FI_SMALL_READ) > 0)); item.setItemIdentifier(itemlistCursor .getString(PodDBAdapter.IDX_FI_SMALL_ITEM_IDENTIFIER)); + item.setFlattrStatus(new FlattrStatus(itemlistCursor + .getLong(PodDBAdapter.IDX_FI_SMALL_FLATTR_STATUS))); // extract chapters boolean hasSimpleChapters = itemlistCursor @@ -307,7 +312,8 @@ public final class DBReader { cursor.getString(PodDBAdapter.KEY_FILE_URL_INDEX), cursor.getString(PodDBAdapter.KEY_DOWNLOAD_URL_INDEX), cursor.getInt(PodDBAdapter.KEY_DOWNLOADED_INDEX) > 0, - playbackCompletionDate); + playbackCompletionDate, + cursor.getInt(PodDBAdapter.KEY_PLAYED_DURATION_INDEX)); } private static Feed extractFeedFromCursorRow(PodDBAdapter adapter, @@ -335,7 +341,9 @@ public final class DBReader { image, cursor.getString(PodDBAdapter.KEY_FILE_URL_INDEX), cursor.getString(PodDBAdapter.KEY_DOWNLOAD_URL_INDEX), - cursor.getInt(PodDBAdapter.KEY_DOWNLOADED_INDEX) > 0); + cursor.getInt(PodDBAdapter.KEY_DOWNLOADED_INDEX) > 0, + new FlattrStatus(cursor.getLong(PodDBAdapter.KEY_FEED_FLATTR_STATUS_INDEX)) + ); if (image != null) { image.setFeed(feed); @@ -775,4 +783,42 @@ public final class DBReader { return media; } + + + public static List getFlattrQueue(Context context) { + List feeds = getFeedList(context); + List l = new LinkedList(); + + for (Feed feed : feeds) { + if (feed.getFlattrStatus().getFlattrQueue()) + l.add(feed); + + for (FeedItem item : getFeedItemList(context, feed)) + if (item.getFlattrStatus().getFlattrQueue()) + l.add(item); + } + + Log.d(TAG, "Returning flattrQueueIterator for queue with " + l.size() + " items."); + return l; + } + + + public static boolean getFlattrQueueEmpty(Context context) { + List feeds = getFeedList(context); + + for (Feed feed : feeds) { + if (feed.getFlattrStatus().getFlattrQueue()) + return false; + } + + for (Feed feed : feeds) { + for (FeedItem item : getFeedItemList(context, feed)) + if (item.getFlattrStatus().getFlattrQueue()) + return false; + } + + Log.d(TAG, "getFlattrQueueEmpty() = true"); + + return true; + } } diff --git a/src/de/danoeh/antennapod/storage/DBTasks.java b/src/de/danoeh/antennapod/storage/DBTasks.java index b9a1fd002..efc40a7bb 100644 --- a/src/de/danoeh/antennapod/storage/DBTasks.java +++ b/src/de/danoeh/antennapod/storage/DBTasks.java @@ -16,6 +16,8 @@ import android.content.Context; import android.content.Intent; import android.database.Cursor; import android.util.Log; +import de.danoeh.antennapod.asynctask.FlattrClickWorker; +import de.danoeh.antennapod.asynctask.FlattrStatusFetcher; import de.danoeh.antennapod.AppConfig; import de.danoeh.antennapod.feed.EventDistributor; import de.danoeh.antennapod.feed.Feed; @@ -149,6 +151,12 @@ public final class DBTasks { GpodnetSyncService.sendSyncIntent(context); } }.start(); + + if (AppConfig.DEBUG) Log.d(TAG, "Flattring all pending things."); + new FlattrClickWorker(context).executeAsync(); // flattr pending things + + if (AppConfig.DEBUG) Log.d(TAG, "Fetching flattr status."); + new FlattrStatusFetcher(context).executeAsync(); } else { if (AppConfig.DEBUG) Log.d(TAG, diff --git a/src/de/danoeh/antennapod/storage/DBWriter.java b/src/de/danoeh/antennapod/storage/DBWriter.java index 9eb0ab643..cdebd5436 100644 --- a/src/de/danoeh/antennapod/storage/DBWriter.java +++ b/src/de/danoeh/antennapod/storage/DBWriter.java @@ -2,20 +2,26 @@ package de.danoeh.antennapod.storage; import java.io.File; import java.util.Date; +import java.util.ArrayList; import java.util.LinkedList; import java.util.List; +import java.net.URI; +import java.net.URISyntaxException; import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.ThreadFactory; +import org.shredzone.flattr4j.model.Flattr; + import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.database.Cursor; import android.preference.PreferenceManager; import android.util.Log; +import android.net.Uri; import de.danoeh.antennapod.AppConfig; import de.danoeh.antennapod.feed.*; import de.danoeh.antennapod.preferences.GpodnetPreferences; @@ -24,6 +30,7 @@ import de.danoeh.antennapod.service.GpodnetSyncService; import de.danoeh.antennapod.service.PlaybackService; import de.danoeh.antennapod.service.download.DownloadStatus; import de.danoeh.antennapod.util.QueueAccess; +import de.danoeh.antennapod.util.flattr.*; /** * Provides methods for writing data to AntennaPod's database. @@ -825,4 +832,99 @@ public class DBWriter { } return false; } + + private static String normalizeURI(String uri) { + String normalizedURI = null; + if (uri != null) { + try { + normalizedURI = (new URI(uri)).normalize().toString(); + if (! normalizedURI.endsWith("/")) + normalizedURI = normalizedURI + "/"; + } + catch (URISyntaxException e) { + } + } + return normalizedURI; + } + + + // Set flattr status of the passed thing (either a FeedItem or a Feed) + public static void setFlattredStatus(Context context, FlattrThing thing) { + // must propagate this to back db + if (thing instanceof FeedItem) + DBWriter.setFeedItem(context, (FeedItem) thing); + else if (thing instanceof Feed) + DBWriter.setCompleteFeed(context, (Feed) thing); + else + Log.e(TAG, "flattrQueue processing - thing is neither FeedItem nor Feed"); + } + + /* + * Set flattr status of the feeds/feeditems in flattrList to flattred at the given timestamp, + * where the information has been retrieved from the flattr API + */ + public static void setFlattredStatus(Context context, List flattrList) { + class FlattrLinkTime { + public String paymentLink; + public long time; + + FlattrLinkTime(String paymentLink, long time) { + this.paymentLink = paymentLink; + this.time = time; + } + } + + // build list with flattred things having normalized URLs + ArrayList flattrLinkTime = new ArrayList(flattrList.size()); + for (Flattr flattr: flattrList) { + flattrLinkTime.add(new FlattrLinkTime(normalizeURI(flattr.getThing().getUrl()), flattr.getCreated().getTime())); + if (AppConfig.DEBUG) + Log.d(TAG, "FlattredUrl: " + flattr.getThing().getUrl()); + } + + + String paymentLink; + List feeds = DBReader.getFeedList(context); + for (Feed feed: feeds) { + // check if the feed has been flattred + paymentLink = feed.getPaymentLink(); + if (paymentLink != null) { + String feedThingUrl = normalizeURI(Uri.parse(paymentLink).getQueryParameter("url")); + + feed.getFlattrStatus().setUnflattred(); // reset our offline status tracking + + if (AppConfig.DEBUG) + Log.d(TAG, "Feed: Trying to match " + feedThingUrl); + for (FlattrLinkTime flattr: flattrLinkTime) { + if (flattr.paymentLink.equals(feedThingUrl)) { + feed.setFlattrStatus(new FlattrStatus(flattr.time)); + setCompleteFeed(context, feed); + break; + } + } + } + + // check if any of the feeditems have been flattred + for (FeedItem item: DBReader.getFeedItemList(context, feed)) { + paymentLink = item.getPaymentLink(); + + if (paymentLink != null) { + String feedItemThingUrl = normalizeURI(Uri.parse(paymentLink).getQueryParameter("url")); + + item.getFlattrStatus().setUnflattred(); // reset our offline status tracking + + if (AppConfig.DEBUG) + Log.d(TAG, "FeedItem: Trying to match " + feedItemThingUrl); + for (FlattrLinkTime flattr: flattrLinkTime) { + if (flattr.paymentLink.equals(feedItemThingUrl)) { + item.setFlattrStatus(new FlattrStatus(flattr.time)); + setFeedItem(context, item); + break; + } + } + } + } + } + } + } diff --git a/src/de/danoeh/antennapod/storage/PodDBAdapter.java b/src/de/danoeh/antennapod/storage/PodDBAdapter.java index e22fe56c9..71c0f787e 100644 --- a/src/de/danoeh/antennapod/storage/PodDBAdapter.java +++ b/src/de/danoeh/antennapod/storage/PodDBAdapter.java @@ -1,6 +1,7 @@ package de.danoeh.antennapod.storage; import java.util.Arrays; +import java.util.Iterator; import java.util.List; import android.content.ContentValues; @@ -20,6 +21,7 @@ import de.danoeh.antennapod.feed.FeedImage; import de.danoeh.antennapod.feed.FeedItem; import de.danoeh.antennapod.feed.FeedMedia; import de.danoeh.antennapod.service.download.DownloadStatus; +import de.danoeh.antennapod.util.flattr.FlattrStatus; // TODO Remove media column from feeditem table @@ -28,7 +30,7 @@ import de.danoeh.antennapod.service.download.DownloadStatus; */ public class PodDBAdapter { private static final String TAG = "PodDBAdapter"; - private static final int DATABASE_VERSION = 9; + private static final int DATABASE_VERSION = 10; public static final String DATABASE_NAME = "Antennapod.db"; /** @@ -58,6 +60,7 @@ public class PodDBAdapter { public static final int KEY_IMAGE_INDEX = 11; public static final int KEY_TYPE_INDEX = 12; public static final int KEY_FEED_IDENTIFIER_INDEX = 13; + public static final int KEY_FEED_FLATTR_STATUS_INDEX = 14; // ----------- FeedItem indices public static final int KEY_CONTENT_ENCODED_INDEX = 2; public static final int KEY_PUBDATE_INDEX = 3; @@ -66,6 +69,7 @@ public class PodDBAdapter { public static final int KEY_FEED_INDEX = 9; public static final int KEY_HAS_SIMPLECHAPTERS_INDEX = 10; public static final int KEY_ITEM_IDENTIFIER_INDEX = 11; + public static final int KEY_ITEM_FLATTR_STATUS_INDEX = 12; // ---------- FeedMedia indices public static final int KEY_DURATION_INDEX = 1; public static final int KEY_POSITION_INDEX = 5; @@ -73,6 +77,7 @@ public class PodDBAdapter { public static final int KEY_MIME_TYPE_INDEX = 7; public static final int KEY_PLAYBACK_COMPLETION_DATE_INDEX = 8; public static final int KEY_MEDIA_FEEDITEM_INDEX = 9; + public static final int KEY_PLAYED_DURATION_INDEX = 10; // --------- Download log indices public static final int KEY_FEEDFILE_INDEX = 1; public static final int KEY_FEEDFILETYPE_INDEX = 2; @@ -123,11 +128,13 @@ public class PodDBAdapter { public static final String KEY_HAS_CHAPTERS = "has_simple_chapters"; public static final String KEY_TYPE = "type"; public static final String KEY_ITEM_IDENTIFIER = "item_identifier"; + public static final String KEY_FLATTR_STATUS = "flattr_status"; public static final String KEY_FEED_IDENTIFIER = "feed_identifier"; public static final String KEY_REASON_DETAILED = "reason_detailed"; public static final String KEY_DOWNLOADSTATUS_TITLE = "title"; public static final String KEY_CHAPTER_TYPE = "type"; public static final String KEY_PLAYBACK_COMPLETION_DATE = "playback_completion_date"; + public static final String KEY_PLAYED_DURATION = "played_duration"; // Table names public static final String TABLE_NAME_FEEDS = "Feeds"; @@ -149,8 +156,8 @@ public class PodDBAdapter { + KEY_DESCRIPTION + " TEXT," + KEY_PAYMENT_LINK + " TEXT," + KEY_LASTUPDATE + " TEXT," + KEY_LANGUAGE + " TEXT," + KEY_AUTHOR + " TEXT," + KEY_IMAGE + " INTEGER," + KEY_TYPE + " TEXT," - + KEY_FEED_IDENTIFIER + " TEXT)"; - ; + + KEY_FEED_IDENTIFIER + " TEXT," + + KEY_FLATTR_STATUS + " LONG)"; private static final String CREATE_TABLE_FEED_ITEMS = "CREATE TABLE " + TABLE_NAME_FEED_ITEMS + " (" + TABLE_PRIMARY_KEY + KEY_TITLE @@ -158,7 +165,8 @@ public class PodDBAdapter { + " INTEGER," + KEY_READ + " INTEGER," + KEY_LINK + " TEXT," + KEY_DESCRIPTION + " TEXT," + KEY_PAYMENT_LINK + " TEXT," + KEY_MEDIA + " INTEGER," + KEY_FEED + " INTEGER," - + KEY_HAS_CHAPTERS + " INTEGER," + KEY_ITEM_IDENTIFIER + " TEXT)"; + + KEY_HAS_CHAPTERS + " INTEGER," + KEY_ITEM_IDENTIFIER + " TEXT," + + KEY_FLATTR_STATUS + " LONG)"; private static final String CREATE_TABLE_FEED_IMAGES = "CREATE TABLE " + TABLE_NAME_FEED_IMAGES + " (" + TABLE_PRIMARY_KEY + KEY_TITLE @@ -171,7 +179,8 @@ public class PodDBAdapter { + " TEXT," + KEY_DOWNLOADED + " INTEGER," + KEY_POSITION + " INTEGER," + KEY_SIZE + " INTEGER," + KEY_MIME_TYPE + " TEXT," + KEY_PLAYBACK_COMPLETION_DATE + " INTEGER," - + KEY_FEEDITEM + " INTEGER)"; + + KEY_FEEDITEM + " INTEGER," + + KEY_PLAYED_DURATION + " INTEGER)"; private static final String CREATE_TABLE_DOWNLOAD_LOG = "CREATE TABLE " + TABLE_NAME_DOWNLOAD_LOG + " (" + TABLE_PRIMARY_KEY + KEY_FEEDFILE @@ -206,7 +215,8 @@ public class PodDBAdapter { TABLE_NAME_FEED_ITEMS + "." + KEY_PAYMENT_LINK, KEY_MEDIA, TABLE_NAME_FEED_ITEMS + "." + KEY_FEED, TABLE_NAME_FEED_ITEMS + "." + KEY_HAS_CHAPTERS, - TABLE_NAME_FEED_ITEMS + "." + KEY_ITEM_IDENTIFIER}; + TABLE_NAME_FEED_ITEMS + "." + KEY_ITEM_IDENTIFIER, + TABLE_NAME_FEED_ITEMS + "." + KEY_FLATTR_STATUS}; /** * Contains SEL_FI_SMALL as comma-separated list. Useful for raw queries. @@ -230,6 +240,7 @@ public class PodDBAdapter { public static final int IDX_FI_SMALL_FEED = 7; public static final int IDX_FI_SMALL_HAS_CHAPTERS = 8; public static final int IDX_FI_SMALL_ITEM_IDENTIFIER = 9; + public static final int IDX_FI_SMALL_FLATTR_STATUS = 10; /** * Select id, description and content-encoded column from feeditems. @@ -311,6 +322,7 @@ public class PodDBAdapter { values.put(KEY_LASTUPDATE, feed.getLastUpdate().getTime()); values.put(KEY_TYPE, feed.getType()); values.put(KEY_FEED_IDENTIFIER, feed.getFeedIdentifier()); + values.put(KEY_FLATTR_STATUS, feed.getFlattrStatus().toLong()); if (feed.getId() == 0) { // Create new entry if (AppConfig.DEBUG) @@ -391,6 +403,7 @@ public class PodDBAdapter { ContentValues values = new ContentValues(); values.put(KEY_POSITION, media.getPosition()); values.put(KEY_DURATION, media.getDuration()); + values.put(KEY_PLAYED_DURATION, media.getPlayedDuration()); db.update(TABLE_NAME_FEED_MEDIA, values, KEY_ID + "=?", new String[]{String.valueOf(media.getId())}); } else { @@ -477,6 +490,7 @@ public class PodDBAdapter { values.put(KEY_READ, item.isRead()); values.put(KEY_HAS_CHAPTERS, item.getChapters() != null); values.put(KEY_ITEM_IDENTIFIER, item.getItemIdentifier()); + values.put(KEY_FLATTR_STATUS, item.getFlattrStatus().toLong()); if (item.getId() == 0) { item.setId(db.insert(TABLE_NAME_FEED_ITEMS, null, values)); } else { @@ -1118,6 +1132,19 @@ public class PodDBAdapter { } feeditemCursor.close(); } + if (oldVersion <= 9) { + db.execSQL("ALTER TABLE " + TABLE_NAME_FEEDS + + " ADD COLUMN " + KEY_FLATTR_STATUS + + " LONG"); + db.execSQL("ALTER TABLE " + TABLE_NAME_FEED_ITEMS + + " ADD COLUMN " + KEY_FLATTR_STATUS + + " LONG"); + } + if (oldVersion <= 10) { + db.execSQL("ALTER TABLE " + TABLE_NAME_FEED_MEDIA + + " ADD COLUMN " + KEY_PLAYED_DURATION + + " INTEGER"); + } } } } diff --git a/src/de/danoeh/antennapod/util/flattr/FlattrStatus.java b/src/de/danoeh/antennapod/util/flattr/FlattrStatus.java new file mode 100644 index 000000000..b7302d937 --- /dev/null +++ b/src/de/danoeh/antennapod/util/flattr/FlattrStatus.java @@ -0,0 +1,67 @@ +package de.danoeh.antennapod.util.flattr; + +import java.util.Calendar; + +public class FlattrStatus { + private static final int STATUS_UNFLATTERED = 0; + private static final int STATUS_QUEUE = 1; + private static final int STATUS_FLATTRED = 2; + + private int status = STATUS_UNFLATTERED; + private Calendar lastFlattred; + + public FlattrStatus() { + lastFlattred = Calendar.getInstance(); + } + + public FlattrStatus(long status) { + lastFlattred = Calendar.getInstance(); + fromLong(status); + } + + public void setFlattred() { + status = STATUS_FLATTRED; + lastFlattred = Calendar.getInstance(); + } + + public void setUnflattred() { + status = STATUS_UNFLATTERED; + } + + public boolean getUnflattred() { + return status == STATUS_UNFLATTERED; + } + + public void setFlattrQueue() { + if (flattrable()) + status = STATUS_QUEUE; + } + + public void fromLong(long status) { + if (status == STATUS_UNFLATTERED || status == STATUS_QUEUE) + this.status = (int) status; + else { + this.status = STATUS_FLATTRED; + lastFlattred.setTimeInMillis(status); + } + } + + public long toLong() { + if (status == STATUS_UNFLATTERED || status == STATUS_QUEUE) + return status; + else { + return lastFlattred.getTimeInMillis(); + } + } + + public boolean flattrable() { + Calendar firstOfMonth = Calendar.getInstance(); + firstOfMonth.set(Calendar.DAY_OF_MONTH, Calendar.getInstance().getActualMinimum(Calendar.DAY_OF_MONTH)); + + return (status == STATUS_UNFLATTERED) || (status == STATUS_FLATTRED && firstOfMonth.after(lastFlattred) ); + } + + public boolean getFlattrQueue() { + return status == STATUS_QUEUE; + } +} diff --git a/src/de/danoeh/antennapod/util/flattr/FlattrThing.java b/src/de/danoeh/antennapod/util/flattr/FlattrThing.java new file mode 100644 index 000000000..f17ef1d83 --- /dev/null +++ b/src/de/danoeh/antennapod/util/flattr/FlattrThing.java @@ -0,0 +1,7 @@ +package de.danoeh.antennapod.util.flattr; + +public interface FlattrThing { + public String getTitle(); + public String getPaymentLink(); + public FlattrStatus getFlattrStatus(); +} diff --git a/src/de/danoeh/antennapod/util/flattr/FlattrUtils.java b/src/de/danoeh/antennapod/util/flattr/FlattrUtils.java index ca2c9eb0f..869435f73 100644 --- a/src/de/danoeh/antennapod/util/flattr/FlattrUtils.java +++ b/src/de/danoeh/antennapod/util/flattr/FlattrUtils.java @@ -1,9 +1,16 @@ package de.danoeh.antennapod.util.flattr; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; import java.util.EnumSet; +import java.util.List; +import java.util.ListIterator; +import java.util.TimeZone; import org.shredzone.flattr4j.FlattrService; import org.shredzone.flattr4j.exception.FlattrException; +import org.shredzone.flattr4j.model.Flattr; import org.shredzone.flattr4j.model.Thing; import org.shredzone.flattr4j.oauth.AccessToken; import org.shredzone.flattr4j.oauth.AndroidAuthenticator; @@ -119,6 +126,58 @@ public class FlattrUtils { Log.e(TAG, "clickUrl was called with null access token"); } } + + public static List retrieveFlattredThings() + throws FlattrException { + ArrayList myFlattrs = new ArrayList(); + + if (hasToken()) { + FlattrService fs = FlattrServiceCreator.getService(retrieveToken()); + + Calendar firstOfMonth = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + firstOfMonth.set(Calendar.MILLISECOND, 0); + firstOfMonth.set(Calendar.SECOND, 0); + firstOfMonth.set(Calendar.MINUTE, 0); + firstOfMonth.set(Calendar.HOUR_OF_DAY, 0); + firstOfMonth.set(Calendar.DAY_OF_MONTH, Calendar.getInstance().getActualMinimum(Calendar.DAY_OF_MONTH)); + + Date firstOfMonthDate = firstOfMonth.getTime(); + + // subscriptions some times get flattrd slightly before midnight - give it an hour leeway + firstOfMonthDate = new Date(firstOfMonthDate.getTime() - 60*60*1000); + + final int FLATTR_COUNT = 30; + final int FLATTR_MAXPAGE = 5; + + int page = 0; + do { + myFlattrs.ensureCapacity(FLATTR_COUNT*(page+1)); + + for (Flattr fl: fs.getMyFlattrs(FLATTR_COUNT, page)) { + if (fl.getCreated().after(firstOfMonthDate)) + myFlattrs.add(fl); + else + break; + } + page++; + } + while (myFlattrs.get(myFlattrs.size()-1).getCreated().after( firstOfMonthDate ) && page < FLATTR_MAXPAGE); + + if (AppConfig.DEBUG) { + Log.d(TAG, "Got my flattrs list of length " + Integer.toString(myFlattrs.size()) + " comparison date" + firstOfMonthDate); + + for (Flattr fl: myFlattrs) { + Thing thing = fl.getThing(); + Log.d(TAG, "Flattr thing: " + fl.getThingId() + " name: " + thing.getTitle() + " url: " + thing.getUrl() + " on: " + fl.getCreated()); + } + } + + } else { + Log.e(TAG, "retrieveFlattrdThings was called with null access token"); + } + + return myFlattrs; + } public static void handleCallback(Context context, Uri uri) { AndroidAuthenticator auth = createAuthenticator(); diff --git a/src/de/danoeh/antennapod/util/menuhandler/FeedItemMenuHandler.java b/src/de/danoeh/antennapod/util/menuhandler/FeedItemMenuHandler.java index e99a733dc..53316f9a4 100644 --- a/src/de/danoeh/antennapod/util/menuhandler/FeedItemMenuHandler.java +++ b/src/de/danoeh/antennapod/util/menuhandler/FeedItemMenuHandler.java @@ -14,6 +14,7 @@ import de.danoeh.antennapod.storage.DownloadRequestException; import de.danoeh.antennapod.storage.DownloadRequester; import de.danoeh.antennapod.util.QueueAccess; import de.danoeh.antennapod.util.ShareUtils; +import de.danoeh.antennapod.util.flattr.FlattrStatus; import java.util.List; @@ -110,7 +111,7 @@ public class FeedItemMenuHandler { mi.setItemVisibility(R.id.visit_website_item, false); } - if (selectedItem.getPaymentLink() == null) { + if (selectedItem.getPaymentLink() == null || !selectedItem.getFlattrStatus().flattrable()) { mi.setItemVisibility(R.id.support_item, false); } return true; @@ -158,8 +159,9 @@ public class FeedItemMenuHandler { context.startActivity(new Intent(Intent.ACTION_VIEW, uri)); break; case R.id.support_item: - new FlattrClickWorker(context, selectedItem.getPaymentLink()) - .executeAsync(); + selectedItem.getFlattrStatus().setFlattrQueue(); + DBWriter.setFlattredStatus(context, selectedItem); + new FlattrClickWorker(context).executeAsync(); break; case R.id.share_link_item: ShareUtils.shareFeedItemLink(context, selectedItem); diff --git a/src/de/danoeh/antennapod/util/menuhandler/FeedMenuHandler.java b/src/de/danoeh/antennapod/util/menuhandler/FeedMenuHandler.java index 446e024d9..ad3ded88b 100644 --- a/src/de/danoeh/antennapod/util/menuhandler/FeedMenuHandler.java +++ b/src/de/danoeh/antennapod/util/menuhandler/FeedMenuHandler.java @@ -19,6 +19,7 @@ import de.danoeh.antennapod.storage.DBWriter; import de.danoeh.antennapod.storage.DownloadRequestException; import de.danoeh.antennapod.storage.DownloadRequester; import de.danoeh.antennapod.util.ShareUtils; +import de.danoeh.antennapod.util.flattr.FlattrStatus; /** Handles interactions with the FeedItemMenu. */ public class FeedMenuHandler { @@ -38,9 +39,10 @@ public class FeedMenuHandler { Log.d(TAG, "Preparing options menu"); menu.findItem(R.id.mark_all_read_item).setVisible( selectedFeed.hasNewItems(true)); - if (selectedFeed.getPaymentLink() != null) { + if (selectedFeed.getPaymentLink() != null && selectedFeed.getFlattrStatus().flattrable()) menu.findItem(R.id.support_item).setVisible(true); - } + else + menu.findItem(R.id.support_item).setVisible(false); MenuItem refresh = menu.findItem(R.id.refresh_item); if (DownloadService.isRunning && DownloadRequester.getInstance().isDownloadingFile( @@ -78,8 +80,9 @@ public class FeedMenuHandler { context.startActivity(new Intent(Intent.ACTION_VIEW, uri)); break; case R.id.support_item: - new FlattrClickWorker(context, selectedFeed.getPaymentLink()) - .executeAsync(); + selectedFeed.getFlattrStatus().setFlattrQueue(); + DBWriter.setFlattredStatus(context, selectedFeed); + new FlattrClickWorker(context).executeAsync(); break; case R.id.share_link_item: ShareUtils.shareFeedlink(context, selectedFeed); diff --git a/src/instrumentationTest/de/test/antennapod/storage/DBTasksTest.java b/src/instrumentationTest/de/test/antennapod/storage/DBTasksTest.java index 7631a5787..6d9ad707a 100644 --- a/src/instrumentationTest/de/test/antennapod/storage/DBTasksTest.java +++ b/src/instrumentationTest/de/test/antennapod/storage/DBTasksTest.java @@ -74,7 +74,7 @@ public class DBTasksTest extends InstrumentationTestCase { File f = new File(destFolder, "file " + i); assertTrue(f.createNewFile()); files.add(f); - item.setMedia(new FeedMedia(0, item, 1, 0, 1L, "m", f.getAbsolutePath(), "url", true, new Date(NUM_ITEMS - i))); + item.setMedia(new FeedMedia(0, item, 1, 0, 1L, "m", f.getAbsolutePath(), "url", true, new Date(NUM_ITEMS - i), 0)); items.add(item); } @@ -112,7 +112,7 @@ public class DBTasksTest extends InstrumentationTestCase { assertTrue(f.createNewFile()); assertTrue(f.exists()); files.add(f); - item.setMedia(new FeedMedia(0, item, 1, 0, 1L, "m", f.getAbsolutePath(), "url", true, new Date(NUM_ITEMS - i))); + item.setMedia(new FeedMedia(0, item, 1, 0, 1L, "m", f.getAbsolutePath(), "url", true, new Date(NUM_ITEMS - i), 0)); items.add(item); } @@ -146,7 +146,7 @@ public class DBTasksTest extends InstrumentationTestCase { assertTrue(f.createNewFile()); assertTrue(f.exists()); files.add(f); - item.setMedia(new FeedMedia(0, item, 1, 0, 1L, "m", f.getAbsolutePath(), "url", true, new Date(NUM_ITEMS - i))); + item.setMedia(new FeedMedia(0, item, 1, 0, 1L, "m", f.getAbsolutePath(), "url", true, new Date(NUM_ITEMS - i), 0)); items.add(item); } diff --git a/src/instrumentationTest/de/test/antennapod/storage/DBWriterTest.java b/src/instrumentationTest/de/test/antennapod/storage/DBWriterTest.java index 0483a3084..527e410d2 100644 --- a/src/instrumentationTest/de/test/antennapod/storage/DBWriterTest.java +++ b/src/instrumentationTest/de/test/antennapod/storage/DBWriterTest.java @@ -64,7 +64,7 @@ public class DBWriterTest extends InstrumentationTestCase { feed.setItems(items); FeedItem item = new FeedItem(0, "Item", "Item", "url", new Date(), true, feed); - FeedMedia media = new FeedMedia(0, item, 1, 1, 1, "mime_type", dest.getAbsolutePath(), "download_url", true, null); + FeedMedia media = new FeedMedia(0, item, 1, 1, 1, "mime_type", dest.getAbsolutePath(), "download_url", true, null, 0); item.setMedia(media); items.add(item); @@ -108,7 +108,7 @@ public class DBWriterTest extends InstrumentationTestCase { assertTrue(enc.createNewFile()); itemFiles.add(enc); - FeedMedia media = new FeedMedia(0, item, 1, 1, 1, "mime_type", enc.getAbsolutePath(), "download_url", true, null); + FeedMedia media = new FeedMedia(0, item, 1, 1, 1, "mime_type", enc.getAbsolutePath(), "download_url", true, null, 0); item.setMedia(media); } @@ -169,7 +169,7 @@ public class DBWriterTest extends InstrumentationTestCase { assertTrue(enc.createNewFile()); itemFiles.add(enc); - FeedMedia media = new FeedMedia(0, item, 1, 1, 1, "mime_type", enc.getAbsolutePath(), "download_url", true, null); + FeedMedia media = new FeedMedia(0, item, 1, 1, 1, "mime_type", enc.getAbsolutePath(), "download_url", true, null, 0); item.setMedia(media); } @@ -317,7 +317,7 @@ public class DBWriterTest extends InstrumentationTestCase { File enc = new File(destFolder, "file " + i); itemFiles.add(enc); - FeedMedia media = new FeedMedia(0, item, 1, 1, 1, "mime_type", enc.getAbsolutePath(), "download_url", false, null); + FeedMedia media = new FeedMedia(0, item, 1, 1, 1, "mime_type", enc.getAbsolutePath(), "download_url", false, null, 0); item.setMedia(media); } @@ -389,7 +389,7 @@ public class DBWriterTest extends InstrumentationTestCase { File enc = new File(destFolder, "file " + i); itemFiles.add(enc); - FeedMedia media = new FeedMedia(0, item, 1, 1, 1, "mime_type", enc.getAbsolutePath(), "download_url", false, null); + FeedMedia media = new FeedMedia(0, item, 1, 1, 1, "mime_type", enc.getAbsolutePath(), "download_url", false, null, 0); item.setMedia(media); } @@ -430,7 +430,7 @@ public class DBWriterTest extends InstrumentationTestCase { Feed feed = new Feed("url", new Date(), "title"); feed.setItems(new ArrayList()); FeedItem item = new FeedItem(0, "title", "id", "link", new Date(), true, feed); - FeedMedia media = new FeedMedia(0, item, 10, 0, 1, "mime", null, "url", false, playbackCompletionDate); + FeedMedia media = new FeedMedia(0, item, 10, 0, 1, "mime", null, "url", false, playbackCompletionDate, 0); feed.getItems().add(item); item.setMedia(media); PodDBAdapter adapter = new PodDBAdapter(context);