diff options
author | Andrew Dolgov <[email protected]> | 2014-10-17 00:06:56 +0400 |
---|---|---|
committer | Andrew Dolgov <[email protected]> | 2014-10-17 00:06:56 +0400 |
commit | 97cc96839d31b6cce59ec29a6681c6fe802552ee (patch) | |
tree | 9f3b8df270095bc65c10cd7208d05b3dad4794b9 /orgfoxttrss/src/main/java/org/fox/ttrss/offline/OfflineDownloadService.java | |
parent | 5775c0d56b7c856b508bb34e478eef53c2460624 (diff) |
initial
Diffstat (limited to 'orgfoxttrss/src/main/java/org/fox/ttrss/offline/OfflineDownloadService.java')
-rw-r--r-- | orgfoxttrss/src/main/java/org/fox/ttrss/offline/OfflineDownloadService.java | 500 |
1 files changed, 500 insertions, 0 deletions
diff --git a/orgfoxttrss/src/main/java/org/fox/ttrss/offline/OfflineDownloadService.java b/orgfoxttrss/src/main/java/org/fox/ttrss/offline/OfflineDownloadService.java new file mode 100644 index 00000000..2d65c890 --- /dev/null +++ b/orgfoxttrss/src/main/java/org/fox/ttrss/offline/OfflineDownloadService.java @@ -0,0 +1,500 @@ +package org.fox.ttrss.offline; + +import java.lang.reflect.Type; +import java.util.HashMap; +import java.util.List; + +import org.fox.ttrss.ApiRequest; +import org.fox.ttrss.OnlineActivity; +import org.fox.ttrss.R; +import org.fox.ttrss.types.Article; +import org.fox.ttrss.types.Feed; +import org.fox.ttrss.types.FeedCategory; +import org.fox.ttrss.util.DatabaseHelper; +import org.fox.ttrss.util.ImageCacheService; +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.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.app.Service; +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.Binder; +import android.os.IBinder; +import android.preference.PreferenceManager; +import android.provider.BaseColumns; +import android.util.Log; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.reflect.TypeToken; + +public class OfflineDownloadService extends Service { + + private final String TAG = this.getClass().getSimpleName(); + + public static final int NOTIFY_DOWNLOADING = 1; + public static final String INTENT_ACTION_SUCCESS = "org.fox.ttrss.intent.action.DownloadComplete"; + public static final String INTENT_ACTION_CANCEL = "org.fox.ttrss.intent.action.Cancel"; + + private static final int OFFLINE_SYNC_SEQ = 50; + private static final int OFFLINE_SYNC_MAX = OFFLINE_SYNC_SEQ * 10; + + private SQLiteDatabase m_writableDb; + private SQLiteDatabase m_readableDb; + private int m_articleOffset = 0; + private String m_sessionId; + private NotificationManager m_nmgr; + + private boolean m_batchMode = false; + private boolean m_downloadInProgress = false; + private boolean m_downloadImages = false; + private int m_syncMax; + private SharedPreferences m_prefs; + private boolean m_canProceed = true; + + private final IBinder m_binder = new LocalBinder(); + + public class LocalBinder extends Binder { + OfflineDownloadService getService() { + return OfflineDownloadService.this; + } + } + + @Override + public IBinder onBind(Intent intent) { + return m_binder; + } + + @Override + 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 = Integer.parseInt(m_prefs.getString("offline_sync_max", String.valueOf(OFFLINE_SYNC_MAX))); + + initDatabase(); + } + + @SuppressWarnings("deprecation") + private void updateNotification(String msg) { + Notification notification = new Notification(R.drawable.icon, + getString(R.string.notify_downloading_title), System.currentTimeMillis()); + + Intent intent = new Intent(this, OnlineActivity.class); + intent.setAction(INTENT_ACTION_CANCEL); + + PendingIntent contentIntent = PendingIntent.getActivity(this, 0, + intent, 0); + + notification.flags |= Notification.FLAG_ONGOING_EVENT; + notification.flags |= Notification.FLAG_ONLY_ALERT_ONCE; + + notification.setLatestEventInfo(this, getString(R.string.notify_downloading_title), msg, contentIntent); + + m_nmgr.notify(NOTIFY_DOWNLOADING, notification); + } + + private void updateNotification(int msgResId) { + updateNotification(getString(msgResId)); + } + + private void downloadFailed() { + m_readableDb.close(); + m_writableDb.close(); + + m_nmgr.cancel(NOTIFY_DOWNLOADING); + + // TODO send notification to activity? + + m_downloadInProgress = false; + stopSelf(); + } + + private boolean isCacheServiceRunning() { + ActivityManager manager = (ActivityManager) getSystemService(ACTIVITY_SERVICE); + for (RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) { + if ("org.fox.ttrss.util.ImageCacheService".equals(service.service.getClassName())) { + return true; + } + } + return false; + } + + public void downloadComplete() { + m_downloadInProgress = false; + + // if cache service is running, it will send a finished intent on its own + if (!isCacheServiceRunning()) { + m_nmgr.cancel(NOTIFY_DOWNLOADING); + + if (m_batchMode) { + + SharedPreferences localPrefs = getSharedPreferences("localprefs", Context.MODE_PRIVATE); + SharedPreferences.Editor editor = localPrefs.edit(); + editor.putBoolean("offline_mode_active", true); + editor.commit(); + + } else { + Intent intent = new Intent(); + intent.setAction(INTENT_ACTION_SUCCESS); + intent.addCategory(Intent.CATEGORY_DEFAULT); + sendBroadcast(intent); + } + } else { + updateNotification(getString(R.string.notify_downloading_images, 0)); + } + + m_readableDb.close(); + m_writableDb.close(); + + stopSelf(); + } + + private void initDatabase() { + DatabaseHelper dh = new DatabaseHelper(getApplicationContext()); + m_writableDb = dh.getWritableDatabase(); + m_readableDb = dh.getReadableDatabase(); + } + + /* private synchronized SQLiteDatabase getReadableDb() { + return m_readableDb; + } */ + + private synchronized SQLiteDatabase getWritableDb() { + return m_writableDb; + } + + @SuppressWarnings("unchecked") + private void downloadArticles() { + Log.d(TAG, "offline: downloading articles... offset=" + m_articleOffset); + + updateNotification(getString(R.string.notify_downloading_articles, m_articleOffset)); + + OfflineArticlesRequest req = new OfflineArticlesRequest(this); + + @SuppressWarnings("serial") + HashMap<String,String> map = new HashMap<String,String>() { + { + put("op", "getHeadlines"); + put("sid", m_sessionId); + put("feed_id", "-4"); + put("view_mode", "unread"); + put("show_content", "true"); + put("skip", String.valueOf(m_articleOffset)); + put("limit", String.valueOf(OFFLINE_SYNC_SEQ)); + } + }; + + req.execute(map); + } + + private void downloadFeeds() { + + updateNotification(R.string.notify_downloading_feeds); + + getWritableDb().execSQL("DELETE FROM feeds;"); + + ApiRequest req = new ApiRequest(getApplicationContext()) { + @Override + protected JsonElement doInBackground(HashMap<String, String>... params) { + JsonElement content = super.doInBackground(params); + + if (content != null) { + + try { + Type listType = new TypeToken<List<Feed>>() {}.getType(); + List<Feed> feeds = new Gson().fromJson(content, listType); + + SQLiteStatement stmtInsert = getWritableDb().compileStatement("INSERT INTO feeds " + + "("+BaseColumns._ID+", title, feed_url, has_icon, cat_id) " + + "VALUES (?, ?, ?, ?, ?);"); + + for (Feed feed : feeds) { + stmtInsert.bindLong(1, feed.id); + stmtInsert.bindString(2, feed.title); + stmtInsert.bindString(3, feed.feed_url); + stmtInsert.bindLong(4, feed.has_icon ? 1 : 0); + stmtInsert.bindLong(5, feed.cat_id); + + stmtInsert.execute(); + } + + stmtInsert.close(); + + Log.d(TAG, "offline: done downloading feeds"); + + m_articleOffset = 0; + + getWritableDb().execSQL("DELETE FROM articles;"); + } catch (Exception e) { + e.printStackTrace(); + updateNotification(R.string.offline_switch_error); + downloadFailed(); + } + } + + return content; + } + + @Override + protected void onPostExecute(JsonElement content) { + if (content != null) { + if (m_canProceed) { + downloadArticles(); + } else { + downloadFailed(); + } + } else { + updateNotification(getErrorMessage()); + downloadFailed(); + } + } + + }; + + @SuppressWarnings("serial") + HashMap<String,String> map = new HashMap<String,String>() { + { + put("op", "getFeeds"); + put("sid", m_sessionId); + put("cat_id", "-3"); + put("unread_only", "true"); + } + }; + + req.execute(map); + } + + private void downloadCategories() { + + updateNotification(R.string.notify_downloading_feeds); + + getWritableDb().execSQL("DELETE FROM categories;"); + + ApiRequest req = new ApiRequest(getApplicationContext()) { + protected JsonElement doInBackground(HashMap<String, String>... params) { + JsonElement content = super.doInBackground(params); + + if (content != null) { + try { + Type listType = new TypeToken<List<FeedCategory>>() {}.getType(); + List<FeedCategory> cats = new Gson().fromJson(content, listType); + + SQLiteStatement stmtInsert = getWritableDb().compileStatement("INSERT INTO categories " + + "("+BaseColumns._ID+", title) " + + "VALUES (?, ?);"); + + for (FeedCategory cat : cats) { + stmtInsert.bindLong(1, cat.id); + stmtInsert.bindString(2, cat.title); + + stmtInsert.execute(); + } + + stmtInsert.close(); + + Log.d(TAG, "offline: done downloading categories"); + + } catch (Exception e) { + e.printStackTrace(); + updateNotification(R.string.offline_switch_error); + downloadFailed(); + } + } + + return content; + } + @Override + protected void onPostExecute(JsonElement content) { + if (content != null) { + if (m_canProceed) { + downloadFeeds(); + } else { + downloadFailed(); + } + } else { + updateNotification(getErrorMessage()); + downloadFailed(); + } + } + + }; + + @SuppressWarnings("serial") + HashMap<String,String> map = new HashMap<String,String>() { + { + put("op", "getCategories"); + put("sid", m_sessionId); + //put("cat_id", "-3"); + put("unread_only", "true"); + } + }; + + req.execute(map); + } + + + @Override + public void onDestroy() { + super.onDestroy(); + m_nmgr.cancel(NOTIFY_DOWNLOADING); + + m_canProceed = false; + Log.d(TAG, "onDestroy"); + + //m_readableDb.close(); + //m_writableDb.close(); + } + + public class OfflineArticlesRequest extends ApiRequest { + List<Article> m_articles; + + public OfflineArticlesRequest(Context context) { + super(context); + } + + @Override + protected JsonElement doInBackground(HashMap<String, String>... params) { + JsonElement content = super.doInBackground(params); + + if (content != null) { + + try { + Type listType = new TypeToken<List<Article>>() {}.getType(); + m_articles = new Gson().fromJson(content, listType); + + SQLiteStatement stmtInsert = getWritableDb().compileStatement("INSERT INTO articles " + + "("+BaseColumns._ID+", unread, marked, published, score, updated, is_updated, title, link, feed_id, tags, content, author) " + + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);"); + + for (Article article : m_articles) { + + String tagsString = ""; + + for (String t : article.tags) { + tagsString += t + ", "; + } + + tagsString = tagsString.replaceAll(", $", ""); + + int index = 1; + stmtInsert.bindLong(index++, article.id); + stmtInsert.bindLong(index++, article.unread ? 1 : 0); + stmtInsert.bindLong(index++, article.marked ? 1 : 0); + stmtInsert.bindLong(index++, article.published ? 1 : 0); + stmtInsert.bindLong(index++, article.score); + stmtInsert.bindLong(index++, article.updated); + stmtInsert.bindLong(index++, article.is_updated ? 1 : 0); + stmtInsert.bindString(index++, article.title); + stmtInsert.bindString(index++, article.link); + stmtInsert.bindLong(index++, article.feed_id); + stmtInsert.bindString(index++, tagsString); // comma-separated tags + stmtInsert.bindString(index++, article.content); + stmtInsert.bindString(index++, article.author != null ? article.author : ""); + + 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(OfflineDownloadService.this, url)) { + Intent intent = new Intent(OfflineDownloadService.this, + ImageCacheService.class); + + intent.putExtra("url", url); + startService(intent); + } + } + } + } + } + + try { + stmtInsert.execute(); + } catch (Exception e) { + e.printStackTrace(); + } + + } + + m_articleOffset += m_articles.size(); + + Log.d(TAG, "offline: received " + m_articles.size() + " articles; canProc=" + m_canProceed); + + stmtInsert.close(); + + } catch (Exception e) { + updateNotification(R.string.offline_switch_error); + Log.d(TAG, "offline: failed: exception when loading articles"); + e.printStackTrace(); + downloadFailed(); + } + + } + + return content; + } + + @Override + protected void onPostExecute(JsonElement content) { + if (content != null) { + + if (m_canProceed && m_articles != null) { + if (m_articles.size() == OFFLINE_SYNC_SEQ && m_articleOffset < m_syncMax) { + downloadArticles(); + } else { + downloadComplete(); + } + } else { + downloadFailed(); + } + + } else { + Log.d(TAG, "offline: failed: " + getErrorMessage()); + updateNotification(getErrorMessage()); + downloadFailed(); + } + } + } + + @Override + public void onStart(Intent intent, int startId) { + try { + if (getWritableDb().isDbLockedByCurrentThread() || getWritableDb().isDbLockedByOtherThreads()) { + return; + } + + m_sessionId = intent.getStringExtra("sessionId"); + m_batchMode = intent.getBooleanExtra("batchMode", false); + + if (!m_downloadInProgress) { + if (m_downloadImages) ImageCacheService.cleanupCache(this, false); + + updateNotification(R.string.notify_downloading_init); + m_downloadInProgress = true; + + downloadCategories(); + } + } catch (Exception e) { + e.printStackTrace(); + } + } +} |