// Get the image from the filesystem if it exists or download from network
  private Observable<Page> getOrDownloadImage(final Page page, Download download) {
    // If the image URL is empty, do nothing
    if (page.getImageUrl() == null) return Observable.just(page);

    String filename = getImageFilename(page);
    File imagePath = new File(download.directory, filename);

    // If the image is already downloaded, do nothing. Otherwise download from network
    Observable<Page> pageObservable =
        isImageDownloaded(imagePath)
            ? Observable.just(page)
            : downloadImage(page, download.source, download.directory, filename);

    return pageObservable
        // When the image is ready, set image path, progress (just in case) and status
        .doOnNext(
            p -> {
              page.setImagePath(imagePath.getAbsolutePath());
              page.setProgress(100);
              download.downloadedImages++;
              page.setStatus(Page.READY);
            })
        // Mark this page as error and allow to download the remaining
        .onErrorResumeNext(
            e -> {
              page.setProgress(0);
              page.setStatus(Page.ERROR);
              return Observable.just(page);
            });
  }
  @Test
  @SuppressWarnings("unchecked")
  public void testThreadName() throws InterruptedException {
    System.out.println("Main Thread: " + Thread.currentThread().getName());
    Observable<String> obs = Observable.just("one", null, "two", "three", "four");

    Observer<String> observer = mock(Observer.class);
    final String parentThreadName = Thread.currentThread().getName();

    final CountDownLatch completedLatch = new CountDownLatch(1);

    // assert subscribe is on main thread
    obs =
        obs.doOnNext(
            new Action1<String>() {

              @Override
              public void call(String s) {
                String threadName = Thread.currentThread().getName();
                System.out.println(
                    "Source ThreadName: " + threadName + "  Expected => " + parentThreadName);
                assertEquals(parentThreadName, threadName);
              }
            });

    // assert observe is on new thread
    obs.observeOn(Schedulers.newThread())
        .doOnNext(
            new Action1<String>() {

              @Override
              public void call(String t1) {
                String threadName = Thread.currentThread().getName();
                boolean correctThreadName = threadName.startsWith("RxNewThreadScheduler");
                System.out.println(
                    "ObserveOn ThreadName: " + threadName + "  Correct => " + correctThreadName);
                assertTrue(correctThreadName);
              }
            })
        .finallyDo(
            new Action0() {

              @Override
              public void call() {
                completedLatch.countDown();
              }
            })
        .subscribe(observer);

    if (!completedLatch.await(1000, TimeUnit.MILLISECONDS)) {
      fail("timed out waiting");
    }

    verify(observer, never()).onError(any(Throwable.class));
    verify(observer, times(5)).onNext(any(String.class));
    verify(observer, times(1)).onCompleted();
  }