mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-10-30 05:47:57 +01:00 
			
		
		
		
	Change the download event. Fix some bugs in download manager. Other minor changes.
This commit is contained in:
		| @@ -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<DownloadChapterEvent> downloadsSubject; | ||||
|     private PublishSubject<DownloadChaptersEvent> downloadsSubject; | ||||
|     private Subscription downloadSubscription; | ||||
|     private Subscription threadNumberSubscription; | ||||
|  | ||||
| @@ -55,7 +57,7 @@ public class DownloadManager { | ||||
|         initializeDownloadSubscription(); | ||||
|     } | ||||
|  | ||||
|     public PublishSubject<DownloadChapterEvent> getDownloadsSubject() { | ||||
|     public PublishSubject<DownloadChaptersEvent> 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<Download> prepareDownloads(DownloadChaptersEvent event) { | ||||
|         final Manga manga = event.getManga(); | ||||
|         final Source source = sourceManager.get(manga.source); | ||||
|         List<Download> 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<Page> savedPages = getSavedPageList(source, event.getManga(), event.getChapter()); | ||||
|         List<Page> 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<Download> 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<Page> 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<List<Page>> 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<List<Page>>() {}.getType(); | ||||
|             return gson.fromJson(reader, collectionType); | ||||
|                 Type collectionType = new TypeToken<List<Page>>() {}.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<Page> 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); | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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; | ||||
|     } | ||||
| } | ||||
| @@ -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<Chapter> chapters; | ||||
|  | ||||
|     public DownloadChaptersEvent(Manga manga, List<Chapter> chapters) { | ||||
|         this.manga = manga; | ||||
|         this.chapters = chapters; | ||||
|     } | ||||
|  | ||||
|     public Manga getManga() { | ||||
|         return manga; | ||||
|     } | ||||
|  | ||||
|     public List<Chapter> getChapters() { | ||||
|         return chapters; | ||||
|     } | ||||
| } | ||||
| @@ -75,21 +75,22 @@ public class DownloadQueuePresenter extends BasePresenter<DownloadQueueFragment> | ||||
|             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); | ||||
|   | ||||
| @@ -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<MangaChaptersFragment> | ||||
|  | ||||
|     public void downloadChapters(Observable<Chapter> 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<MangaChaptersFragment> | ||||
|         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; | ||||
|   | ||||
| @@ -98,7 +98,7 @@ public class ReaderPresenter extends BasePresenter<ReaderActivity> { | ||||
|  | ||||
|     private Observable<List<Page>> getPageListObservable() { | ||||
|         if (!isDownloaded) | ||||
|             return source.pullPageListFromNetwork(chapter.url) | ||||
|             return source.getCachedPageListOrPullFromNetwork(chapter.url) | ||||
|                     .subscribeOn(Schedulers.io()) | ||||
|                     .observeOn(AndroidSchedulers.mainThread()); | ||||
|         else | ||||
|   | ||||
| @@ -63,20 +63,23 @@ public abstract class Source extends BaseSource { | ||||
|                         Observable.just(parseHtmlToChapters(unparsedHtml))); | ||||
|     } | ||||
|  | ||||
|     public Observable<List<Page>> pullPageListFromNetwork(final String chapterUrl) { | ||||
|     public Observable<List<Page>> getCachedPageListOrPullFromNetwork(final String chapterUrl) { | ||||
|         return mCacheManager.getPageUrlsFromDiskCache(chapterUrl) | ||||
|                 .onErrorResumeNext(throwable -> { | ||||
|                     return mNetworkService | ||||
|                             .getStringResponse(overrideChapterPageUrl(chapterUrl), mRequestHeaders, null) | ||||
|                             .flatMap(unparsedHtml -> { | ||||
|                                 List<String> pageUrls = parseHtmlToPageUrls(unparsedHtml); | ||||
|                                 return Observable.just(getFirstImageFromPageUrls(pageUrls, unparsedHtml)); | ||||
|                             }) | ||||
|                             .doOnNext(pages -> savePageList(chapterUrl, pages)); | ||||
|                     return pullPageListFromNetwork(chapterUrl); | ||||
|                 }) | ||||
|                 .onBackpressureBuffer(); | ||||
|     } | ||||
|  | ||||
|     public Observable<List<Page>> pullPageListFromNetwork(final String chapterUrl) { | ||||
|         return mNetworkService | ||||
|                 .getStringResponse(overrideChapterPageUrl(chapterUrl), mRequestHeaders, null) | ||||
|                 .flatMap(unparsedHtml -> { | ||||
|                     List<String> pageUrls = parseHtmlToPageUrls(unparsedHtml); | ||||
|                     return Observable.just(getFirstImageFromPageUrls(pageUrls, unparsedHtml)); | ||||
|                 }); | ||||
|     } | ||||
|  | ||||
|     // Get the URLs of the images of a chapter | ||||
|     public Observable<Page> getRemainingImageUrlsFromPageList(final List<Page> pages) { | ||||
|         return Observable.from(pages) | ||||
|   | ||||
| @@ -64,15 +64,6 @@ public class MangaChaptersFragment extends BaseRxFragment<MangaChaptersPresenter | ||||
|         return view; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onStart() { | ||||
|         super.onStart(); | ||||
|         if (!DownloadService.isRunning(getActivity())) { | ||||
|             Intent intent = DownloadService.getStartIntent(getActivity()); | ||||
|             getActivity().startService(intent); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { | ||||
|         inflater.inflate(R.menu.chapters, menu); | ||||
| @@ -140,6 +131,8 @@ public class MangaChaptersFragment extends BaseRxFragment<MangaChaptersPresenter | ||||
|                 getPresenter().markChaptersRead(getSelectedChapters(), false); | ||||
|                 return true; | ||||
|             case R.id.action_download: | ||||
|                 Intent intent = DownloadService.getStartIntent(getActivity()); | ||||
|                 getActivity().startService(intent); | ||||
|                 getPresenter().downloadChapters(getSelectedChapters()); | ||||
|                 closeActionMode(); | ||||
|                 return true; | ||||
|   | ||||
| @@ -115,10 +115,8 @@ public final class DiskUtils { | ||||
|     } | ||||
|  | ||||
|     public static File saveBufferedSourceToDirectory(BufferedSource bufferedSource, File directory, String name) throws IOException { | ||||
|         if (!directory.exists()) { | ||||
|             if (!directory.mkdirs()) { | ||||
|                 throw new IOException("Failed Creating  Directory"); | ||||
|             } | ||||
|         if (!directory.exists() && !directory.mkdirs()) { | ||||
|             throw new IOException("Failed Creating Directory"); | ||||
|         } | ||||
|  | ||||
|         File writeFile = new File(directory, name); | ||||
| @@ -155,5 +153,12 @@ public final class DiskUtils { | ||||
|  | ||||
|         inputFile.delete(); | ||||
|     } | ||||
|  | ||||
|     public static synchronized void createDirectory(File directory) throws IOException { | ||||
|         if (!directory.exists() && !directory.mkdirs()) { | ||||
|             throw new IOException("Failed creating directory"); | ||||
|         } | ||||
|  | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user