package com.newsrob;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.net.UnknownHostException;
import java.text.ParseException;
import java.util.Collection;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import javax.xml.parsers.FactoryConfigurationError;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.http.NoHttpResponseException;
import org.apache.http.client.ClientProtocolException;
import org.xml.sax.SAXException;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.wifi.WifiManager;
import android.net.wifi.WifiManager.WifiLock;
import android.os.Handler;
import android.os.IBinder;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.util.Log;
import com.newsrob.EntryManager.SyncJobStatus;
import com.newsrob.activities.UIHelper;
import com.newsrob.download.DownloadCancelledException;
import com.newsrob.download.DownloadContext;
import com.newsrob.download.DownloadException;
import com.newsrob.download.DownloadTimedOutException;
import com.newsrob.download.WebPageDownloadDirector;
import com.newsrob.jobs.Job;
import com.newsrob.jobs.ModelUpdateResult;
import com.newsrob.jobs.SynchronizeModelFailed;
import com.newsrob.jobs.SynchronizeModelSucceeded;
import com.newsrob.storage.IStorageAdapter;
import com.newsrob.util.PreviewGenerator;
import com.newsrob.util.SDK9Helper;
import com.newsrob.util.Timing;
import com.newsrob.util.U;
public class SynchronizationService extends Service {
private static final String TAG = SynchronizationService.class.getSimpleName();
static final String ACTION_SYNC_UPLOAD_ONLY = "upload_only";
public static final String EXTRA_MANUAL_SYNC = "manual_sync";
private static final String PREF_KEY_LAST_STARTED = "com.newsrob.synchronization.lastStarted";
private static WakeLock wl;
private Handler handler;
private static final Class[] mStartForegroundSignature = new Class[] { int.class, Notification.class };
private static final Class[] mStopForegroundSignature = new Class[] { boolean.class };
private NotificationManager mNM;
private Method mStartForeground;
private Method mStopForeground;
private Object[] mStartForegroundArgs = new Object[2];
private Object[] mStopForegroundArgs = new Object[1];
private EntryManager entryManager;
private boolean shouldDownloadArticlesInParallel;
@Override
public void onCreate() {
super.onCreate();
shouldDownloadArticlesInParallel = "1".equals(NewsRob.getDebugProperties(this).getProperty(
"downloadArticlesInParallel", "1"));
mNM = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
try {
mStartForeground = getClass().getMethod("startForeground", mStartForegroundSignature);
mStopForeground = getClass().getMethod("stopForeground", mStopForegroundSignature);
} catch (NoSuchMethodException e) {
// Running on an older platform.
mStartForeground = mStopForeground = null;
}
if (NewsRob.isDebuggingEnabled(this))
Log.d(TAG, "onCreate() called.");
handler = new Handler();
}
@Override
public void onStart(Intent intent, int startId) {
super.onStart(intent, startId);
if (intent == null) {
stopSelf();
getEntryManager().getNewsRobNotificationManager().cancelSyncInProgressNotification();
Log.d(TAG, "onStart() called with intent == null. Stopping self.");
}
if (getEntryManager().isModelCurrentlyUpdated())
return;
String lastStarted = getEntryManager().getSharedPreferences().getString(PREF_KEY_LAST_STARTED, "");
if (!"".equals(lastStarted)) {
String message = "The synchronization started at " + lastStarted + " was ended prematurely.";
if (NewsRob.isDebuggingEnabled(this))
PL.log(message, this);
else
Log.w(TAG, message);
}
setLastStarted(new Date().toString());
if (NewsRob.isDebuggingEnabled(this))
Log.d(TAG, "onStart() called. startId=" + startId + " intent=" + intent);
boolean uO = false;
boolean mS = false;
try {
uO = ACTION_SYNC_UPLOAD_ONLY.equals(intent.getAction());
mS = intent.getBooleanExtra(EXTRA_MANUAL_SYNC, false);
} catch (NullPointerException npe) {
//
}
final boolean uploadOnly = uO;
final boolean manualSync = mS;
new Thread(new Runnable() {
public void run() {
try {
if (wl == null)
// throw new
// RuntimeException("Oh, oh. No wake lock acquired!");
acquireWakeLock(getApplicationContext());
doSync(uploadOnly, manualSync);
} finally {
handler.post(new Runnable() {
public void run() {
stopSelf();
}
});
}
}
}).start();
}
private void setLastStarted(String value) {
SDK9Helper.apply(getEntryManager().getSharedPreferences().edit().putString(PREF_KEY_LAST_STARTED, value));
}
private void resetLastStarted() {
setLastStarted("");
}
@Override
public void onLowMemory() {
super.onLowMemory();
PL.log(this, "onLowMemory() called.", null, getApplicationContext());
}
@Override
public void onDestroy() {
super.onDestroy();
PL.log(this, "SynchronizationService.onDestroy() called.", null, getApplicationContext());
}
@Override
public IBinder onBind(Intent intent) {
PL.log(this, "onBind() called.", null, getApplicationContext());
return null;
}
@Override
public void onRebind(Intent intent) {
super.onRebind(intent);
PL.log(this, "onRebind() called.", null, getApplicationContext());
}
@Override
public boolean onUnbind(Intent intent) {
PL.log(this, "onUnbind() called.", null, getApplicationContext());
return super.onUnbind(intent);
}
protected synchronized void doSync(final boolean uploadOnly, final boolean manualSync) {
PL.log(this, "doSync invoked. (-1) wl=" + wl, null, getApplicationContext());
final Context ctx = getApplicationContext();
try {
PL.log(this, "doSync invoked. (0)", null, getApplicationContext());
WifiManager wifiManager = null; // (WifiManager)
// getSystemService(Context.WIFI_SERVICE);
if (false) {
WifiLock wiFiLock = wifiManager.createWifiLock("NewsRobSync");
wiFiLock.acquire();
}
PL.log(this, "doSync invoked. (1)", null, getApplicationContext());
U.setLowPrio();
PL.log(this, "doSync invoked. (2)", null, getApplicationContext());
final EntryManager entryManager = getEntryManager();
final EntriesRetriever grf = entryManager.getEntriesRetriever();
final IStorageAdapter fileContextAdapter = entryManager.getStorageAdapter();
PL.log(this, "doSync invoked. (3)", null, getApplicationContext());
if (entryManager.isModelCurrentlyUpdated()) {
PL.log(this, "doSync invoked. (3.4)", null, getApplicationContext());
return;
}
PL.log(this, "doSync invoked. (3.5)", null, getApplicationContext());
entryManager.lockModel("SSer.doSync");
PL.log(this, "doSync invoked. (4)", null, getApplicationContext());
PL.log("SynchronizationService. Used settings: "
+ SettingsRenderer.renderSettings(entryManager, new StringBuilder("\n")),
SynchronizationService.this);
Throwable caughtThrowable = null;
PL.log("SynchronizationService - start", SynchronizationService.this);
PL.log("Battery level=" + U.getBatteryChargedPercent(ctx) + "%.", ctx);
PL.log("Last successful login: " + entryManager.getLastSuccessfulLogin(), SynchronizationService.this);
Timing t = new Timing("Synchronization Runnable", this);
long started = System.currentTimeMillis();
Log.i(TAG, "Synchronization started at " + new Date().toString() + ". started=" + started);
final SyncJobStatus syncJobStatus = new SyncJobStatus();
// last used
long lastUsed = entryManager.getLastUsed();
final DownloadContext downloadContext = new DownloadContext();
PL.log(this, "doSync invoked. (5)", null, getApplicationContext());
ModelUpdateResult result = null;
try {
PL.log("Run Mark - in Try", SynchronizationService.this);
if (!uploadOnly) {
try {
if (!Feed.restoreFeedsIfNeccesary(this))
Feed.saveFeedSettings(this);
} catch (Exception e) {
e.printStackTrace();
}
}
entryManager.fireModelUpdateStarted("Synchronization", uploadOnly, manualSync);
List<Job> jobList = new LinkedList<Job>();
Job deleteReadArticlesJob = new DeleteArticlesJob(SynchronizationService.this, entryManager,
syncJobStatus);
Job reduceToCapacityJob = new ReduceToCapacityJob(SynchronizationService.this, entryManager,
syncJobStatus);
if (!uploadOnly) {
if (entryManager.shouldReadItemsBeDeleted())
jobList.add(deleteReadArticlesJob);
jobList.add(reduceToCapacityJob);
}
if (false)
jobList.add(new Job("Submitting annotated articles", entryManager) {
@Override
public void run() throws Exception {
if (entryManager.syncCurrentlyEnabled(manualSync))
entryManager.getEntriesRetriever().submitNotes(this);
}
});
jobList.add(new SyncChangedArticlesStatusJob(SynchronizationService.this, entryManager, syncJobStatus,
manualSync));
if (!uploadOnly && entryManager.shouldReadItemsBeDeleted())
jobList.add(deleteReadArticlesJob);
if (!uploadOnly) {
jobList.add(new Job("Unsubscribing from feeds", entryManager) {
@Override
public void run() throws Exception {
if (!entryManager.syncCurrentlyEnabled(manualSync))
return;
Cursor c = entryManager.getFeeds2UnsubscribeCursor();
try {
while (c.moveToNext()) {
String feedAtomId = c.getString(1);
PL.log("Unsubscribing: " + feedAtomId, SynchronizationService.this);
entryManager.getEntriesRetriever().unsubscribeFeed(feedAtomId);
}
} finally {
c.close();
}
}
});
}
if (!uploadOnly)
jobList.add(new FetchUnreadArticlesJob(SynchronizationService.this, entryManager, syncJobStatus,
manualSync));
if (!uploadOnly)
jobList.add(new Job("Daily update of subscriptions (feed titles)", entryManager) {
@Override
public void run() throws IOException, ParserConfigurationException, SAXException,
GRTokenExpiredException {
if (entryManager.syncCurrentlyEnabled(manualSync)) {
grf.updateSubscriptionList(entryManager, this);
entryManager.fireModelUpdated();
}
}
});
if (!uploadOnly)
jobList.add(reduceToCapacityJob);
if (!uploadOnly && entryManager.shouldReadItemsBeDeleted())
jobList.add(deleteReadArticlesJob);
PL.log(this, "doSync invoked. (6)", null, getApplicationContext());
if (!uploadOnly) {
// make sure that a manual sync moves the automatic sync
// forward,
// i.e. when pushing "Refresh" in the middle of a 24h sync,
// reset timer to 0, so that it will be another 24h from
// this
// point on
entryManager.getScheduler().resetBackgroundSchedule();
final SyncJobStatus sjStatus = new SyncJobStatus();
jobList.add(new SyncJob(ctx, entryManager, sjStatus, "Downloading articles") {
private Collection<Long> entries2Download;
@Override
public int doRun() {
if (entryManager.getSharedPreferences()
.getString(EntryManager.SETTINGS_STORAGE_ASSET_DOWNLOAD, EntryManager.DOWNLOAD_YES)
.equals(EntryManager.DOWNLOAD_NO)) {
Log.d(TAG,
"Downloading of assets is disabled in the settings. Therefore skipping downloading webpages.");
return actual;
}
Timing tSql = new Timing("SQL Query findAllToDownload", SynchronizationService.this);
entries2Download = entryManager.findAllArticleIds2Download();
target = entries2Download.size();
tSql.stop();
Timing tOutter = new Timing("Downloading all " + entries2Download.size()
+ " pages or well, the ones that were downloaded", SynchronizationService.this);
// shouldDownloadArticlesInParallel = true;
final int numberOfThreads = shouldDownloadArticlesInParallel && !U.isScreenOn(ctx) ? 3 : 1;
PL.log("Instantiating Download Articles ScheduledExecutorService for " + numberOfThreads
+ " threads.", ctx);
final ScheduledExecutorService pool = Executors.newScheduledThreadPool(numberOfThreads);
int count = 0;
try {
actual = 1;
entryManager.fireStatusUpdated();
for (Long articleId : entries2Download) {
// get the current data
// LATER use a real cursor and somehow find
// out
// when
// data became stale
Entry entry = entryManager.findArticleById(articleId);
if (entry == null)
continue;
if (!entryManager.downloadContentCurrentlyEnabled(manualSync))
return actual;
if (!fileContextAdapter.canWrite()) {
Log.d(TAG,
"File context adapter ("
+ fileContextAdapter.getClass().getName()
+ ") cannot be written to at the moment. Mounted? Read Only? Not downloading web pages.");
return actual;
}
if (isCancelled())
break;
if (entry.getReadState() == ReadState.READ && !entry.isStarred())
continue;
int resolvedDownloadPref = entry.getResolvedDownloadPref(entryManager);
if (resolvedDownloadPref == Feed.DOWNLOAD_HEADERS_ONLY) {
// entry.setDownloaded(Entry.STATE_DOWNLOADED_FULL_PAGE
// :
// Entry.STATE_DOWNLOADED_FEED_CONTENT);
// entry.setError(null);
// entryManager.fireModelUpdated();
continue;
}
// check against the db, because in the
// meantime
// the
// read status might have changed
boolean downloadTheWholePage = (resolvedDownloadPref == Feed.DOWNLOAD_PREF_FEED_AND_MOBILE_WEBPAGE || resolvedDownloadPref == Feed.DOWNLOAD_PREF_FEED_AND_WEBPAGE);
String summary = entry.getContent() != null ? entry.getContent() : UIHelper
.linkize(entry.getAlternateHRef(), entry.getTitle());
WebPageDownloadTask task = new WebPageDownloadTask(entryManager,
fileContextAdapter, this, entry, summary, downloadTheWholePage, manualSync,
downloadContext);
if (true)
// pool.submit(task);
pool.schedule(task, count++ * 500, TimeUnit.MILLISECONDS);
else
try {
task.call();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
} finally {
PL.log(this, "doSync invoked. (6.1 Pool Shutdown 1)", null, getApplicationContext());
pool.shutdown();
PL.log(this, "doSync invoked. (6.2 Pool Shutdown 2)", null, getApplicationContext());
try {
while (true) {
// wait and check if the pool is done.
boolean terminated = pool.awaitTermination(2, TimeUnit.SECONDS);
// done?
if (terminated)
break;
boolean terminate = false;
if (!entryManager.downloadContentCurrentlyEnabled(manualSync))
terminate = true;
if (!terminate && !fileContextAdapter.canWrite()) {
Log.d(TAG,
"File context adapter ("
+ fileContextAdapter.getClass().getName()
+ ") cannot be written to at the moment. Mounted? Read Only? Not downloading web pages.");
terminate = true;
}
if (!terminate && isCancelled())
terminate = true;
if (terminate) {
PL.log("Terminating downloadpagetask pool", ctx);
pool.shutdownNow();
break;
}
// all good so far go back to the
// beginning
// and check those
}
} catch (InterruptedException e) {
// Ignore
e.printStackTrace();
PL.log(this, "Interrupted Exception", e, ctx);
} finally {
PL.log(this, "doSync invoked. (6.3 Pool ShutdownNow 1)", null,
getApplicationContext());
pool.shutdownNow();
PL.log(this, "doSync invoked. (6.4 Pool ShutdownNow 2)", null,
getApplicationContext());
}
}
tOutter.stop();
return actual;
}
});
jobList.add(new Job("Vacuuming database ... ", entryManager) {
@Override
public void run() throws Throwable {
entryManager.vacuumDb();
}
});
}
PL.log("Run Mark - Jobs added", this);
PL.log(this, "doSync invoked. (7)", null, getApplicationContext());
entryManager.runJobs(jobList);
PL.log(this, "doSync invoked. (7.1 After Run Jobs)", null, getApplicationContext());
Log.d(TAG, "NoOfEntriesUpdated=" + syncJobStatus.noOfEntriesUpdated);
Log.d(TAG, "NoOfEntriesFetched=" + syncJobStatus.noOfEntriesFetched);
PL.log("Run Mark - Mission accomplished. -> complete ", this);
result = new SynchronizeModelSucceeded(syncJobStatus.noOfEntriesUpdated);
} catch (Throwable throwable) {
result = new SynchronizeModelFailed(throwable);
Log.d(TAG, "Problem during synchronization.", throwable);
PL.log(this, "Problem during synchronization", throwable, ctx);
} finally {
PL.log("Run Mark - In Finally", this);
entryManager.unlockModel("SSer.doSync");
entryManager.clearCancelState();
entryManager.fireModelUpdateFinished(result);
entryManager.fireStatusUpdated();
Log.i(TAG, "Synchronization finished at " + new Date().toString() + ". started=" + started);
t.stop();
PL.log(this, "doSync invoked. (7.2)", null, getApplicationContext());
if (!uploadOnly)
entryManager.setLastSync(caughtThrowable == null);
int noOfNewArticles = entryManager.getNoOfNewArticlesSinceLastUsed(lastUsed);
entryManager.getNewsRobNotificationManager().notifyNewArticles(entryManager, lastUsed, noOfNewArticles);
PL.log("Run Mark - End of Finally", this);
PL.log("Battery level=" + U.getBatteryChargedPercent(ctx) + "%.", ctx);
resetLastStarted();
PL.log(this, "doSync invoked. (8)", null, getApplicationContext());
}
} finally {
PL.log(this, "doSync invoked. (9)", null, getApplicationContext());
releaseWakeLock(ctx);
PL.log(this, "doSync invoked. (9.1)", null, getApplicationContext());
// wiFiLock.release(); // TODO
}
PL.log(this, "doSync invoked. (10)", null, getApplicationContext());
PL.log("SynchronizationService. Used settings: "
+ SettingsRenderer.renderSettings(entryManager, new StringBuilder("\n")), SynchronizationService.this);
}
private EntryManager getEntryManager() {
if (entryManager == null)
entryManager = EntryManager.getInstance(getApplicationContext());
return entryManager;
}
public static synchronized void acquireWakeLock(Context ctx) {
PL.log(SynchronizationService.class.getSimpleName() + ": Trying to acquire WakeLock wl=" + wl, ctx);
if (wl != null) {
PL.log(SynchronizationService.class.getSimpleName()
+ ": Trying to acquire WakeLock, even though it exists already. wl=" + wl, ctx);
return;
}
PowerManager pm = (PowerManager) ctx.getSystemService(Context.POWER_SERVICE);
wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
wl.acquire();
PL.log(SynchronizationService.class.getSimpleName() + ": Trying to acquire WakeLock: success wl=" + wl, ctx);
}
public static synchronized void releaseWakeLock(Context ctx) {
PL.log(SynchronizationService.class.getSimpleName() + ": Trying to release WakeLock wl=" + wl, ctx);
try {
if (wl != null)
wl.release();
else
PL.log(SynchronizationService.class.getSimpleName() + ": Trying to release WakeLock: WL Was null. wl="
+ wl, ctx);
wl = null;
PL.log(SynchronizationService.class.getSimpleName() + ": Trying to release WakeLock: success wl=" + wl, ctx);
} catch (Exception e) {
Log.e(TAG, "Oops. Problem when releasing wake lock.", e);
PL.log(SynchronizationService.class.getSimpleName() + ": Trying to release WakeLock: error wl=" + wl, e,
ctx);
}
}
}
abstract class SyncJob extends Job {
private EntryManager entryManager;
private Context context;
private SyncJobStatus status;
public int target;
public int actual;
SyncJob(Context context, EntryManager entryManager, SyncJobStatus status, String message) {
super(message, entryManager);
this.entryManager = entryManager;
this.context = context;
this.status = status;
}
protected EntryManager getEntryManager() {
return entryManager;
}
protected Context getContext() {
return context;
}
protected SyncJobStatus getSyncJobStatus() {
return status;
}
@Override
public boolean isProgressMeassurable() {
return target != -1;
}
@Override
public int[] getProgress() {
return new int[] { actual, target };
}
protected abstract int doRun() throws Throwable;
public void run() throws Throwable {
PL.log("About to be executed: " + getJobDescription(), context);
target = -1;
actual = -1;
int noOfArticlesAffected = doRun();
PL.log("No of articles affected=" + noOfArticlesAffected, context);
status.noOfEntriesUpdated += noOfArticlesAffected;
if (status.noOfEntriesUpdated > 0)
entryManager.fireModelUpdated();
}
}
class DeleteArticlesJob extends SyncJob {
public DeleteArticlesJob(Context context, EntryManager entryManager, SyncJobStatus status) {
super(context, entryManager, status, "Deleting read articles");
}
@Override
public int doRun() {
return getEntryManager().deleteReadArticles(this);
}
}
class ReduceToCapacityJob extends SyncJob {
public ReduceToCapacityJob(Context context, EntryManager entryManager, SyncJobStatus status) {
super(context, entryManager, status, "Deleting oldest articles over capacity");
}
@Override
public int doRun() {
return getEntryManager().reduceToCapacity(this);
}
}
class FetchUnreadArticlesJob extends SyncJob {
private boolean manualSync;
public FetchUnreadArticlesJob(Context context, EntryManager entryManager, SyncJobStatus status, boolean manualSync) {
super(context, entryManager, status, "Fetching new articles from Google Reader");
this.manualSync = manualSync;
}
@Override
public int doRun() throws ClientProtocolException, IllegalStateException, IOException, NeedsSessionException,
SAXException, ParserConfigurationException, FactoryConfigurationError, ReaderAPIException,
GRTokenExpiredException {
if (!getEntryManager().syncCurrentlyEnabled(manualSync))
return 0;
final EntriesRetriever grf = getEntryManager().getEntriesRetriever();
int noOfEntriesFetched = 0;
noOfEntriesFetched = grf.fetchNewEntries(getEntryManager(), this, manualSync);
getSyncJobStatus().noOfEntriesFetched = noOfEntriesFetched;
getEntryManager().fireModelUpdated();
return noOfEntriesFetched;
}
}
class SyncChangedArticlesStatusJob extends SyncJob {
private boolean manualSync;
SyncChangedArticlesStatusJob(Context context, EntryManager entryManager, SyncJobStatus status, boolean manualSync) {
super(context, entryManager, status, "Sync status of changed articles");
this.manualSync = manualSync;
}
@Override
public int doRun() throws MalformedURLException, IOException, ParserConfigurationException,
FactoryConfigurationError, SAXException, ParseException, NeedsSessionException {
if (!getEntryManager().syncCurrentlyEnabled(manualSync))
return 0;
int noOfEntriesUpdated = getEntryManager().getEntriesRetriever().synchronizeWithGoogleReader(getEntryManager(),
this);
getSyncJobStatus().noOfEntriesUpdated += noOfEntriesUpdated;
if (noOfEntriesUpdated > 0)
getEntryManager().fireModelUpdated();
return noOfEntriesUpdated;
}
}
class WebPageDownloadTask implements Callable<Void> {
private static final String TAG = WebPageDownloadTask.class.getSimpleName();
private static ReentrantLock instapaperLock = new ReentrantLock();
private EntryManager entryManager;
private IStorageAdapter fileContextAdapter;
private SyncJob job;
private String entryShortAtomId;
private String pageUrl;
Entry entry;
private String summary;
private boolean downloadCompleteWebPage;
private boolean manualSync;
private DownloadContext downloadContext;
public WebPageDownloadTask(EntryManager entryManager, IStorageAdapter fileContextAdapter, SyncJob job, Entry entry,
String summary, boolean downloadCompleteWebPage, boolean manualSync, DownloadContext downloadContext) {
this.entryManager = entryManager;
this.fileContextAdapter = fileContextAdapter;
this.job = job;
this.entryShortAtomId = entry.getShortAtomId();
this.entry = entry;
this.pageUrl = entry.getBaseUrl(entryManager);
this.summary = summary;
this.downloadCompleteWebPage = downloadCompleteWebPage;
this.manualSync = manualSync;
this.downloadContext = downloadContext;
}
@Override
public Void call() throws Exception {
try {
U.setLowPrio();
Context ctx = entryManager.getContext();
// Tracking the hosts that timed out once, so
// that we then can skip trying other articles for that host.
// This is for pages like FAZ.net
Timing tInner = new Timing("Downloading page " + pageUrl, ctx);
final String downloadHost = new URL(entry.getAlternateHRef()).getHost().toString();
boolean downloadingFromInstapaper = false;
try {
// Single download only / Instapaper
if (downloadHost.contains("instapaper")) {
instapaperLock.lock();
downloadingFromInstapaper = true;
}
if (downloadContext.containsTimedOutHost(downloadHost)) {
Log.w(SynchronizationService.class.getSimpleName(), "Article " + entry.getTitle()
+ " not downloaded, because the host is on the timeout list.");
entry.setError("This host (" + downloadHost
+ ") timed out during the sync. We'll try again during next sync.");
} else {
// check free space
float freeSpaceLeft = fileContextAdapter.megaBytesFree();
Log.d(TAG, String.format("Free space remaining for downloads: %.2f MB.", freeSpaceLeft));
if (freeSpaceLeft < 0) {
PL.log(TAG + ": Oh no, free space left is a negative value ;-( Ignoring it.", ctx);
} else if (freeSpaceLeft < fileContextAdapter.megaBytesThreshold()) {
PL.log(TAG + ": Not enough space left to download page.", ctx);
entryManager.getNewsRobNotificationManager().createSyncSpaceExceededProblemNotification(
fileContextAdapter.megaBytesThreshold());
return null;
}
final long downloadStartedAt = System.currentTimeMillis();
WebPageDownloadDirector.downloadWebPage(entryShortAtomId, new URL(pageUrl), fileContextAdapter,
job, summary, downloadCompleteWebPage, entryManager, manualSync);
generatePreview(ctx);
entry.setDownloaded(downloadCompleteWebPage ? Entry.STATE_DOWNLOADED_FULL_PAGE
: Entry.STATE_DOWNLOADED_FEED_CONTENT);
entry.setError("Download took " + (System.currentTimeMillis() - downloadStartedAt) + " ms.");
}
} catch (Exception e) {
Log.e(TAG, "Problem dowloading page " + entry.getAlternateHRef() + ".", e);
Throwable cause = null;
if (e instanceof DownloadException) {
cause = ((DownloadException) e).getCause();
Log.d(TAG, "DownloadException cause=" + cause);
} else
Log.d(TAG, "Exception=" + e);
boolean downloadError = false;
if (e instanceof DownloadTimedOutException) {
Log.w(SynchronizationService.class.getSimpleName(), "Download for " + entry.getAlternateHRef()
+ " timed out. Adding host to timed out hosts list.");
downloadContext.addTimedOutHost(downloadHost);
entry.setError("Download timed out.");
entry.setDownloaded(Entry.STATE_DOWNLOAD_ERROR);
} else {
if (e instanceof DownloadCancelledException
|| cause != null
&& (cause instanceof FileNotFoundException || cause instanceof SocketTimeoutException
|| cause instanceof SocketException || cause instanceof NoHttpResponseException
|| cause instanceof UnknownHostException || cause instanceof DownloadCancelledException)) {
Log.d(TAG, "Caught a FNFE");
} else {
Log.d(TAG, "Marked download as error.");
downloadError = true;
}
StringBuilder renderedStackTrace = new StringBuilder();
U.renderStackTrace(e, renderedStackTrace);
entry.setError(cause != null ? "Cause: " + cause.getClass().getSimpleName() + ": "
+ cause.getMessage() : e.getClass().getSimpleName() + ": " + e.getMessage()
+ "\nStacktrace: " + renderedStackTrace);
entry.setDownloaded(downloadError ? Entry.STATE_DOWNLOAD_ERROR : Entry.STATE_NOT_DOWNLOADED);
}
} finally {
if (instapaperLock.isHeldByCurrentThread())
instapaperLock.unlock();
}
entryManager.updatedDownloaded(entry);
job.actual++;
entryManager.fireModelUpdated(entry.getAtomId());
entryManager.fireStatusUpdated();
tInner.stop();
} catch (Exception e) {
e.printStackTrace();
throw e;
}
return null;
}
private void generatePreview(Context ctx) {
// TODO
// only
// one
// instance
// TODO use display metrics?
// TODO use orientation? Larger thumbs for
// larger screens?
final float screenSizeFactor = getScreenSizeFactor(ctx);
final float previewScaleFactor = ctx.getResources().getDisplayMetrics().density;
File assetsDir = entry.getAssetsDir(entryManager);
PL.log("Generating preview for page "
+ entry.getAlternateHRef()
+ " successful?="
+ new PreviewGenerator(ctx, assetsDir, (int) (100 * previewScaleFactor * screenSizeFactor),
(int) (100 * previewScaleFactor * screenSizeFactor), (int) (6 * previewScaleFactor))
.generatePreview(), ctx);
}
private float getScreenSizeFactor(final Context ctx) {
int size = U.getScreenSize(ctx);
if (size > 0)
return 1.25f;
else if (size < 0)
return 0.75f;
return 1.0f;
}
}