From 42516faa2e2aa57114e824a262396e443563a140 Mon Sep 17 00:00:00 2001 From: Andrew Dolgov Date: Wed, 7 Dec 2011 14:59:30 +0300 Subject: implement offline image caching --- src/org/fox/ttrss/FeedsFragment.java | 5 +- src/org/fox/ttrss/ImageCacheService.java | 181 ++++++++++++++++++++++++++ src/org/fox/ttrss/MainActivity.java | 5 + src/org/fox/ttrss/OfflineArticleFragment.java | 28 +++- src/org/fox/ttrss/OfflineDownloadService.java | 76 +++++++++-- 5 files changed, 282 insertions(+), 13 deletions(-) create mode 100644 src/org/fox/ttrss/ImageCacheService.java (limited to 'src') diff --git a/src/org/fox/ttrss/FeedsFragment.java b/src/org/fox/ttrss/FeedsFragment.java index 0ead6de9..bb823dd0 100644 --- a/src/org/fox/ttrss/FeedsFragment.java +++ b/src/org/fox/ttrss/FeedsFragment.java @@ -7,6 +7,7 @@ import java.io.InputStream; import java.lang.reflect.Type; import java.net.MalformedURLException; import java.net.URL; +import java.net.URLConnection; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -308,7 +309,9 @@ public class FeedsFragment extends Fragment implements OnItemClickListener, OnSh else setLoadingStatus(R.string.blank, false); - if (m_enableFeedIcons && !m_feedIconsChecked) getFeedIcons(); + if (m_enableFeedIcons && !m_feedIconsChecked && + Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) + getFeedIcons(); return; } diff --git a/src/org/fox/ttrss/ImageCacheService.java b/src/org/fox/ttrss/ImageCacheService.java new file mode 100644 index 00000000..999b7437 --- /dev/null +++ b/src/org/fox/ttrss/ImageCacheService.java @@ -0,0 +1,181 @@ +package org.fox.ttrss; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.net.URLConnection; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Date; + +import android.app.ActivityManager; +import android.app.IntentService; +import android.app.NotificationManager; +import android.app.ActivityManager.RunningServiceInfo; +import android.content.Intent; +import android.os.Environment; +import android.util.Log; + +public class ImageCacheService extends IntentService { + + private final String TAG = this.getClass().getSimpleName(); + + public static final int NOTIFY_DOWNLOADING = 1; + + private static final String CACHE_PATH = "/org.fox.ttrss/image-cache/"; + + private int m_imagesDownloaded = 0; + + private NotificationManager m_nmgr; + + public ImageCacheService() { + super("ImageCacheService"); + } + + private boolean isDownloadServiceRunning() { + ActivityManager manager = (ActivityManager) getSystemService(ACTIVITY_SERVICE); + for (RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) { + if ("org.fox.ttrss.OfflineDownloadService".equals(service.service.getClassName())) { + return true; + } + } + return false; + } + + + @Override + public void onCreate() { + super.onCreate(); + m_nmgr = (NotificationManager)getSystemService(NOTIFICATION_SERVICE); + } + + protected static boolean isUrlCached(String url) { + String hashedUrl = md5(url); + + File storage = Environment.getExternalStorageDirectory(); + + File file = new File(storage.getAbsolutePath() + CACHE_PATH + "/" + hashedUrl + ".png"); + + return file.exists(); + } + + protected static String getCacheFileName(String url) { + String hashedUrl = md5(url); + + File storage = Environment.getExternalStorageDirectory(); + + File file = new File(storage.getAbsolutePath() + CACHE_PATH + "/" + hashedUrl + ".png"); + + return file.getAbsolutePath(); + } + + protected static void cleanupCache(boolean deleteAll) { + if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) { + File storage = Environment.getExternalStorageDirectory(); + File cachePath = new File(storage.getAbsolutePath() + CACHE_PATH); + + long now = new Date().getTime(); + + if (cachePath.isDirectory()) { + for (File file : cachePath.listFiles()) { + if (deleteAll || now - file.lastModified() > 1000*60*60*24*7) { + file.delete(); + } + } + } + } + } + + protected static String md5(String s) { + try { + MessageDigest digest = java.security.MessageDigest.getInstance("MD5"); + digest.update(s.getBytes()); + byte messageDigest[] = digest.digest(); + + StringBuffer hexString = new StringBuffer(); + for (int i=0; i" + @@ -134,7 +160,7 @@ public class OfflineArticleFragment extends Fragment implements OnClickListener "body { text-align : justify; }" + "" + "" + - "" + m_cursor.getString(m_cursor.getColumnIndex("content")) + ""; + "" + articleContent + ""; web.loadDataWithBaseURL(null, content, "text/html", "utf-8", null); diff --git a/src/org/fox/ttrss/OfflineDownloadService.java b/src/org/fox/ttrss/OfflineDownloadService.java index 7a44d48b..933cc75e 100644 --- a/src/org/fox/ttrss/OfflineDownloadService.java +++ b/src/org/fox/ttrss/OfflineDownloadService.java @@ -4,15 +4,25 @@ import java.lang.reflect.Type; import java.util.HashMap; import java.util.List; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.jsoup.select.Elements; + +import android.app.ActivityManager; +import android.app.ActivityManager.RunningServiceInfo; import android.app.IntentService; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; +import android.content.SharedPreferences; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteStatement; import android.os.Bundle; +import android.os.Environment; +import android.preference.PreferenceManager; import android.provider.BaseColumns; import android.util.Log; @@ -37,6 +47,9 @@ public class OfflineDownloadService extends IntentService { private NotificationManager m_nmgr; private boolean m_downloadInProgress = false; + private boolean m_downloadImages = false; + private int m_syncMax; + private SharedPreferences m_prefs; public OfflineDownloadService() { super("OfflineDownloadService"); @@ -46,13 +59,15 @@ public class OfflineDownloadService extends IntentService { public void onCreate() { super.onCreate(); m_nmgr = (NotificationManager)getSystemService(NOTIFICATION_SERVICE); + m_prefs = PreferenceManager + .getDefaultSharedPreferences(getApplicationContext()); + + m_downloadImages = m_prefs.getBoolean("offline_image_cache_enabled", false); + m_syncMax = m_prefs.getInt("offline_sync_max", OFFLINE_SYNC_MAX); + initDatabase(); } - /* public boolean getDownloadInProgress() { - return m_downloadInProgress; - } */ - private void updateNotification(String msg) { Notification notification = new Notification(R.drawable.icon, getString(R.string.notify_downloading_title), System.currentTimeMillis()); @@ -78,15 +93,30 @@ public class OfflineDownloadService extends IntentService { m_downloadInProgress = false; } + private boolean isCacheServiceRunning() { + ActivityManager manager = (ActivityManager) getSystemService(ACTIVITY_SERVICE); + for (RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) { + if ("org.fox.ttrss.ImageCacheService".equals(service.service.getClassName())) { + return true; + } + } + return false; + } + public void downloadComplete() { m_downloadInProgress = false; - m_nmgr.cancel(NOTIFY_DOWNLOADING); - - Intent intent = new Intent(); - intent.setAction(INTENT_ACTION_SUCCESS); - intent.addCategory(Intent.CATEGORY_DEFAULT); - sendBroadcast(intent); + // if cache service is running, it will send a finished intent on its own + if (!isCacheServiceRunning()) { + m_nmgr.cancel(NOTIFY_DOWNLOADING); + + Intent intent = new Intent(); + intent.setAction(INTENT_ACTION_SUCCESS); + intent.addCategory(Intent.CATEGORY_DEFAULT); + sendBroadcast(intent); + } else { + updateNotification("Downloading images..."); + } m_readableDb.close(); m_writableDb.close(); @@ -242,6 +272,28 @@ public class OfflineDownloadService extends IntentService { stmtInsert.bindString(10, tagsString); // comma-separated tags stmtInsert.bindString(11, article.content); + if (m_downloadImages) { + Document doc = Jsoup.parse(article.content); + + if (doc != null) { + Elements images = doc.select("img"); + + for (Element img : images) { + String url = img.attr("src"); + + if (url.indexOf("://") != -1) { + if (!ImageCacheService.isUrlCached(url)) { + Intent intent = new Intent(OfflineDownloadService.this, + ImageCacheService.class); + + intent.putExtra("url", url); + startService(intent); + } + } + } + } + } + try { stmtInsert.execute(); } catch (Exception e) { @@ -257,7 +309,7 @@ public class OfflineDownloadService extends IntentService { Log.d(TAG, "offline: received " + articles.size() + " articles"); - if (articles.size() == OFFLINE_SYNC_SEQ && m_articleOffset < OFFLINE_SYNC_MAX) { + if (articles.size() == OFFLINE_SYNC_SEQ && m_articleOffset < m_syncMax) { downloadArticles(); } else { downloadComplete(); @@ -285,6 +337,8 @@ public class OfflineDownloadService extends IntentService { m_sessionId = intent.getStringExtra("sessionId"); if (!m_downloadInProgress) { + if (m_downloadImages) ImageCacheService.cleanupCache(false); + updateNotification(R.string.notify_downloading_init); m_downloadInProgress = true; -- cgit v1.2.3