Fix ConcurrentModificationException on saving a book

This commit is contained in:
Abdul Wadood 2018-07-23 19:04:45 +05:30 committed by Isaac Hutt
parent 7de43bc074
commit 0967e2981a
6 changed files with 302 additions and 165 deletions

View File

@ -17,7 +17,11 @@ import io.reactivex.Single;
public interface DataSource { public interface DataSource {
Single<List<LibraryNetworkEntity.Book>> getLanguageCategorizedBooks(); Single<List<LibraryNetworkEntity.Book>> getLanguageCategorizedBooks();
void saveBooks(List<LibraryNetworkEntity.Book> book); Completable saveBook(LibraryNetworkEntity.Book book);
Completable saveBooks(List<LibraryNetworkEntity.Book> book);
Completable deleteBook(LibraryNetworkEntity.Book book);
Completable saveLanguages(List<Language> languages); Completable saveLanguages(List<Language> languages);

View File

@ -83,8 +83,21 @@ public class Repository implements DataSource {
} }
@Override @Override
public void saveBooks(List<LibraryNetworkEntity.Book> books) { public Completable saveBooks(List<LibraryNetworkEntity.Book> books) {
bookDao.saveBooks((ArrayList<LibraryNetworkEntity.Book>) books); return Completable.fromAction(() -> bookDao.saveBooks((ArrayList<LibraryNetworkEntity.Book>) books))
.subscribeOn(io);
}
@Override
public Completable saveBook(LibraryNetworkEntity.Book book) {
return Completable.fromAction(() -> bookDao.saveBook(book))
.subscribeOn(io);
}
@Override
public Completable deleteBook(LibraryNetworkEntity.Book book) {
return Completable.fromAction(() -> bookDao.deleteBook(book.getId()))
.subscribeOn(io);
} }
@Override @Override

View File

@ -38,7 +38,7 @@ import android.widget.Toast;
import org.kiwix.kiwixmobile.KiwixApplication; import org.kiwix.kiwixmobile.KiwixApplication;
import org.kiwix.kiwixmobile.R; import org.kiwix.kiwixmobile.R;
import org.kiwix.kiwixmobile.data.local.dao.BookDao; import org.kiwix.kiwixmobile.data.DataSource;
import org.kiwix.kiwixmobile.data.remote.KiwixService; import org.kiwix.kiwixmobile.data.remote.KiwixService;
import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity; import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity;
import org.kiwix.kiwixmobile.main.MainActivity; import org.kiwix.kiwixmobile.main.MainActivity;
@ -61,8 +61,11 @@ import java.util.concurrent.TimeUnit;
import javax.inject.Inject; import javax.inject.Inject;
import io.reactivex.CompletableObserver;
import io.reactivex.Observable; import io.reactivex.Observable;
import io.reactivex.Observer;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
import okhttp3.Request; import okhttp3.Request;
import okhttp3.Response; import okhttp3.Response;
@ -77,15 +80,6 @@ import static org.kiwix.kiwixmobile.utils.files.FileUtils.getCurrentSize;
public class DownloadService extends Service { public class DownloadService extends Service {
@Inject KiwixService kiwixService;
@Inject OkHttpClient httpClient;
@Inject NotificationManager notificationManager;
private static String SD_CARD;
// 1024 / 100
private static final double BOOK_SIZE_OFFSET = 10.24;
private static final String KIWIX_TAG = "kiwixdownloadservice";
public static String KIWIX_ROOT;
public static final int PLAY = 1; public static final int PLAY = 1;
public static final int PAUSE = 2; public static final int PAUSE = 2;
public static final int FINISH = 3; public static final int FINISH = 3;
@ -94,22 +88,33 @@ public class DownloadService extends Service {
public static final String ACTION_STOP = "STOP"; public static final String ACTION_STOP = "STOP";
public static final String ACTION_NO_WIFI = "NO_WIFI"; public static final String ACTION_NO_WIFI = "NO_WIFI";
public static final String NOTIFICATION_ID = "NOTIFICATION_ID"; public static final String NOTIFICATION_ID = "NOTIFICATION_ID";
public static final Object pauseLock = new Object();
// 1024 / 100
private static final double BOOK_SIZE_OFFSET = 10.24;
private static final String KIWIX_TAG = "kiwixdownloadservice";
public static String KIWIX_ROOT;
public static ArrayList<String> notifications = new ArrayList<>(); public static ArrayList<String> notifications = new ArrayList<>();
private static String SD_CARD;
private static DownloadFragment downloadFragment;
private final IBinder mBinder = new LocalBinder();
public String notificationTitle; public String notificationTitle;
private SparseArray<NotificationCompat.Builder> notification = new SparseArray<>();
public SparseIntArray downloadStatus = new SparseIntArray(); public SparseIntArray downloadStatus = new SparseIntArray();
public SparseIntArray downloadProgress = new SparseIntArray(); public SparseIntArray downloadProgress = new SparseIntArray();
public SparseIntArray timeRemaining = new SparseIntArray(); public SparseIntArray timeRemaining = new SparseIntArray();
public static final Object pauseLock = new Object(); @Inject
private static DownloadFragment downloadFragment; KiwixService kiwixService;
@Inject
OkHttpClient httpClient;
@Inject
NotificationManager notificationManager;
Handler handler = new Handler(Looper.getMainLooper()); Handler handler = new Handler(Looper.getMainLooper());
@Inject @Inject
SharedPreferenceUtil sharedPreferenceUtil; SharedPreferenceUtil sharedPreferenceUtil;
@Inject @Inject
BookDao bookDao; DataSource dataSource;
private SparseArray<NotificationCompat.Builder> notification = new SparseArray<>();
public static void setDownloadFragment(DownloadFragment dFragment) { public static void setDownloadFragment(DownloadFragment dFragment) {
downloadFragment = dFragment; downloadFragment = dFragment;
@ -167,7 +172,7 @@ public class DownloadService extends Service {
LibraryNetworkEntity.Book book = (LibraryNetworkEntity.Book) intent.getSerializableExtra(EXTRA_BOOK); LibraryNetworkEntity.Book book = (LibraryNetworkEntity.Book) intent.getSerializableExtra(EXTRA_BOOK);
int notificationID = book.getId().hashCode(); int notificationID = book.getId().hashCode();
if ( downloadStatus.get(notificationID, -1) == PAUSE || downloadStatus.get(notificationID, -1) == PLAY ) { if (downloadStatus.get(notificationID, -1) == PAUSE || downloadStatus.get(notificationID, -1) == PLAY) {
return START_NOT_STICKY; return START_NOT_STICKY;
} }
@ -177,7 +182,7 @@ public class DownloadService extends Service {
PendingIntent pendingIntent = PendingIntent.getActivity PendingIntent pendingIntent = PendingIntent.getActivity
(getBaseContext(), notificationID, (getBaseContext(), notificationID,
target, PendingIntent.FLAG_CANCEL_CURRENT); target, PendingIntent.FLAG_CANCEL_CURRENT);
Intent pauseIntent = new Intent(this, this.getClass()).setAction(ACTION_PAUSE).putExtra(NOTIFICATION_ID, notificationID); Intent pauseIntent = new Intent(this, this.getClass()).setAction(ACTION_PAUSE).putExtra(NOTIFICATION_ID, notificationID);
Intent stopIntent = new Intent(this, this.getClass()).setAction(ACTION_STOP).putExtra(NOTIFICATION_ID, notificationID); Intent stopIntent = new Intent(this, this.getClass()).setAction(ACTION_STOP).putExtra(NOTIFICATION_ID, notificationID);
@ -187,10 +192,10 @@ public class DownloadService extends Service {
NotificationCompat.Action pause = new NotificationCompat.Action(R.drawable.ic_pause_black_24dp, getString(R.string.download_pause), pausePending); NotificationCompat.Action pause = new NotificationCompat.Action(R.drawable.ic_pause_black_24dp, getString(R.string.download_pause), pausePending);
NotificationCompat.Action stop = new NotificationCompat.Action(R.drawable.ic_stop_black_24dp, getString(R.string.download_stop), stopPending); NotificationCompat.Action stop = new NotificationCompat.Action(R.drawable.ic_stop_black_24dp, getString(R.string.download_stop), stopPending);
if(flags == START_FLAG_REDELIVERY && book.file == null) { if (flags == START_FLAG_REDELIVERY && book.file == null) {
return START_NOT_STICKY; return START_NOT_STICKY;
} else { } else {
notification.put(notificationID , new NotificationCompat.Builder(this, ONGOING_DOWNLOAD_CHANNEL_ID) notification.put(notificationID, new NotificationCompat.Builder(this, ONGOING_DOWNLOAD_CHANNEL_ID)
.setContentTitle(getResources().getString(R.string.zim_file_downloading) + " " + notificationTitle) .setContentTitle(getResources().getString(R.string.zim_file_downloading) + " " + notificationTitle)
.setProgress(100, 0, false) .setProgress(100, 0, false)
.setSmallIcon(R.drawable.kiwix_notification) .setSmallIcon(R.drawable.kiwix_notification)
@ -199,7 +204,7 @@ public class DownloadService extends Service {
.addAction(pause) .addAction(pause)
.addAction(stop) .addAction(stop)
.setOngoing(true)); .setOngoing(true));
notificationManager.notify(notificationID, notification.get(notificationID).build()); notificationManager.notify(notificationID, notification.get(notificationID).build());
downloadStatus.put(notificationID, PLAY); downloadStatus.put(notificationID, PLAY);
LibraryFragment.downloadingBooks.remove(book); LibraryFragment.downloadingBooks.remove(book);
@ -229,7 +234,7 @@ public class DownloadService extends Service {
notificationManager.cancel(notificationID); notificationManager.cancel(notificationID);
} }
public String checkWritable(String path){ public String checkWritable(String path) {
try { try {
File f = new File(path); File f = new File(path);
f.mkdir(); f.mkdir();
@ -238,13 +243,13 @@ public class DownloadService extends Service {
} }
Toast.makeText(this, getResources().getString(R.string.path_not_writable), Toast.LENGTH_LONG).show(); Toast.makeText(this, getResources().getString(R.string.path_not_writable), Toast.LENGTH_LONG).show();
return Environment.getExternalStorageDirectory().getPath(); return Environment.getExternalStorageDirectory().getPath();
} catch (Exception e){ } catch (Exception e) {
Toast.makeText(this, getResources().getString(R.string.path_not_writable), Toast.LENGTH_LONG).show(); Toast.makeText(this, getResources().getString(R.string.path_not_writable), Toast.LENGTH_LONG).show();
return Environment.getExternalStorageDirectory().getPath(); return Environment.getExternalStorageDirectory().getPath();
} }
} }
public void toggleDownload (int notificationID) { public void toggleDownload(int notificationID) {
if (downloadStatus.get(notificationID) == PAUSE) { if (downloadStatus.get(notificationID) == PAUSE) {
playDownload(notificationID); playDownload(notificationID);
} else { } else {
@ -255,7 +260,7 @@ public class DownloadService extends Service {
public void pauseDownload(int notificationID) { public void pauseDownload(int notificationID) {
Log.i(KIWIX_TAG, "Pausing ZIM Download for notificationID: " + notificationID); Log.i(KIWIX_TAG, "Pausing ZIM Download for notificationID: " + notificationID);
downloadStatus.put(notificationID, PAUSE); downloadStatus.put(notificationID, PAUSE);
notification.get(notificationID).mActions.get(0).title = getString(R.string.download_play); notification.get(notificationID).mActions.get(0).title = getString(R.string.download_play);
notification.get(notificationID).mActions.get(0).icon = R.drawable.ic_play_arrow_black_24dp; notification.get(notificationID).mActions.get(0).icon = R.drawable.ic_play_arrow_black_24dp;
notification.get(notificationID).setContentText(getString(R.string.download_paused)); notification.get(notificationID).setContentText(getString(R.string.download_paused));
notificationManager.notify(notificationID, notification.get(notificationID).build()); notificationManager.notify(notificationID, notification.get(notificationID).build());
@ -303,36 +308,70 @@ public class DownloadService extends Service {
.flatMap(pair -> Observable.fromIterable(ChunkUtils.getChunks(pair.first, pair.second, notificationID))) .flatMap(pair -> Observable.fromIterable(ChunkUtils.getChunks(pair.first, pair.second, notificationID)))
.concatMap(this::downloadChunk) .concatMap(this::downloadChunk)
.distinctUntilChanged().doOnComplete(() -> updateDownloadFragmentComplete(notificationID)) .distinctUntilChanged().doOnComplete(() -> updateDownloadFragmentComplete(notificationID))
.subscribe(progress -> { .subscribe(new Observer<Integer>() {
if (progress == 100) { @Override
notification.get(notificationID).setOngoing(false); public void onSubscribe(Disposable d) {
notification.get(notificationID).setContentTitle(notificationTitle + " " + getResources().getString(R.string.zim_file_downloaded));
notification.get(notificationID).setContentText(getString(R.string.zim_file_downloaded));
final Intent target = new Intent(this, MainActivity.class);
target.putExtra(EXTRA_ZIM_FILE, KIWIX_ROOT + StorageUtils.getFileNameFromUrl(book.getUrl()));
target.putExtra(EXTRA_NOTIFICATION_ID, notificationID);
PendingIntent pendingIntent = PendingIntent.getActivity
(getBaseContext(), 0,
target, PendingIntent.FLAG_CANCEL_CURRENT);
book.downloaded = true;
bookDao.deleteBook(book.id);
notification.get(notificationID).setContentIntent(pendingIntent);
notification.get(notificationID).mActions.clear();
TestingUtils.unbindResource(DownloadService.class);
} }
notification.get(notificationID).setProgress(100, progress, false);
if (progress != 100 && timeRemaining.get(notificationID) != -1) @Override
notification.get(notificationID).setContentText(DownloadFragment.toHumanReadableTime(timeRemaining.get(notificationID))); public void onNext(Integer progress) {
notificationManager.notify(notificationID, notification.get(notificationID).build()); if (progress == 100) {
if (progress == 0 || progress == 100) { notification.get(notificationID).setOngoing(false);
// Tells android to not kill the service notification.get(notificationID).setContentTitle(notificationTitle + " " + getResources().getString(R.string.zim_file_downloaded));
updateForeground(); notification.get(notificationID).setContentText(getString(R.string.zim_file_downloaded));
final Intent target = new Intent(DownloadService.this, MainActivity.class);
target.putExtra(EXTRA_ZIM_FILE, KIWIX_ROOT + StorageUtils.getFileNameFromUrl(book.getUrl()));
target.putExtra(EXTRA_NOTIFICATION_ID, notificationID);
PendingIntent pendingIntent = PendingIntent.getActivity
(getBaseContext(), 0,
target, PendingIntent.FLAG_CANCEL_CURRENT);
book.downloaded = true;
dataSource.deleteBook(book)
.subscribe(new CompletableObserver() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onComplete() {
}
@Override
public void onError(Throwable e) {
Log.e("DownloadService", "Unable to delete book", e);
}
});
notification.get(notificationID).setContentIntent(pendingIntent);
notification.get(notificationID).mActions.clear();
TestingUtils.unbindResource(DownloadService.class);
}
notification.get(notificationID).setProgress(100, progress, false);
if (progress != 100 && timeRemaining.get(notificationID) != -1)
notification.get(notificationID).setContentText(DownloadFragment.toHumanReadableTime(timeRemaining.get(notificationID)));
notificationManager.notify(notificationID, notification.get(notificationID).build());
if (progress == 0 || progress == 100) {
// Tells android to not kill the service
updateForeground();
}
updateDownloadFragmentProgress(progress, notificationID);
if (progress == 100) {
stopSelf();
}
} }
updateDownloadFragmentProgress(progress, notificationID);
if (progress == 100) { @Override
stopSelf(); public void onError(Throwable e) {
} }
}, Throwable::printStackTrace);
@Override
public void onComplete() {
}
});
} }
private void updateDownloadFragmentProgress(int progress, int notificationID) { private void updateDownloadFragmentProgress(int progress, int notificationID) {
@ -358,15 +397,15 @@ public class DownloadService extends Service {
private void updateForeground() { private void updateForeground() {
// Allow notification to be dismissible while ensuring integrity of service if active downloads // Allow notification to be dismissible while ensuring integrity of service if active downloads
stopForeground(true); stopForeground(true);
for(int i = 0; i < downloadStatus.size(); i++) { for (int i = 0; i < downloadStatus.size(); i++) {
if (downloadStatus.get(i) == PLAY && downloadStatus.get(i) == PAUSE ){ if (downloadStatus.get(i) == PLAY && downloadStatus.get(i) == PAUSE) {
startForeground( downloadStatus.keyAt(i), notification.get(downloadStatus.keyAt(i)).build()); startForeground(downloadStatus.keyAt(i), notification.get(downloadStatus.keyAt(i)).build());
} }
} }
} }
private Observable<Pair<String, Long>> getMetaLinkContentLength(String url) { private Observable<Pair<String, Long>> getMetaLinkContentLength(String url) {
Log.d("KiwixDownloadSSL","url=" + url); Log.d("KiwixDownloadSSL", "url=" + url);
final String urlToUse = UseHttpOnAndroidVersion4(url); final String urlToUse = UseHttpOnAndroidVersion4(url);
return Observable.create(subscriber -> { return Observable.create(subscriber -> {
try { try {
@ -436,7 +475,23 @@ public class DownloadService extends Service {
.get(chunk.getNotificationID()); .get(chunk.getNotificationID());
book.remoteUrl = book.getUrl(); book.remoteUrl = book.getUrl();
book.file = fullFile; book.file = fullFile;
bookDao.saveBook(book); dataSource.saveBook(book)
.subscribe(new CompletableObserver() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onComplete() {
}
@Override
public void onError(Throwable e) {
Log.e("DownloadService", "Unable to save book", e);
}
});
} }
downloadStatus.put(chunk.getNotificationID(), PLAY); downloadStatus.put(chunk.getNotificationID(), PLAY);
downloadProgress.put(chunk.getNotificationID(), 0); downloadProgress.put(chunk.getNotificationID(), 0);
@ -515,7 +570,7 @@ public class DownloadService extends Service {
output.write(buffer, 0, read); output.write(buffer, 0, read);
int progress = (int) ((100 * downloaded) / chunk.getContentLength()); int progress = (int) ((100 * downloaded) / chunk.getContentLength());
downloadProgress.put(chunk.getNotificationID(), progress); downloadProgress.put(chunk.getNotificationID(), progress);
if (progress == 100){ if (progress == 100) {
downloadStatus.put(chunk.getNotificationID(), FINISH); downloadStatus.put(chunk.getNotificationID(), FINISH);
} }
subscriber.onNext(progress); subscriber.onNext(progress);
@ -564,7 +619,7 @@ public class DownloadService extends Service {
* Creates and registers notification channel with system for notifications of * Creates and registers notification channel with system for notifications of
* type: download in progress. * type: download in progress.
*/ */
private void createOngoingDownloadChannel () { private void createOngoingDownloadChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
CharSequence name = getString(R.string.ongoing_download_channel_name); CharSequence name = getString(R.string.ongoing_download_channel_name);
String description = getString(R.string.ongoing_download_channel_desc); String description = getString(R.string.ongoing_download_channel_desc);
@ -578,7 +633,10 @@ public class DownloadService extends Service {
} }
} }
private final IBinder mBinder = new LocalBinder(); @Override
public IBinder onBind(Intent intent) {
return mBinder;
}
/** /**
* Class used for the client Binder. Because we know this service always * Class used for the client Binder. Because we know this service always
@ -590,10 +648,4 @@ public class DownloadService extends Service {
return DownloadService.this; return DownloadService.this;
} }
} }
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
} }

View File

@ -24,6 +24,7 @@ import io.reactivex.disposables.Disposable;
@PerActivity @PerActivity
class MainPresenter extends BasePresenter<MainContract.View> implements MainContract.Presenter { class MainPresenter extends BasePresenter<MainContract.View> implements MainContract.Presenter {
private static final String TAG = "MainPresenter";
private final DataSource dataSource; private final DataSource dataSource;
@Inject @Inject
@ -47,15 +48,30 @@ class MainPresenter extends BasePresenter<MainContract.View> implements MainCont
@Override @Override
public void onError(Throwable e) { public void onError(Throwable e) {
Log.d("MainPresenter", e.toString()); Log.e(TAG, "Unable to load books", e);
} }
}); });
} }
@Override @Override
public void saveBooks(List<LibraryNetworkEntity.Book> book) { public void saveBooks(List<LibraryNetworkEntity.Book> book) {
dataSource.saveBooks(book); dataSource.saveBooks(book)
showHome(); .subscribe(new CompletableObserver() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onComplete() {
showHome();
}
@Override
public void onError(Throwable e) {
Log.e(TAG, "Unable to save books", e);
}
});
} }
@Override @Override
@ -74,7 +90,7 @@ class MainPresenter extends BasePresenter<MainContract.View> implements MainCont
@Override @Override
public void onError(Throwable e) { public void onError(Throwable e) {
Log.e("MainPresenter", e.toString()); Log.e(TAG, "Unable to save history", e);
} }
}); });
} }
@ -82,7 +98,7 @@ class MainPresenter extends BasePresenter<MainContract.View> implements MainCont
@Override @Override
public void loadCurrentZimBookmarksUrl() { public void loadCurrentZimBookmarksUrl() {
compositeDisposable.add(dataSource.getCurrentZimBookmarksUrl() compositeDisposable.add(dataSource.getCurrentZimBookmarksUrl()
.subscribe(view::refreshBookmarksUrl, e -> Log.e("MainPresenter", e.toString()))); .subscribe(view::refreshBookmarksUrl, e -> Log.e(TAG, "Unable to load current ZIM urls", e)));
} }
@Override @Override
@ -101,7 +117,7 @@ class MainPresenter extends BasePresenter<MainContract.View> implements MainCont
@Override @Override
public void onError(Throwable e) { public void onError(Throwable e) {
Log.e("MainPresenter", e.toString()); Log.e(TAG, "Unable to save bookmark", e);
} }
}); });
} }
@ -122,7 +138,7 @@ class MainPresenter extends BasePresenter<MainContract.View> implements MainCont
@Override @Override
public void onError(Throwable e) { public void onError(Throwable e) {
Log.e("MainPresenter", e.toString()); Log.e(TAG, "Unable to delete bookmark", e);
} }
}); });
} }

View File

@ -24,6 +24,7 @@ import android.content.Context;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.content.ContextCompat; import android.support.v4.content.ContextCompat;
import android.support.v4.widget.SwipeRefreshLayout; import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.app.AlertDialog; import android.support.v7.app.AlertDialog;
@ -68,11 +69,18 @@ import static org.kiwix.kiwixmobile.utils.NetworkUtils.parseURL;
import static org.kiwix.kiwixmobile.utils.StyleUtils.dialogStyle; import static org.kiwix.kiwixmobile.utils.StyleUtils.dialogStyle;
public class ZimFileSelectFragment extends BaseFragment public class ZimFileSelectFragment extends BaseFragment
implements OnItemClickListener, AdapterView.OnItemLongClickListener, ZimFileSelectViewCallback{ implements OnItemClickListener, AdapterView.OnItemLongClickListener, ZimFileSelectViewCallback {
public RelativeLayout llLayout; public RelativeLayout llLayout;
public SwipeRefreshLayout swipeRefreshLayout; public SwipeRefreshLayout swipeRefreshLayout;
@Inject
ZimFileSelectPresenter presenter;
@Inject
BookUtils bookUtils;
@Inject
SharedPreferenceUtil sharedPreferenceUtil;
@Inject
BookDao bookDao;
private ZimManageActivity zimManageActivity; private ZimManageActivity zimManageActivity;
private RescanDataAdapter mRescanAdapter; private RescanDataAdapter mRescanAdapter;
private ArrayList<LibraryNetworkEntity.Book> mFiles; private ArrayList<LibraryNetworkEntity.Book> mFiles;
@ -80,20 +88,14 @@ public class ZimFileSelectFragment extends BaseFragment
private TextView mFileMessage; private TextView mFileMessage;
private boolean mHasRefresh; private boolean mHasRefresh;
@Inject ZimFileSelectPresenter presenter;
@Inject BookUtils bookUtils;
@Inject SharedPreferenceUtil sharedPreferenceUtil;
@Inject
BookDao bookDao;
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
KiwixApplication.getApplicationComponent().inject(this); KiwixApplication.getApplicationComponent().inject(this);
zimManageActivity = (ZimManageActivity) super.getActivity(); zimManageActivity = (ZimManageActivity) super.getActivity();
presenter.attachView(this); presenter.attachView(this);
// Replace LinearLayout by the type of the root element of the layout you're trying to load // Replace LinearLayout by the type of the root element of the layout you're trying to load
llLayout = (RelativeLayout) inflater.inflate(R.layout.zim_list, container, false); llLayout = (RelativeLayout) inflater.inflate(R.layout.zim_list, container, false);
new LanguageUtils(super.getActivity()).changeFont(super.getActivity().getLayoutInflater(), sharedPreferenceUtil); new LanguageUtils(zimManageActivity).changeFont(zimManageActivity.getLayoutInflater(), sharedPreferenceUtil);
mFileMessage = llLayout.findViewById(R.id.file_management_no_files); mFileMessage = llLayout.findViewById(R.id.file_management_no_files);
mZimFileList = llLayout.findViewById(R.id.zimfilelist); mZimFileList = llLayout.findViewById(R.id.zimfilelist);
@ -154,26 +156,19 @@ public class ZimFileSelectFragment extends BaseFragment
if (book != null) { if (book != null) {
mFiles.add(book); mFiles.add(book);
mRescanAdapter.notifyDataSetChanged(); mRescanAdapter.notifyDataSetChanged();
bookDao.saveBooks(mFiles); presenter.saveBooks(mFiles);
checkEmpty(); checkEmpty();
} }
} }
private class FileComparator implements Comparator<LibraryNetworkEntity.Book> { public void checkPermissions() {
@Override if (ContextCompat.checkSelfPermission(zimManageActivity,
public int compare(LibraryNetworkEntity.Book b1, LibraryNetworkEntity.Book b2) {
return b1.getTitle().compareTo(b2.getTitle());
}
}
public void checkPermissions(){
if (ContextCompat.checkSelfPermission(super.getActivity(),
Manifest.permission.READ_EXTERNAL_STORAGE) Manifest.permission.READ_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED && Build.VERSION.SDK_INT > 18) { != PackageManager.PERMISSION_GRANTED && Build.VERSION.SDK_INT > 18) {
Toast.makeText(super.getActivity(), getResources().getString(R.string.request_storage), Toast.LENGTH_LONG) Toast.makeText(super.getActivity(), getResources().getString(R.string.request_storage), Toast.LENGTH_LONG)
.show(); .show();
requestPermissions( new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
REQUEST_STORAGE_PERMISSION); REQUEST_STORAGE_PERMISSION);
} else { } else {
getFiles(); getFiles();
} }
@ -225,7 +220,7 @@ public class ZimFileSelectFragment extends BaseFragment
// Save the current list of books // Save the current list of books
zimManageActivity.runOnUiThread(() -> { zimManageActivity.runOnUiThread(() -> {
mRescanAdapter.notifyDataSetChanged(); mRescanAdapter.notifyDataSetChanged();
bookDao.saveBooks(mFiles); presenter.saveBooks(mFiles);
checkEmpty(); checkEmpty();
TestingUtils.unbindResource(ZimFileSelectFragment.class); TestingUtils.unbindResource(ZimFileSelectFragment.class);
@ -238,12 +233,12 @@ public class ZimFileSelectFragment extends BaseFragment
@Override @Override
public void onRequestPermissionsResult(int requestCode, public void onRequestPermissionsResult(int requestCode,
String permissions[], int[] grantResults) { @NonNull String permissions[], @NonNull int[] grantResults) {
switch (requestCode) { switch (requestCode) {
case REQUEST_STORAGE_PERMISSION: { case REQUEST_STORAGE_PERMISSION: {
if (grantResults.length > 0 if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) { && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
getFiles(); getFiles();
} else if (grantResults.length != 0) { } else if (grantResults.length != 0) {
zimManageActivity.finish(); zimManageActivity.finish();
} }
@ -298,7 +293,7 @@ public class ZimFileSelectFragment extends BaseFragment
if (file.exists()) { if (file.exists()) {
return false; return false;
} }
bookDao.deleteBook(mFiles.get(position).getId()); presenter.deleteBook(mFiles.get(position));
mFiles.remove(position); mFiles.remove(position);
mRescanAdapter.notifyDataSetChanged(); mRescanAdapter.notifyDataSetChanged();
checkEmpty(); checkEmpty();
@ -308,93 +303,101 @@ public class ZimFileSelectFragment extends BaseFragment
return true; return true;
} }
public void checkEmpty(){ public void checkEmpty() {
if (mZimFileList.getCount() == 0){ if (mZimFileList.getCount() == 0) {
mFileMessage.setVisibility(View.VISIBLE); mFileMessage.setVisibility(View.VISIBLE);
} else } else
mFileMessage.setVisibility(View.GONE); mFileMessage.setVisibility(View.GONE);
} }
private class FileComparator implements Comparator<LibraryNetworkEntity.Book> {
@Override
public int compare(LibraryNetworkEntity.Book b1, LibraryNetworkEntity.Book b2) {
return b1.getTitle().compareTo(b2.getTitle());
}
}
// The Adapter for the ListView for when the ListView is populated with the rescanned files // The Adapter for the ListView for when the ListView is populated with the rescanned files
private class RescanDataAdapter extends ArrayAdapter<LibraryNetworkEntity.Book> { private class RescanDataAdapter extends ArrayAdapter<LibraryNetworkEntity.Book> {
public RescanDataAdapter(Context context, int textViewResourceId, List<LibraryNetworkEntity.Book> objects) { RescanDataAdapter(Context context, int textViewResourceId, List<LibraryNetworkEntity.Book> objects) {
super(context, textViewResourceId, objects); super(context, textViewResourceId, objects);
} }
@NonNull
@Override @Override
public View getView(int position, View convertView, ViewGroup parent) { public View getView(int position, View convertView, @NonNull ViewGroup parent) {
ViewHolder holder; ViewHolder holder;
LibraryNetworkEntity.Book book = getItem(position); LibraryNetworkEntity.Book book = getItem(position);
if (convertView == null) { if (convertView == null) {
convertView = View.inflate(zimManageActivity, R.layout.library_item, null); convertView = View.inflate(zimManageActivity, R.layout.library_item, null);
holder = new ViewHolder(); holder = new ViewHolder();
holder.title = convertView.findViewById(R.id.title); holder.title = convertView.findViewById(R.id.title);
holder.description = convertView.findViewById(R.id.description); holder.description = convertView.findViewById(R.id.description);
holder.language = convertView.findViewById(R.id.language); holder.language = convertView.findViewById(R.id.language);
holder.creator = convertView.findViewById(R.id.creator); holder.creator = convertView.findViewById(R.id.creator);
holder.publisher = convertView.findViewById(R.id.publisher); holder.publisher = convertView.findViewById(R.id.publisher);
holder.date = convertView.findViewById(R.id.date); holder.date = convertView.findViewById(R.id.date);
holder.size = convertView.findViewById(R.id.size); holder.size = convertView.findViewById(R.id.size);
holder.fileName = convertView.findViewById(R.id.fileName); holder.fileName = convertView.findViewById(R.id.fileName);
holder.favicon = convertView.findViewById(R.id.favicon); holder.favicon = convertView.findViewById(R.id.favicon);
convertView.setTag(holder); convertView.setTag(holder);
} else { } else {
holder = (ViewHolder) convertView.getTag(); holder = (ViewHolder) convertView.getTag();
} }
if (book == null) { if (book == null) {
return convertView; return convertView;
} }
holder.title.setText(book.getTitle()); holder.title.setText(book.getTitle());
holder.description.setText(book.getDescription()); holder.description.setText(book.getDescription());
holder.language.setText(bookUtils.getLanguage(book.getLanguage())); holder.language.setText(bookUtils.getLanguage(book.getLanguage()));
holder.creator.setText(book.getCreator()); holder.creator.setText(book.getCreator());
holder.publisher.setText(book.getPublisher()); holder.publisher.setText(book.getPublisher());
holder.date.setText(book.getDate()); holder.date.setText(book.getDate());
holder.size.setText(LibraryAdapter.createGbString(book.getSize())); holder.size.setText(LibraryAdapter.createGbString(book.getSize()));
holder.fileName.setText(parseURL(getActivity(), book.file.getPath())); holder.fileName.setText(parseURL(getActivity(), book.file.getPath()));
holder.favicon.setImageBitmap(LibraryAdapter.createBitmapFromEncodedString(book.getFavicon(), zimManageActivity)); holder.favicon.setImageBitmap(LibraryAdapter.createBitmapFromEncodedString(book.getFavicon(), zimManageActivity));
//// Check if no value is empty. Set the view to View.GONE, if it is. To View.VISIBLE, if not. //// Check if no value is empty. Set the view to View.GONE, if it is. To View.VISIBLE, if not.
if (book.getTitle() == null || book.getTitle().isEmpty()) { if (book.getTitle() == null || book.getTitle().isEmpty()) {
holder.title.setVisibility(View.GONE); holder.title.setVisibility(View.GONE);
} else { } else {
holder.title.setVisibility(View.VISIBLE); holder.title.setVisibility(View.VISIBLE);
} }
if (book.getDescription() == null || book.getDescription().isEmpty()) { if (book.getDescription() == null || book.getDescription().isEmpty()) {
holder.description.setVisibility(View.GONE); holder.description.setVisibility(View.GONE);
} else { } else {
holder.description.setVisibility(View.VISIBLE); holder.description.setVisibility(View.VISIBLE);
} }
if (book.getCreator() == null || book.getCreator().isEmpty()) { if (book.getCreator() == null || book.getCreator().isEmpty()) {
holder.creator.setVisibility(View.GONE); holder.creator.setVisibility(View.GONE);
} else { } else {
holder.creator.setVisibility(View.VISIBLE); holder.creator.setVisibility(View.VISIBLE);
} }
if (book.getPublisher() == null || book.getPublisher().isEmpty()) { if (book.getPublisher() == null || book.getPublisher().isEmpty()) {
holder.publisher.setVisibility(View.GONE); holder.publisher.setVisibility(View.GONE);
} else { } else {
holder.publisher.setVisibility(View.VISIBLE); holder.publisher.setVisibility(View.VISIBLE);
} }
if (book.getDate() == null || book.getDate().isEmpty()) { if (book.getDate() == null || book.getDate().isEmpty()) {
holder.date.setVisibility(View.GONE); holder.date.setVisibility(View.GONE);
} else { } else {
holder.date.setVisibility(View.VISIBLE); holder.date.setVisibility(View.VISIBLE);
} }
if (book.getSize() == null || book.getSize().isEmpty()) { if (book.getSize() == null || book.getSize().isEmpty()) {
holder.size.setVisibility(View.GONE); holder.size.setVisibility(View.GONE);
} else { } else {
holder.size.setVisibility(View.VISIBLE); holder.size.setVisibility(View.VISIBLE);
} }
return convertView; return convertView;

View File

@ -17,7 +17,10 @@
*/ */
package org.kiwix.kiwixmobile.zim_manager.fileselect_view; package org.kiwix.kiwixmobile.zim_manager.fileselect_view;
import android.util.Log;
import org.kiwix.kiwixmobile.base.BasePresenter; import org.kiwix.kiwixmobile.base.BasePresenter;
import org.kiwix.kiwixmobile.data.DataSource;
import org.kiwix.kiwixmobile.data.local.dao.BookDao; import org.kiwix.kiwixmobile.data.local.dao.BookDao;
import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity; import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity;
@ -25,16 +28,23 @@ import java.util.ArrayList;
import javax.inject.Inject; import javax.inject.Inject;
import io.reactivex.CompletableObserver;
import io.reactivex.disposables.Disposable;
/** /**
* Created by EladKeyshawn on 06/04/2017. * Created by EladKeyshawn on 06/04/2017.
*/ */
public class ZimFileSelectPresenter extends BasePresenter<ZimFileSelectViewCallback> { public class ZimFileSelectPresenter extends BasePresenter<ZimFileSelectViewCallback> {
private static final String TAG = "ZimFileSelectPresenter";
private final DataSource dataSource;
@Inject @Inject
BookDao bookDao; BookDao bookDao;
@Inject @Inject
ZimFileSelectPresenter() { ZimFileSelectPresenter(DataSource dataSource) {
this.dataSource = dataSource;
} }
@Override @Override
@ -47,4 +57,43 @@ public class ZimFileSelectPresenter extends BasePresenter<ZimFileSelectViewCallb
view.showFiles(books); view.showFiles(books);
} }
void saveBooks(ArrayList<LibraryNetworkEntity.Book> books) {
dataSource.saveBooks(books)
.subscribe(new CompletableObserver() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onComplete() {
}
@Override
public void onError(Throwable e) {
Log.e(TAG, "Unable to save books", e);
}
});
}
public void deleteBook(LibraryNetworkEntity.Book book) {
dataSource.deleteBook(book)
.subscribe(new CompletableObserver() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onComplete() {
}
@Override
public void onError(Throwable e) {
Log.e(TAG, "Unable to delete book", e);
}
});
}
} }