mirror of
https://github.com/kiwix/kiwix-android.git
synced 2025-09-08 14:52:13 -04:00
#1238 Unit test FileSearch
This commit is contained in:
parent
0539d56849
commit
fd9f797105
@ -218,7 +218,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2:6862771806221961183",
|
"id": "2:6862771806221961183",
|
||||||
"name": "zimID"
|
"name": "zimId"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "3:4312769031500860715",
|
"id": "3:4312769031500860715",
|
||||||
|
@ -18,10 +18,13 @@
|
|||||||
package org.kiwix.kiwixmobile.downloader.model
|
package org.kiwix.kiwixmobile.downloader.model
|
||||||
|
|
||||||
import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity.Book
|
import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity.Book
|
||||||
|
import org.kiwix.kiwixmobile.utils.StorageUtils
|
||||||
|
|
||||||
data class DownloadModel(
|
data class DownloadModel(
|
||||||
val databaseId: Long? = null,
|
val databaseId: Long? = null,
|
||||||
val downloadId: Long ,
|
val downloadId: Long,
|
||||||
val book: Book
|
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.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.CountDownTimer;
|
import android.os.CountDownTimer;
|
||||||
import android.os.Environment;
|
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.provider.Settings;
|
import android.provider.Settings;
|
||||||
import android.text.SpannableString;
|
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.floatingactionbutton.FloatingActionButton;
|
||||||
import com.google.android.material.navigation.NavigationView;
|
import com.google.android.material.navigation.NavigationView;
|
||||||
import com.google.android.material.snackbar.Snackbar;
|
import com.google.android.material.snackbar.Snackbar;
|
||||||
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
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.BookmarkItem;
|
||||||
import org.kiwix.kiwixmobile.bookmark.BookmarksActivity;
|
import org.kiwix.kiwixmobile.bookmark.BookmarksActivity;
|
||||||
import org.kiwix.kiwixmobile.data.ZimContentProvider;
|
import org.kiwix.kiwixmobile.data.ZimContentProvider;
|
||||||
import org.kiwix.kiwixmobile.data.local.entity.Bookmark;
|
|
||||||
import org.kiwix.kiwixmobile.help.HelpActivity;
|
import org.kiwix.kiwixmobile.help.HelpActivity;
|
||||||
import org.kiwix.kiwixmobile.history.HistoryActivity;
|
import org.kiwix.kiwixmobile.history.HistoryActivity;
|
||||||
import org.kiwix.kiwixmobile.history.HistoryListItem;
|
import org.kiwix.kiwixmobile.history.HistoryListItem;
|
||||||
import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity;
|
|
||||||
import org.kiwix.kiwixmobile.search.SearchActivity;
|
import org.kiwix.kiwixmobile.search.SearchActivity;
|
||||||
import org.kiwix.kiwixmobile.settings.KiwixSettingsActivity;
|
import org.kiwix.kiwixmobile.settings.KiwixSettingsActivity;
|
||||||
import org.kiwix.kiwixmobile.utils.DimenUtils;
|
import org.kiwix.kiwixmobile.utils.DimenUtils;
|
||||||
import org.kiwix.kiwixmobile.utils.LanguageUtils;
|
import org.kiwix.kiwixmobile.utils.LanguageUtils;
|
||||||
import org.kiwix.kiwixmobile.utils.NetworkUtils;
|
import org.kiwix.kiwixmobile.utils.NetworkUtils;
|
||||||
import org.kiwix.kiwixmobile.utils.StyleUtils;
|
import org.kiwix.kiwixmobile.utils.StyleUtils;
|
||||||
import org.kiwix.kiwixmobile.utils.files.FileSearch;
|
|
||||||
import org.kiwix.kiwixmobile.utils.files.FileUtils;
|
import org.kiwix.kiwixmobile.utils.files.FileUtils;
|
||||||
import org.kiwix.kiwixmobile.zim_manager.ZimManageActivity;
|
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.BookOnDiskDelegate;
|
||||||
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.adapter.BooksOnDiskAdapter;
|
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.adapter.BooksOnDiskAdapter;
|
||||||
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.adapter.BooksOnDiskListItem;
|
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.adapter.BooksOnDiskListItem;
|
||||||
@ -201,14 +198,18 @@ public class MainActivity extends BaseActivity implements WebViewCallback,
|
|||||||
ImageView bottomToolbarArrowBack;
|
ImageView bottomToolbarArrowBack;
|
||||||
@BindView(R.id.bottom_toolbar_arrow_forward)
|
@BindView(R.id.bottom_toolbar_arrow_forward)
|
||||||
ImageView bottomToolbarArrowForward;
|
ImageView bottomToolbarArrowForward;
|
||||||
@Inject
|
|
||||||
MainContract.Presenter presenter;
|
|
||||||
@BindView(R.id.tab_switcher_recycler_view)
|
@BindView(R.id.tab_switcher_recycler_view)
|
||||||
RecyclerView tabRecyclerView;
|
RecyclerView tabRecyclerView;
|
||||||
@BindView(R.id.activity_main_tab_switcher)
|
@BindView(R.id.activity_main_tab_switcher)
|
||||||
View tabSwitcherRoot;
|
View tabSwitcherRoot;
|
||||||
@BindView(R.id.tab_switcher_close_all_tabs)
|
@BindView(R.id.tab_switcher_close_all_tabs)
|
||||||
FloatingActionButton closeAllTabsButton;
|
FloatingActionButton closeAllTabsButton;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
MainContract.Presenter presenter;
|
||||||
|
@Inject
|
||||||
|
StorageObserver storageObserver;
|
||||||
|
|
||||||
private CountDownTimer hideBackToTopTimer = new CountDownTimer(1200, 1200) {
|
private CountDownTimer hideBackToTopTimer = new CountDownTimer(1200, 1200) {
|
||||||
@Override
|
@Override
|
||||||
public void onTick(long millisUntilFinished) {
|
public void onTick(long millisUntilFinished) {
|
||||||
@ -274,21 +275,7 @@ public class MainActivity extends BaseActivity implements WebViewCallback,
|
|||||||
closeTab(viewHolder.getAdapterPosition());
|
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) {
|
private static void updateWidgets(Context context) {
|
||||||
Intent intent = new Intent(context.getApplicationContext(), KiwixSearchWidget.class);
|
Intent intent = new Intent(context.getApplicationContext(), KiwixSearchWidget.class);
|
||||||
@ -739,7 +726,6 @@ public class MainActivity extends BaseActivity implements WebViewCallback,
|
|||||||
downloadBookButton = null;
|
downloadBookButton = null;
|
||||||
hideBackToTopTimer.cancel();
|
hideBackToTopTimer.cancel();
|
||||||
hideBackToTopTimer = null;
|
hideBackToTopTimer = null;
|
||||||
fileSearch = null;
|
|
||||||
// TODO create a base Activity class that class this.
|
// TODO create a base Activity class that class this.
|
||||||
FileUtils.deleteCachedFiles(this);
|
FileUtils.deleteCachedFiles(this);
|
||||||
tts.shutdown();
|
tts.shutdown();
|
||||||
@ -1199,7 +1185,7 @@ public class MainActivity extends BaseActivity implements WebViewCallback,
|
|||||||
case REQUEST_READ_STORAGE_PERMISSION: {
|
case REQUEST_READ_STORAGE_PERMISSION: {
|
||||||
if (grantResults.length > 0
|
if (grantResults.length > 0
|
||||||
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||||
fileSearch.scan(sharedPreferenceUtil.getPrefStorage());
|
scanStorageForZims();
|
||||||
} else {
|
} else {
|
||||||
Snackbar.make(drawerLayout, R.string.request_storage, Snackbar.LENGTH_LONG)
|
Snackbar.make(drawerLayout, R.string.request_storage, Snackbar.LENGTH_LONG)
|
||||||
.setAction(R.string.menu_settings, view -> {
|
.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
|
// Workaround for popup bottom menu on older devices
|
||||||
private void StyleMenuButtons(Menu m) {
|
private void StyleMenuButtons(Menu m) {
|
||||||
// Find each menu item and set its text colour
|
// 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 },
|
new String[] { Manifest.permission.READ_EXTERNAL_STORAGE },
|
||||||
REQUEST_READ_STORAGE_PERMISSION);
|
REQUEST_READ_STORAGE_PERMISSION);
|
||||||
} else {
|
} 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
|
package org.kiwix.kiwixmobile.zim_manager.fileselect_view
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import io.reactivex.processors.PublishProcessor
|
import io.reactivex.functions.BiFunction
|
||||||
import io.reactivex.schedulers.Schedulers
|
import io.reactivex.schedulers.Schedulers
|
||||||
|
import org.kiwix.kiwixmobile.data.ZimContentProvider
|
||||||
import org.kiwix.kiwixmobile.database.newdb.dao.NewDownloadDao
|
import org.kiwix.kiwixmobile.database.newdb.dao.NewDownloadDao
|
||||||
import org.kiwix.kiwixmobile.downloader.model.DownloadModel
|
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.SharedPreferenceUtil
|
||||||
import org.kiwix.kiwixmobile.utils.files.FileSearch
|
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 org.kiwix.kiwixmobile.zim_manager.fileselect_view.adapter.BooksOnDiskListItem.BookOnDisk
|
||||||
|
import java.io.File
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class StorageObserver @Inject constructor(
|
class StorageObserver @Inject constructor(
|
||||||
private val context: Context,
|
|
||||||
private val sharedPreferenceUtil: SharedPreferenceUtil,
|
private val sharedPreferenceUtil: SharedPreferenceUtil,
|
||||||
private val downloadDao: NewDownloadDao
|
downloadDao: NewDownloadDao,
|
||||||
|
private val fileSearch: FileSearch
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val _booksOnFileSystem = PublishProcessor.create<List<BookOnDisk>>()
|
val booksOnFileSystem = scanFiles()
|
||||||
val booksOnFileSystem = _booksOnFileSystem.distinctUntilChanged()
|
.withLatestFrom(
|
||||||
.doOnSubscribe {
|
downloadDao.downloads(),
|
||||||
downloadDao.downloads()
|
BiFunction(this::toFilesThatAreNotDownloading)
|
||||||
.subscribeOn(Schedulers.io())
|
)
|
||||||
.take(1)
|
.map {
|
||||||
.subscribe(this::scanFiles, Throwable::printStackTrace)
|
it.mapNotNull { file -> convertToBookOnDisk(file) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun scanFiles(downloads: List<DownloadModel>) {
|
private fun toFilesThatAreNotDownloading(
|
||||||
FileSearch(context, downloads, object : ResultListener {
|
files: List<File>,
|
||||||
val foundBooks = mutableSetOf<BookOnDisk>()
|
downloads: List<DownloadModel>
|
||||||
|
) = files.filter { fileHasNoMatchingDownload(downloads, it) }
|
||||||
|
|
||||||
override fun onBookFound(book: BookOnDisk) {
|
private fun fileHasNoMatchingDownload(
|
||||||
foundBooks.add(book)
|
downloads: List<DownloadModel>,
|
||||||
Log.i("Scanner", "File Search: Found Book " + book.book.title)
|
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() {
|
private fun bookFromZimContentProvider() = Book().apply {
|
||||||
_booksOnFileSystem.onNext(foundBooks.toList())
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
private fun resetZimContentProvider() {
|
||||||
}).scan(sharedPreferenceUtil.prefStorage)
|
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(
|
fun bookOnDisk(
|
||||||
book: Book = book(),
|
book: Book = book(),
|
||||||
databaseId: Long = 0L,
|
databaseId: Long? = 0L,
|
||||||
file: File = File("")
|
file: File = File("")
|
||||||
) = BookOnDisk(databaseId, book, 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(
|
fun downloadStatus(
|
||||||
downloadId: Long = 0L,
|
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 android.app.Application
|
||||||
import com.jraska.livedata.test
|
import com.jraska.livedata.test
|
||||||
import io.mockk.clearMocks
|
import io.mockk.clearAllMocks
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
import io.mockk.verify
|
import io.mockk.verify
|
||||||
import io.reactivex.Scheduler
|
|
||||||
import io.reactivex.Single
|
import io.reactivex.Single
|
||||||
import io.reactivex.android.plugins.RxAndroidPlugins
|
|
||||||
import io.reactivex.plugins.RxJavaPlugins
|
|
||||||
import io.reactivex.processors.PublishProcessor
|
import io.reactivex.processors.PublishProcessor
|
||||||
import io.reactivex.schedulers.Schedulers
|
|
||||||
import io.reactivex.schedulers.TestScheduler
|
import io.reactivex.schedulers.TestScheduler
|
||||||
import org.junit.jupiter.api.AfterAll
|
import org.junit.jupiter.api.AfterAll
|
||||||
import org.junit.jupiter.api.BeforeEach
|
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.downloader.model.UriToFileConverter
|
||||||
import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity
|
import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity
|
||||||
import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity.Book
|
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.utils.BookUtils
|
||||||
import org.kiwix.kiwixmobile.zim_manager.Fat32Checker.FileSystemState
|
import org.kiwix.kiwixmobile.zim_manager.Fat32Checker.FileSystemState
|
||||||
import org.kiwix.kiwixmobile.zim_manager.Fat32Checker.FileSystemState.CanWrite4GbFile
|
import org.kiwix.kiwixmobile.zim_manager.Fat32Checker.FileSystemState.CanWrite4GbFile
|
||||||
@ -104,26 +102,14 @@ class ZimManageViewModelTest {
|
|||||||
setScheduler(testScheduler)
|
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
|
@AfterAll
|
||||||
fun teardown() {
|
fun teardown() {
|
||||||
RxJavaPlugins.reset()
|
resetSchedulers()
|
||||||
RxAndroidPlugins.reset()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
fun init() {
|
fun init() {
|
||||||
clearMocks(
|
clearAllMocks()
|
||||||
newDownloadDao, newBookDao, newLanguagesDao, downloader,
|
|
||||||
storageObserver, kiwixService, application, connectivityBroadcastReceiver, bookUtils,
|
|
||||||
fat32Checker, uriToFileConverter, defaultLanguageProvider, dataSource
|
|
||||||
)
|
|
||||||
every { connectivityBroadcastReceiver.action } returns "test"
|
every { connectivityBroadcastReceiver.action } returns "test"
|
||||||
every { newDownloadDao.downloads() } returns downloads
|
every { newDownloadDao.downloads() } returns downloads
|
||||||
every { newBookDao.books() } returns books
|
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("powerMockVersion", "1.6.6")
|
||||||
set("powerMockJUnitVersion", "1.7.4")
|
set("powerMockJUnitVersion", "1.7.4")
|
||||||
set("baristaVersion", "2.7.1")
|
set("baristaVersion", "2.7.1")
|
||||||
set("kotlinVersion", "1.3.31")
|
set("kotlinVersion", "1.3.40")
|
||||||
set("objectboxVersion", "2.3.4")
|
set("objectboxVersion", "2.3.4")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user