move offline data synchronization to an intentservice, bump version

This commit is contained in:
Andrew Dolgov 2011-12-07 12:33:47 +03:00
parent 24f0b1617a
commit 14461fa146
5 changed files with 392 additions and 383 deletions

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.fox.ttrss" package="org.fox.ttrss"
android:versionCode="35" android:versionCode="36"
android:versionName="0.3.2" > android:versionName="0.3.3" >
<uses-sdk android:minSdkVersion="4" /> <uses-sdk android:minSdkVersion="4" />
@ -37,6 +37,7 @@
</activity> </activity>
<service android:enabled="true" android:name=".OfflineDownloadService" /> <service android:enabled="true" android:name=".OfflineDownloadService" />
<service android:enabled="true" android:name=".OfflineUploadService" />
<activity <activity
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|uiMode|screenSize|smallestScreenSize" android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|uiMode|screenSize|smallestScreenSize"

View File

@ -106,4 +106,8 @@
<string name="notify_downloading_articles">Downloading articles (%1$d)...</string> <string name="notify_downloading_articles">Downloading articles (%1$d)...</string>
<string name="notify_downloading_init">Starting download...</string> <string name="notify_downloading_init">Starting download...</string>
<string name="notify_downloading_feeds">Downloading feeds...</string> <string name="notify_downloading_feeds">Downloading feeds...</string>
<string name="notify_uploading_sending_data">Sending data to server...</string>
<string name="notify_downloading_title">Preparing offline mode</string>
<string name="notify_uploading_title">Synchronizing offline data</string>
<string name="offline_sync_success">Finished synchronizing your offline data</string>
</resources> </resources>

View File

@ -8,12 +8,10 @@ import java.util.TimerTask;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.app.Dialog; import android.app.Dialog;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.pm.PackageInfo; import android.content.pm.PackageInfo;
import android.database.Cursor; import android.database.Cursor;
@ -21,7 +19,6 @@ import android.database.sqlite.SQLiteDatabase;
import android.net.ConnectivityManager; import android.net.ConnectivityManager;
import android.net.NetworkInfo; import android.net.NetworkInfo;
import android.os.Bundle; import android.os.Bundle;
import android.os.IBinder;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.support.v4.app.FragmentActivity; import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentTransaction; import android.support.v4.app.FragmentTransaction;
@ -36,6 +33,7 @@ import android.view.animation.AnimationUtils;
import android.widget.AdapterView.AdapterContextMenuInfo; import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
@ -60,6 +58,7 @@ public class MainActivity extends FragmentActivity implements OnlineServices {
private int m_isLicensed = -1; private int m_isLicensed = -1;
private int m_apiLevel = 0; private int m_apiLevel = 0;
private boolean m_isOffline = false; private boolean m_isOffline = false;
private boolean m_offlineModeReady = false;
private SQLiteDatabase m_readableDb; private SQLiteDatabase m_readableDb;
private SQLiteDatabase m_writableDb; private SQLiteDatabase m_writableDb;
@ -69,30 +68,23 @@ public class MainActivity extends FragmentActivity implements OnlineServices {
@Override @Override
public void onReceive(Context content, Intent intent) { public void onReceive(Context content, Intent intent) {
AlertDialog.Builder builder = new AlertDialog.Builder( if (intent.getAction().equals(OfflineDownloadService.INTENT_ACTION_SUCCESS)) {
MainActivity.this)
.setMessage(R.string.dialog_offline_success)
.setPositiveButton(R.string.dialog_offline_go,
new Dialog.OnClickListener() {
public void onClick(DialogInterface dialog,
int which) {
Intent refresh = new Intent(
MainActivity.this,
OfflineActivity.class);
startActivity(refresh);
finish();
}
})
.setNegativeButton(R.string.dialog_cancel,
new Dialog.OnClickListener() {
public void onClick(DialogInterface dialog,
int which) {
//
}
});
AlertDialog dlg = builder.create(); m_offlineModeReady = true;
dlg.show();
switchOffline();
} else if (intent.getAction().equals(OfflineUploadService.INTENT_ACTION_SUCCESS)) {
//Log.d(TAG, "offline upload service reports success");
if (!m_enableCats || m_activeCategory != null)
refreshFeeds();
else
refreshCategories();
Toast toast = Toast.makeText(MainActivity.this, R.string.offline_sync_success, Toast.LENGTH_SHORT);
toast.show();
}
} }
}; };
@ -133,10 +125,6 @@ public class MainActivity extends FragmentActivity implements OnlineServices {
return false; return false;
} }
public void clearPendingOfflineData() {
getWritableDb().execSQL("UPDATE articles SET modified = 0");
}
private boolean hasOfflineData() { private boolean hasOfflineData() {
try { try {
Cursor c = getReadableDb().query("articles", Cursor c = getReadableDb().query("articles",
@ -425,6 +413,7 @@ public class MainActivity extends FragmentActivity implements OnlineServices {
.getParcelable("activeCategory"); .getParcelable("activeCategory");
m_apiLevel = savedInstanceState.getInt("apiLevel"); m_apiLevel = savedInstanceState.getInt("apiLevel");
m_isLicensed = savedInstanceState.getInt("isLicensed"); m_isLicensed = savedInstanceState.getInt("isLicensed");
m_offlineModeReady = savedInstanceState.getBoolean("offlineModeReady");
} }
m_enableCats = m_prefs.getBoolean("enable_cats", false); m_enableCats = m_prefs.getBoolean("enable_cats", false);
@ -446,8 +435,9 @@ public class MainActivity extends FragmentActivity implements OnlineServices {
initDatabase(); initDatabase();
IntentFilter filter = new IntentFilter( IntentFilter filter = new IntentFilter();
"org.fox.ttrss.intent.action.DownloadComplete"); filter.addAction(OfflineDownloadService.INTENT_ACTION_SUCCESS);
filter.addAction(OfflineUploadService.INTENT_ACTION_SUCCESS);
filter.addCategory(Intent.CATEGORY_DEFAULT); filter.addCategory(Intent.CATEGORY_DEFAULT);
registerReceiver(m_broadcastReceiver, filter); registerReceiver(m_broadcastReceiver, filter);
@ -552,47 +542,20 @@ public class MainActivity extends FragmentActivity implements OnlineServices {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private void switchOffline() { private void switchOffline() {
if (m_offlineModeReady) {
AlertDialog.Builder builder = new AlertDialog.Builder(this) AlertDialog.Builder builder = new AlertDialog.Builder(
.setMessage(R.string.dialog_offline_switch_prompt) MainActivity.this)
.setMessage(R.string.dialog_offline_success)
.setPositiveButton(R.string.dialog_offline_go, .setPositiveButton(R.string.dialog_offline_go,
new Dialog.OnClickListener() { new Dialog.OnClickListener() {
public void onClick(DialogInterface dialog, public void onClick(DialogInterface dialog,
int which) { int which) {
Intent refresh = new Intent(
if (m_sessionId != null) {
Log.d(TAG, "offline: starting");
ServiceConnection m_serviceConnection = new ServiceConnection() {
@Override
public void onServiceDisconnected(
ComponentName name) {
Log.d(TAG,
"download service disconnected");
}
@Override
public void onServiceConnected(
ComponentName name,
IBinder service) {
Log.d(TAG,
"download service connected");
// ((OfflineDownloadService.LocalBinder)service).getService().download();
}
};
Intent intent = new Intent(
MainActivity.this, MainActivity.this,
OfflineDownloadService.class); OfflineActivity.class);
intent.putExtra("sessionId", m_sessionId); startActivity(refresh);
finish();
startService(intent);
// bindService(intent, m_serviceConnection,
// Context.BIND_AUTO_CREATE);
}
} }
}) })
.setNegativeButton(R.string.dialog_cancel, .setNegativeButton(R.string.dialog_cancel,
@ -606,6 +569,38 @@ public class MainActivity extends FragmentActivity implements OnlineServices {
AlertDialog dlg = builder.create(); AlertDialog dlg = builder.create();
dlg.show(); dlg.show();
} else {
AlertDialog.Builder builder = new AlertDialog.Builder(this)
.setMessage(R.string.dialog_offline_switch_prompt)
.setPositiveButton(R.string.dialog_offline_go,
new Dialog.OnClickListener() {
public void onClick(DialogInterface dialog,
int which) {
if (m_sessionId != null) {
Log.d(TAG, "offline: starting");
Intent intent = new Intent(
MainActivity.this,
OfflineDownloadService.class);
intent.putExtra("sessionId", m_sessionId);
startService(intent);
}
}
})
.setNegativeButton(R.string.dialog_cancel,
new Dialog.OnClickListener() {
public void onClick(DialogInterface dialog,
int which) {
//
}
});
AlertDialog dlg = builder.create();
dlg.show();
}
} }
private void switchOfflineSuccess() { private void switchOfflineSuccess() {
@ -648,6 +643,7 @@ public class MainActivity extends FragmentActivity implements OnlineServices {
out.putParcelable("activeCategory", m_activeCategory); out.putParcelable("activeCategory", m_activeCategory);
out.putInt("apiLevel", m_apiLevel); out.putInt("apiLevel", m_apiLevel);
out.putInt("isLicensed", m_isLicensed); out.putInt("isLicensed", m_isLicensed);
out.putBoolean("offlineModeReady", m_offlineModeReady);
} }
@Override @Override
@ -1095,133 +1091,16 @@ public class MainActivity extends FragmentActivity implements OnlineServices {
} }
private void syncOfflineRead() {
Log.d(TAG, "syncing modified offline data... (read)");
final String ids = getOfflineModifiedIds(ModifiedCriteria.READ);
if (ids.length() > 0) {
ApiRequest req = new ApiRequest(getApplicationContext()) {
@Override
protected void onPostExecute(JsonElement result) {
if (result != null) {
syncOfflineMarked();
} else {
setLoadingStatus(getErrorMessage(), false);
}
}
};
@SuppressWarnings("serial")
HashMap<String, String> map = new HashMap<String, String>() {
{
put("sid", m_sessionId);
put("op", "updateArticle");
put("article_ids", ids);
put("mode", "0");
put("field", "2");
}
};
req.execute(map);
} else {
syncOfflineMarked();
}
}
private void syncOfflineMarked() {
Log.d(TAG, "syncing modified offline data... (marked)");
final String ids = getOfflineModifiedIds(ModifiedCriteria.MARKED);
if (ids.length() > 0) {
ApiRequest req = new ApiRequest(getApplicationContext()) {
@Override
protected void onPostExecute(JsonElement result) {
if (result != null) {
syncOfflinePublished();
} else {
setLoadingStatus(getErrorMessage(), false);
}
}
};
@SuppressWarnings("serial")
HashMap<String, String> map = new HashMap<String, String>() {
{
put("sid", m_sessionId);
put("op", "updateArticle");
put("article_ids", ids);
put("mode", "0");
put("field", "0");
}
};
req.execute(map);
} else {
syncOfflinePublished();
}
}
private void syncOfflinePublished() {
Log.d(TAG, "syncing modified offline data... (published)");
final String ids = getOfflineModifiedIds(ModifiedCriteria.MARKED);
if (ids.length() > 0) {
ApiRequest req = new ApiRequest(getApplicationContext()) {
@Override
protected void onPostExecute(JsonElement result) {
if (result != null) {
loginSuccessInitUI();
loginSuccess();
clearPendingOfflineData();
} else {
setLoadingStatus(getErrorMessage(), false);
}
}
};
@SuppressWarnings("serial")
HashMap<String, String> map = new HashMap<String, String>() {
{
put("sid", m_sessionId);
put("op", "updateArticle");
put("article_ids", ids);
put("mode", "0");
put("field", "1");
}
};
req.execute(map);
} else {
loginSuccessInitUI();
loginSuccess();
clearPendingOfflineData();
}
}
private void syncOfflineData() { private void syncOfflineData() {
setLoadingStatus(R.string.syncing_offline_data, true); Log.d(TAG, "offlineSync: starting");
syncOfflineRead();
}
private void loginSuccessInitUI() { Intent intent = new Intent(
FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); MainActivity.this,
OfflineUploadService.class);
if (m_enableCats) { intent.putExtra("sessionId", m_sessionId);
FeedCategoriesFragment frag = new FeedCategoriesFragment();
ft.replace(R.id.cats_fragment, frag);
} else {
FeedsFragment frag = new FeedsFragment();
ft.replace(R.id.feeds_fragment, frag);
}
try { startService(intent);
ft.commit();
} catch (IllegalStateException e) {
e.printStackTrace();
}
} }
private void loginSuccess() { private void loginSuccess() {
@ -1248,43 +1127,6 @@ public class MainActivity extends FragmentActivity implements OnlineServices {
m_refreshTimer.schedule(m_refreshTask, 60 * 1000L, 120 * 1000L); m_refreshTimer.schedule(m_refreshTask, 60 * 1000L, 120 * 1000L);
} }
private enum ModifiedCriteria {
READ, MARKED, PUBLISHED
};
private String getOfflineModifiedIds(ModifiedCriteria criteria) {
String criteriaStr = "";
switch (criteria) {
case READ:
criteriaStr = "unread = 0";
break;
case MARKED:
criteriaStr = "marked = 1";
break;
case PUBLISHED:
criteriaStr = "published = 1";
break;
}
Cursor c = getReadableDb().query("articles", null,
"modified = 1 AND " + criteriaStr, null, null, null, null);
String tmp = "";
while (c.moveToNext()) {
tmp += c.getInt(0) + ",";
}
tmp = tmp.replaceAll(",$", "");
// Log.d(TAG, "getOfflineModifiedIds " + criteria + " = " + tmp);
c.close();
return tmp;
}
private class LoginRequest extends ApiRequest { private class LoginRequest extends ApiRequest {
public LoginRequest(Context context) { public LoginRequest(Context context) {
@ -1312,16 +1154,27 @@ public class MainActivity extends FragmentActivity implements OnlineServices {
Log.d(TAG, "Received API level: " + m_apiLevel); Log.d(TAG, "Received API level: " + m_apiLevel);
if (hasPendingOfflineData()) { if (hasPendingOfflineData())
syncOfflineData(); syncOfflineData();
// loginSuccess(); FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
if (m_enableCats) {
FeedCategoriesFragment frag = new FeedCategoriesFragment();
ft.replace(R.id.cats_fragment, frag);
} else { } else {
loginSuccessInitUI(); FeedsFragment frag = new FeedsFragment();
loginSuccess(); ft.replace(R.id.feeds_fragment, frag);
} }
try {
ft.commit();
} catch (IllegalStateException e) {
e.printStackTrace();
}
loginSuccess();
} }
}; };

View File

@ -13,7 +13,6 @@ import android.content.Intent;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteStatement; import android.database.sqlite.SQLiteStatement;
import android.os.Bundle; import android.os.Bundle;
import android.os.IBinder;
import android.provider.BaseColumns; import android.provider.BaseColumns;
import android.util.Log; import android.util.Log;
@ -26,6 +25,7 @@ public class OfflineDownloadService extends IntentService {
private final String TAG = this.getClass().getSimpleName(); private final String TAG = this.getClass().getSimpleName();
public static final int NOTIFY_DOWNLOADING = 1; public static final int NOTIFY_DOWNLOADING = 1;
public static final String INTENT_ACTION_SUCCESS = "org.fox.ttrss.intent.action.DownloadComplete";
private static final int OFFLINE_SYNC_SEQ = 60; private static final int OFFLINE_SYNC_SEQ = 60;
private static final int OFFLINE_SYNC_MAX = 500; private static final int OFFLINE_SYNC_MAX = 500;
@ -42,10 +42,6 @@ public class OfflineDownloadService extends IntentService {
super("OfflineDownloadService"); super("OfflineDownloadService");
} }
public OfflineDownloadService(String name) {
super(name);
}
@Override @Override
public void onCreate() { public void onCreate() {
super.onCreate(); super.onCreate();
@ -53,18 +49,18 @@ public class OfflineDownloadService extends IntentService {
initDatabase(); initDatabase();
} }
public boolean getDownloadInProgress() { /* public boolean getDownloadInProgress() {
return m_downloadInProgress; return m_downloadInProgress;
} } */
private void updateNotification(String msg) { private void updateNotification(String msg) {
Notification notification = new Notification(R.drawable.icon, Notification notification = new Notification(R.drawable.icon,
getString(R.string.app_name), System.currentTimeMillis()); getString(R.string.notify_downloading_title), System.currentTimeMillis());
PendingIntent contentIntent = PendingIntent.getActivity(this, 0, PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
new Intent(this, MainActivity.class), 0); new Intent(this, MainActivity.class), 0);
notification.setLatestEventInfo(this, getString(R.string.go_offline), msg, contentIntent); notification.setLatestEventInfo(this, getString(R.string.notify_downloading_title), msg, contentIntent);
m_nmgr.notify(NOTIFY_DOWNLOADING, notification); m_nmgr.notify(NOTIFY_DOWNLOADING, notification);
} }
@ -73,9 +69,13 @@ public class OfflineDownloadService extends IntentService {
updateNotification(getString(msgResId)); updateNotification(getString(msgResId));
} }
@Override private void downloadFailed() {
public IBinder onBind(Intent intent) { m_readableDb.close();
return null; m_writableDb.close();
// TODO send notification to activity?
m_downloadInProgress = false;
} }
public void downloadComplete() { public void downloadComplete() {
@ -84,7 +84,7 @@ public class OfflineDownloadService extends IntentService {
m_nmgr.cancel(NOTIFY_DOWNLOADING); m_nmgr.cancel(NOTIFY_DOWNLOADING);
Intent intent = new Intent(); Intent intent = new Intent();
intent.setAction("org.fox.ttrss.intent.action.DownloadComplete"); intent.setAction(INTENT_ACTION_SUCCESS);
intent.addCategory(Intent.CATEGORY_DEFAULT); intent.addCategory(Intent.CATEGORY_DEFAULT);
sendBroadcast(intent); sendBroadcast(intent);
@ -98,11 +98,11 @@ public class OfflineDownloadService extends IntentService {
m_readableDb = dh.getReadableDatabase(); m_readableDb = dh.getReadableDatabase();
} }
public synchronized SQLiteDatabase getReadableDb() { private synchronized SQLiteDatabase getReadableDb() {
return m_readableDb; return m_readableDb;
} }
public synchronized SQLiteDatabase getWritableDb() { private synchronized SQLiteDatabase getWritableDb() {
return m_writableDb; return m_writableDb;
} }
@ -131,12 +131,6 @@ public class OfflineDownloadService extends IntentService {
} }
private void downloadFeeds() { private void downloadFeeds() {
//findViewById(R.id.loading_container).setVisibility(View.VISIBLE);
//findViewById(R.id.main).setVisibility(View.INVISIBLE);
//setLoadingStatus(R.string.offline_downloading, true);
// Download feeds
updateNotification(R.string.notify_downloading_feeds); updateNotification(R.string.notify_downloading_feeds);
@ -176,14 +170,12 @@ public class OfflineDownloadService extends IntentService {
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
updateNotification(R.string.offline_switch_error); updateNotification(R.string.offline_switch_error);
m_downloadInProgress = false; downloadFailed();
//setLoadingStatus(R.string.offline_switch_error, false);
} }
} else { } else {
updateNotification(getErrorMessage()); updateNotification(getErrorMessage());
m_downloadInProgress = false; downloadFailed();
// TODO error, could not download feeds, properly report API error (toast)
} }
} }
@ -202,106 +194,6 @@ public class OfflineDownloadService extends IntentService {
req.execute(map); req.execute(map);
} }
/* @SuppressWarnings("unchecked")
private void switchOffline() {
AlertDialog.Builder builder = new AlertDialog.Builder(this).
setMessage(R.string.dialog_offline_switch_prompt).
setPositiveButton(R.string.dialog_offline_go, new Dialog.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
Log.d(TAG, "offline: starting");
if (m_sessionId != null) {
//findViewById(R.id.loading_container).setVisibility(View.VISIBLE);
//findViewById(R.id.main).setVisibility(View.INVISIBLE);
//setLoadingStatus(R.string.offline_downloading, true);
// Download feeds
getWritableDb().execSQL("DELETE FROM feeds;");
ApiRequest req = new ApiRequest(getApplicationContext()) {
@Override
protected void onPostExecute(JsonElement content) {
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;");
downloadArticles();
} catch (Exception e) {
e.printStackTrace();
//setLoadingStatus(R.string.offline_switch_error, false);
}
} else {
//setLoadingStatus(getErrorMessage(), false);
// TODO error, could not download feeds, properly report API error (toast)
}
}
};
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);
} else {
downloadComplete();
}
}
}).
setNegativeButton(R.string.dialog_cancel, new Dialog.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
//
}
});
AlertDialog dlg = builder.create();
dlg.show();
} */
public void download() {
if (!m_downloadInProgress) {
updateNotification(R.string.notify_downloading_init);
m_downloadInProgress = true;
downloadFeeds();
}
}
@Override @Override
public void onDestroy() { public void onDestroy() {
super.onDestroy(); super.onDestroy();
@ -377,23 +269,26 @@ public class OfflineDownloadService extends IntentService {
updateNotification(R.string.offline_switch_error); updateNotification(R.string.offline_switch_error);
Log.d(TAG, "offline: failed: exception when loading articles"); Log.d(TAG, "offline: failed: exception when loading articles");
e.printStackTrace(); e.printStackTrace();
m_downloadInProgress = false; downloadFailed();
} }
} else { } else {
Log.d(TAG, "offline: failed: " + getErrorMessage()); Log.d(TAG, "offline: failed: " + getErrorMessage());
m_downloadInProgress = false;
updateNotification(getErrorMessage()); updateNotification(getErrorMessage());
downloadFailed();
} }
} }
} }
@Override @Override
protected void onHandleIntent(Intent intent) { protected void onHandleIntent(Intent intent) {
Bundle extras = intent.getExtras(); m_sessionId = intent.getStringExtra("sessionId");
m_sessionId = extras.getString("sessionId"); if (!m_downloadInProgress) {
updateNotification(R.string.notify_downloading_init);
m_downloadInProgress = true;
download(); downloadFeeds();
}
} }
} }

View File

@ -0,0 +1,256 @@
package org.fox.ttrss;
import java.util.HashMap;
import com.google.gson.JsonElement;
import android.app.IntentService;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Intent;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.util.Log;
public class OfflineUploadService extends IntentService {
private final String TAG = this.getClass().getSimpleName();
public static final int NOTIFY_UPLOADING = 2;
public static final String INTENT_ACTION_SUCCESS = "org.fox.ttrss.intent.action.UploadComplete";
private SQLiteDatabase m_writableDb;
private SQLiteDatabase m_readableDb;
private String m_sessionId;
private NotificationManager m_nmgr;
private boolean m_uploadInProgress = false;
public OfflineUploadService() {
super("OfflineUploadService");
}
@Override
public void onCreate() {
super.onCreate();
m_nmgr = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
initDatabase();
}
@Override
public void onDestroy() {
super.onDestroy();
m_nmgr.cancel(NOTIFY_UPLOADING);
}
private void updateNotification(String msg) {
Notification notification = new Notification(R.drawable.icon,
getString(R.string.notify_uploading_title), System.currentTimeMillis());
PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
new Intent(this, MainActivity.class), 0);
notification.setLatestEventInfo(this, getString(R.string.notify_uploading_title), msg, contentIntent);
m_nmgr.notify(NOTIFY_UPLOADING, notification);
}
private void updateNotification(int msgResId) {
updateNotification(getString(msgResId));
}
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;
}
private void uploadRead() {
Log.d(TAG, "syncing modified offline data... (read)");
final String ids = getModifiedIds(ModifiedCriteria.READ);
if (ids.length() > 0) {
ApiRequest req = new ApiRequest(getApplicationContext()) {
@Override
protected void onPostExecute(JsonElement result) {
if (result != null) {
uploadMarked();
} else {
updateNotification(getErrorMessage());
uploadFailed();
}
}
};
@SuppressWarnings("serial")
HashMap<String, String> map = new HashMap<String, String>() {
{
put("sid", m_sessionId);
put("op", "updateArticle");
put("article_ids", ids);
put("mode", "0");
put("field", "2");
}
};
req.execute(map);
} else {
uploadMarked();
}
}
private enum ModifiedCriteria {
READ, MARKED, PUBLISHED
};
private String getModifiedIds(ModifiedCriteria criteria) {
String criteriaStr = "";
switch (criteria) {
case READ:
criteriaStr = "unread = 0";
break;
case MARKED:
criteriaStr = "marked = 1";
break;
case PUBLISHED:
criteriaStr = "published = 1";
break;
}
Cursor c = getReadableDb().query("articles", null,
"modified = 1 AND " + criteriaStr, null, null, null, null);
String tmp = "";
while (c.moveToNext()) {
tmp += c.getInt(0) + ",";
}
tmp = tmp.replaceAll(",$", "");
c.close();
return tmp;
}
private void uploadMarked() {
Log.d(TAG, "syncing modified offline data... (marked)");
final String ids = getModifiedIds(ModifiedCriteria.MARKED);
if (ids.length() > 0) {
ApiRequest req = new ApiRequest(getApplicationContext()) {
@Override
protected void onPostExecute(JsonElement result) {
if (result != null) {
uploadPublished();
} else {
updateNotification(getErrorMessage());
uploadFailed();
}
}
};
@SuppressWarnings("serial")
HashMap<String, String> map = new HashMap<String, String>() {
{
put("sid", m_sessionId);
put("op", "updateArticle");
put("article_ids", ids);
put("mode", "0");
put("field", "0");
}
};
req.execute(map);
} else {
uploadPublished();
}
}
private void uploadFailed() {
m_readableDb.close();
m_writableDb.close();
// TODO send notification to activity?
m_uploadInProgress = false;
}
private void uploadSuccess() {
getWritableDb().execSQL("UPDATE articles SET modified = 0");
Intent intent = new Intent();
intent.setAction(INTENT_ACTION_SUCCESS);
intent.addCategory(Intent.CATEGORY_DEFAULT);
sendBroadcast(intent);
m_readableDb.close();
m_writableDb.close();
m_uploadInProgress = false;
m_nmgr.cancel(NOTIFY_UPLOADING);
}
private void uploadPublished() {
Log.d(TAG, "syncing modified offline data... (published)");
final String ids = getModifiedIds(ModifiedCriteria.MARKED);
if (ids.length() > 0) {
ApiRequest req = new ApiRequest(getApplicationContext()) {
@Override
protected void onPostExecute(JsonElement result) {
if (result != null) {
uploadSuccess();
} else {
updateNotification(getErrorMessage());
uploadFailed();
}
}
};
@SuppressWarnings("serial")
HashMap<String, String> map = new HashMap<String, String>() {
{
put("sid", m_sessionId);
put("op", "updateArticle");
put("article_ids", ids);
put("mode", "0");
put("field", "1");
}
};
req.execute(map);
} else {
uploadSuccess();
}
}
@Override
protected void onHandleIntent(Intent intent) {
m_sessionId = intent.getStringExtra("sessionId");
if (!m_uploadInProgress) {
m_uploadInProgress = true;
updateNotification(R.string.notify_uploading_sending_data);
uploadRead();
}
}
}