Skip to content

Commit

Permalink
Run the audiobooks file scan on an IO thread.
Browse files Browse the repository at this point in the history
This will allow triggering the scan directly from the UI without
blocking it.
  • Loading branch information
msimonides committed Mar 4, 2018
1 parent bbbfcfc commit eb01e53
Show file tree
Hide file tree
Showing 16 changed files with 369 additions and 204 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.net.Uri;
import android.os.Handler;
import android.os.HandlerThread;
import android.preference.PreferenceManager;

import com.studio4plus.homerplayer.analytics.AnalyticsTracker;
import com.studio4plus.homerplayer.concurrency.BackgroundExecutor;

import java.util.Locale;

Expand Down Expand Up @@ -64,4 +67,12 @@ EventBus provideEventBus() {
AnalyticsTracker provideAnalyticsTracker(GlobalSettings globalSettings, EventBus eventBus) {
return new AnalyticsTracker(globalSettings, eventBus);
}

@Provides @Singleton @Named("IO_EXECUTOR")
BackgroundExecutor provideIoExecutor(Context applicationContext) {
HandlerThread ioThread = new HandlerThread("IO");
ioThread.start();
Handler ioHandler = new Handler(ioThread.getLooper());
return new BackgroundExecutor(new Handler(applicationContext.getMainLooper()), ioHandler);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.studio4plus.homerplayer.concurrency;

import android.os.Handler;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;

public class BackgroundDeferred<V> extends BaseDeferred<V> implements Runnable {

private final @NonNull List<Listener<V>> listeners = new ArrayList<>();

private final @NonNull Callable<V> task;
private final @NonNull Handler mainThreadHandler;

BackgroundDeferred(@NonNull Callable<V> task, @NonNull Handler mainThreadHandler) {
this.task = task;
this.mainThreadHandler = mainThreadHandler;
}

@Override
public void run() {
try {
final @NonNull V newResult = task.call();
mainThreadHandler.post(new Runnable() {
@Override
public void run() {
setResult(newResult);
}
});
} catch (final Exception e) {
mainThreadHandler.post(new Runnable() {
@Override
public void run() {
setException(e);
}
});
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.studio4plus.homerplayer.concurrency;

import android.os.Handler;
import android.support.annotation.NonNull;

import java.util.concurrent.Callable;

public class BackgroundExecutor {

private final @NonNull Handler mainThreadHandler;
private final @NonNull Handler taskHandler;

public BackgroundExecutor(@NonNull Handler mainThreadHandler, @NonNull Handler taskHandler) {
this.mainThreadHandler = mainThreadHandler;
this.taskHandler = taskHandler;
}

public <V> SimpleFuture<V> postTask(@NonNull Callable<V> task) {
BackgroundDeferred<V> deferred = new BackgroundDeferred<>(task, mainThreadHandler);
taskHandler.post(deferred);
return deferred;
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.studio4plus.homerplayer.util;
package com.studio4plus.homerplayer.concurrency;

import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
Expand All @@ -8,11 +8,11 @@

/**
* A straightforward implementation of SimpleFuture.
* It's intended of use only on a single thread.
* It's intended for use only on a single thread.
*
* Note: I don't need the full power of ListenableFutures nor Rx yet.
*/
public class SimpleDeferred<V> implements SimpleFuture<V> {
public class BaseDeferred<V> implements SimpleFuture<V> {

private final @NonNull List<Listener<V>> listeners = new ArrayList<>();
private @Nullable V result;
Expand All @@ -32,15 +32,15 @@ public void removeListener(@NonNull Listener<V> listener) {
listeners.remove(listener);
}

public void setResult(@NonNull V result) {
protected void setResult(@NonNull V result) {
this.result = result;
for (Listener<V> listener : listeners)
listener.onResult(result);
}

public void setException(@NonNull Throwable exception) {
protected void setException(@NonNull Throwable exception) {
this.exception = exception;
for (Listener<V> listener : listeners)
listener.onException(exception);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.studio4plus.homerplayer.concurrency;

import android.support.annotation.NonNull;

public class SimpleDeferred<V> extends BaseDeferred<V> {

public void setResult(@NonNull V result) {
super.setResult(result);
}

public void setException(@NonNull Throwable exception) {
super.setException(exception);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.studio4plus.homerplayer.util;
package com.studio4plus.homerplayer.concurrency;

import android.support.annotation.NonNull;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package com.studio4plus.homerplayer.filescanner;

import android.content.Context;
import android.os.Environment;

import com.studio4plus.homerplayer.ApplicationScope;
import com.studio4plus.homerplayer.concurrency.BackgroundExecutor;
import com.studio4plus.homerplayer.concurrency.SimpleFuture;

import java.io.File;
import java.util.List;

import javax.inject.Inject;
import javax.inject.Named;

@ApplicationScope
public class FileScanner {
public static final String SAMPLE_BOOK_FILE_NAME = ".sample";

private final String audioBooksDirectoryPath;
private final BackgroundExecutor ioExecutor;
private final Context applicationContext;

@Inject
public FileScanner(
@Named("AUDIOBOOKS_DIRECTORY") String audioBooksDirectoryPath,
@Named("IO_EXECUTOR") BackgroundExecutor ioExecutor,
Context applicationContext) {
this.audioBooksDirectoryPath = audioBooksDirectoryPath;
this.ioExecutor = ioExecutor;
this.applicationContext = applicationContext;
}

public SimpleFuture<List<FileSet>> scanAudioBooksDirectories() {
ScanFilesTask task = new ScanFilesTask(applicationContext, audioBooksDirectoryPath);
return ioExecutor.postTask(task);
}

/**
* Provide the default directory for audio books.
*
* The directory is in the devices external storage. Other than that there is nothing
* special about it (e.g. it may be on an removable storage).
*/
public File getDefaultAudioBooksDirectory() {
File externalStorage = Environment.getExternalStorageDirectory();
return new File(externalStorage, audioBooksDirectoryPath);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.studio4plus.homerplayer.model;
package com.studio4plus.homerplayer.filescanner;

import com.google.common.base.Preconditions;

Expand Down
Loading

0 comments on commit eb01e53

Please sign in to comment.