mirror of
https://github.com/kiwix/kiwix-android.git
synced 2025-08-03 02:36:24 -04:00
#1238 Unit test FileSearch
This commit is contained in:
parent
0539d56849
commit
fd9f797105
@ -218,7 +218,7 @@
|
||||
},
|
||||
{
|
||||
"id": "2:6862771806221961183",
|
||||
"name": "zimID"
|
||||
"name": "zimId"
|
||||
},
|
||||
{
|
||||
"id": "3:4312769031500860715",
|
||||
|
@ -18,10 +18,13 @@
|
||||
package org.kiwix.kiwixmobile.downloader.model
|
||||
|
||||
import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity.Book
|
||||
import org.kiwix.kiwixmobile.utils.StorageUtils
|
||||
|
||||
data class DownloadModel(
|
||||
val databaseId: Long? = null,
|
||||
val downloadId: Long ,
|
||||
val downloadId: Long,
|
||||
val book: Book
|
||||
)
|
||||
) {
|
||||
val fileNameFromUrl: String get() = StorageUtils.getFileNameFromUrl(book.url)
|
||||
}
|
||||
|
||||
|
@ -37,7 +37,6 @@ import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.CountDownTimer;
|
||||
import android.os.Environment;
|
||||
import android.os.Handler;
|
||||
import android.provider.Settings;
|
||||
import android.text.SpannableString;
|
||||
@ -84,10 +83,10 @@ import com.google.android.material.appbar.AppBarLayout;
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import com.google.android.material.navigation.NavigationView;
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import java.io.File;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
@ -100,20 +99,18 @@ import org.kiwix.kiwixmobile.base.BaseActivity;
|
||||
import org.kiwix.kiwixmobile.bookmark.BookmarkItem;
|
||||
import org.kiwix.kiwixmobile.bookmark.BookmarksActivity;
|
||||
import org.kiwix.kiwixmobile.data.ZimContentProvider;
|
||||
import org.kiwix.kiwixmobile.data.local.entity.Bookmark;
|
||||
import org.kiwix.kiwixmobile.help.HelpActivity;
|
||||
import org.kiwix.kiwixmobile.history.HistoryActivity;
|
||||
import org.kiwix.kiwixmobile.history.HistoryListItem;
|
||||
import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity;
|
||||
import org.kiwix.kiwixmobile.search.SearchActivity;
|
||||
import org.kiwix.kiwixmobile.settings.KiwixSettingsActivity;
|
||||
import org.kiwix.kiwixmobile.utils.DimenUtils;
|
||||
import org.kiwix.kiwixmobile.utils.LanguageUtils;
|
||||
import org.kiwix.kiwixmobile.utils.NetworkUtils;
|
||||
import org.kiwix.kiwixmobile.utils.StyleUtils;
|
||||
import org.kiwix.kiwixmobile.utils.files.FileSearch;
|
||||
import org.kiwix.kiwixmobile.utils.files.FileUtils;
|
||||
import org.kiwix.kiwixmobile.zim_manager.ZimManageActivity;
|
||||
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.StorageObserver;
|
||||
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.adapter.BookOnDiskDelegate;
|
||||
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.adapter.BooksOnDiskAdapter;
|
||||
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.adapter.BooksOnDiskListItem;
|
||||
@ -201,14 +198,18 @@ public class MainActivity extends BaseActivity implements WebViewCallback,
|
||||
ImageView bottomToolbarArrowBack;
|
||||
@BindView(R.id.bottom_toolbar_arrow_forward)
|
||||
ImageView bottomToolbarArrowForward;
|
||||
@Inject
|
||||
MainContract.Presenter presenter;
|
||||
@BindView(R.id.tab_switcher_recycler_view)
|
||||
RecyclerView tabRecyclerView;
|
||||
@BindView(R.id.activity_main_tab_switcher)
|
||||
View tabSwitcherRoot;
|
||||
@BindView(R.id.tab_switcher_close_all_tabs)
|
||||
FloatingActionButton closeAllTabsButton;
|
||||
|
||||
@Inject
|
||||
MainContract.Presenter presenter;
|
||||
@Inject
|
||||
StorageObserver storageObserver;
|
||||
|
||||
private CountDownTimer hideBackToTopTimer = new CountDownTimer(1200, 1200) {
|
||||
@Override
|
||||
public void onTick(long millisUntilFinished) {
|
||||
@ -274,21 +275,7 @@ public class MainActivity extends BaseActivity implements WebViewCallback,
|
||||
closeTab(viewHolder.getAdapterPosition());
|
||||
}
|
||||
};
|
||||
private FileSearch fileSearch =
|
||||
new FileSearch(this, Collections.emptyList(), new FileSearch.ResultListener() {
|
||||
final List<BooksOnDiskListItem.BookOnDisk> newBooks = new ArrayList<>();
|
||||
|
||||
@Override public void onBookFound(BooksOnDiskListItem.BookOnDisk bookOnDisk) {
|
||||
runOnUiThread(() -> {
|
||||
newBooks.add(bookOnDisk);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScanCompleted() {
|
||||
presenter.saveBooks(newBooks);
|
||||
}
|
||||
});
|
||||
|
||||
private static void updateWidgets(Context context) {
|
||||
Intent intent = new Intent(context.getApplicationContext(), KiwixSearchWidget.class);
|
||||
@ -739,7 +726,6 @@ public class MainActivity extends BaseActivity implements WebViewCallback,
|
||||
downloadBookButton = null;
|
||||
hideBackToTopTimer.cancel();
|
||||
hideBackToTopTimer = null;
|
||||
fileSearch = null;
|
||||
// TODO create a base Activity class that class this.
|
||||
FileUtils.deleteCachedFiles(this);
|
||||
tts.shutdown();
|
||||
@ -1199,7 +1185,7 @@ public class MainActivity extends BaseActivity implements WebViewCallback,
|
||||
case REQUEST_READ_STORAGE_PERMISSION: {
|
||||
if (grantResults.length > 0
|
||||
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
fileSearch.scan(sharedPreferenceUtil.getPrefStorage());
|
||||
scanStorageForZims();
|
||||
} else {
|
||||
Snackbar.make(drawerLayout, R.string.request_storage, Snackbar.LENGTH_LONG)
|
||||
.setAction(R.string.menu_settings, view -> {
|
||||
@ -1228,6 +1214,13 @@ public class MainActivity extends BaseActivity implements WebViewCallback,
|
||||
}
|
||||
}
|
||||
|
||||
private void scanStorageForZims() {
|
||||
storageObserver.getBooksOnFileSystem()
|
||||
.take(1)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(presenter::saveBooks, Throwable::printStackTrace);
|
||||
}
|
||||
|
||||
// Workaround for popup bottom menu on older devices
|
||||
private void StyleMenuButtons(Menu m) {
|
||||
// Find each menu item and set its text colour
|
||||
@ -2116,7 +2109,7 @@ public class MainActivity extends BaseActivity implements WebViewCallback,
|
||||
new String[] { Manifest.permission.READ_EXTERNAL_STORAGE },
|
||||
REQUEST_READ_STORAGE_PERMISSION);
|
||||
} else {
|
||||
fileSearch.scan(sharedPreferenceUtil.getPrefStorage());
|
||||
scanStorageForZims();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,296 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Rashiq Ahmad <rashiq.z@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
package org.kiwix.kiwixmobile.utils.files;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Environment;
|
||||
import android.provider.MediaStore;
|
||||
import android.util.Log;
|
||||
import eu.mhutti1.utils.storage.StorageDevice;
|
||||
import eu.mhutti1.utils.storage.StorageDeviceUtils;
|
||||
import java.io.File;
|
||||
import java.io.FilenameFilter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Vector;
|
||||
import org.kiwix.kiwixmobile.data.ZimContentProvider;
|
||||
import org.kiwix.kiwixmobile.downloader.model.DownloadModel;
|
||||
import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity;
|
||||
import org.kiwix.kiwixmobile.utils.StorageUtils;
|
||||
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.adapter.BooksOnDiskListItem;
|
||||
|
||||
import static org.kiwix.kiwixmobile.utils.Constants.TAG_KIWIX;
|
||||
|
||||
public class FileSearch {
|
||||
|
||||
// Array of zim file extensions
|
||||
public static final String[] zimFiles = { "zim", "zimaa" };
|
||||
|
||||
private final Context context;
|
||||
private final List<DownloadModel> downloads;
|
||||
private final ResultListener listener;
|
||||
|
||||
private boolean fileSystemScanCompleted = false;
|
||||
private boolean mediaStoreScanCompleted = false;
|
||||
|
||||
public FileSearch(Context ctx, List<DownloadModel> downloads, ResultListener listener) {
|
||||
this.context = ctx;
|
||||
this.downloads = downloads;
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
public static synchronized LibraryNetworkEntity.Book fileToBook(String filePath) {
|
||||
LibraryNetworkEntity.Book book = null;
|
||||
|
||||
if (ZimContentProvider.zimFileName != null) {
|
||||
ZimContentProvider.originalFileName = ZimContentProvider.zimFileName;
|
||||
}
|
||||
// Check a file isn't being opened and temporally use content provider to access details
|
||||
// This is not a great solution as we shouldn't need to fully open our ZIM files to get their metadata
|
||||
if (ZimContentProvider.canIterate) {
|
||||
if (ZimContentProvider.setZimFile(filePath) != null) {
|
||||
try {
|
||||
book = new LibraryNetworkEntity.Book();
|
||||
book.title = ZimContentProvider.getZimFileTitle();
|
||||
book.id = ZimContentProvider.getId();
|
||||
book.file = new File(filePath);
|
||||
book.size = String.valueOf(ZimContentProvider.getFileSize());
|
||||
book.favicon = ZimContentProvider.getFavicon();
|
||||
book.creator = ZimContentProvider.getCreator();
|
||||
book.publisher = ZimContentProvider.getPublisher();
|
||||
book.date = ZimContentProvider.getDate();
|
||||
book.description = ZimContentProvider.getDescription();
|
||||
book.language = ZimContentProvider.getLanguage();
|
||||
} catch (Exception e) {
|
||||
// TODO 20171215 Consider more elegant approaches.
|
||||
// This is to see if we can catch the exception at all!
|
||||
Log.e("kiwix-filesearch", "Problem parsing a book entry from the library file. ", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Return content provider to its previous state
|
||||
if (!ZimContentProvider.originalFileName.equals("")) {
|
||||
ZimContentProvider.setZimFile(ZimContentProvider.originalFileName);
|
||||
}
|
||||
ZimContentProvider.originalFileName = "";
|
||||
|
||||
return book;
|
||||
}
|
||||
|
||||
public void scan(String defaultPath) {
|
||||
// Start custom file search
|
||||
new Thread(() -> {
|
||||
scanFileSystem(defaultPath);
|
||||
fileSystemScanCompleted = true;
|
||||
checkCompleted();
|
||||
}).start();
|
||||
|
||||
// Star mediastore search
|
||||
new Thread(() -> {
|
||||
scanMediaStore();
|
||||
mediaStoreScanCompleted = true;
|
||||
checkCompleted();
|
||||
}).start();
|
||||
}
|
||||
|
||||
// If both searches are complete callback
|
||||
private synchronized void checkCompleted() {
|
||||
if (mediaStoreScanCompleted && fileSystemScanCompleted) {
|
||||
listener.onScanCompleted();
|
||||
}
|
||||
}
|
||||
|
||||
public void scanMediaStore() {
|
||||
ContentResolver contentResolver = context.getContentResolver();
|
||||
Uri uri = MediaStore.Files.getContentUri("external");
|
||||
|
||||
String[] projection = { MediaStore.MediaColumns.DATA };
|
||||
String selection =
|
||||
MediaStore.MediaColumns.DATA + " like ? or " + MediaStore.MediaColumns.DATA + " like ? ";
|
||||
|
||||
Cursor query = contentResolver.query(uri, projection, selection,
|
||||
new String[] { "%." + zimFiles[0], "%." + zimFiles[1] }, null);
|
||||
|
||||
if (query == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
while (query.moveToNext()) {
|
||||
File file = new File(query.getString(0));
|
||||
if (file.canRead()) {
|
||||
onFileFound(file.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
query.close();
|
||||
}
|
||||
}
|
||||
|
||||
// Scan through the file system and find all the files with .zim and .zimaa extensions
|
||||
public void scanFileSystem(String defaultPath) {
|
||||
FilenameFilter[] filter = new FilenameFilter[zimFiles.length];
|
||||
|
||||
// Search all external directories that we can find.
|
||||
final ArrayList<StorageDevice> storageDevices =
|
||||
StorageDeviceUtils.getStorageDevices(context, false);
|
||||
String[] tempRoots = new String[storageDevices.size() + 2];
|
||||
int j = 0;
|
||||
tempRoots[j++] = "/mnt";
|
||||
tempRoots[j++] = defaultPath;
|
||||
for (StorageDevice storageDevice : storageDevices) {
|
||||
tempRoots[j++] = storageDevice.getName();
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
for (final String extension : zimFiles) {
|
||||
filter[i] = (dir, name) -> name.endsWith("." + extension);
|
||||
i++;
|
||||
}
|
||||
|
||||
String dirNamePrimary = new File(
|
||||
Environment.getExternalStorageDirectory().getAbsolutePath()).toString();
|
||||
|
||||
for (final String dirName : tempRoots) {
|
||||
if (dirNamePrimary.equals(dirName)) {
|
||||
// We already got this directory from getExternalStorageDirectory().
|
||||
continue;
|
||||
}
|
||||
File f = new File(dirName);
|
||||
if (f.isDirectory()) {
|
||||
scanDirectory(dirName, filter);
|
||||
} else {
|
||||
Log.i(TAG_KIWIX, "Skipping missing directory " + dirName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Iterate through the file system
|
||||
private Collection<File> listFiles(File directory, FilenameFilter[] filter, int recurse) {
|
||||
|
||||
Vector<File> files = new Vector<>();
|
||||
|
||||
File[] entries = directory.listFiles();
|
||||
|
||||
if (entries != null) {
|
||||
for (File entry : entries) {
|
||||
for (FilenameFilter filefilter : filter) {
|
||||
if (filter == null || filefilter.accept(directory, entry.getName())) {
|
||||
files.add(entry);
|
||||
}
|
||||
}
|
||||
if ((recurse <= -1) || (recurse > 0 && entry.isDirectory())) {
|
||||
recurse--;
|
||||
files.addAll(listFiles(entry, filter, recurse));
|
||||
recurse++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
private File[] listFilesAsArray(File directory, FilenameFilter[] filter, int recurse) {
|
||||
Collection<File> files = listFiles(directory, filter, recurse);
|
||||
|
||||
File[] arr = new File[files.size()];
|
||||
return files.toArray(arr);
|
||||
}
|
||||
|
||||
public static synchronized BooksOnDiskListItem.BookOnDisk fileToBookOnDisk(String filePath) {
|
||||
LibraryNetworkEntity.Book book = null;
|
||||
|
||||
if (ZimContentProvider.zimFileName != null) {
|
||||
ZimContentProvider.originalFileName = ZimContentProvider.zimFileName;
|
||||
}
|
||||
// Check a file isn't being opened and temporally use content provider to access details
|
||||
// This is not a great solution as we shouldn't need to fully open our ZIM files to get their metadata
|
||||
if (ZimContentProvider.canIterate) {
|
||||
if (ZimContentProvider.setZimFile(filePath) != null) {
|
||||
try {
|
||||
book = new LibraryNetworkEntity.Book();
|
||||
book.title = ZimContentProvider.getZimFileTitle();
|
||||
book.id = ZimContentProvider.getId();
|
||||
book.size = String.valueOf(ZimContentProvider.getFileSize());
|
||||
book.favicon = ZimContentProvider.getFavicon();
|
||||
book.creator = ZimContentProvider.getCreator();
|
||||
book.publisher = ZimContentProvider.getPublisher();
|
||||
book.date = ZimContentProvider.getDate();
|
||||
book.description = ZimContentProvider.getDescription();
|
||||
book.language = ZimContentProvider.getLanguage();
|
||||
} catch (Exception e) {
|
||||
// TODO 20171215 Consider more elegant approaches.
|
||||
// This is to see if we can catch the exception at all!
|
||||
Log.e("kiwix-filesearch", "Problem parsing a book entry from the library file. ", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Return content provider to its previous state
|
||||
if (!ZimContentProvider.originalFileName.equals("")) {
|
||||
ZimContentProvider.setZimFile(ZimContentProvider.originalFileName);
|
||||
}
|
||||
ZimContentProvider.originalFileName = "";
|
||||
|
||||
return book == null ? null
|
||||
: new BooksOnDiskListItem.BookOnDisk(null, book, new File(filePath),0L);
|
||||
}
|
||||
|
||||
// Fill fileList with files found in the specific directory
|
||||
private void scanDirectory(String directory, FilenameFilter[] filter) {
|
||||
Log.d(TAG_KIWIX, "Searching directory " + directory);
|
||||
File[] foundFiles = listFilesAsArray(new File(directory), filter, -1);
|
||||
for (File f : foundFiles) {
|
||||
Log.d(TAG_KIWIX, "Found " + f.getAbsolutePath());
|
||||
onFileFound(f.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
|
||||
// Callback that a new file has been found
|
||||
public void onFileFound(String filePath) {
|
||||
if (fileIsDownloading(filePath)) {
|
||||
return;
|
||||
}
|
||||
BooksOnDiskListItem.BookOnDisk book = fileToBookOnDisk(filePath);
|
||||
|
||||
if (book != null) {
|
||||
listener.onBookFound(book);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean fileIsDownloading(String filePath) {
|
||||
for (DownloadModel download : downloads) {
|
||||
if (filePath.endsWith(StorageUtils.getFileNameFromUrl(download.getBook().getUrl()))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public interface ResultListener {
|
||||
void onBookFound(BooksOnDiskListItem.BookOnDisk book);
|
||||
|
||||
void onScanCompleted();
|
||||
}
|
||||
}
|
@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright 2013 Rashiq Ahmad <rashiq.z@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
package org.kiwix.kiwixmobile.utils.files
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Environment
|
||||
import android.provider.MediaStore.Files
|
||||
import android.provider.MediaStore.MediaColumns
|
||||
import eu.mhutti1.utils.storage.StorageDeviceUtils
|
||||
import io.reactivex.Flowable
|
||||
import io.reactivex.functions.BiFunction
|
||||
import org.kiwix.kiwixmobile.extensions.forEachRow
|
||||
import org.kiwix.kiwixmobile.extensions.get
|
||||
import java.io.File
|
||||
import javax.inject.Inject
|
||||
|
||||
class FileSearch @Inject constructor(private val context: Context) {
|
||||
|
||||
val zimFileExtensions = arrayOf("zim", "zimaa")
|
||||
|
||||
fun scan(defaultPath: String) =
|
||||
Flowable.combineLatest(
|
||||
Flowable.fromCallable { scanFileSystem(defaultPath) },
|
||||
Flowable.fromCallable(this::scanMediaStore),
|
||||
BiFunction<List<File>, List<File>, List<File>> { filesSystemFiles, mediaStoreFiles ->
|
||||
mutableListOf<File>().apply {
|
||||
addAll(filesSystemFiles)
|
||||
addAll(mediaStoreFiles)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
private fun scanMediaStore() = mutableListOf<File>().apply {
|
||||
queryMediaStore()
|
||||
?.forEachRow { cursor ->
|
||||
File(cursor.get<String>(MediaColumns.DATA)).takeIf(File::canRead)
|
||||
?.also { add(it) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun queryMediaStore() = context.contentResolver
|
||||
.query(
|
||||
Files.getContentUri("external"),
|
||||
arrayOf(MediaColumns.DATA),
|
||||
MediaColumns.DATA + " like ? or " + MediaColumns.DATA + " like ? ",
|
||||
arrayOf("%." + zimFileExtensions[0], "%." + zimFileExtensions[1]),
|
||||
null
|
||||
)
|
||||
|
||||
private fun scanFileSystem(defaultPath: String) =
|
||||
directoryRoots(defaultPath)
|
||||
.minus(Environment.getExternalStorageDirectory().absolutePath)
|
||||
.fold(mutableListOf<File>(), { acc, root ->
|
||||
if (File(root).isDirectory) acc.addAll(scanDirectory(root))
|
||||
acc
|
||||
})
|
||||
|
||||
private fun directoryRoots(defaultPath: String) = listOf(
|
||||
"/mnt",
|
||||
defaultPath,
|
||||
*StorageDeviceUtils.getStorageDevices(context, false).map { it.name }.toTypedArray()
|
||||
)
|
||||
|
||||
private fun scanDirectory(directory: String) = filesMatchingExtensions(directory) ?: emptyList()
|
||||
|
||||
private fun filesMatchingExtensions(directory: String) = File(directory)
|
||||
.listFiles { dir, name -> name?.endsWithAny(*zimFileExtensions) ?: false }
|
||||
?.toList()
|
||||
|
||||
}
|
||||
|
||||
private fun String.endsWithAny(vararg suffixes: String) =
|
||||
suffixes.fold(false, {acc, s -> acc or endsWith(s) })
|
@ -1,45 +1,87 @@
|
||||
package org.kiwix.kiwixmobile.zim_manager.fileselect_view
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import io.reactivex.processors.PublishProcessor
|
||||
import io.reactivex.functions.BiFunction
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import org.kiwix.kiwixmobile.data.ZimContentProvider
|
||||
import org.kiwix.kiwixmobile.database.newdb.dao.NewDownloadDao
|
||||
import org.kiwix.kiwixmobile.downloader.model.DownloadModel
|
||||
import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity.Book
|
||||
import org.kiwix.kiwixmobile.utils.SharedPreferenceUtil
|
||||
import org.kiwix.kiwixmobile.utils.files.FileSearch
|
||||
import org.kiwix.kiwixmobile.utils.files.FileSearch.ResultListener
|
||||
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.adapter.BooksOnDiskListItem.BookOnDisk
|
||||
import java.io.File
|
||||
import javax.inject.Inject
|
||||
|
||||
class StorageObserver @Inject constructor(
|
||||
private val context: Context,
|
||||
private val sharedPreferenceUtil: SharedPreferenceUtil,
|
||||
private val downloadDao: NewDownloadDao
|
||||
downloadDao: NewDownloadDao,
|
||||
private val fileSearch: FileSearch
|
||||
) {
|
||||
|
||||
private val _booksOnFileSystem = PublishProcessor.create<List<BookOnDisk>>()
|
||||
val booksOnFileSystem = _booksOnFileSystem.distinctUntilChanged()
|
||||
.doOnSubscribe {
|
||||
downloadDao.downloads()
|
||||
.subscribeOn(Schedulers.io())
|
||||
.take(1)
|
||||
.subscribe(this::scanFiles, Throwable::printStackTrace)
|
||||
val booksOnFileSystem = scanFiles()
|
||||
.withLatestFrom(
|
||||
downloadDao.downloads(),
|
||||
BiFunction(this::toFilesThatAreNotDownloading)
|
||||
)
|
||||
.map {
|
||||
it.mapNotNull { file -> convertToBookOnDisk(file) }
|
||||
}
|
||||
|
||||
private fun scanFiles(downloads: List<DownloadModel>) {
|
||||
FileSearch(context, downloads, object : ResultListener {
|
||||
val foundBooks = mutableSetOf<BookOnDisk>()
|
||||
private fun toFilesThatAreNotDownloading(
|
||||
files: List<File>,
|
||||
downloads: List<DownloadModel>
|
||||
) = files.filter { fileHasNoMatchingDownload(downloads, it) }
|
||||
|
||||
override fun onBookFound(book: BookOnDisk) {
|
||||
foundBooks.add(book)
|
||||
Log.i("Scanner", "File Search: Found Book " + book.book.title)
|
||||
private fun fileHasNoMatchingDownload(
|
||||
downloads: List<DownloadModel>,
|
||||
file: File
|
||||
) = downloads.firstOrNull {
|
||||
file.absolutePath.endsWith(it.fileNameFromUrl)
|
||||
} == null
|
||||
|
||||
private fun scanFiles() = fileSearch.scan(sharedPreferenceUtil.prefStorage)
|
||||
.subscribeOn(Schedulers.io())
|
||||
|
||||
private fun convertToBookOnDisk(file: File): BookOnDisk? {
|
||||
configureZimContentProvider()
|
||||
if (ZimContentProvider.canIterate && ZimContentProvider.setZimFile(file.absolutePath) != null) {
|
||||
try {
|
||||
return BookOnDisk(book = bookFromZimContentProvider(), file = file)
|
||||
} catch (e: Exception) {
|
||||
// TODO 20171215 Consider more elegant approaches.
|
||||
// This is to see if we can catch the exception at all!
|
||||
Log.e("kiwix-filesearch", "Problem parsing a book entry from the library file. ", e)
|
||||
} finally {
|
||||
resetZimContentProvider()
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
override fun onScanCompleted() {
|
||||
_booksOnFileSystem.onNext(foundBooks.toList())
|
||||
private fun bookFromZimContentProvider() = Book().apply {
|
||||
title = ZimContentProvider.getZimFileTitle()
|
||||
id = ZimContentProvider.getId()
|
||||
size = ZimContentProvider.getFileSize()
|
||||
.toString()
|
||||
favicon = ZimContentProvider.getFavicon()
|
||||
creator = ZimContentProvider.getCreator()
|
||||
publisher = ZimContentProvider.getPublisher()
|
||||
date = ZimContentProvider.getDate()
|
||||
description = ZimContentProvider.getDescription()
|
||||
language = ZimContentProvider.getLanguage()
|
||||
}
|
||||
|
||||
}
|
||||
}).scan(sharedPreferenceUtil.prefStorage)
|
||||
private fun resetZimContentProvider() {
|
||||
if (ZimContentProvider.originalFileName != "") {
|
||||
ZimContentProvider.setZimFile(ZimContentProvider.originalFileName)
|
||||
}
|
||||
ZimContentProvider.originalFileName = ""
|
||||
}
|
||||
|
||||
private fun configureZimContentProvider() {
|
||||
if (ZimContentProvider.zimFileName != null) {
|
||||
ZimContentProvider.originalFileName = ZimContentProvider.zimFileName
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -27,11 +27,31 @@ import java.io.File
|
||||
|
||||
fun bookOnDisk(
|
||||
book: Book = book(),
|
||||
databaseId: Long = 0L,
|
||||
databaseId: Long? = 0L,
|
||||
file: File = File("")
|
||||
) = BookOnDisk(databaseId, book, file)
|
||||
|
||||
fun book(id: String = "0") = Book().apply { this.id = id }
|
||||
fun book(
|
||||
id: String = "0",
|
||||
title: String = "",
|
||||
size: String = "",
|
||||
favicon: String = "",
|
||||
creator: String = "",
|
||||
publisher: String = "",
|
||||
date: String = "",
|
||||
description: String = "",
|
||||
language: String = ""
|
||||
) = Book().apply {
|
||||
this.id = id
|
||||
this.title = title
|
||||
this.size = size
|
||||
this.favicon = favicon
|
||||
this.creator = creator
|
||||
this.publisher = publisher
|
||||
this.date = date
|
||||
this.description = description
|
||||
this.language = language
|
||||
}
|
||||
|
||||
fun downloadStatus(
|
||||
downloadId: Long = 0L,
|
||||
|
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Kiwix Android
|
||||
* Copyright (C) 2018 Kiwix <android.kiwix.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.kiwix.kiwixmobile
|
||||
|
||||
import io.reactivex.Scheduler
|
||||
import io.reactivex.android.plugins.RxAndroidPlugins
|
||||
import io.reactivex.plugins.RxJavaPlugins
|
||||
|
||||
fun setScheduler(replacementScheduler: Scheduler) {
|
||||
RxJavaPlugins.setIoSchedulerHandler { scheduler -> replacementScheduler }
|
||||
RxJavaPlugins.setComputationSchedulerHandler { scheduler -> replacementScheduler }
|
||||
RxJavaPlugins.setNewThreadSchedulerHandler { scheduler -> replacementScheduler }
|
||||
RxAndroidPlugins.setInitMainThreadSchedulerHandler { scheduler -> replacementScheduler }
|
||||
}
|
||||
|
||||
fun resetSchedulers() {
|
||||
RxJavaPlugins.reset()
|
||||
RxAndroidPlugins.reset()
|
||||
}
|
@ -0,0 +1,139 @@
|
||||
/*
|
||||
* Kiwix Android
|
||||
* Copyright (C) 2018 Kiwix <android.kiwix.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.kiwix.kiwixmobile.utils.files
|
||||
|
||||
import android.content.ContentResolver
|
||||
import android.content.Context
|
||||
import android.database.Cursor
|
||||
import android.os.Environment
|
||||
import android.provider.MediaStore.MediaColumns
|
||||
import eu.mhutti1.utils.storage.StorageDevice
|
||||
import eu.mhutti1.utils.storage.StorageDeviceUtils
|
||||
import io.mockk.clearMocks
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.mockkStatic
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.jupiter.api.AfterAll
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Nested
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.io.File
|
||||
|
||||
class FileSearchTest {
|
||||
|
||||
private val context: Context = mockk()
|
||||
private lateinit var fileSearch: FileSearch
|
||||
|
||||
private val externalStorageDirectory: File = mockk()
|
||||
private val contentResolver: ContentResolver = mockk()
|
||||
private val storageDevice: StorageDevice = mockk()
|
||||
|
||||
private val unitTestTempDirectoryPath = "unittest${File.separator}"
|
||||
|
||||
@BeforeEach
|
||||
fun init() {
|
||||
clearMocks(context, externalStorageDirectory, contentResolver, storageDevice)
|
||||
deleteTempDirectory()
|
||||
mockkStatic(StorageDeviceUtils::class)
|
||||
mockkStatic(Environment::class)
|
||||
every { Environment.getExternalStorageDirectory() } returns externalStorageDirectory
|
||||
every { externalStorageDirectory.absolutePath } returns "/externalStorageDirectory"
|
||||
every { context.contentResolver } returns contentResolver
|
||||
every { StorageDeviceUtils.getStorageDevices(context, false) } returns arrayListOf(
|
||||
storageDevice
|
||||
)
|
||||
every { storageDevice.name } returns "/deviceDir"
|
||||
fileSearch = FileSearch(context)
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
fun teardown() {
|
||||
deleteTempDirectory()
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class FileSystem {
|
||||
|
||||
@Test
|
||||
fun `scan of directory that doesn't exist returns nothing`() {
|
||||
every { contentResolver.query(any(), any(), any(), any(), any()) } returns null
|
||||
fileSearch.scan("doesNotExist")
|
||||
.test()
|
||||
.assertValue(listOf())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `scan of directory that has files returns files`() {
|
||||
val zimFile = File.createTempFile("${unitTestTempDirectoryPath}fileToFind", ".zim")
|
||||
val zimaaFile = File.createTempFile("${unitTestTempDirectoryPath}fileToFind2", ".zimaa")
|
||||
File.createTempFile("${unitTestTempDirectoryPath}willNotFind", ".txt")
|
||||
every { contentResolver.query(any(), any(), any(), any(), any()) } returns null
|
||||
val fileList = fileSearch.scan(zimFile.parent)
|
||||
.test()
|
||||
.values()[0]
|
||||
assertThat(fileList).containsExactlyInAnyOrder(zimFile, zimaaFile)
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class MediaStore {
|
||||
|
||||
@Test
|
||||
fun `scan media store, if files are readable they are returned`() {
|
||||
val fileToFind = File.createTempFile("${unitTestTempDirectoryPath}fileToFind", ".zim")
|
||||
expectFromMediaStore(fileToFind)
|
||||
fileSearch.scan("")
|
||||
.test()
|
||||
.assertValue(listOf(fileToFind))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `scan media store, if files are not readable they are not returned`() {
|
||||
val unreadableFile = File.createTempFile("${unitTestTempDirectoryPath}fileToFind", ".zim")
|
||||
expectFromMediaStore(unreadableFile)
|
||||
unreadableFile.delete()
|
||||
fileSearch.scan("")
|
||||
.test()
|
||||
.assertValue(listOf())
|
||||
}
|
||||
|
||||
private fun expectFromMediaStore(fileToFind: File) {
|
||||
val cursor = mockk<Cursor>()
|
||||
every {
|
||||
contentResolver.query(
|
||||
null,
|
||||
arrayOf(MediaColumns.DATA),
|
||||
MediaColumns.DATA + " like ? or " + MediaColumns.DATA + " like ? ",
|
||||
arrayOf("%." + "zim", "%." + "zimaa"),
|
||||
null
|
||||
)
|
||||
} returns cursor
|
||||
every { cursor.moveToNext() } returnsMany listOf(true, false)
|
||||
every { cursor.columnNames } returns arrayOf(MediaColumns.DATA)
|
||||
every { cursor.getColumnIndex(MediaColumns.DATA) } returns 0
|
||||
every { cursor.getString(0) } returns fileToFind.absolutePath
|
||||
}
|
||||
}
|
||||
|
||||
private fun deleteTempDirectory() {
|
||||
File.createTempFile("${unitTestTempDirectoryPath}temp", ".txt")
|
||||
.parentFile.deleteRecursively()
|
||||
}
|
||||
}
|
@ -20,16 +20,12 @@ package org.kiwix.kiwixmobile.zim_manager
|
||||
|
||||
import android.app.Application
|
||||
import com.jraska.livedata.test
|
||||
import io.mockk.clearMocks
|
||||
import io.mockk.clearAllMocks
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
import io.reactivex.Scheduler
|
||||
import io.reactivex.Single
|
||||
import io.reactivex.android.plugins.RxAndroidPlugins
|
||||
import io.reactivex.plugins.RxJavaPlugins
|
||||
import io.reactivex.processors.PublishProcessor
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import io.reactivex.schedulers.TestScheduler
|
||||
import org.junit.jupiter.api.AfterAll
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
@ -55,6 +51,8 @@ import org.kiwix.kiwixmobile.downloader.model.DownloadStatus
|
||||
import org.kiwix.kiwixmobile.downloader.model.UriToFileConverter
|
||||
import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity
|
||||
import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity.Book
|
||||
import org.kiwix.kiwixmobile.resetSchedulers
|
||||
import org.kiwix.kiwixmobile.setScheduler
|
||||
import org.kiwix.kiwixmobile.utils.BookUtils
|
||||
import org.kiwix.kiwixmobile.zim_manager.Fat32Checker.FileSystemState
|
||||
import org.kiwix.kiwixmobile.zim_manager.Fat32Checker.FileSystemState.CanWrite4GbFile
|
||||
@ -104,26 +102,14 @@ class ZimManageViewModelTest {
|
||||
setScheduler(testScheduler)
|
||||
}
|
||||
|
||||
private fun setScheduler(replacementScheduler: Scheduler) {
|
||||
RxJavaPlugins.setIoSchedulerHandler { scheduler -> replacementScheduler }
|
||||
RxJavaPlugins.setComputationSchedulerHandler { scheduler -> replacementScheduler }
|
||||
RxJavaPlugins.setNewThreadSchedulerHandler { scheduler -> replacementScheduler }
|
||||
RxAndroidPlugins.setInitMainThreadSchedulerHandler { scheduler -> Schedulers.trampoline() }
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
fun teardown() {
|
||||
RxJavaPlugins.reset()
|
||||
RxAndroidPlugins.reset()
|
||||
resetSchedulers()
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
fun init() {
|
||||
clearMocks(
|
||||
newDownloadDao, newBookDao, newLanguagesDao, downloader,
|
||||
storageObserver, kiwixService, application, connectivityBroadcastReceiver, bookUtils,
|
||||
fat32Checker, uriToFileConverter, defaultLanguageProvider, dataSource
|
||||
)
|
||||
clearAllMocks()
|
||||
every { connectivityBroadcastReceiver.action } returns "test"
|
||||
every { newDownloadDao.downloads() } returns downloads
|
||||
every { newBookDao.books() } returns books
|
||||
|
@ -0,0 +1,138 @@
|
||||
package org.kiwix.kiwixmobile.zim_manager.fileselect_view
|
||||
|
||||
/*
|
||||
* Kiwix Android
|
||||
* Copyright (C) 2018 Kiwix <android.kiwix.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import io.mockk.clearAllMocks
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.mockkStatic
|
||||
import io.reactivex.processors.PublishProcessor
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import org.junit.jupiter.api.AfterAll
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.kiwix.kiwixmobile.book
|
||||
import org.kiwix.kiwixmobile.bookOnDisk
|
||||
import org.kiwix.kiwixmobile.data.ZimContentProvider
|
||||
import org.kiwix.kiwixmobile.database.newdb.dao.NewDownloadDao
|
||||
import org.kiwix.kiwixmobile.downloader.model.DownloadModel
|
||||
import org.kiwix.kiwixmobile.resetSchedulers
|
||||
import org.kiwix.kiwixmobile.setScheduler
|
||||
import org.kiwix.kiwixmobile.utils.SharedPreferenceUtil
|
||||
import org.kiwix.kiwixmobile.utils.files.FileSearch
|
||||
import java.io.File
|
||||
|
||||
class StorageObserverTest {
|
||||
|
||||
private val sharedPreferenceUtil: SharedPreferenceUtil = mockk()
|
||||
private val newDownloadDao: NewDownloadDao = mockk()
|
||||
private val fileSearch: FileSearch = mockk()
|
||||
private val downloadModel = mockk<DownloadModel>()
|
||||
private val file = mockk<File>()
|
||||
|
||||
private val files: PublishProcessor<List<File>> = PublishProcessor.create()
|
||||
private val downloads: PublishProcessor<List<DownloadModel>> = PublishProcessor.create()
|
||||
|
||||
private lateinit var storageObserver: StorageObserver
|
||||
|
||||
init {
|
||||
setScheduler(Schedulers.trampoline())
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
fun teardown() {
|
||||
resetSchedulers()
|
||||
}
|
||||
|
||||
@BeforeEach fun init() {
|
||||
clearAllMocks()
|
||||
every { sharedPreferenceUtil.prefStorage } returns "a"
|
||||
every { fileSearch.scan("a") } returns files
|
||||
every { newDownloadDao.downloads() } returns downloads
|
||||
storageObserver = StorageObserver(sharedPreferenceUtil, newDownloadDao, fileSearch)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `books from disk are filtered by current downloads`() {
|
||||
every { downloadModel.fileNameFromUrl } returns "test"
|
||||
every { file.absolutePath } returns "This is a test"
|
||||
storageObserver.booksOnFileSystem
|
||||
.test()
|
||||
.also {
|
||||
downloads.offer(listOf(downloadModel))
|
||||
files.offer(listOf(file))
|
||||
}
|
||||
.assertValues(listOf())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `null books from ZimContentProvider are filtered out`() {
|
||||
every { downloadModel.fileNameFromUrl } returns "test"
|
||||
every { file.absolutePath } returns "This won't match"
|
||||
|
||||
storageObserver.booksOnFileSystem
|
||||
.test()
|
||||
.also {
|
||||
downloads.offer(listOf(downloadModel))
|
||||
files.offer(listOf(file))
|
||||
}
|
||||
.assertValues(listOf())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `iterable ZimContentProvider with zim file produces a book`() {
|
||||
val expectedBook = book(
|
||||
"id", "title", "1", "favicon", "creator", "publisher", "date",
|
||||
"description", "language"
|
||||
)
|
||||
mockkStatic(ZimContentProvider::class)
|
||||
every { downloadModel.fileNameFromUrl } returns "test"
|
||||
every { file.absolutePath } returns "This won't match"
|
||||
|
||||
ZimContentProvider.canIterate = true
|
||||
every { ZimContentProvider.setZimFile("This won't match") } returns ""
|
||||
|
||||
every { ZimContentProvider.getZimFileTitle() } returns expectedBook.title
|
||||
every { ZimContentProvider.getId() } returns expectedBook.id
|
||||
every { ZimContentProvider.getFileSize() } returns expectedBook.size.toInt()
|
||||
every { ZimContentProvider.getFavicon() } returns expectedBook.favicon
|
||||
every { ZimContentProvider.getCreator() } returns expectedBook.creator
|
||||
every { ZimContentProvider.getPublisher() } returns expectedBook.publisher
|
||||
every { ZimContentProvider.getDate() } returns expectedBook.date
|
||||
every { ZimContentProvider.getDescription() } returns expectedBook.description
|
||||
every { ZimContentProvider.getLanguage() } returns expectedBook.language
|
||||
|
||||
storageObserver.booksOnFileSystem
|
||||
.test()
|
||||
.also {
|
||||
downloads.offer(listOf(downloadModel))
|
||||
files.offer(listOf(file))
|
||||
}
|
||||
.assertValues(
|
||||
listOf(
|
||||
bookOnDisk(
|
||||
book = expectedBook,
|
||||
file = file,
|
||||
databaseId = null
|
||||
)
|
||||
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
@ -35,7 +35,7 @@ ext {
|
||||
set("powerMockVersion", "1.6.6")
|
||||
set("powerMockJUnitVersion", "1.7.4")
|
||||
set("baristaVersion", "2.7.1")
|
||||
set("kotlinVersion", "1.3.31")
|
||||
set("kotlinVersion", "1.3.40")
|
||||
set("objectboxVersion", "2.3.4")
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user