Allow to start/stop queue from download queue fragment. DownloadQueue now extends from ArrayList.
|
@ -42,11 +42,11 @@ public class DownloadManager {
|
||||||
|
|
||||||
private PublishSubject<Download> downloadsQueueSubject;
|
private PublishSubject<Download> downloadsQueueSubject;
|
||||||
private BehaviorSubject<Integer> threadsNumber;
|
private BehaviorSubject<Integer> threadsNumber;
|
||||||
|
private BehaviorSubject<Boolean> runningSubject;
|
||||||
private Subscription downloadsSubscription;
|
private Subscription downloadsSubscription;
|
||||||
private Subscription threadsNumberSubscription;
|
private Subscription threadsNumberSubscription;
|
||||||
|
|
||||||
private DownloadQueue queue;
|
private DownloadQueue queue;
|
||||||
private volatile boolean isQueuePaused;
|
|
||||||
private volatile boolean isRunning;
|
private volatile boolean isRunning;
|
||||||
|
|
||||||
public static final String PAGE_LIST_FILE = "index.json";
|
public static final String PAGE_LIST_FILE = "index.json";
|
||||||
|
@ -61,9 +61,10 @@ public class DownloadManager {
|
||||||
|
|
||||||
downloadsQueueSubject = PublishSubject.create();
|
downloadsQueueSubject = PublishSubject.create();
|
||||||
threadsNumber = BehaviorSubject.create();
|
threadsNumber = BehaviorSubject.create();
|
||||||
|
runningSubject = BehaviorSubject.create();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void initializeSubscriptions() {
|
private void initializeSubscriptions() {
|
||||||
if (downloadsSubscription != null && !downloadsSubscription.isUnsubscribed())
|
if (downloadsSubscription != null && !downloadsSubscription.isUnsubscribed())
|
||||||
downloadsSubscription.unsubscribe();
|
downloadsSubscription.unsubscribe();
|
||||||
|
|
||||||
|
@ -71,8 +72,6 @@ public class DownloadManager {
|
||||||
threadsNumberSubscription.unsubscribe();
|
threadsNumberSubscription.unsubscribe();
|
||||||
|
|
||||||
threadsNumberSubscription = preferences.getDownloadTheadsObservable()
|
threadsNumberSubscription = preferences.getDownloadTheadsObservable()
|
||||||
.filter(n -> !isQueuePaused)
|
|
||||||
.doOnNext(n -> isQueuePaused = (n == 0))
|
|
||||||
.subscribe(threadsNumber::onNext);
|
.subscribe(threadsNumber::onNext);
|
||||||
|
|
||||||
downloadsSubscription = downloadsQueueSubject
|
downloadsSubscription = downloadsQueueSubject
|
||||||
|
@ -86,11 +85,17 @@ public class DownloadManager {
|
||||||
}
|
}
|
||||||
}, e -> Timber.e(e.getCause(), e.getMessage()));
|
}, e -> Timber.e(e.getCause(), e.getMessage()));
|
||||||
|
|
||||||
|
if (!isRunning) {
|
||||||
isRunning = true;
|
isRunning = true;
|
||||||
|
runningSubject.onNext(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void destroySubscriptions() {
|
public void destroySubscriptions() {
|
||||||
|
if (isRunning) {
|
||||||
isRunning = false;
|
isRunning = false;
|
||||||
|
runningSubject.onNext(false);
|
||||||
|
}
|
||||||
|
|
||||||
if (downloadsSubscription != null && !downloadsSubscription.isUnsubscribed()) {
|
if (downloadsSubscription != null && !downloadsSubscription.isUnsubscribed()) {
|
||||||
downloadsSubscription.unsubscribe();
|
downloadsSubscription.unsubscribe();
|
||||||
|
@ -131,7 +136,7 @@ public class DownloadManager {
|
||||||
// Prepare the download. Returns true if the chapter is already downloaded
|
// Prepare the download. Returns true if the chapter is already downloaded
|
||||||
private boolean prepareDownload(Download download) {
|
private boolean prepareDownload(Download download) {
|
||||||
// If the chapter is already queued, don't add it again
|
// If the chapter is already queued, don't add it again
|
||||||
for (Download queuedDownload : queue.get()) {
|
for (Download queuedDownload : queue) {
|
||||||
if (download.chapter.id.equals(queuedDownload.chapter.id))
|
if (download.chapter.id.equals(queuedDownload.chapter.id))
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -376,28 +381,22 @@ public class DownloadManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean areAllDownloadsFinished() {
|
public boolean areAllDownloadsFinished() {
|
||||||
for (Download download : queue.get()) {
|
for (Download download : queue) {
|
||||||
if (download.getStatus() <= Download.DOWNLOADING)
|
if (download.getStatus() <= Download.DOWNLOADING)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void resumeDownloads() {
|
|
||||||
isQueuePaused = false;
|
|
||||||
threadsNumber.onNext(preferences.getDownloadThreads());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void pauseDownloads() {
|
|
||||||
threadsNumber.onNext(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean startDownloads() {
|
public boolean startDownloads() {
|
||||||
|
if (queue.isEmpty())
|
||||||
|
return false;
|
||||||
|
|
||||||
boolean hasPendingDownloads = false;
|
boolean hasPendingDownloads = false;
|
||||||
if (downloadsSubscription == null || threadsNumberSubscription == null)
|
if (downloadsSubscription == null || threadsNumberSubscription == null)
|
||||||
initializeSubscriptions();
|
initializeSubscriptions();
|
||||||
|
|
||||||
for (Download download : queue.get()) {
|
for (Download download : queue) {
|
||||||
if (download.getStatus() != Download.DOWNLOADED) {
|
if (download.getStatus() != Download.DOWNLOADED) {
|
||||||
if (download.getStatus() != Download.QUEUE) download.setStatus(Download.QUEUE);
|
if (download.getStatus() != Download.QUEUE) download.setStatus(Download.QUEUE);
|
||||||
if (!hasPendingDownloads) hasPendingDownloads = true;
|
if (!hasPendingDownloads) hasPendingDownloads = true;
|
||||||
|
@ -409,15 +408,15 @@ public class DownloadManager {
|
||||||
|
|
||||||
public void stopDownloads() {
|
public void stopDownloads() {
|
||||||
destroySubscriptions();
|
destroySubscriptions();
|
||||||
for (Download download : queue.get()) {
|
for (Download download : queue) {
|
||||||
if (download.getStatus() == Download.DOWNLOADING) {
|
if (download.getStatus() == Download.DOWNLOADING) {
|
||||||
download.setStatus(Download.ERROR);
|
download.setStatus(Download.ERROR);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isRunning() {
|
public BehaviorSubject<Boolean> getRunningSubject() {
|
||||||
return isRunning;
|
return runningSubject;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@ public class DownloadService extends Service {
|
||||||
|
|
||||||
private PowerManager.WakeLock wakeLock;
|
private PowerManager.WakeLock wakeLock;
|
||||||
private Subscription networkChangeSubscription;
|
private Subscription networkChangeSubscription;
|
||||||
|
private Subscription queueRunningSubscription;
|
||||||
|
|
||||||
public static void start(Context context) {
|
public static void start(Context context) {
|
||||||
context.startService(new Intent(context, DownloadService.class));
|
context.startService(new Intent(context, DownloadService.class));
|
||||||
|
@ -40,6 +41,7 @@ public class DownloadService extends Service {
|
||||||
|
|
||||||
createWakeLock();
|
createWakeLock();
|
||||||
|
|
||||||
|
listenQueueRunningChanges();
|
||||||
EventBus.getDefault().registerSticky(this);
|
EventBus.getDefault().registerSticky(this);
|
||||||
listenNetworkChanges();
|
listenNetworkChanges();
|
||||||
}
|
}
|
||||||
|
@ -52,6 +54,7 @@ public class DownloadService extends Service {
|
||||||
@Override
|
@Override
|
||||||
public void onDestroy() {
|
public void onDestroy() {
|
||||||
EventBus.getDefault().unregister(this);
|
EventBus.getDefault().unregister(this);
|
||||||
|
queueRunningSubscription.unsubscribe();
|
||||||
networkChangeSubscription.unsubscribe();
|
networkChangeSubscription.unsubscribe();
|
||||||
downloadManager.destroySubscriptions();
|
downloadManager.destroySubscriptions();
|
||||||
destroyWakeLock();
|
destroyWakeLock();
|
||||||
|
@ -67,8 +70,6 @@ public class DownloadService extends Service {
|
||||||
public void onEvent(DownloadChaptersEvent event) {
|
public void onEvent(DownloadChaptersEvent event) {
|
||||||
EventBus.getDefault().removeStickyEvent(event);
|
EventBus.getDefault().removeStickyEvent(event);
|
||||||
downloadManager.onDownloadChaptersEvent(event);
|
downloadManager.onDownloadChaptersEvent(event);
|
||||||
if (downloadManager.isRunning())
|
|
||||||
acquireWakeLock();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void listenNetworkChanges() {
|
private void listenNetworkChanges() {
|
||||||
|
@ -79,15 +80,22 @@ public class DownloadService extends Service {
|
||||||
// If there are no remaining downloads, destroy the service
|
// If there are no remaining downloads, destroy the service
|
||||||
if (!downloadManager.startDownloads())
|
if (!downloadManager.startDownloads())
|
||||||
stopSelf();
|
stopSelf();
|
||||||
else
|
|
||||||
acquireWakeLock();
|
|
||||||
} else {
|
} else {
|
||||||
downloadManager.stopDownloads();
|
downloadManager.stopDownloads();
|
||||||
releaseWakeLock();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void listenQueueRunningChanges() {
|
||||||
|
queueRunningSubscription = downloadManager.getRunningSubject()
|
||||||
|
.subscribe(running -> {
|
||||||
|
if (running)
|
||||||
|
acquireWakeLock();
|
||||||
|
else
|
||||||
|
releaseWakeLock();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private void createWakeLock() {
|
private void createWakeLock() {
|
||||||
wakeLock = ((PowerManager)getSystemService(POWER_SERVICE)).newWakeLock(
|
wakeLock = ((PowerManager)getSystemService(POWER_SERVICE)).newWakeLock(
|
||||||
PowerManager.PARTIAL_WAKE_LOCK, "DownloadService:WakeLock");
|
PowerManager.PARTIAL_WAKE_LOCK, "DownloadService:WakeLock");
|
||||||
|
|
|
@ -8,29 +8,28 @@ import eu.kanade.mangafeed.data.source.model.Page;
|
||||||
import rx.Observable;
|
import rx.Observable;
|
||||||
import rx.subjects.PublishSubject;
|
import rx.subjects.PublishSubject;
|
||||||
|
|
||||||
public class DownloadQueue {
|
public class DownloadQueue extends ArrayList<Download> {
|
||||||
|
|
||||||
private List<Download> queue;
|
|
||||||
private PublishSubject<Download> statusSubject;
|
private PublishSubject<Download> statusSubject;
|
||||||
|
|
||||||
public DownloadQueue() {
|
public DownloadQueue() {
|
||||||
queue = new ArrayList<>();
|
super();
|
||||||
statusSubject = PublishSubject.create();
|
statusSubject = PublishSubject.create();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void add(Download download) {
|
public boolean add(Download download) {
|
||||||
download.setStatusSubject(statusSubject);
|
download.setStatusSubject(statusSubject);
|
||||||
download.setStatus(Download.QUEUE);
|
download.setStatus(Download.QUEUE);
|
||||||
queue.add(download);
|
return super.add(download);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void remove(Download download) {
|
public void remove(Download download) {
|
||||||
queue.remove(download);
|
super.remove(download);
|
||||||
download.setStatusSubject(null);
|
download.setStatusSubject(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void remove(Chapter chapter) {
|
public void remove(Chapter chapter) {
|
||||||
for (Download download : queue) {
|
for (Download download : this) {
|
||||||
if (download.chapter.id.equals(chapter.id)) {
|
if (download.chapter.id.equals(chapter.id)) {
|
||||||
remove(download);
|
remove(download);
|
||||||
break;
|
break;
|
||||||
|
@ -38,12 +37,8 @@ public class DownloadQueue {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Download> get() {
|
|
||||||
return queue;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Observable<Download> getActiveDownloads() {
|
public Observable<Download> getActiveDownloads() {
|
||||||
return Observable.from(queue)
|
return Observable.from(this)
|
||||||
.filter(download -> download.getStatus() == Download.DOWNLOADING);
|
.filter(download -> download.getStatus() == Download.DOWNLOADING);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,9 @@ import android.support.annotation.Nullable;
|
||||||
import android.support.v7.widget.LinearLayoutManager;
|
import android.support.v7.widget.LinearLayoutManager;
|
||||||
import android.support.v7.widget.RecyclerView;
|
import android.support.v7.widget.RecyclerView;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuInflater;
|
||||||
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
@ -13,9 +16,11 @@ import java.util.List;
|
||||||
import butterknife.Bind;
|
import butterknife.Bind;
|
||||||
import butterknife.ButterKnife;
|
import butterknife.ButterKnife;
|
||||||
import eu.kanade.mangafeed.R;
|
import eu.kanade.mangafeed.R;
|
||||||
|
import eu.kanade.mangafeed.data.download.DownloadService;
|
||||||
import eu.kanade.mangafeed.data.download.model.Download;
|
import eu.kanade.mangafeed.data.download.model.Download;
|
||||||
import eu.kanade.mangafeed.ui.base.fragment.BaseRxFragment;
|
import eu.kanade.mangafeed.ui.base.fragment.BaseRxFragment;
|
||||||
import nucleus.factory.RequiresPresenter;
|
import nucleus.factory.RequiresPresenter;
|
||||||
|
import rx.Subscription;
|
||||||
|
|
||||||
@RequiresPresenter(DownloadPresenter.class)
|
@RequiresPresenter(DownloadPresenter.class)
|
||||||
public class DownloadFragment extends BaseRxFragment<DownloadPresenter> {
|
public class DownloadFragment extends BaseRxFragment<DownloadPresenter> {
|
||||||
|
@ -23,10 +28,22 @@ public class DownloadFragment extends BaseRxFragment<DownloadPresenter> {
|
||||||
@Bind(R.id.download_list) RecyclerView recyclerView;
|
@Bind(R.id.download_list) RecyclerView recyclerView;
|
||||||
private DownloadAdapter adapter;
|
private DownloadAdapter adapter;
|
||||||
|
|
||||||
|
private MenuItem startButton;
|
||||||
|
private MenuItem stopButton;
|
||||||
|
|
||||||
|
private Subscription queueStatusSubscription;
|
||||||
|
private boolean isRunning;
|
||||||
|
|
||||||
public static DownloadFragment newInstance() {
|
public static DownloadFragment newInstance() {
|
||||||
return new DownloadFragment();
|
return new DownloadFragment();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle bundle) {
|
||||||
|
super.onCreate(bundle);
|
||||||
|
setHasOptionsMenu(true);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
|
@ -43,6 +60,51 @@ public class DownloadFragment extends BaseRxFragment<DownloadPresenter> {
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||||
|
inflater.inflate(R.menu.download_queue, menu);
|
||||||
|
startButton = menu.findItem(R.id.start_queue);
|
||||||
|
stopButton = menu.findItem(R.id.stop_queue);
|
||||||
|
|
||||||
|
// Menu seems to be inflated after onResume in fragments, so we initialize them here
|
||||||
|
startButton.setVisible(!isRunning && !getPresenter().downloadManager.getQueue().isEmpty());
|
||||||
|
stopButton.setVisible(isRunning);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
switch (item.getItemId()) {
|
||||||
|
case R.id.start_queue:
|
||||||
|
DownloadService.start(getActivity());
|
||||||
|
break;
|
||||||
|
case R.id.stop_queue:
|
||||||
|
DownloadService.stop(getActivity());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
queueStatusSubscription = getPresenter().downloadManager.getRunningSubject()
|
||||||
|
.subscribe(this::onRunningChange);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPause() {
|
||||||
|
queueStatusSubscription.unsubscribe();
|
||||||
|
super.onPause();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onRunningChange(boolean running) {
|
||||||
|
isRunning = running;
|
||||||
|
if (startButton != null)
|
||||||
|
startButton.setVisible(!running && !getPresenter().downloadManager.getQueue().isEmpty());
|
||||||
|
if (stopButton != null)
|
||||||
|
stopButton.setVisible(running);
|
||||||
|
}
|
||||||
|
|
||||||
private void createAdapter() {
|
private void createAdapter() {
|
||||||
adapter = new DownloadAdapter(getActivity());
|
adapter = new DownloadAdapter(getActivity());
|
||||||
recyclerView.setAdapter(adapter);
|
recyclerView.setAdapter(adapter);
|
||||||
|
|
|
@ -37,7 +37,7 @@ public class DownloadPresenter extends BasePresenter<DownloadFragment> {
|
||||||
progressSubscriptions = new HashMap<>();
|
progressSubscriptions = new HashMap<>();
|
||||||
|
|
||||||
restartableLatestCache(GET_DOWNLOAD_QUEUE,
|
restartableLatestCache(GET_DOWNLOAD_QUEUE,
|
||||||
() -> Observable.just(downloadQueue.get()),
|
() -> Observable.just(downloadQueue),
|
||||||
DownloadFragment::onNextDownloads,
|
DownloadFragment::onNextDownloads,
|
||||||
(view, error) -> Timber.e(error.getMessage()));
|
(view, error) -> Timber.e(error.getMessage()));
|
||||||
|
|
||||||
|
|
|
@ -148,7 +148,7 @@ public class ChaptersPresenter extends BasePresenter<ChaptersFragment> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setChapterStatus(Chapter chapter) {
|
private void setChapterStatus(Chapter chapter) {
|
||||||
for (Download download : downloadManager.getQueue().get()) {
|
for (Download download : downloadManager.getQueue()) {
|
||||||
if (chapter.id.equals(download.chapter.id)) {
|
if (chapter.id.equals(download.chapter.id)) {
|
||||||
chapter.status = download.getStatus();
|
chapter.status = download.getStatus();
|
||||||
return;
|
return;
|
||||||
|
|
BIN
app/src/main/res/drawable-hdpi/ic_play_arrow.png
Normal file
After Width: | Height: | Size: 205 B |
BIN
app/src/main/res/drawable-hdpi/ic_stop.png
Normal file
After Width: | Height: | Size: 99 B |
BIN
app/src/main/res/drawable-ldpi/ic_play_arrow.png
Normal file
After Width: | Height: | Size: 149 B |
BIN
app/src/main/res/drawable-ldpi/ic_stop.png
Normal file
After Width: | Height: | Size: 158 B |
BIN
app/src/main/res/drawable-mdpi/ic_play_arrow.png
Normal file
After Width: | Height: | Size: 161 B |
BIN
app/src/main/res/drawable-mdpi/ic_stop.png
Normal file
After Width: | Height: | Size: 86 B |
BIN
app/src/main/res/drawable-xhdpi/ic_play_arrow.png
Normal file
After Width: | Height: | Size: 233 B |
BIN
app/src/main/res/drawable-xhdpi/ic_stop.png
Normal file
After Width: | Height: | Size: 110 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_play_arrow.png
Normal file
After Width: | Height: | Size: 316 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_stop.png
Normal file
After Width: | Height: | Size: 131 B |
BIN
app/src/main/res/drawable-xxxhdpi/ic_play_arrow.png
Normal file
After Width: | Height: | Size: 394 B |
BIN
app/src/main/res/drawable-xxxhdpi/ic_stop.png
Normal file
After Width: | Height: | Size: 164 B |
17
app/src/main/res/menu/download_queue.xml
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
|
<item android:title="@string/action_start"
|
||||||
|
android:id="@+id/start_queue"
|
||||||
|
android:icon="@drawable/ic_play_arrow"
|
||||||
|
android:visible="false"
|
||||||
|
app:showAsAction="ifRoom"/>
|
||||||
|
|
||||||
|
<item android:title="@string/action_stop"
|
||||||
|
android:id="@+id/stop_queue"
|
||||||
|
android:icon="@drawable/ic_stop"
|
||||||
|
android:visible="false"
|
||||||
|
app:showAsAction="ifRoom"/>
|
||||||
|
|
||||||
|
</menu>
|
|
@ -24,6 +24,8 @@
|
||||||
<string name="action_show_unread">Unread</string>
|
<string name="action_show_unread">Unread</string>
|
||||||
<string name="action_show_downloaded">Downloaded</string>
|
<string name="action_show_downloaded">Downloaded</string>
|
||||||
<string name="action_next_unread">Next unread</string>
|
<string name="action_next_unread">Next unread</string>
|
||||||
|
<string name="action_start">Start</string>
|
||||||
|
<string name="action_stop">Stop</string>
|
||||||
|
|
||||||
<!-- Buttons -->
|
<!-- Buttons -->
|
||||||
<string name="button_ok">OK</string>
|
<string name="button_ok">OK</string>
|
||||||
|
|