Move check on adding a download pre db insertion. Handle downloads cancelled outside application. Use transaction to mediate refresh rate of observables

This commit is contained in:
Sean Mac Gillicuddy 2019-05-13 15:01:55 +01:00
parent e86b3ef11e
commit e5773391c9
10 changed files with 114 additions and 66 deletions

View File

@ -39,8 +39,6 @@ abstract class BaseDao {
}
});
updates
.debounce(10, TimeUnit.MILLISECONDS)
.onBackpressureDrop()
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.io())
.subscribe(unit -> {

View File

@ -19,7 +19,6 @@ package org.kiwix.kiwixmobile.database;
import com.yahoo.squidb.data.SquidCursor;
import com.yahoo.squidb.sql.Query;
import com.yahoo.squidb.sql.Table;
import com.yahoo.squidb.sql.TableStatement;
import io.reactivex.Flowable;
import io.reactivex.processors.BehaviorProcessor;
@ -27,7 +26,6 @@ import java.io.File;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import org.jetbrains.annotations.NotNull;
import org.kiwix.kiwixmobile.database.entity.BookDatabaseEntity;
import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity.Book;
@ -91,32 +89,39 @@ public class BookDao extends BaseDao {
}
public List<Book> getBooks() {
SquidCursor<BookDatabaseEntity> bookCursor = kiwixDatabase.query(
BookDatabaseEntity.class,
Query.select());
kiwixDatabase.beginTransaction();
ArrayList<Book> books = new ArrayList<>();
while (bookCursor.moveToNext()) {
Book book = new Book();
setBookDetails(book, bookCursor);
if (!hasParts(book.file)) {
if (book.file.exists()) {
books.add(book);
} else {
kiwixDatabase.deleteWhere(BookDatabaseEntity.class, BookDatabaseEntity.URL.eq(book.file));
try(SquidCursor<BookDatabaseEntity> bookCursor = kiwixDatabase.query(
BookDatabaseEntity.class,
Query.select())) {
while (bookCursor.moveToNext()) {
Book book = new Book();
setBookDetails(book, bookCursor);
if (!hasParts(book.file)) {
if (book.file.exists()) {
books.add(book);
} else {
kiwixDatabase.deleteWhere(BookDatabaseEntity.class,
BookDatabaseEntity.URL.eq(book.file));
}
}
}
}
bookCursor.close();
kiwixDatabase.setTransactionSuccessful();
kiwixDatabase.endTransaction();
return books;
}
public void saveBooks(List<Book> books) {
kiwixDatabase.beginTransaction();
for (Book book : books) {
if (book != null) {
BookDatabaseEntity bookDatabaseEntity = new BookDatabaseEntity();
setBookDatabaseEntity(book, bookDatabaseEntity);
}
}
kiwixDatabase.setTransactionSuccessful();
kiwixDatabase.endTransaction();
}
public void deleteBook(String id) {

View File

@ -49,16 +49,14 @@ public class DownloadDao extends BaseDao{
}
public void insert(final DownloadModel downloadModel) {
if (doesNotAlreadyExist(downloadModel)) {
kiwixDatabase.persistWithOnConflict(databaseEntity(downloadModel),
TableStatement.ConflictAlgorithm.REPLACE);
}
}
private boolean doesNotAlreadyExist(DownloadModel downloadModel) {
public boolean doesNotAlreadyExist(LibraryNetworkEntity.Book book) {
return kiwixDatabase.count(
DownloadDatabaseEntity.class,
DownloadDatabaseEntity.BOOK_ID.eq(downloadModel.getBook().getId())
DownloadDatabaseEntity.BOOK_ID.eq(book.getId())
) == 0;
}

View File

@ -37,12 +37,12 @@ public class NetworkLanguageDao extends BaseDao {
@Inject
public NetworkLanguageDao(KiwixDatabase kiwikDatabase) {
super(kiwikDatabase,NetworkLanguageDatabaseEntity.TABLE);
super(kiwikDatabase, NetworkLanguageDatabaseEntity.TABLE);
}
@Override
protected void onUpdateToTable() {
allLanguageProcessor.onNext(fetchAllLanguages());
allLanguageProcessor.onNext(fetchAllLanguages());
}
public Flowable<List<Language>> allLanguages() {
@ -75,10 +75,13 @@ public class NetworkLanguageDao extends BaseDao {
}
public void saveFilteredLanguages(List<Language> languages) {
kiwixDatabase.beginTransaction();
kiwixDatabase.deleteAll(NetworkLanguageDatabaseEntity.class);
Collections.sort(languages,
(language, t1) -> language.getLanguage().compareTo(t1.getLanguage()));
for (Language language : languages) {
for (int i = 0; i < languages.size(); i++) {
Language language = languages.get(i);
NetworkLanguageDatabaseEntity networkLanguageDatabaseEntity =
new NetworkLanguageDatabaseEntity();
networkLanguageDatabaseEntity.setLanguageISO3(language.getLanguageCode());
@ -86,5 +89,7 @@ public class NetworkLanguageDao extends BaseDao {
networkLanguageDatabaseEntity.setNumberOfOccurences(language.getOccurencesOfLanguage());
kiwixDatabase.persist(networkLanguageDatabaseEntity);
}
kiwixDatabase.setTransactionSuccessful();
kiwixDatabase.endTransaction();
}
}

View File

@ -36,15 +36,18 @@ class DownloaderImpl @Inject constructor(
override fun download(book: LibraryNetworkEntity.Book) {
kiwixService.getMetaLinks(book.url)
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.io())
.take(1)
.subscribe(
{
val downloadId = downloadRequester.enqueue(
DownloadRequest(it, book)
)
downloadDao.insert(
DownloadModel(downloadId, book)
)
if(downloadDao.doesNotAlreadyExist(book)){
val downloadId = downloadRequester.enqueue(
DownloadRequest(it, book)
)
downloadDao.insert(
DownloadModel(downloadId, book)
)
}
},
Throwable::printStackTrace
)

View File

@ -101,6 +101,7 @@ class ZimManageViewModel @Inject constructor(
return arrayOf(
updateDownloadItems(downloadStatuses),
removeCompletedDownloadsFromDb(downloadStatuses),
removeNonExistingDownloadsFromDb(downloadStatuses, downloads),
updateBookItems(booksFromDao),
checkFileSystemForBooksOnRequest(booksFromDao),
updateLibraryItems(booksFromDao, downloads, library),
@ -110,6 +111,46 @@ class ZimManageViewModel @Inject constructor(
)
}
private fun removeNonExistingDownloadsFromDb(
downloadStatuses: Flowable<List<DownloadStatus>>,
downloads: Flowable<MutableList<DownloadModel>>
) = downloadStatuses
.withLatestFrom(
downloads,
BiFunction(this::combineToDownloadsWithoutStatuses)
)
.buffer(3, SECONDS)
.map(this::downloadIdsWithNoStatusesOverBufferPeriod)
.subscribe(
{
downloadDao.delete(*it.toTypedArray())
},
Throwable::printStackTrace
)
private fun downloadIdsWithNoStatusesOverBufferPeriod(it: List<MutableList<Long>>) =
it.flatten()
.fold(mutableMapOf<Long, Int>(),
{ acc, id -> acc.increment(id) })
.filter { (_, value) -> value == it.size }
.map { (key, _) -> key }
private fun combineToDownloadsWithoutStatuses(
statuses: List<DownloadStatus>,
downloads: List<DownloadModel>
): MutableList<Long> {
val downloadIdsWithStatuses = statuses.map { it.downloadId }
return downloads.fold(
mutableListOf(),
{ acc, downloadModel ->
if (!downloadIdsWithStatuses.contains(downloadModel.downloadId)) {
acc.add(downloadModel.downloadId)
}
acc
}
)
}
private fun updateLanguageItemsForDialog() = requestLanguagesDialog
.withLatestFrom(languageDao.allLanguages(),
BiFunction<Unit, List<Language>, List<Language>> { _, languages -> languages })
@ -144,13 +185,11 @@ class ZimManageViewModel @Inject constructor(
) = Flowable.combineLatest(
booksFromDao,
downloads,
languageDao.allLanguages()
.debounce(100, MILLISECONDS)
.filter { it.isNotEmpty() },
languageDao.allLanguages().filter { it.isNotEmpty() },
library,
requestFiltering
.doOnNext { libraryListIsRefreshing.postValue(true) }
.debounce(1, SECONDS)
.debounce(500, MILLISECONDS)
.observeOn(Schedulers.io()),
Function5(this::combineLibrarySources)
)
@ -178,22 +217,26 @@ class ZimManageViewModel @Inject constructor(
private fun combineToLanguageList(
booksFromNetwork: List<Book>,
allLanguages: List<Language>
): List<Language> {
val networkLanguageCounts = booksFromNetwork.mapNotNull { it.language }
) = when {
booksFromNetwork.isEmpty() && allLanguages.isEmpty() -> defaultLanguage()
booksFromNetwork.isEmpty() && allLanguages.isNotEmpty() -> emptyList()
booksFromNetwork.isNotEmpty() && allLanguages.isEmpty() ->
fromLocalesWithNetworkMatchesSetActiveBy(
networkLanguageCounts(booksFromNetwork), defaultLanguage()
)
booksFromNetwork.isNotEmpty() && allLanguages.isNotEmpty() ->
fromLocalesWithNetworkMatchesSetActiveBy(
networkLanguageCounts(booksFromNetwork), allLanguages
)
else -> throw RuntimeException("Impossible state")
}
private fun networkLanguageCounts(booksFromNetwork: List<Book>) =
booksFromNetwork.mapNotNull { it.language }
.fold(
mutableMapOf<String, Int>(),
{ acc, language -> acc.increment(language) }
)
return when {
booksFromNetwork.isEmpty() && allLanguages.isEmpty() -> defaultLanguage()
booksFromNetwork.isEmpty() && allLanguages.isNotEmpty() -> allLanguages
booksFromNetwork.isNotEmpty() && allLanguages.isEmpty() ->
fromLocalesWithNetworkMatchesSetActiveBy(networkLanguageCounts, defaultLanguage())
booksFromNetwork.isNotEmpty() && allLanguages.isNotEmpty() ->
fromLocalesWithNetworkMatchesSetActiveBy(networkLanguageCounts, allLanguages)
else -> throw RuntimeException("Impossible state")
}
}
private fun <K> MutableMap<K, Int>.increment(key: K) =
apply { set(key, getOrElse(key, { 0 }) + 1) }
@ -201,18 +244,16 @@ class ZimManageViewModel @Inject constructor(
private fun fromLocalesWithNetworkMatchesSetActiveBy(
networkLanguageCounts: MutableMap<String, Int>,
listToActivateBy: List<Language>
): List<Language> {
return Locale.getISOLanguages()
.map { Locale(it) }
.filter { networkLanguageCounts.containsKey(it.isO3Language) }
.map { locale ->
Language(
locale.isO3Language,
languageIsActive(listToActivateBy, locale),
networkLanguageCounts.getOrElse(locale.isO3Language, { 0 })
)
}
}
) = Locale.getISOLanguages()
.map { Locale(it) }
.filter { networkLanguageCounts.containsKey(it.isO3Language) }
.map { locale ->
Language(
locale.isO3Language,
languageIsActive(listToActivateBy, locale),
networkLanguageCounts.getOrElse(locale.isO3Language, { 0 })
)
}
private fun defaultLanguage() =
listOf(

View File

@ -2,7 +2,6 @@ package org.kiwix.kiwixmobile.zim_manager.fileselect_view
import android.support.v7.widget.RecyclerView.ViewHolder
import android.view.View
import butterknife.internal.DebouncingOnClickListener
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.library_item.creator
import kotlinx.android.synthetic.main.library_item.date
@ -20,7 +19,7 @@ import org.kiwix.kiwixmobile.extensions.setTextAndVisibility
import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity.Book
import org.kiwix.kiwixmobile.utils.BookUtils
import org.kiwix.kiwixmobile.utils.NetworkUtils
import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.MegaByte
import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.KiloByte
class BooksViewHolder(
override val containerView: View,
@ -37,7 +36,7 @@ class BooksViewHolder(
creator.setTextAndVisibility(book.creator)
publisher.setTextAndVisibility(book.publisher)
date.setTextAndVisibility(book.date)
size.setTextAndVisibility(MegaByte(book.size).humanReadable)
size.setTextAndVisibility(KiloByte(book.size).humanReadable)
language.text = bookUtils.getLanguage(book.getLanguage())
fileName.text = NetworkUtils.parseURL(
KiwixApplication.getInstance(), book.file.path

View File

@ -213,11 +213,11 @@ class LibraryFragment : BaseFragment() {
arguments = Bundle().apply {
putString(
StorageSelectDialog.STORAGE_DIALOG_INTERNAL,
getString(string.internal_storage)
this@LibraryFragment.getString(string.internal_storage)
)
putString(
StorageSelectDialog.STORAGE_DIALOG_EXTERNAL,
getString(string.external_storage)
this@LibraryFragment.getString(string.external_storage)
)
putInt(StorageSelectDialog.STORAGE_DIALOG_THEME, StyleUtils.dialogStyle())
}

View File

@ -2,9 +2,9 @@ package org.kiwix.kiwixmobile.zim_manager.library_view.adapter
import java.text.DecimalFormat
inline class MegaByte(val megabyteString: String?) {
inline class KiloByte(val kilobyteString: String?) {
val humanReadable
get() = megabyteString?.toLongOrNull()?.let {
get() = kilobyteString?.toLongOrNull()?.let {
val units = arrayOf("KB", "MB", "GB", "TB")
val conversion = (Math.log10(it.toDouble()) / Math.log10(1024.0)).toInt()
(DecimalFormat("#,##0.#")

View File

@ -2,7 +2,6 @@ package org.kiwix.kiwixmobile.zim_manager.library_view.adapter
import android.support.v7.widget.RecyclerView
import android.view.View
import butterknife.internal.DebouncingOnClickListener
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.library_divider.divider_text
import kotlinx.android.synthetic.main.library_item.creator
@ -40,7 +39,7 @@ sealed class LibraryViewHolder<T : LibraryListItem>(override val containerView:
creator.setTextAndVisibility(item.book.creator)
publisher.setTextAndVisibility(item.book.publisher)
date.setTextAndVisibility(item.book.date)
size.setTextAndVisibility(MegaByte(item.book.size).humanReadable)
size.setTextAndVisibility(KiloByte(item.book.size).humanReadable)
language.text = bookUtils.getLanguage(item.book.getLanguage())
fileName.text = NetworkUtils.parseURL(
KiwixApplication.getInstance(), item.book.file?.path ?: ""