/*******************************************************************************
* Copyright 2012 Crazywater
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package de.knufficast.watchers;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import org.xmlpull.v1.XmlPullParserException;
import android.app.AlarmManager;
import android.app.IntentService;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.SystemClock;
import android.util.Log;
import de.knufficast.App;
import de.knufficast.logic.FeedDownloader;
import de.knufficast.logic.db.Configuration;
import de.knufficast.logic.db.DBEpisode;
import de.knufficast.logic.db.DBFeed;
import de.knufficast.logic.db.XMLToDBWriter;
import de.knufficast.logic.xml.XMLFeed;
import de.knufficast.util.BooleanCallback;
import de.knufficast.util.NetUtil;
/**
* A service to refresh the feeds.
*
* @author crazywater
*
*/
public class UpdaterService extends IntentService {
private final NetUtil netUtil;
private final BooleanCallback<DBFeed, DBFeed> callback;
private static AtomicBoolean refreshing = new AtomicBoolean();
public UpdaterService() {
super("UpdaterService");
netUtil = new NetUtil(this);
callback = null;
}
public UpdaterService(BooleanCallback<DBFeed, DBFeed> callback) {
super("UpdaterService");
netUtil = new NetUtil(this);
this.callback = callback;
}
/**
* Refreshes all subscribed feeds. Locks the state such that only one
* refresher can run at the same time.
*/
public boolean refreshAll() {
HttpURLConnection.setFollowRedirects(true);
if (!refreshing.getAndSet(true)) {
Configuration config = App.get().getConfiguration();
List<DBFeed> allFeeds = config.getAllFeeds();
boolean refreshSuccessful = true;
// refresh feeds
for (DBFeed feed : allFeeds) {
Log.d("UpdaterService", "Refreshing Feed " + feed.getFeedUrl());
try {
refresh(config, feed);
if (callback != null) {
callback.success(feed);
}
} catch (Exception e) {
e.printStackTrace();
refreshSuccessful = false;
if (callback != null) {
callback.fail(feed);
}
}
}
// auto-enqueue all new episodes
if (App.get().getConfiguration().autoEnqueue()) {
for (DBFeed feed : allFeeds) {
for (DBEpisode episode : feed.getEpisodes()) {
if (episode.isNew() && episode.hasDownload()) {
App.get().getQueue().add(episode);
episode.setNew(false);
}
}
}
}
App.get().save();
refreshing.set(false);
return refreshSuccessful;
}
return false;
}
private void retryDownloads() {
// restart downloads of new items
QueueDownloader.get().restartDownloads();
}
private void cancelDownloads() {
QueueDownloader.get().cancelDownloads();
}
private void refresh(Configuration config, DBFeed feed) throws IOException,
XmlPullParserException {
boolean needsUpdate = true;
HttpURLConnection conn = (HttpURLConnection) new URL(feed.getFeedUrl())
.openConnection();
if (feed.getLastUpdated() > 0) {
conn.setIfModifiedSince(feed.getLastUpdated());
}
if (feed.getETag() != null) {
conn.addRequestProperty("If-None-Match", feed.getETag());
}
conn.connect();
if (conn.getResponseCode() == HttpURLConnection.HTTP_NOT_MODIFIED) {
needsUpdate = false;
}
long lastModifiedTimestamp = conn.getLastModified();
if (lastModifiedTimestamp > 0
&& lastModifiedTimestamp <= feed.getLastUpdated()) {
needsUpdate = false;
}
if (needsUpdate) {
List<XMLFeed> feeds = new FeedDownloader().getFeeds(conn);
new XMLToDBWriter().mergeFeeds(feeds);
}
}
@Override
protected void onHandleIntent(Intent intent) {
long nowTime = System.currentTimeMillis();
Configuration config = App.get().getConfiguration();
// only update feeds if last time is long enough ago
// we need to check this because otherwise we'd refresh
// upon every network change
if (nowTime - config.getLastUpdate() >= config.getUpdateInterval()) {
boolean refreshSuccessful = netUtil.isOnline();
if (netUtil.isOnline()) {
if (!config.refreshNeedsWifi() || netUtil.isOnWifi()) {
refreshSuccessful = refreshAll();
} else {
refreshSuccessful = false;
}
}
if (refreshSuccessful) {
config.setLastUpdate(nowTime);
App.get().save();
}
}
if (netUtil.isOnline() && config.autoRetry()) {
retryDownloads();
}
if (!netUtil.isOnWifi() && config.downloadNeedsWifi()) {
cancelDownloads();
}
}
public static void init() {
AlarmManager alarmMgr = (AlarmManager) App.get().getSystemService(
Context.ALARM_SERVICE);
Intent intent = new Intent(App.get(), UpdateAlarmReceiver.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast(App.get(), 0,
intent, 0);
// remove any pending events
alarmMgr.cancel(pendingIntent);
// start a repeating intent
alarmMgr.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime(), App.get().getConfiguration()
.getUpdateInterval(), pendingIntent);
}
}