mirror of
https://github.com/aniyomiorg/aniyomi.git
synced 2024-11-28 00:37:58 +03:00
Remove simultaneous downloads
This commit is contained in:
parent
d75c6b0c36
commit
7b4ac7998a
7 changed files with 22 additions and 306 deletions
|
@ -36,7 +36,6 @@ internal class DownloadNotifier(private val context: Context) {
|
||||||
* The size of queue on start download.
|
* The size of queue on start download.
|
||||||
*/
|
*/
|
||||||
var initialQueueSize = 0
|
var initialQueueSize = 0
|
||||||
get() = field
|
|
||||||
set(value) {
|
set(value) {
|
||||||
if (value != 0){
|
if (value != 0){
|
||||||
isSingleChapter = (value == 1)
|
isSingleChapter = (value == 1)
|
||||||
|
@ -44,11 +43,6 @@ internal class DownloadNotifier(private val context: Context) {
|
||||||
field = value
|
field = value
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Simultaneous download setting > 1.
|
|
||||||
*/
|
|
||||||
var multipleDownloadThreads = false
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updated when error is thrown
|
* Updated when error is thrown
|
||||||
*/
|
*/
|
||||||
|
@ -91,36 +85,10 @@ internal class DownloadNotifier(private val context: Context) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when download progress changes.
|
* Called when download progress changes.
|
||||||
* Note: Only accepted when multi download active.
|
|
||||||
*
|
|
||||||
* @param queue the queue containing downloads.
|
|
||||||
*/
|
|
||||||
fun onProgressChange(queue: DownloadQueue) {
|
|
||||||
if (multipleDownloadThreads) {
|
|
||||||
doOnProgressChange(null, queue)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when download progress changes.
|
|
||||||
* Note: Only accepted when single download active.
|
|
||||||
*
|
*
|
||||||
* @param download download object containing download information.
|
* @param download download object containing download information.
|
||||||
* @param queue the queue containing downloads.
|
|
||||||
*/
|
*/
|
||||||
fun onProgressChange(download: Download, queue: DownloadQueue) {
|
fun onProgressChange(download: Download) {
|
||||||
if (!multipleDownloadThreads) {
|
|
||||||
doOnProgressChange(download, queue)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show notification progress of chapter.
|
|
||||||
*
|
|
||||||
* @param download download object containing download information.
|
|
||||||
* @param queue the queue containing downloads.
|
|
||||||
*/
|
|
||||||
private fun doOnProgressChange(download: Download?, queue: DownloadQueue) {
|
|
||||||
// Create notification
|
// Create notification
|
||||||
with(notification) {
|
with(notification) {
|
||||||
// Check if first call.
|
// Check if first call.
|
||||||
|
@ -133,28 +101,13 @@ internal class DownloadNotifier(private val context: Context) {
|
||||||
isDownloading = true
|
isDownloading = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if (multipleDownloadThreads) {
|
val title = download.manga.title.chop(15)
|
||||||
setContentTitle(context.getString(R.string.app_name))
|
val quotedTitle = Pattern.quote(title)
|
||||||
|
val chapter = download.chapter.name.replaceFirst("$quotedTitle[\\s]*[-]*[\\s]*".toRegex(RegexOption.IGNORE_CASE), "")
|
||||||
// Reset the queue size if the download progress is negative
|
setContentTitle("$title - $chapter".chop(30))
|
||||||
if ((initialQueueSize - queue.size) < 0)
|
setContentText(context.getString(R.string.chapter_downloading_progress)
|
||||||
initialQueueSize = queue.size
|
.format(download.downloadedImages, download.pages!!.size))
|
||||||
|
setProgress(download.pages!!.size, download.downloadedImages, false)
|
||||||
setContentText(context.getString(R.string.chapter_downloading_progress)
|
|
||||||
.format(initialQueueSize - queue.size, initialQueueSize))
|
|
||||||
setProgress(initialQueueSize, initialQueueSize - queue.size, false)
|
|
||||||
} else {
|
|
||||||
download?.let {
|
|
||||||
val title = it.manga.title.chop(15)
|
|
||||||
val quotedTitle = Pattern.quote(title)
|
|
||||||
val chapter = download.chapter.name.replaceFirst("$quotedTitle[\\s]*[-]*[\\s]*".toRegex(RegexOption.IGNORE_CASE), "")
|
|
||||||
setContentTitle("$title - $chapter".chop(30))
|
|
||||||
setContentText(context.getString(R.string.chapter_downloading_progress)
|
|
||||||
.format(it.downloadedImages, it.pages!!.size))
|
|
||||||
setProgress(it.pages!!.size, it.downloadedImages, false)
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// Displays the progress bar on notification
|
// Displays the progress bar on notification
|
||||||
notification.show()
|
notification.show()
|
||||||
|
|
|
@ -9,8 +9,6 @@ import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.data.download.model.Download
|
import eu.kanade.tachiyomi.data.download.model.Download
|
||||||
import eu.kanade.tachiyomi.data.download.model.DownloadQueue
|
import eu.kanade.tachiyomi.data.download.model.DownloadQueue
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
|
||||||
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
|
||||||
import eu.kanade.tachiyomi.source.SourceManager
|
import eu.kanade.tachiyomi.source.SourceManager
|
||||||
import eu.kanade.tachiyomi.source.model.Page
|
import eu.kanade.tachiyomi.source.model.Page
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
|
@ -21,7 +19,6 @@ import okhttp3.Response
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import rx.android.schedulers.AndroidSchedulers
|
import rx.android.schedulers.AndroidSchedulers
|
||||||
import rx.schedulers.Schedulers
|
import rx.schedulers.Schedulers
|
||||||
import rx.subjects.BehaviorSubject
|
|
||||||
import rx.subscriptions.CompositeSubscription
|
import rx.subscriptions.CompositeSubscription
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
@ -39,9 +36,11 @@ import uy.kohesive.injekt.injectLazy
|
||||||
* @param provider the downloads directory provider.
|
* @param provider the downloads directory provider.
|
||||||
* @param cache the downloads cache, used to add the downloads to the cache after their completion.
|
* @param cache the downloads cache, used to add the downloads to the cache after their completion.
|
||||||
*/
|
*/
|
||||||
class Downloader(private val context: Context,
|
class Downloader(
|
||||||
private val provider: DownloadProvider,
|
private val context: Context,
|
||||||
private val cache: DownloadCache) {
|
private val provider: DownloadProvider,
|
||||||
|
private val cache: DownloadCache
|
||||||
|
) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Store for persisting downloads across restarts.
|
* Store for persisting downloads across restarts.
|
||||||
|
@ -58,11 +57,6 @@ class Downloader(private val context: Context,
|
||||||
*/
|
*/
|
||||||
private val sourceManager: SourceManager by injectLazy()
|
private val sourceManager: SourceManager by injectLazy()
|
||||||
|
|
||||||
/**
|
|
||||||
* Preferences.
|
|
||||||
*/
|
|
||||||
private val preferences: PreferencesHelper by injectLazy()
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notifier for the downloader state and progress.
|
* Notifier for the downloader state and progress.
|
||||||
*/
|
*/
|
||||||
|
@ -73,11 +67,6 @@ class Downloader(private val context: Context,
|
||||||
*/
|
*/
|
||||||
private val subscriptions = CompositeSubscription()
|
private val subscriptions = CompositeSubscription()
|
||||||
|
|
||||||
/**
|
|
||||||
* Subject to do a live update of the number of simultaneous downloads.
|
|
||||||
*/
|
|
||||||
private val threadsSubject = BehaviorSubject.create<Int>()
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Relay to send a list of downloads to the downloader.
|
* Relay to send a list of downloads to the downloader.
|
||||||
*/
|
*/
|
||||||
|
@ -116,9 +105,6 @@ class Downloader(private val context: Context,
|
||||||
val pending = queue.filter { it.status != Download.DOWNLOADED }
|
val pending = queue.filter { it.status != Download.DOWNLOADED }
|
||||||
pending.forEach { if (it.status != Download.QUEUE) it.status = Download.QUEUE }
|
pending.forEach { if (it.status != Download.QUEUE) it.status = Download.QUEUE }
|
||||||
|
|
||||||
// Show download notification when simultaneous download > 1.
|
|
||||||
notifier.onProgressChange(queue)
|
|
||||||
|
|
||||||
downloadsRelay.call(pending)
|
downloadsRelay.call(pending)
|
||||||
return !pending.isEmpty()
|
return !pending.isEmpty()
|
||||||
}
|
}
|
||||||
|
@ -185,14 +171,8 @@ class Downloader(private val context: Context,
|
||||||
|
|
||||||
subscriptions.clear()
|
subscriptions.clear()
|
||||||
|
|
||||||
subscriptions += preferences.downloadThreads().asObservable()
|
subscriptions += downloadsRelay.concatMapIterable { it }
|
||||||
.subscribe {
|
.concatMap { downloadChapter(it).subscribeOn(Schedulers.io()) }
|
||||||
threadsSubject.onNext(it)
|
|
||||||
notifier.multipleDownloadThreads = it > 1
|
|
||||||
}
|
|
||||||
|
|
||||||
subscriptions += downloadsRelay.flatMap { Observable.from(it) }
|
|
||||||
.lift(DynamicConcurrentMergeOperator<Download, Download>({ downloadChapter(it) }, threadsSubject))
|
|
||||||
.onBackpressureBuffer()
|
.onBackpressureBuffer()
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe({ completeDownload(it)
|
.subscribe({ completeDownload(it)
|
||||||
|
@ -250,15 +230,9 @@ class Downloader(private val context: Context,
|
||||||
// Initialize queue size.
|
// Initialize queue size.
|
||||||
notifier.initialQueueSize = queue.size
|
notifier.initialQueueSize = queue.size
|
||||||
|
|
||||||
// Initial multi-thread
|
|
||||||
notifier.multipleDownloadThreads = preferences.downloadThreads().getOrDefault() > 1
|
|
||||||
|
|
||||||
if (isRunning) {
|
if (isRunning) {
|
||||||
// Send the list of downloads to the downloader.
|
// Send the list of downloads to the downloader.
|
||||||
downloadsRelay.call(chaptersToQueue)
|
downloadsRelay.call(chaptersToQueue)
|
||||||
} else {
|
|
||||||
// Show initial notification.
|
|
||||||
notifier.onProgressChange(queue)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start downloader if needed
|
// Start downloader if needed
|
||||||
|
@ -273,7 +247,8 @@ class Downloader(private val context: Context,
|
||||||
*
|
*
|
||||||
* @param download the chapter to be downloaded.
|
* @param download the chapter to be downloaded.
|
||||||
*/
|
*/
|
||||||
private fun downloadChapter(download: Download): Observable<Download> {
|
private fun downloadChapter(download: Download): Observable<Download> = Observable.defer {
|
||||||
|
Timber.e("Thread: ${Thread.currentThread()}")
|
||||||
val chapterDirname = provider.getChapterDirName(download.chapter)
|
val chapterDirname = provider.getChapterDirName(download.chapter)
|
||||||
val mangaDir = provider.getMangaDir(download.manga, download.source)
|
val mangaDir = provider.getMangaDir(download.manga, download.source)
|
||||||
val tmpDir = mangaDir.createDirectory("${chapterDirname}_tmp")
|
val tmpDir = mangaDir.createDirectory("${chapterDirname}_tmp")
|
||||||
|
@ -292,7 +267,7 @@ class Downloader(private val context: Context,
|
||||||
Observable.just(download.pages!!)
|
Observable.just(download.pages!!)
|
||||||
}
|
}
|
||||||
|
|
||||||
return pageListObservable
|
pageListObservable
|
||||||
.doOnNext { _ ->
|
.doOnNext { _ ->
|
||||||
// Delete all temporary (unfinished) files
|
// Delete all temporary (unfinished) files
|
||||||
tmpDir.listFiles()
|
tmpDir.listFiles()
|
||||||
|
@ -307,7 +282,7 @@ class Downloader(private val context: Context,
|
||||||
// Start downloading images, consider we can have downloaded images already
|
// Start downloading images, consider we can have downloaded images already
|
||||||
.concatMap { page -> getOrDownloadImage(page, download, tmpDir) }
|
.concatMap { page -> getOrDownloadImage(page, download, tmpDir) }
|
||||||
// Do when page is downloaded.
|
// Do when page is downloaded.
|
||||||
.doOnNext { notifier.onProgressChange(download, queue) }
|
.doOnNext { notifier.onProgressChange(download) }
|
||||||
.toList()
|
.toList()
|
||||||
.map { _ -> download }
|
.map { _ -> download }
|
||||||
// Do after download completes
|
// Do after download completes
|
||||||
|
@ -318,7 +293,7 @@ class Downloader(private val context: Context,
|
||||||
notifier.onError(error.message, download.chapter.name)
|
notifier.onError(error.message, download.chapter.name)
|
||||||
download
|
download
|
||||||
}
|
}
|
||||||
.subscribeOn(Schedulers.io())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -448,7 +423,6 @@ class Downloader(private val context: Context,
|
||||||
if (download.status == Download.DOWNLOADED) {
|
if (download.status == Download.DOWNLOADED) {
|
||||||
// remove downloaded chapter from queue
|
// remove downloaded chapter from queue
|
||||||
queue.remove(download)
|
queue.remove(download)
|
||||||
notifier.onProgressChange(queue)
|
|
||||||
}
|
}
|
||||||
if (areAllDownloadsFinished()) {
|
if (areAllDownloadsFinished()) {
|
||||||
if (notifier.isSingleChapter && !notifier.errorThrown) {
|
if (notifier.isSingleChapter && !notifier.errorThrown) {
|
||||||
|
@ -465,4 +439,4 @@ class Downloader(private val context: Context,
|
||||||
return queue.none { it.status <= Download.DOWNLOADING }
|
return queue.none { it.status <= Download.DOWNLOADING }
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,8 +67,6 @@ object PreferenceKeys {
|
||||||
|
|
||||||
const val downloadsDirectory = "download_directory"
|
const val downloadsDirectory = "download_directory"
|
||||||
|
|
||||||
const val downloadThreads = "pref_download_slots_key"
|
|
||||||
|
|
||||||
const val downloadOnlyOverWifi = "pref_download_only_over_wifi_key"
|
const val downloadOnlyOverWifi = "pref_download_only_over_wifi_key"
|
||||||
|
|
||||||
const val numberOfBackups = "backup_slots"
|
const val numberOfBackups = "backup_slots"
|
||||||
|
|
|
@ -123,8 +123,6 @@ class PreferencesHelper(val context: Context) {
|
||||||
|
|
||||||
fun downloadsDirectory() = rxPrefs.getString(Keys.downloadsDirectory, defaultDownloadsDir.toString())
|
fun downloadsDirectory() = rxPrefs.getString(Keys.downloadsDirectory, defaultDownloadsDir.toString())
|
||||||
|
|
||||||
fun downloadThreads() = rxPrefs.getInteger(Keys.downloadThreads, 1)
|
|
||||||
|
|
||||||
fun downloadOnlyOverWifi() = prefs.getBoolean(Keys.downloadOnlyOverWifi, true)
|
fun downloadOnlyOverWifi() = prefs.getBoolean(Keys.downloadOnlyOverWifi, true)
|
||||||
|
|
||||||
fun numberOfBackups() = rxPrefs.getInteger(Keys.numberOfBackups, 1)
|
fun numberOfBackups() = rxPrefs.getInteger(Keys.numberOfBackups, 1)
|
||||||
|
|
|
@ -12,7 +12,6 @@ import android.support.v4.content.ContextCompat
|
||||||
import android.support.v7.preference.PreferenceScreen
|
import android.support.v7.preference.PreferenceScreen
|
||||||
import com.afollestad.materialdialogs.MaterialDialog
|
import com.afollestad.materialdialogs.MaterialDialog
|
||||||
import com.hippo.unifile.UniFile
|
import com.hippo.unifile.UniFile
|
||||||
import com.nononsenseapps.filepicker.FilePickerActivity
|
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
|
@ -20,7 +19,6 @@ import eu.kanade.tachiyomi.data.preference.getOrDefault
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.DialogController
|
import eu.kanade.tachiyomi.ui.base.controller.DialogController
|
||||||
import eu.kanade.tachiyomi.util.DiskUtil
|
import eu.kanade.tachiyomi.util.DiskUtil
|
||||||
import eu.kanade.tachiyomi.util.getFilePicker
|
import eu.kanade.tachiyomi.util.getFilePicker
|
||||||
import eu.kanade.tachiyomi.widget.CustomLayoutPickerActivity
|
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
@ -63,14 +61,6 @@ class SettingsDownloadController : SettingsController() {
|
||||||
titleRes = R.string.pref_download_only_over_wifi
|
titleRes = R.string.pref_download_only_over_wifi
|
||||||
defaultValue = true
|
defaultValue = true
|
||||||
}
|
}
|
||||||
intListPreference {
|
|
||||||
key = Keys.downloadThreads
|
|
||||||
titleRes = R.string.pref_download_slots
|
|
||||||
entries = arrayOf("1", "2", "3")
|
|
||||||
entryValues = arrayOf("1", "2", "3")
|
|
||||||
defaultValue = "1"
|
|
||||||
summary = "%s"
|
|
||||||
}
|
|
||||||
preferenceCategory {
|
preferenceCategory {
|
||||||
titleRes = R.string.pref_remove_after_read
|
titleRes = R.string.pref_remove_after_read
|
||||||
|
|
||||||
|
@ -206,4 +196,4 @@ class SettingsDownloadController : SettingsController() {
|
||||||
const val DOWNLOAD_DIR_PRE_L = 103
|
const val DOWNLOAD_DIR_PRE_L = 103
|
||||||
const val DOWNLOAD_DIR_L = 104
|
const val DOWNLOAD_DIR_L = 104
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,196 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.util;
|
|
||||||
|
|
||||||
import java.util.Queue;
|
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
|
|
||||||
import rx.Observable;
|
|
||||||
import rx.Observable.Operator;
|
|
||||||
import rx.Subscriber;
|
|
||||||
import rx.Subscription;
|
|
||||||
import rx.functions.Action0;
|
|
||||||
import rx.functions.Action1;
|
|
||||||
import rx.functions.Func1;
|
|
||||||
import rx.subscriptions.CompositeSubscription;
|
|
||||||
import rx.subscriptions.Subscriptions;
|
|
||||||
|
|
||||||
public class DynamicConcurrentMergeOperator<T, R> implements Operator<R, T> {
|
|
||||||
private final Func1<? super T, ? extends Observable<? extends R>> mapper;
|
|
||||||
private final Observable<Integer> workerCount;
|
|
||||||
|
|
||||||
public DynamicConcurrentMergeOperator(
|
|
||||||
Func1<? super T, ? extends Observable<? extends R>> mapper,
|
|
||||||
Observable<Integer> workerCount) {
|
|
||||||
this.mapper = mapper;
|
|
||||||
this.workerCount = workerCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Subscriber<? super T> call(Subscriber<? super R> t) {
|
|
||||||
DynamicConcurrentMerge<T, R> parent = new DynamicConcurrentMerge<>(t, mapper);
|
|
||||||
t.add(parent);
|
|
||||||
parent.init(workerCount);
|
|
||||||
|
|
||||||
return parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
static final class DynamicConcurrentMerge<T, R> extends Subscriber<T> {
|
|
||||||
private final Subscriber<? super R> actual;
|
|
||||||
private final Func1<? super T, ? extends Observable<? extends R>> mapper;
|
|
||||||
private final Queue<T> queue;
|
|
||||||
private final CopyOnWriteArrayList<DynamicWorker<T, R>> workers;
|
|
||||||
private final CompositeSubscription composite;
|
|
||||||
private final AtomicInteger wipActive;
|
|
||||||
private final AtomicBoolean once;
|
|
||||||
private long id;
|
|
||||||
|
|
||||||
public DynamicConcurrentMerge(Subscriber<? super R> actual,
|
|
||||||
Func1<? super T, ? extends Observable<? extends R>> mapper) {
|
|
||||||
this.actual = actual;
|
|
||||||
this.mapper = mapper;
|
|
||||||
this.queue = new ConcurrentLinkedQueue<>();
|
|
||||||
this.workers = new CopyOnWriteArrayList<>();
|
|
||||||
this.composite = new CompositeSubscription();
|
|
||||||
this.wipActive = new AtomicInteger(1);
|
|
||||||
this.once = new AtomicBoolean();
|
|
||||||
this.add(composite);
|
|
||||||
this.request(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void init(Observable<Integer> workerCount) {
|
|
||||||
Subscription wc = workerCount.subscribe(new Action1<Integer>() {
|
|
||||||
@Override
|
|
||||||
public void call(Integer n) {
|
|
||||||
int n0 = workers.size();
|
|
||||||
if (n0 < n) {
|
|
||||||
for (int i = n0; i < n; i++) {
|
|
||||||
DynamicWorker<T, R> dw = new DynamicWorker<>(++id, DynamicConcurrentMerge.this);
|
|
||||||
workers.add(dw);
|
|
||||||
DynamicConcurrentMerge.this.request(1);
|
|
||||||
dw.tryNext();
|
|
||||||
}
|
|
||||||
} else if (n0 > n) {
|
|
||||||
for (int i = 0; i < n; i++) {
|
|
||||||
workers.get(i).start();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = n0 - 1; i >= n; i--) {
|
|
||||||
workers.get(i).stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!once.get() && once.compareAndSet(false, true)) {
|
|
||||||
DynamicConcurrentMerge.this.request(n);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, new Action1<Throwable>() {
|
|
||||||
@Override
|
|
||||||
public void call(Throwable e) {DynamicConcurrentMerge.this.onError(e);}
|
|
||||||
});
|
|
||||||
|
|
||||||
composite.add(wc);
|
|
||||||
}
|
|
||||||
|
|
||||||
void requestMore(long n) {
|
|
||||||
request(n);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onNext(T t) {
|
|
||||||
queue.offer(t);
|
|
||||||
wipActive.getAndIncrement();
|
|
||||||
for (DynamicWorker<T, R> w : workers) {
|
|
||||||
w.tryNext();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(Throwable e) {
|
|
||||||
composite.unsubscribe();
|
|
||||||
actual.onError(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCompleted() {
|
|
||||||
if (wipActive.decrementAndGet() == 0) {
|
|
||||||
actual.onCompleted();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static final class DynamicWorker<T, R> {
|
|
||||||
private final long id;
|
|
||||||
private final AtomicBoolean running;
|
|
||||||
private final DynamicConcurrentMerge<T, R> parent;
|
|
||||||
private final AtomicBoolean stop;
|
|
||||||
|
|
||||||
public DynamicWorker(long id, DynamicConcurrentMerge<T, R> parent) {
|
|
||||||
this.id = id;
|
|
||||||
this.parent = parent;
|
|
||||||
this.stop = new AtomicBoolean();
|
|
||||||
this.running = new AtomicBoolean();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void tryNext() {
|
|
||||||
if (!running.get() && running.compareAndSet(false, true)) {
|
|
||||||
T t;
|
|
||||||
if (stop.get()) {
|
|
||||||
parent.workers.remove(this);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
t = parent.queue.poll();
|
|
||||||
if (t == null) {
|
|
||||||
running.set(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Observable<? extends R> out = parent.mapper.call(t);
|
|
||||||
|
|
||||||
final Subscriber<R> s = new Subscriber<R>() {
|
|
||||||
@Override
|
|
||||||
public void onNext(R t) {
|
|
||||||
parent.actual.onNext(t);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(Throwable e) {
|
|
||||||
parent.onError(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCompleted() {
|
|
||||||
parent.onCompleted();
|
|
||||||
if (parent.wipActive.get() != 0) {
|
|
||||||
running.set(false);
|
|
||||||
parent.requestMore(1);
|
|
||||||
tryNext();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
parent.composite.add(s);
|
|
||||||
s.add(Subscriptions.create(new Action0() {
|
|
||||||
@Override
|
|
||||||
public void call() {parent.composite.remove(s);}
|
|
||||||
}));
|
|
||||||
|
|
||||||
out.subscribe(s);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void start() {
|
|
||||||
stop.set(false);
|
|
||||||
tryNext();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void stop() {
|
|
||||||
stop.set(true);
|
|
||||||
if (running.compareAndSet(false, true)) {
|
|
||||||
parent.workers.remove(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -219,7 +219,6 @@
|
||||||
|
|
||||||
<!-- Downloads section -->
|
<!-- Downloads section -->
|
||||||
<string name="pref_download_directory">Downloads directory</string>
|
<string name="pref_download_directory">Downloads directory</string>
|
||||||
<string name="pref_download_slots">Simultaneous downloads</string>
|
|
||||||
<string name="pref_download_only_over_wifi">Only download over Wi-Fi</string>
|
<string name="pref_download_only_over_wifi">Only download over Wi-Fi</string>
|
||||||
<string name="pref_remove_after_marked_as_read">Remove when marked as read</string>
|
<string name="pref_remove_after_marked_as_read">Remove when marked as read</string>
|
||||||
<string name="pref_remove_after_read">Remove after read</string>
|
<string name="pref_remove_after_read">Remove after read</string>
|
||||||
|
|
Loading…
Reference in a new issue