diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 99c68eb9bb..73870258b0 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -42,6 +42,17 @@
android:label="@string/title_activity_settings"
android:parentActivityName=".ui.activity.MainActivity" >
+
+
+
+
+
+
+
+
diff --git a/app/src/main/java/eu/kanade/mangafeed/data/helpers/DatabaseHelper.java b/app/src/main/java/eu/kanade/mangafeed/data/helpers/DatabaseHelper.java
index beb7ad8f50..76ecfadea8 100644
--- a/app/src/main/java/eu/kanade/mangafeed/data/helpers/DatabaseHelper.java
+++ b/app/src/main/java/eu/kanade/mangafeed/data/helpers/DatabaseHelper.java
@@ -103,6 +103,11 @@ public class DatabaseHelper implements MangaManager, ChapterManager {
return mMangaManager.getMangasWithUnread();
}
+ @Override
+ public Observable> getFavoriteMangas() {
+ return mMangaManager.getFavoriteMangas();
+ }
+
@Override
public Observable> getManga(String url) {
return mMangaManager.getManga(url);
diff --git a/app/src/main/java/eu/kanade/mangafeed/data/helpers/SourceManager.java b/app/src/main/java/eu/kanade/mangafeed/data/helpers/SourceManager.java
index 0444cc167c..25dbc3a0a2 100644
--- a/app/src/main/java/eu/kanade/mangafeed/data/helpers/SourceManager.java
+++ b/app/src/main/java/eu/kanade/mangafeed/data/helpers/SourceManager.java
@@ -6,7 +6,6 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
-import eu.kanade.mangafeed.data.caches.CacheManager;
import eu.kanade.mangafeed.sources.Batoto;
import eu.kanade.mangafeed.sources.MangaHere;
import eu.kanade.mangafeed.sources.base.Source;
@@ -17,8 +16,6 @@ public class SourceManager {
public static final int MANGAHERE = 2;
private HashMap mSourcesMap;
- private NetworkHelper mNetworkHelper;
- private CacheManager mCacheManager;
private Context context;
public SourceManager(Context context) {
diff --git a/app/src/main/java/eu/kanade/mangafeed/data/managers/MangaManager.java b/app/src/main/java/eu/kanade/mangafeed/data/managers/MangaManager.java
index 1ff99c2317..6aa717da09 100644
--- a/app/src/main/java/eu/kanade/mangafeed/data/managers/MangaManager.java
+++ b/app/src/main/java/eu/kanade/mangafeed/data/managers/MangaManager.java
@@ -16,6 +16,8 @@ public interface MangaManager {
Observable> getMangasWithUnread();
+ Observable> getFavoriteMangas();
+
Observable> getManga(String url);
Observable> getManga(long id);
diff --git a/app/src/main/java/eu/kanade/mangafeed/data/managers/MangaManagerImpl.java b/app/src/main/java/eu/kanade/mangafeed/data/managers/MangaManagerImpl.java
index 5d546c3042..a4ae052fd2 100644
--- a/app/src/main/java/eu/kanade/mangafeed/data/managers/MangaManagerImpl.java
+++ b/app/src/main/java/eu/kanade/mangafeed/data/managers/MangaManagerImpl.java
@@ -55,6 +55,19 @@ public class MangaManagerImpl extends BaseManager implements MangaManager {
.createObservable();
}
+ @Override
+ public Observable> getFavoriteMangas() {
+ return db.get()
+ .listOfObjects(Manga.class)
+ .withQuery(Query.builder()
+ .table(MangasTable.TABLE)
+ .where(MangasTable.COLUMN_FAVORITE + "=?")
+ .whereArgs(1)
+ .build())
+ .prepare()
+ .createObservable();
+ }
+
public Observable> getManga(String url) {
return db.get()
.listOfObjects(Manga.class)
diff --git a/app/src/main/java/eu/kanade/mangafeed/data/services/LibraryUpdateService.java b/app/src/main/java/eu/kanade/mangafeed/data/services/LibraryUpdateService.java
new file mode 100644
index 0000000000..a692e32f50
--- /dev/null
+++ b/app/src/main/java/eu/kanade/mangafeed/data/services/LibraryUpdateService.java
@@ -0,0 +1,168 @@
+package eu.kanade.mangafeed.data.services;
+
+import android.app.Service;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.IBinder;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.inject.Inject;
+
+import eu.kanade.mangafeed.App;
+import eu.kanade.mangafeed.BuildConfig;
+import eu.kanade.mangafeed.R;
+import eu.kanade.mangafeed.data.helpers.DatabaseHelper;
+import eu.kanade.mangafeed.data.helpers.SourceManager;
+import eu.kanade.mangafeed.data.models.Manga;
+import eu.kanade.mangafeed.util.AndroidComponentUtil;
+import eu.kanade.mangafeed.util.NetworkUtil;
+import eu.kanade.mangafeed.util.NotificationUtil;
+import eu.kanade.mangafeed.util.PostResult;
+import rx.Observable;
+import rx.Subscription;
+import timber.log.Timber;
+
+public class LibraryUpdateService extends Service {
+
+ @Inject DatabaseHelper db;
+ @Inject SourceManager sourceManager;
+
+ private Subscription updateSubscription;
+ private Subscription favoriteMangasSubscription;
+
+ public static final int UPDATE_NOTIFICATION_ID = 1;
+
+ public static Intent getStartIntent(Context context) {
+ return new Intent(context, LibraryUpdateService.class);
+ }
+
+ public static boolean isRunning(Context context) {
+ return AndroidComponentUtil.isServiceRunning(context, LibraryUpdateService.class);
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ App.get(this).getComponent().inject(this);
+ }
+
+ @Override
+ public void onDestroy() {
+ if (updateSubscription != null)
+ updateSubscription.unsubscribe();
+ super.onDestroy();
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, final int startId) {
+ Timber.i("Starting sync...");
+
+ if (!NetworkUtil.isNetworkConnected(this)) {
+ Timber.i("Sync canceled, connection not available");
+ AndroidComponentUtil.toggleComponent(this, SyncOnConnectionAvailable.class, true);
+ stopSelf(startId);
+ return START_NOT_STICKY;
+ }
+
+ if (favoriteMangasSubscription != null && !favoriteMangasSubscription.isUnsubscribed())
+ favoriteMangasSubscription.unsubscribe();
+
+ favoriteMangasSubscription = db.getFavoriteMangas()
+ .subscribe(mangas -> {
+ // Don't receive further db updates
+ favoriteMangasSubscription.unsubscribe();
+ this.startUpdating(mangas, startId);
+ });
+
+ return START_STICKY;
+ }
+
+ private void startUpdating(final List mangas, final int startId) {
+ if (updateSubscription != null && !updateSubscription.isUnsubscribed())
+ updateSubscription.unsubscribe();
+
+ final AtomicInteger count = new AtomicInteger(0);
+
+ List updates = new ArrayList<>();
+
+ updateSubscription = Observable.from(mangas)
+ .doOnNext(manga -> {
+ NotificationUtil.create(this, UPDATE_NOTIFICATION_ID,
+ getString(R.string.notification_progress, count.incrementAndGet(), mangas.size()),
+ manga.title);
+ })
+ .concatMap(manga -> sourceManager.get(manga.source)
+ .pullChaptersFromNetwork(manga.url)
+ .flatMap(chapters -> db.insertOrRemoveChapters(manga, chapters))
+ .filter(result -> result.getNumberOfRowsInserted() > 0)
+ .flatMap(result -> Observable.just(new MangaUpdate(manga, result)))
+ )
+ .subscribe(update -> {
+ updates.add(update);
+ }, error -> {
+ Timber.e("Error syncing");
+ stopSelf(startId);
+ }, () -> {
+ NotificationUtil.createBigText(this, UPDATE_NOTIFICATION_ID,
+ getString(R.string.notification_completed), getUpdatedMangas(updates));
+ stopSelf(startId);
+ });
+ }
+
+ private String getUpdatedMangas(List updates) {
+ final StringBuilder result = new StringBuilder();
+ if (updates.isEmpty()) {
+ result.append(getString(R.string.notification_no_new_chapters)).append("\n");
+ } else {
+ result.append(getString(R.string.notification_new_chapters));
+
+ for (MangaUpdate update : updates) {
+ result.append("\n").append(update.getManga().title);
+ }
+ }
+
+ return result.toString();
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+
+ public static class SyncOnConnectionAvailable extends BroadcastReceiver {
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (NetworkUtil.isNetworkConnected(context)) {
+ if (BuildConfig.DEBUG) {
+ Timber.i("Connection is now available, triggering sync...");
+ }
+ AndroidComponentUtil.toggleComponent(context, this.getClass(), false);
+ context.startService(getStartIntent(context));
+ }
+ }
+ }
+
+ private static class MangaUpdate {
+ private Manga manga;
+ private PostResult result;
+
+ public MangaUpdate(Manga manga, PostResult result) {
+ this.manga = manga;
+ this.result = result;
+ }
+
+ public Manga getManga() {
+ return manga;
+ }
+
+ public PostResult getResult() {
+ return result;
+ }
+ }
+
+}
diff --git a/app/src/main/java/eu/kanade/mangafeed/injection/component/AppComponent.java b/app/src/main/java/eu/kanade/mangafeed/injection/component/AppComponent.java
index 0a93bb7550..90a117d18c 100644
--- a/app/src/main/java/eu/kanade/mangafeed/injection/component/AppComponent.java
+++ b/app/src/main/java/eu/kanade/mangafeed/injection/component/AppComponent.java
@@ -5,6 +5,7 @@ import android.app.Application;
import javax.inject.Singleton;
import dagger.Component;
+import eu.kanade.mangafeed.data.services.LibraryUpdateService;
import eu.kanade.mangafeed.injection.module.AppModule;
import eu.kanade.mangafeed.injection.module.DataModule;
import eu.kanade.mangafeed.presenter.CataloguePresenter;
@@ -40,6 +41,8 @@ public interface AppComponent {
void inject(Source source);
+ void inject(LibraryUpdateService libraryUpdateService);
+
Application application();
}
diff --git a/app/src/main/java/eu/kanade/mangafeed/ui/fragment/LibraryFragment.java b/app/src/main/java/eu/kanade/mangafeed/ui/fragment/LibraryFragment.java
index 6d6ee81c6e..e1191bf94c 100644
--- a/app/src/main/java/eu/kanade/mangafeed/ui/fragment/LibraryFragment.java
+++ b/app/src/main/java/eu/kanade/mangafeed/ui/fragment/LibraryFragment.java
@@ -19,6 +19,7 @@ import butterknife.ButterKnife;
import butterknife.OnItemClick;
import eu.kanade.mangafeed.R;
import eu.kanade.mangafeed.data.models.Manga;
+import eu.kanade.mangafeed.data.services.LibraryUpdateService;
import eu.kanade.mangafeed.presenter.LibraryPresenter;
import eu.kanade.mangafeed.ui.activity.MainActivity;
import eu.kanade.mangafeed.ui.activity.MangaDetailActivity;
@@ -68,6 +69,21 @@ public class LibraryFragment extends BaseRxFragment {
initializeSearch(menu);
}
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.action_refresh:
+ if (!LibraryUpdateService.isRunning(activity)) {
+ Intent intent = LibraryUpdateService.getStartIntent(activity);
+ activity.startService(intent);
+ }
+
+ return true;
+ }
+
+ return super.onOptionsItemSelected(item);
+ }
+
private void initializeSearch(Menu menu) {
final SearchView sv = (SearchView) menu.findItem(R.id.action_search).getActionView();
sv.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
diff --git a/app/src/main/java/eu/kanade/mangafeed/util/AndroidComponentUtil.java b/app/src/main/java/eu/kanade/mangafeed/util/AndroidComponentUtil.java
new file mode 100644
index 0000000000..3789160fec
--- /dev/null
+++ b/app/src/main/java/eu/kanade/mangafeed/util/AndroidComponentUtil.java
@@ -0,0 +1,33 @@
+package eu.kanade.mangafeed.util;
+
+import android.app.ActivityManager;
+import android.app.ActivityManager.RunningServiceInfo;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+
+import timber.log.Timber;
+
+public class AndroidComponentUtil {
+
+ public static void toggleComponent(Context context, Class componentClass, boolean enable) {
+ Timber.i((enable ? "Enabling " : "Disabling ") + componentClass.getSimpleName());
+ ComponentName componentName = new ComponentName(context, componentClass);
+ PackageManager pm = context.getPackageManager();
+ pm.setComponentEnabledSetting(componentName,
+ enable ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED :
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+ PackageManager.DONT_KILL_APP);
+ }
+
+ public static boolean isServiceRunning(Context context, Class serviceClass) {
+ ActivityManager manager =
+ (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
+ for (RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
+ if (serviceClass.getName().equals(service.service.getClassName())) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/app/src/main/java/eu/kanade/mangafeed/util/NotificationUtil.java b/app/src/main/java/eu/kanade/mangafeed/util/NotificationUtil.java
new file mode 100644
index 0000000000..3f20bf7297
--- /dev/null
+++ b/app/src/main/java/eu/kanade/mangafeed/util/NotificationUtil.java
@@ -0,0 +1,44 @@
+package eu.kanade.mangafeed.util;
+
+import android.app.NotificationManager;
+import android.content.Context;
+import android.support.v4.app.NotificationCompat;
+
+import eu.kanade.mangafeed.R;
+
+public class NotificationUtil {
+
+ public static void create(Context context, int nId, String title, String body, int iconRes) {
+ NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(context)
+ .setSmallIcon(iconRes == -1 ? R.drawable.ic_action_refresh : iconRes)
+ .setContentTitle(title)
+ .setContentText(body);
+
+
+ NotificationManager mNotificationManager =
+ (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+
+ mNotificationManager.notify(nId, mBuilder.build());
+ }
+
+ public static void createBigText(Context context, int nId, String title, String body, int iconRes) {
+ NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(context)
+ .setSmallIcon(iconRes == -1 ? R.drawable.ic_action_refresh : iconRes)
+ .setContentTitle(title)
+ .setStyle(new NotificationCompat.BigTextStyle().bigText(body));
+
+ NotificationManager mNotificationManager =
+ (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+
+ mNotificationManager.notify(nId, mBuilder.build());
+ }
+
+ public static void create(Context context, int nId, String title, String body) {
+ create(context, nId, title, body, -1);
+ }
+
+ public static void createBigText(Context context, int nId, String title, String body) {
+ createBigText(context, nId, title, body, -1);
+ }
+
+}
diff --git a/app/src/main/java/eu/kanade/mangafeed/util/PostResult.java b/app/src/main/java/eu/kanade/mangafeed/util/PostResult.java
index d64c3fd866..f76bacdd1c 100644
--- a/app/src/main/java/eu/kanade/mangafeed/util/PostResult.java
+++ b/app/src/main/java/eu/kanade/mangafeed/util/PostResult.java
@@ -19,17 +19,14 @@ public class PostResult {
this.numberOfRowsDeleted = numberOfRowsDeleted;
}
- @Nullable
public Integer getNumberOfRowsUpdated() {
return numberOfRowsUpdated;
}
- @Nullable
public Integer getNumberOfRowsInserted() {
return numberOfRowsInserted;
}
- @Nullable
public Integer getNumberOfRowsDeleted() {
return numberOfRowsDeleted;
}
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index ba03e13781..747235e062 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -82,4 +82,10 @@
Mark as unread
Selected chapters: %1$d
+ Update progress: %1$d/%2$d
+ Update completed
+ No new chapters found
+ Found new chapters for:
+
+