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 BehaviorSubject<Integer> threadsNumber; | ||||
|     private BehaviorSubject<Boolean> runningSubject; | ||||
|     private Subscription downloadsSubscription; | ||||
|     private Subscription threadsNumberSubscription; | ||||
|  | ||||
|     private DownloadQueue queue; | ||||
|     private volatile boolean isQueuePaused; | ||||
|     private volatile boolean isRunning; | ||||
|  | ||||
|     public static final String PAGE_LIST_FILE = "index.json"; | ||||
| @@ -61,9 +61,10 @@ public class DownloadManager { | ||||
|  | ||||
|         downloadsQueueSubject = PublishSubject.create(); | ||||
|         threadsNumber = BehaviorSubject.create(); | ||||
|         runningSubject = BehaviorSubject.create(); | ||||
|     } | ||||
|  | ||||
|     public void initializeSubscriptions() { | ||||
|     private void initializeSubscriptions() { | ||||
|         if (downloadsSubscription != null && !downloadsSubscription.isUnsubscribed()) | ||||
|             downloadsSubscription.unsubscribe(); | ||||
|  | ||||
| @@ -71,8 +72,6 @@ public class DownloadManager { | ||||
|             threadsNumberSubscription.unsubscribe(); | ||||
|  | ||||
|         threadsNumberSubscription = preferences.getDownloadTheadsObservable() | ||||
|                 .filter(n -> !isQueuePaused) | ||||
|                 .doOnNext(n -> isQueuePaused = (n == 0)) | ||||
|                 .subscribe(threadsNumber::onNext); | ||||
|  | ||||
|         downloadsSubscription = downloadsQueueSubject | ||||
| @@ -86,11 +85,17 @@ public class DownloadManager { | ||||
|                     } | ||||
|                 }, e -> Timber.e(e.getCause(), e.getMessage())); | ||||
|  | ||||
|         isRunning = true; | ||||
|         if (!isRunning) { | ||||
|             isRunning = true; | ||||
|             runningSubject.onNext(true); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public void destroySubscriptions() { | ||||
|         isRunning = false; | ||||
|         if (isRunning) { | ||||
|             isRunning = false; | ||||
|             runningSubject.onNext(false); | ||||
|         } | ||||
|  | ||||
|         if (downloadsSubscription != null && !downloadsSubscription.isUnsubscribed()) { | ||||
|             downloadsSubscription.unsubscribe(); | ||||
| @@ -131,7 +136,7 @@ public class DownloadManager { | ||||
|     // Prepare the download. Returns true if the chapter is already downloaded | ||||
|     private boolean prepareDownload(Download download) { | ||||
|         // 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)) | ||||
|                 return true; | ||||
|         } | ||||
| @@ -376,28 +381,22 @@ public class DownloadManager { | ||||
|     } | ||||
|  | ||||
|     public boolean areAllDownloadsFinished() { | ||||
|         for (Download download : queue.get()) { | ||||
|         for (Download download : queue) { | ||||
|             if (download.getStatus() <= Download.DOWNLOADING) | ||||
|                 return false; | ||||
|         } | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     public void resumeDownloads() { | ||||
|         isQueuePaused = false; | ||||
|         threadsNumber.onNext(preferences.getDownloadThreads()); | ||||
|     } | ||||
|  | ||||
|     public void pauseDownloads() { | ||||
|         threadsNumber.onNext(0); | ||||
|     } | ||||
|  | ||||
|     public boolean startDownloads() { | ||||
|         if (queue.isEmpty()) | ||||
|             return false; | ||||
|  | ||||
|         boolean hasPendingDownloads = false; | ||||
|         if (downloadsSubscription == null || threadsNumberSubscription == null) | ||||
|             initializeSubscriptions(); | ||||
|  | ||||
|         for (Download download : queue.get()) { | ||||
|         for (Download download : queue) { | ||||
|             if (download.getStatus() != Download.DOWNLOADED) { | ||||
|                 if (download.getStatus() != Download.QUEUE) download.setStatus(Download.QUEUE); | ||||
|                 if (!hasPendingDownloads) hasPendingDownloads = true; | ||||
| @@ -409,15 +408,15 @@ public class DownloadManager { | ||||
|  | ||||
|     public void stopDownloads() { | ||||
|         destroySubscriptions(); | ||||
|         for (Download download : queue.get()) { | ||||
|         for (Download download : queue) { | ||||
|             if (download.getStatus() == Download.DOWNLOADING) { | ||||
|                 download.setStatus(Download.ERROR); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public boolean isRunning() { | ||||
|         return isRunning; | ||||
|     public BehaviorSubject<Boolean> getRunningSubject() { | ||||
|         return runningSubject; | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -24,6 +24,7 @@ public class DownloadService extends Service { | ||||
|  | ||||
|     private PowerManager.WakeLock wakeLock; | ||||
|     private Subscription networkChangeSubscription; | ||||
|     private Subscription queueRunningSubscription; | ||||
|  | ||||
|     public static void start(Context context) { | ||||
|         context.startService(new Intent(context, DownloadService.class)); | ||||
| @@ -40,6 +41,7 @@ public class DownloadService extends Service { | ||||
|  | ||||
|         createWakeLock(); | ||||
|  | ||||
|         listenQueueRunningChanges(); | ||||
|         EventBus.getDefault().registerSticky(this); | ||||
|         listenNetworkChanges(); | ||||
|     } | ||||
| @@ -52,6 +54,7 @@ public class DownloadService extends Service { | ||||
|     @Override | ||||
|     public void onDestroy() { | ||||
|         EventBus.getDefault().unregister(this); | ||||
|         queueRunningSubscription.unsubscribe(); | ||||
|         networkChangeSubscription.unsubscribe(); | ||||
|         downloadManager.destroySubscriptions(); | ||||
|         destroyWakeLock(); | ||||
| @@ -67,8 +70,6 @@ public class DownloadService extends Service { | ||||
|     public void onEvent(DownloadChaptersEvent event) { | ||||
|         EventBus.getDefault().removeStickyEvent(event); | ||||
|         downloadManager.onDownloadChaptersEvent(event); | ||||
|         if (downloadManager.isRunning()) | ||||
|             acquireWakeLock(); | ||||
|     } | ||||
|  | ||||
|     private void listenNetworkChanges() { | ||||
| @@ -79,15 +80,22 @@ public class DownloadService extends Service { | ||||
|                         // If there are no remaining downloads, destroy the service | ||||
|                         if (!downloadManager.startDownloads()) | ||||
|                             stopSelf(); | ||||
|                         else | ||||
|                             acquireWakeLock(); | ||||
|                     } else { | ||||
|                         downloadManager.stopDownloads(); | ||||
|                         releaseWakeLock(); | ||||
|                     } | ||||
|                 }); | ||||
|     } | ||||
|  | ||||
|     private void listenQueueRunningChanges() { | ||||
|         queueRunningSubscription = downloadManager.getRunningSubject() | ||||
|                 .subscribe(running -> { | ||||
|                     if (running) | ||||
|                         acquireWakeLock(); | ||||
|                     else | ||||
|                         releaseWakeLock(); | ||||
|                 }); | ||||
|     } | ||||
|  | ||||
|     private void createWakeLock() { | ||||
|         wakeLock = ((PowerManager)getSystemService(POWER_SERVICE)).newWakeLock( | ||||
|                 PowerManager.PARTIAL_WAKE_LOCK, "DownloadService:WakeLock"); | ||||
|   | ||||
| @@ -8,29 +8,28 @@ import eu.kanade.mangafeed.data.source.model.Page; | ||||
| import rx.Observable; | ||||
| import rx.subjects.PublishSubject; | ||||
|  | ||||
| public class DownloadQueue { | ||||
| public class DownloadQueue extends ArrayList<Download> { | ||||
|  | ||||
|     private List<Download> queue; | ||||
|     private PublishSubject<Download> statusSubject; | ||||
|  | ||||
|     public DownloadQueue() { | ||||
|         queue = new ArrayList<>(); | ||||
|         super(); | ||||
|         statusSubject = PublishSubject.create(); | ||||
|     } | ||||
|  | ||||
|     public void add(Download download) { | ||||
|     public boolean add(Download download) { | ||||
|         download.setStatusSubject(statusSubject); | ||||
|         download.setStatus(Download.QUEUE); | ||||
|         queue.add(download); | ||||
|         return super.add(download); | ||||
|     } | ||||
|  | ||||
|     public void remove(Download download) { | ||||
|         queue.remove(download); | ||||
|         super.remove(download); | ||||
|         download.setStatusSubject(null); | ||||
|     } | ||||
|  | ||||
|     public void remove(Chapter chapter) { | ||||
|         for (Download download : queue) { | ||||
|         for (Download download : this) { | ||||
|             if (download.chapter.id.equals(chapter.id)) { | ||||
|                 remove(download); | ||||
|                 break; | ||||
| @@ -38,12 +37,8 @@ public class DownloadQueue { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public List<Download> get() { | ||||
|         return queue; | ||||
|     } | ||||
|  | ||||
|     public Observable<Download> getActiveDownloads() { | ||||
|         return Observable.from(queue) | ||||
|         return Observable.from(this) | ||||
|                 .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.RecyclerView; | ||||
| import android.view.LayoutInflater; | ||||
| import android.view.Menu; | ||||
| import android.view.MenuInflater; | ||||
| import android.view.MenuItem; | ||||
| import android.view.View; | ||||
| import android.view.ViewGroup; | ||||
|  | ||||
| @@ -13,9 +16,11 @@ import java.util.List; | ||||
| import butterknife.Bind; | ||||
| import butterknife.ButterKnife; | ||||
| import eu.kanade.mangafeed.R; | ||||
| import eu.kanade.mangafeed.data.download.DownloadService; | ||||
| import eu.kanade.mangafeed.data.download.model.Download; | ||||
| import eu.kanade.mangafeed.ui.base.fragment.BaseRxFragment; | ||||
| import nucleus.factory.RequiresPresenter; | ||||
| import rx.Subscription; | ||||
|  | ||||
| @RequiresPresenter(DownloadPresenter.class) | ||||
| public class DownloadFragment extends BaseRxFragment<DownloadPresenter> { | ||||
| @@ -23,10 +28,22 @@ public class DownloadFragment extends BaseRxFragment<DownloadPresenter> { | ||||
|     @Bind(R.id.download_list) RecyclerView recyclerView; | ||||
|     private DownloadAdapter adapter; | ||||
|  | ||||
|     private MenuItem startButton; | ||||
|     private MenuItem stopButton; | ||||
|  | ||||
|     private Subscription queueStatusSubscription; | ||||
|     private boolean isRunning; | ||||
|  | ||||
|     public static DownloadFragment newInstance() { | ||||
|         return new DownloadFragment(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onCreate(Bundle bundle) { | ||||
|         super.onCreate(bundle); | ||||
|         setHasOptionsMenu(true); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public View onCreateView(LayoutInflater inflater, ViewGroup container, | ||||
|                              Bundle savedInstanceState) { | ||||
| @@ -43,6 +60,51 @@ public class DownloadFragment extends BaseRxFragment<DownloadPresenter> { | ||||
|         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() { | ||||
|         adapter = new DownloadAdapter(getActivity()); | ||||
|         recyclerView.setAdapter(adapter); | ||||
|   | ||||
| @@ -37,7 +37,7 @@ public class DownloadPresenter extends BasePresenter<DownloadFragment> { | ||||
|         progressSubscriptions = new HashMap<>(); | ||||
|  | ||||
|         restartableLatestCache(GET_DOWNLOAD_QUEUE, | ||||
|                 () -> Observable.just(downloadQueue.get()), | ||||
|                 () -> Observable.just(downloadQueue), | ||||
|                 DownloadFragment::onNextDownloads, | ||||
|                 (view, error) -> Timber.e(error.getMessage())); | ||||
|  | ||||
|   | ||||
| @@ -148,7 +148,7 @@ public class ChaptersPresenter extends BasePresenter<ChaptersFragment> { | ||||
|     } | ||||
|  | ||||
|     private void setChapterStatus(Chapter chapter) { | ||||
|         for (Download download : downloadManager.getQueue().get()) { | ||||
|         for (Download download : downloadManager.getQueue()) { | ||||
|             if (chapter.id.equals(download.chapter.id)) { | ||||
|                 chapter.status = download.getStatus(); | ||||
|                 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_downloaded">Downloaded</string> | ||||
|     <string name="action_next_unread">Next unread</string> | ||||
|     <string name="action_start">Start</string> | ||||
|     <string name="action_stop">Stop</string> | ||||
|  | ||||
|     <!-- Buttons --> | ||||
|     <string name="button_ok">OK</string> | ||||
|   | ||||