Change the download event. Fix some bugs in download manager. Other minor changes.

This commit is contained in:
inorichi 2015-11-06 03:29:23 +01:00
parent 2683cad5b5
commit d3a32da62c
10 changed files with 148 additions and 100 deletions

View file

@ -12,6 +12,7 @@ import java.io.FileOutputStream;
import java.io.FileReader; import java.io.FileReader;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import eu.kanade.mangafeed.data.models.Chapter; 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.DownloadQueue;
import eu.kanade.mangafeed.data.models.Manga; import eu.kanade.mangafeed.data.models.Manga;
import eu.kanade.mangafeed.data.models.Page; 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.sources.base.Source;
import eu.kanade.mangafeed.util.DiskUtils; import eu.kanade.mangafeed.util.DiskUtils;
import eu.kanade.mangafeed.util.DynamicConcurrentMergeOperator; import eu.kanade.mangafeed.util.DynamicConcurrentMergeOperator;
@ -28,10 +29,11 @@ import rx.Subscription;
import rx.schedulers.Schedulers; import rx.schedulers.Schedulers;
import rx.subjects.BehaviorSubject; import rx.subjects.BehaviorSubject;
import rx.subjects.PublishSubject; import rx.subjects.PublishSubject;
import timber.log.Timber;
public class DownloadManager { public class DownloadManager {
private PublishSubject<DownloadChapterEvent> downloadsSubject; private PublishSubject<DownloadChaptersEvent> downloadsSubject;
private Subscription downloadSubscription; private Subscription downloadSubscription;
private Subscription threadNumberSubscription; private Subscription threadNumberSubscription;
@ -55,7 +57,7 @@ public class DownloadManager {
initializeDownloadSubscription(); initializeDownloadSubscription();
} }
public PublishSubject<DownloadChapterEvent> getDownloadsSubject() { public PublishSubject<DownloadChaptersEvent> getDownloadsSubject() {
return downloadsSubject; return downloadsSubject;
} }
@ -76,62 +78,82 @@ public class DownloadManager {
// Listen for download events, add them to queue and download // Listen for download events, add them to queue and download
downloadSubscription = downloadsSubject downloadSubscription = downloadsSubject
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.filter(event -> !isChapterDownloaded(event)) .flatMap(this::prepareDownloads)
.flatMap(this::prepareDownload)
.lift(new DynamicConcurrentMergeOperator<>(this::downloadChapter, threads)) .lift(new DynamicConcurrentMergeOperator<>(this::downloadChapter, threads))
.onBackpressureBuffer() .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 // Check if a chapter is already downloaded
private boolean isChapterDownloaded(DownloadChapterEvent event) { private boolean isChapterDownloaded(Download download) {
final Source source = sourceManager.get(event.getManga().source);
// If the chapter is already queued, don't add it again // If the chapter is already queued, don't add it again
for (Download download : queue.get()) { for (Download queuedDownload : queue.get()) {
if (download.chapter.id == event.getChapter().id) if (download.chapter.id == queuedDownload.chapter.id)
return true; return true;
} }
// If the directory doesn't exist, the chapter isn't downloaded // Add the directory to the download object for future access
File dir = getAbsoluteChapterDirectory(source, event.getManga(), event.getChapter()); download.directory = getAbsoluteChapterDirectory(download);
if (!dir.exists())
// 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; return false;
}
// If the page list doesn't exist, the chapter isn't download (or maybe it's, // If the page list doesn't exist, the chapter isn't download (or maybe it's,
// but we consider it's not) // but we consider it's not)
List<Page> savedPages = getSavedPageList(source, event.getManga(), event.getChapter()); List<Page> savedPages = getSavedPageList(download);
if (savedPages == null) if (savedPages == null)
return false; 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. // 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 // We have the index file, so we check one file more
return (dir.listFiles().length - 1) == savedPages.size(); return savedPages.size() + 1 == download.directory.listFiles().length;
}
// 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);
} }
// Download the entire chapter // Download the entire chapter
private Observable<Page> downloadChapter(Download download) { private Observable<Page> downloadChapter(Download download) {
return download.source Observable<List<Page>> pageListObservable = download.pages == null ?
.pullPageListFromNetwork(download.chapter.url) // Pull page list from network and add them to download object
// Add resulting pages to download object download.source
.doOnNext(pages -> { .pullPageListFromNetwork(download.chapter.url)
download.pages = pages; .doOnNext(pages -> download.pages = pages)
download.setStatus(Download.DOWNLOADING); .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 // Get all the URLs to the source images, fetch pages if necessary
.flatMap(pageList -> Observable.merge( .flatMap(pageList -> Observable.merge(
Observable.from(pageList).filter(page -> page.getImageUrl() != null), Observable.from(pageList).filter(page -> page.getImageUrl() != null),
@ -173,7 +195,7 @@ public class DownloadManager {
try { try {
DiskUtils.saveBufferedSourceToDirectory(resp.body().source(), chapterDir, imageFilename); DiskUtils.saveBufferedSourceToDirectory(resp.body().source(), chapterDir, imageFilename);
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); Timber.e(e.fillInStackTrace(), e.getMessage());
throw new IllegalStateException("Unable to save image"); throw new IllegalStateException("Unable to save image");
} }
return Observable.just(page); return Observable.just(page);
@ -193,6 +215,7 @@ public class DownloadManager {
private void onChapterDownloaded(final Download download) { private void onChapterDownloaded(final Download download) {
download.setStatus(Download.DOWNLOADED); download.setStatus(Download.DOWNLOADED);
download.totalProgress = download.pages.size() * 100;
savePageList(download.source, download.manga, download.chapter, download.pages); savePageList(download.source, download.manga, download.chapter, download.pages);
} }
@ -202,13 +225,21 @@ public class DownloadManager {
File pagesFile = new File(chapterDir, PAGE_LIST_FILE); File pagesFile = new File(chapterDir, PAGE_LIST_FILE);
try { 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(); Type collectionType = new TypeToken<List<Page>>() {}.getType();
return gson.fromJson(reader, collectionType); return gson.fromJson(reader, collectionType);
}
} catch (FileNotFoundException e) { } 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 // Save the page list to the chapter's directory
@ -223,10 +254,15 @@ public class DownloadManager {
out.flush(); out.flush();
out.close(); out.close();
} catch (Exception e) { } 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 // Get the absolute path to the chapter directory
public File getAbsoluteChapterDirectory(Source source, Manga manga, Chapter chapter) { public File getAbsoluteChapterDirectory(Source source, Manga manga, Chapter chapter) {
String chapterRelativePath = source.getName() + String chapterRelativePath = source.getName() +
@ -238,6 +274,11 @@ public class DownloadManager {
return new File(preferences.getDownloadsDirectory(), chapterRelativePath); 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) { public void deleteChapter(Source source, Manga manga, Chapter chapter) {
File path = getAbsoluteChapterDirectory(source, manga, chapter); File path = getAbsoluteChapterDirectory(source, manga, chapter);
DiskUtils.deleteFiles(path); DiskUtils.deleteFiles(path);

View file

@ -10,7 +10,7 @@ import javax.inject.Inject;
import de.greenrobot.event.EventBus; import de.greenrobot.event.EventBus;
import eu.kanade.mangafeed.App; import eu.kanade.mangafeed.App;
import eu.kanade.mangafeed.data.helpers.DownloadManager; 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.AndroidComponentUtil;
import eu.kanade.mangafeed.util.EventBusHook; import eu.kanade.mangafeed.util.EventBusHook;
@ -31,7 +31,7 @@ public class DownloadService extends Service {
super.onCreate(); super.onCreate();
App.get(this).getComponent().inject(this); App.get(this).getComponent().inject(this);
EventBus.getDefault().register(this); EventBus.getDefault().registerSticky(this);
} }
@Override @Override
@ -45,8 +45,9 @@ public class DownloadService extends Service {
} }
@EventBusHook @EventBusHook
public void onEvent(DownloadChapterEvent event) { public void onEvent(DownloadChaptersEvent event) {
downloadManager.getDownloadsSubject().onNext(event); downloadManager.getDownloadsSubject().onNext(event);
EventBus.getDefault().removeStickyEvent(event);
} }
@Override @Override

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -75,21 +75,22 @@ public class DownloadQueuePresenter extends BasePresenter<DownloadQueueFragment>
case Download.DOWNLOADED: case Download.DOWNLOADED:
unsubscribeProgress(download); unsubscribeProgress(download);
unsubscribePagesStatus(download); unsubscribePagesStatus(download);
download.totalProgress = download.pages.size() * 100;
view.updateProgress(download); view.updateProgress(download);
break; break;
} }
} }
private void observeProgress(Download download, DownloadQueueFragment view) { 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) .flatMap(tick -> Observable.from(download.pages)
.map(Page::getProgress) .map(Page::getProgress)
.reduce((x, y) -> x + y)) .reduce((x, y) -> x + y))
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe(progress -> { .subscribe(progress -> {
download.totalProgress = progress; if (download.totalProgress != progress) {
view.updateProgress(download); download.totalProgress = progress;
view.updateProgress(download);
}
}); });
progressSubscriptions.put(download, subscription); progressSubscriptions.put(download, subscription);

View file

@ -15,7 +15,7 @@ import eu.kanade.mangafeed.data.helpers.SourceManager;
import eu.kanade.mangafeed.data.models.Chapter; import eu.kanade.mangafeed.data.models.Chapter;
import eu.kanade.mangafeed.data.models.Manga; import eu.kanade.mangafeed.data.models.Manga;
import eu.kanade.mangafeed.events.ChapterCountEvent; 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.events.SourceMangaChapterEvent;
import eu.kanade.mangafeed.sources.base.Source; import eu.kanade.mangafeed.sources.base.Source;
import eu.kanade.mangafeed.ui.fragment.MangaChaptersFragment; import eu.kanade.mangafeed.ui.fragment.MangaChaptersFragment;
@ -135,9 +135,10 @@ public class MangaChaptersPresenter extends BasePresenter<MangaChaptersFragment>
public void downloadChapters(Observable<Chapter> selectedChapters) { public void downloadChapters(Observable<Chapter> selectedChapters) {
add(downloadSubscription = selectedChapters add(downloadSubscription = selectedChapters
.doOnCompleted(() -> remove(downloadSubscription)) .toList()
.subscribe(chapter -> { .subscribe(chapters -> {
EventBus.getDefault().post(new DownloadChapterEvent(manga, chapter)); 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 dir = downloadManager.getAbsoluteChapterDirectory(source, manga, chapter);
File pageList = new File(dir, DownloadManager.PAGE_LIST_FILE); 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; chapter.downloaded = Chapter.DOWNLOADED;
} else { } else {
chapter.downloaded = Chapter.NOT_DOWNLOADED; chapter.downloaded = Chapter.NOT_DOWNLOADED;

View file

@ -98,7 +98,7 @@ public class ReaderPresenter extends BasePresenter<ReaderActivity> {
private Observable<List<Page>> getPageListObservable() { private Observable<List<Page>> getPageListObservable() {
if (!isDownloaded) if (!isDownloaded)
return source.pullPageListFromNetwork(chapter.url) return source.getCachedPageListOrPullFromNetwork(chapter.url)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()); .observeOn(AndroidSchedulers.mainThread());
else else

View file

@ -63,20 +63,23 @@ public abstract class Source extends BaseSource {
Observable.just(parseHtmlToChapters(unparsedHtml))); Observable.just(parseHtmlToChapters(unparsedHtml)));
} }
public Observable<List<Page>> pullPageListFromNetwork(final String chapterUrl) { public Observable<List<Page>> getCachedPageListOrPullFromNetwork(final String chapterUrl) {
return mCacheManager.getPageUrlsFromDiskCache(chapterUrl) return mCacheManager.getPageUrlsFromDiskCache(chapterUrl)
.onErrorResumeNext(throwable -> { .onErrorResumeNext(throwable -> {
return mNetworkService return pullPageListFromNetwork(chapterUrl);
.getStringResponse(overrideChapterPageUrl(chapterUrl), mRequestHeaders, null)
.flatMap(unparsedHtml -> {
List<String> pageUrls = parseHtmlToPageUrls(unparsedHtml);
return Observable.just(getFirstImageFromPageUrls(pageUrls, unparsedHtml));
})
.doOnNext(pages -> savePageList(chapterUrl, pages));
}) })
.onBackpressureBuffer(); .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 // Get the URLs of the images of a chapter
public Observable<Page> getRemainingImageUrlsFromPageList(final List<Page> pages) { public Observable<Page> getRemainingImageUrlsFromPageList(final List<Page> pages) {
return Observable.from(pages) return Observable.from(pages)

View file

@ -64,15 +64,6 @@ public class MangaChaptersFragment extends BaseRxFragment<MangaChaptersPresenter
return view; return view;
} }
@Override
public void onStart() {
super.onStart();
if (!DownloadService.isRunning(getActivity())) {
Intent intent = DownloadService.getStartIntent(getActivity());
getActivity().startService(intent);
}
}
@Override @Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.chapters, menu); inflater.inflate(R.menu.chapters, menu);
@ -140,6 +131,8 @@ public class MangaChaptersFragment extends BaseRxFragment<MangaChaptersPresenter
getPresenter().markChaptersRead(getSelectedChapters(), false); getPresenter().markChaptersRead(getSelectedChapters(), false);
return true; return true;
case R.id.action_download: case R.id.action_download:
Intent intent = DownloadService.getStartIntent(getActivity());
getActivity().startService(intent);
getPresenter().downloadChapters(getSelectedChapters()); getPresenter().downloadChapters(getSelectedChapters());
closeActionMode(); closeActionMode();
return true; return true;

View file

@ -115,10 +115,8 @@ public final class DiskUtils {
} }
public static File saveBufferedSourceToDirectory(BufferedSource bufferedSource, File directory, String name) throws IOException { public static File saveBufferedSourceToDirectory(BufferedSource bufferedSource, File directory, String name) throws IOException {
if (!directory.exists()) { if (!directory.exists() && !directory.mkdirs()) {
if (!directory.mkdirs()) { throw new IOException("Failed Creating Directory");
throw new IOException("Failed Creating Directory");
}
} }
File writeFile = new File(directory, name); File writeFile = new File(directory, name);
@ -155,5 +153,12 @@ public final class DiskUtils {
inputFile.delete(); inputFile.delete();
} }
public static synchronized void createDirectory(File directory) throws IOException {
if (!directory.exists() && !directory.mkdirs()) {
throw new IOException("Failed creating directory");
}
}
} }