diff --git a/app/src/main/java/eu/kanade/mangafeed/data/helpers/DownloadManager.java b/app/src/main/java/eu/kanade/mangafeed/data/helpers/DownloadManager.java index 8a7400e735..6040ce2570 100644 --- a/app/src/main/java/eu/kanade/mangafeed/data/helpers/DownloadManager.java +++ b/app/src/main/java/eu/kanade/mangafeed/data/helpers/DownloadManager.java @@ -12,6 +12,7 @@ import java.io.FileOutputStream; import java.io.FileReader; import java.io.IOException; import java.lang.reflect.Type; +import java.util.ArrayList; import java.util.List; import eu.kanade.mangafeed.data.models.Chapter; @@ -19,7 +20,7 @@ import eu.kanade.mangafeed.data.models.Download; import eu.kanade.mangafeed.data.models.DownloadQueue; import eu.kanade.mangafeed.data.models.Manga; import eu.kanade.mangafeed.data.models.Page; -import eu.kanade.mangafeed.events.DownloadChapterEvent; +import eu.kanade.mangafeed.events.DownloadChaptersEvent; import eu.kanade.mangafeed.sources.base.Source; import eu.kanade.mangafeed.util.DiskUtils; import eu.kanade.mangafeed.util.DynamicConcurrentMergeOperator; @@ -28,10 +29,11 @@ import rx.Subscription; import rx.schedulers.Schedulers; import rx.subjects.BehaviorSubject; import rx.subjects.PublishSubject; +import timber.log.Timber; public class DownloadManager { - private PublishSubject downloadsSubject; + private PublishSubject downloadsSubject; private Subscription downloadSubscription; private Subscription threadNumberSubscription; @@ -55,7 +57,7 @@ public class DownloadManager { initializeDownloadSubscription(); } - public PublishSubject getDownloadsSubject() { + public PublishSubject getDownloadsSubject() { return downloadsSubject; } @@ -76,62 +78,82 @@ public class DownloadManager { // Listen for download events, add them to queue and download downloadSubscription = downloadsSubject .subscribeOn(Schedulers.io()) - .filter(event -> !isChapterDownloaded(event)) - .flatMap(this::prepareDownload) + .flatMap(this::prepareDownloads) .lift(new DynamicConcurrentMergeOperator<>(this::downloadChapter, threads)) .onBackpressureBuffer() - .subscribe(); + .subscribe(page -> {}, + e -> Timber.e(e.fillInStackTrace(), e.getMessage())); + } + + // Create a download object for every chapter and add it to the downloads queue + private Observable prepareDownloads(DownloadChaptersEvent event) { + final Manga manga = event.getManga(); + final Source source = sourceManager.get(manga.source); + List downloads = new ArrayList<>(); + + for (Chapter chapter : event.getChapters()) { + Download download = new Download(source, manga, chapter); + + if (!isChapterDownloaded(download)) { + queue.add(download); + downloads.add(download); + } + } + + return Observable.from(downloads); } // Check if a chapter is already downloaded - private boolean isChapterDownloaded(DownloadChapterEvent event) { - final Source source = sourceManager.get(event.getManga().source); - + private boolean isChapterDownloaded(Download download) { // If the chapter is already queued, don't add it again - for (Download download : queue.get()) { - if (download.chapter.id == event.getChapter().id) + for (Download queuedDownload : queue.get()) { + if (download.chapter.id == queuedDownload.chapter.id) return true; } - // If the directory doesn't exist, the chapter isn't downloaded - File dir = getAbsoluteChapterDirectory(source, event.getManga(), event.getChapter()); - if (!dir.exists()) + // Add the directory to the download object for future access + download.directory = getAbsoluteChapterDirectory(download); + + // If the directory doesn't exist, the chapter isn't downloaded. Create it in this case + if (!download.directory.exists()) { + // FIXME Sometimes it's failing to create the directory... My fault? + try { + DiskUtils.createDirectory(download.directory); + } catch (IOException e) { + Timber.e("Unable to create directory for chapter"); + } return false; + } + // If the page list doesn't exist, the chapter isn't download (or maybe it's, // but we consider it's not) - List savedPages = getSavedPageList(source, event.getManga(), event.getChapter()); + List savedPages = getSavedPageList(download); if (savedPages == null) return false; + // Add the page list to the download object for future access + download.pages = savedPages; + // If the number of files matches the number of pages, the chapter is downloaded. - // We have the index file, so we check one file less - return (dir.listFiles().length - 1) == savedPages.size(); - } - - // Create a download object and add it to the downloads queue - private Observable prepareDownload(DownloadChapterEvent event) { - Download download = new Download( - sourceManager.get(event.getManga().source), - event.getManga(), - event.getChapter()); - - download.directory = getAbsoluteChapterDirectory( - download.source, download.manga, download.chapter); - - queue.add(download); - return Observable.just(download); + // We have the index file, so we check one file more + return savedPages.size() + 1 == download.directory.listFiles().length; } // Download the entire chapter private Observable downloadChapter(Download download) { - return download.source - .pullPageListFromNetwork(download.chapter.url) - // Add resulting pages to download object - .doOnNext(pages -> { - download.pages = pages; - download.setStatus(Download.DOWNLOADING); - }) + Observable> pageListObservable = download.pages == null ? + // Pull page list from network and add them to download object + download.source + .pullPageListFromNetwork(download.chapter.url) + .doOnNext(pages -> download.pages = pages) + .doOnNext(pages -> savePageList(download)) : + // Or if the file exists, start from here + Observable.just(download.pages); + + return pageListObservable + .subscribeOn(Schedulers.io()) + .doOnNext(pages -> download.setStatus(Download.DOWNLOADING)) // Get all the URLs to the source images, fetch pages if necessary .flatMap(pageList -> Observable.merge( Observable.from(pageList).filter(page -> page.getImageUrl() != null), @@ -173,7 +195,7 @@ public class DownloadManager { try { DiskUtils.saveBufferedSourceToDirectory(resp.body().source(), chapterDir, imageFilename); } catch (IOException e) { - e.printStackTrace(); + Timber.e(e.fillInStackTrace(), e.getMessage()); throw new IllegalStateException("Unable to save image"); } return Observable.just(page); @@ -193,6 +215,7 @@ public class DownloadManager { private void onChapterDownloaded(final Download download) { download.setStatus(Download.DOWNLOADED); + download.totalProgress = download.pages.size() * 100; savePageList(download.source, download.manga, download.chapter, download.pages); } @@ -202,13 +225,21 @@ public class DownloadManager { File pagesFile = new File(chapterDir, PAGE_LIST_FILE); try { - JsonReader reader = new JsonReader(new FileReader(pagesFile.getAbsolutePath())); + if (pagesFile.exists()) { + JsonReader reader = new JsonReader(new FileReader(pagesFile.getAbsolutePath())); - Type collectionType = new TypeToken>() {}.getType(); - return gson.fromJson(reader, collectionType); + Type collectionType = new TypeToken>() {}.getType(); + return gson.fromJson(reader, collectionType); + } } catch (FileNotFoundException e) { - return null; + Timber.e(e.fillInStackTrace(), e.getMessage()); } + return null; + } + + // Shortcut for the method above + private List getSavedPageList(Download download) { + return getSavedPageList(download.source, download.manga, download.chapter); } // Save the page list to the chapter's directory @@ -223,10 +254,15 @@ public class DownloadManager { out.flush(); out.close(); } catch (Exception e) { - e.printStackTrace(); + Timber.e(e.fillInStackTrace(), e.getMessage()); } } + // Shortcut for the method above + private void savePageList(Download download) { + savePageList(download.source, download.manga, download.chapter, download.pages); + } + // Get the absolute path to the chapter directory public File getAbsoluteChapterDirectory(Source source, Manga manga, Chapter chapter) { String chapterRelativePath = source.getName() + @@ -238,6 +274,11 @@ public class DownloadManager { return new File(preferences.getDownloadsDirectory(), chapterRelativePath); } + // Shortcut for the method above + private File getAbsoluteChapterDirectory(Download download) { + return getAbsoluteChapterDirectory(download.source, download.manga, download.chapter); + } + public void deleteChapter(Source source, Manga manga, Chapter chapter) { File path = getAbsoluteChapterDirectory(source, manga, chapter); DiskUtils.deleteFiles(path); diff --git a/app/src/main/java/eu/kanade/mangafeed/data/services/DownloadService.java b/app/src/main/java/eu/kanade/mangafeed/data/services/DownloadService.java index f97873f17e..94fd04a178 100644 --- a/app/src/main/java/eu/kanade/mangafeed/data/services/DownloadService.java +++ b/app/src/main/java/eu/kanade/mangafeed/data/services/DownloadService.java @@ -10,7 +10,7 @@ import javax.inject.Inject; import de.greenrobot.event.EventBus; import eu.kanade.mangafeed.App; import eu.kanade.mangafeed.data.helpers.DownloadManager; -import eu.kanade.mangafeed.events.DownloadChapterEvent; +import eu.kanade.mangafeed.events.DownloadChaptersEvent; import eu.kanade.mangafeed.util.AndroidComponentUtil; import eu.kanade.mangafeed.util.EventBusHook; @@ -31,7 +31,7 @@ public class DownloadService extends Service { super.onCreate(); App.get(this).getComponent().inject(this); - EventBus.getDefault().register(this); + EventBus.getDefault().registerSticky(this); } @Override @@ -45,8 +45,9 @@ public class DownloadService extends Service { } @EventBusHook - public void onEvent(DownloadChapterEvent event) { + public void onEvent(DownloadChaptersEvent event) { downloadManager.getDownloadsSubject().onNext(event); + EventBus.getDefault().removeStickyEvent(event); } @Override diff --git a/app/src/main/java/eu/kanade/mangafeed/events/DownloadChapterEvent.java b/app/src/main/java/eu/kanade/mangafeed/events/DownloadChapterEvent.java deleted file mode 100644 index 4cc9352657..0000000000 --- a/app/src/main/java/eu/kanade/mangafeed/events/DownloadChapterEvent.java +++ /dev/null @@ -1,22 +0,0 @@ -package eu.kanade.mangafeed.events; - -import eu.kanade.mangafeed.data.models.Chapter; -import eu.kanade.mangafeed.data.models.Manga; - -public class DownloadChapterEvent { - private Manga manga; - private Chapter chapter; - - public DownloadChapterEvent(Manga manga, Chapter chapter) { - this.manga = manga; - this.chapter = chapter; - } - - public Manga getManga() { - return manga; - } - - public Chapter getChapter() { - return chapter; - } -} diff --git a/app/src/main/java/eu/kanade/mangafeed/events/DownloadChaptersEvent.java b/app/src/main/java/eu/kanade/mangafeed/events/DownloadChaptersEvent.java new file mode 100644 index 0000000000..0ff3de9789 --- /dev/null +++ b/app/src/main/java/eu/kanade/mangafeed/events/DownloadChaptersEvent.java @@ -0,0 +1,24 @@ +package eu.kanade.mangafeed.events; + +import java.util.List; + +import eu.kanade.mangafeed.data.models.Chapter; +import eu.kanade.mangafeed.data.models.Manga; + +public class DownloadChaptersEvent { + private Manga manga; + private List chapters; + + public DownloadChaptersEvent(Manga manga, List chapters) { + this.manga = manga; + this.chapters = chapters; + } + + public Manga getManga() { + return manga; + } + + public List getChapters() { + return chapters; + } +} diff --git a/app/src/main/java/eu/kanade/mangafeed/presenter/DownloadQueuePresenter.java b/app/src/main/java/eu/kanade/mangafeed/presenter/DownloadQueuePresenter.java index 4600dc7865..f272566736 100644 --- a/app/src/main/java/eu/kanade/mangafeed/presenter/DownloadQueuePresenter.java +++ b/app/src/main/java/eu/kanade/mangafeed/presenter/DownloadQueuePresenter.java @@ -75,21 +75,22 @@ public class DownloadQueuePresenter extends BasePresenter case Download.DOWNLOADED: unsubscribeProgress(download); unsubscribePagesStatus(download); - download.totalProgress = download.pages.size() * 100; view.updateProgress(download); break; } } private void observeProgress(Download download, DownloadQueueFragment view) { - Subscription subscription = Observable.interval(50, TimeUnit.MILLISECONDS, Schedulers.newThread()) + Subscription subscription = Observable.interval(75, TimeUnit.MILLISECONDS, Schedulers.newThread()) .flatMap(tick -> Observable.from(download.pages) .map(Page::getProgress) .reduce((x, y) -> x + y)) .observeOn(AndroidSchedulers.mainThread()) .subscribe(progress -> { - download.totalProgress = progress; - view.updateProgress(download); + if (download.totalProgress != progress) { + download.totalProgress = progress; + view.updateProgress(download); + } }); progressSubscriptions.put(download, subscription); diff --git a/app/src/main/java/eu/kanade/mangafeed/presenter/MangaChaptersPresenter.java b/app/src/main/java/eu/kanade/mangafeed/presenter/MangaChaptersPresenter.java index 4013d0f1b9..b34e7e8906 100644 --- a/app/src/main/java/eu/kanade/mangafeed/presenter/MangaChaptersPresenter.java +++ b/app/src/main/java/eu/kanade/mangafeed/presenter/MangaChaptersPresenter.java @@ -15,7 +15,7 @@ import eu.kanade.mangafeed.data.helpers.SourceManager; import eu.kanade.mangafeed.data.models.Chapter; import eu.kanade.mangafeed.data.models.Manga; import eu.kanade.mangafeed.events.ChapterCountEvent; -import eu.kanade.mangafeed.events.DownloadChapterEvent; +import eu.kanade.mangafeed.events.DownloadChaptersEvent; import eu.kanade.mangafeed.events.SourceMangaChapterEvent; import eu.kanade.mangafeed.sources.base.Source; import eu.kanade.mangafeed.ui.fragment.MangaChaptersFragment; @@ -135,9 +135,10 @@ public class MangaChaptersPresenter extends BasePresenter public void downloadChapters(Observable selectedChapters) { add(downloadSubscription = selectedChapters - .doOnCompleted(() -> remove(downloadSubscription)) - .subscribe(chapter -> { - EventBus.getDefault().post(new DownloadChapterEvent(manga, chapter)); + .toList() + .subscribe(chapters -> { + EventBus.getDefault().postSticky(new DownloadChaptersEvent(manga, chapters)); + remove(downloadSubscription); })); } @@ -154,7 +155,8 @@ public class MangaChaptersPresenter extends BasePresenter File dir = downloadManager.getAbsoluteChapterDirectory(source, manga, chapter); File pageList = new File(dir, DownloadManager.PAGE_LIST_FILE); - if (dir.exists() && dir.listFiles().length > 0 && pageList.exists()) { + if (dir.exists() && pageList.exists() && downloadManager + .getSavedPageList(source, manga, chapter).size() + 1 == dir.listFiles().length) { chapter.downloaded = Chapter.DOWNLOADED; } else { chapter.downloaded = Chapter.NOT_DOWNLOADED; diff --git a/app/src/main/java/eu/kanade/mangafeed/presenter/ReaderPresenter.java b/app/src/main/java/eu/kanade/mangafeed/presenter/ReaderPresenter.java index 3f83d1bcdc..ae5fbe0daf 100644 --- a/app/src/main/java/eu/kanade/mangafeed/presenter/ReaderPresenter.java +++ b/app/src/main/java/eu/kanade/mangafeed/presenter/ReaderPresenter.java @@ -98,7 +98,7 @@ public class ReaderPresenter extends BasePresenter { private Observable> getPageListObservable() { if (!isDownloaded) - return source.pullPageListFromNetwork(chapter.url) + return source.getCachedPageListOrPullFromNetwork(chapter.url) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()); else diff --git a/app/src/main/java/eu/kanade/mangafeed/sources/base/Source.java b/app/src/main/java/eu/kanade/mangafeed/sources/base/Source.java index 9005084f2d..82183dbceb 100644 --- a/app/src/main/java/eu/kanade/mangafeed/sources/base/Source.java +++ b/app/src/main/java/eu/kanade/mangafeed/sources/base/Source.java @@ -63,20 +63,23 @@ public abstract class Source extends BaseSource { Observable.just(parseHtmlToChapters(unparsedHtml))); } - public Observable> pullPageListFromNetwork(final String chapterUrl) { + public Observable> getCachedPageListOrPullFromNetwork(final String chapterUrl) { return mCacheManager.getPageUrlsFromDiskCache(chapterUrl) .onErrorResumeNext(throwable -> { - return mNetworkService - .getStringResponse(overrideChapterPageUrl(chapterUrl), mRequestHeaders, null) - .flatMap(unparsedHtml -> { - List pageUrls = parseHtmlToPageUrls(unparsedHtml); - return Observable.just(getFirstImageFromPageUrls(pageUrls, unparsedHtml)); - }) - .doOnNext(pages -> savePageList(chapterUrl, pages)); + return pullPageListFromNetwork(chapterUrl); }) .onBackpressureBuffer(); } + public Observable> pullPageListFromNetwork(final String chapterUrl) { + return mNetworkService + .getStringResponse(overrideChapterPageUrl(chapterUrl), mRequestHeaders, null) + .flatMap(unparsedHtml -> { + List pageUrls = parseHtmlToPageUrls(unparsedHtml); + return Observable.just(getFirstImageFromPageUrls(pageUrls, unparsedHtml)); + }); + } + // Get the URLs of the images of a chapter public Observable getRemainingImageUrlsFromPageList(final List pages) { return Observable.from(pages) diff --git a/app/src/main/java/eu/kanade/mangafeed/ui/fragment/MangaChaptersFragment.java b/app/src/main/java/eu/kanade/mangafeed/ui/fragment/MangaChaptersFragment.java index 3fe706f092..2c1bfe09d1 100644 --- a/app/src/main/java/eu/kanade/mangafeed/ui/fragment/MangaChaptersFragment.java +++ b/app/src/main/java/eu/kanade/mangafeed/ui/fragment/MangaChaptersFragment.java @@ -64,15 +64,6 @@ public class MangaChaptersFragment extends BaseRxFragment