From 411ef31c5e9e3e74c6cb2815bac1faae114900b9 Mon Sep 17 00:00:00 2001 From: Kanahia Date: Sun, 30 Jun 2024 13:24:05 +0530 Subject: [PATCH] Implemented caching of places --- .../fr/free/nrw/commons/db/AppDatabase.kt | 5 +- .../fr/free/nrw/commons/db/Converters.java | 14 +++ .../commons/di/CommonsApplicationModule.java | 6 + .../fr/free/nrw/commons/nearby/Place.java | 33 +++-- .../fr/free/nrw/commons/nearby/PlaceDao.java | 24 ++++ .../commons/nearby/PlacesLocalDataSource.java | 24 ++++ .../nrw/commons/nearby/PlacesRepository.java | 25 ++++ .../fragments/NearbyParentFragment.java | 113 ++++++++++++------ .../NearbyParentFragmentPresenter.java | 5 + 9 files changed, 206 insertions(+), 43 deletions(-) create mode 100644 app/src/main/java/fr/free/nrw/commons/nearby/PlaceDao.java create mode 100644 app/src/main/java/fr/free/nrw/commons/nearby/PlacesLocalDataSource.java create mode 100644 app/src/main/java/fr/free/nrw/commons/nearby/PlacesRepository.java diff --git a/app/src/main/java/fr/free/nrw/commons/db/AppDatabase.kt b/app/src/main/java/fr/free/nrw/commons/db/AppDatabase.kt index 6d63e58a1..594f087c8 100644 --- a/app/src/main/java/fr/free/nrw/commons/db/AppDatabase.kt +++ b/app/src/main/java/fr/free/nrw/commons/db/AppDatabase.kt @@ -6,6 +6,8 @@ import androidx.room.TypeConverters import fr.free.nrw.commons.contributions.Contribution import fr.free.nrw.commons.contributions.ContributionDao import fr.free.nrw.commons.customselector.database.* +import fr.free.nrw.commons.nearby.Place +import fr.free.nrw.commons.nearby.PlaceDao import fr.free.nrw.commons.review.ReviewDao import fr.free.nrw.commons.review.ReviewEntity import fr.free.nrw.commons.upload.depicts.Depicts @@ -15,10 +17,11 @@ import fr.free.nrw.commons.upload.depicts.DepictsDao * The database for accessing the respective DAOs * */ -@Database(entities = [Contribution::class, Depicts::class, UploadedStatus::class, NotForUploadStatus::class, ReviewEntity::class], version = 16, exportSchema = false) +@Database(entities = [Contribution::class, Depicts::class, UploadedStatus::class, NotForUploadStatus::class, ReviewEntity::class, Place::class], version = 18, exportSchema = false) @TypeConverters(Converters::class) abstract class AppDatabase : RoomDatabase() { abstract fun contributionDao(): ContributionDao + abstract fun PlaceDao(): PlaceDao abstract fun DepictsDao(): DepictsDao; abstract fun UploadedStatusDao(): UploadedStatusDao; abstract fun NotForUploadStatusDao(): NotForUploadStatusDao diff --git a/app/src/main/java/fr/free/nrw/commons/db/Converters.java b/app/src/main/java/fr/free/nrw/commons/db/Converters.java index 6f0c8c1fc..a70cdc815 100644 --- a/app/src/main/java/fr/free/nrw/commons/db/Converters.java +++ b/app/src/main/java/fr/free/nrw/commons/db/Converters.java @@ -8,8 +8,10 @@ import fr.free.nrw.commons.CommonsApplication; import fr.free.nrw.commons.contributions.ChunkInfo; import fr.free.nrw.commons.di.ApplicationlessInjection; import fr.free.nrw.commons.location.LatLng; +import fr.free.nrw.commons.nearby.Sitelinks; import fr.free.nrw.commons.upload.WikidataPlace; import fr.free.nrw.commons.upload.structure.depictions.DepictedItem; +import java.lang.reflect.Type; import java.util.Date; import java.util.List; import java.util.Map; @@ -134,6 +136,18 @@ public class Converters { return readObjectWithTypeToken(depictedItems, new TypeToken>() {}); } + @TypeConverter + public static Sitelinks sitelinksFromString(String value) { + Type type = new TypeToken() {}.getType(); + return new Gson().fromJson(value, type); + } + + @TypeConverter + public static String fromSitelinks(Sitelinks sitelinks) { + Gson gson = new Gson(); + return gson.toJson(sitelinks); + } + private static String writeObjectToString(Object object) { return object == null ? null : getGson().toJson(object); } diff --git a/app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationModule.java b/app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationModule.java index abf7c6fb1..cd7324c63 100644 --- a/app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationModule.java +++ b/app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationModule.java @@ -24,6 +24,7 @@ import fr.free.nrw.commons.data.DBOpenHelper; import fr.free.nrw.commons.db.AppDatabase; import fr.free.nrw.commons.kvstore.JsonKvStore; import fr.free.nrw.commons.location.LocationServiceManager; +import fr.free.nrw.commons.nearby.PlaceDao; import fr.free.nrw.commons.review.ReviewDao; import fr.free.nrw.commons.settings.Prefs; import fr.free.nrw.commons.upload.UploadController; @@ -275,6 +276,11 @@ public class CommonsApplicationModule { return appDatabase.contributionDao(); } + @Provides + public PlaceDao providesPlaceDao(AppDatabase appDatabase) { + return appDatabase.PlaceDao(); + } + /** * Get the reference of DepictsDao class. */ diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/Place.java b/app/src/main/java/fr/free/nrw/commons/nearby/Place.java index 2ad9a5892..dfa367938 100644 --- a/app/src/main/java/fr/free/nrw/commons/nearby/Place.java +++ b/app/src/main/java/fr/free/nrw/commons/nearby/Place.java @@ -3,7 +3,10 @@ package fr.free.nrw.commons.nearby; import android.net.Uri; import android.os.Parcel; import android.os.Parcelable; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.room.Entity; +import androidx.room.PrimaryKey; import fr.free.nrw.commons.location.LatLng; import fr.free.nrw.commons.nearby.model.NearbyResultItem; import fr.free.nrw.commons.utils.LocationUtils; @@ -14,21 +17,23 @@ import timber.log.Timber; /** * A single geolocated Wikidata item */ +@Entity(tableName = "place") public class Place implements Parcelable { - public final String language; - public final String name; - private final Label label; - private final String longDescription; + public String language; + public String name; + private Label label; + private String longDescription; + @PrimaryKey @NonNull public LatLng location; - private final String category; - public final String pic; + private String category; + public String pic; // exists boolean will tell whether the place exists or not, // For a place to be existing both destroyed and endTime property should be null but it is also not necessary for a non-existing place to have both properties either one property is enough (in such case that not given property will be considered as null). - public final Boolean exists; + public Boolean exists; public String distance; - public final Sitelinks siteLinks; + public Sitelinks siteLinks; private boolean isMonument; private String thumb; @@ -332,4 +337,16 @@ public class Place implements Parcelable { public void setThumb(String thumb) { this.thumb = thumb; } + + public void setLabel(Label label) { + this.label = label; + } + + public void setLongDescription(String longDescription) { + this.longDescription = longDescription; + } + + public void setCategory(String category) { + this.category = category; + } } diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/PlaceDao.java b/app/src/main/java/fr/free/nrw/commons/nearby/PlaceDao.java new file mode 100644 index 000000000..777ac5e41 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/nearby/PlaceDao.java @@ -0,0 +1,24 @@ +package fr.free.nrw.commons.nearby; + +import androidx.room.Dao; +import androidx.room.Insert; +import androidx.room.OnConflictStrategy; +import androidx.room.Query; +import fr.free.nrw.commons.location.LatLng; +import io.reactivex.Completable; + +@Dao +public abstract class PlaceDao { + @Insert(onConflict = OnConflictStrategy.REPLACE) + public abstract void saveSynchronous(Place place); + + @Query("SELECT * from place WHERE location=:l") + public abstract Place getPlace(LatLng l); + + public Completable save(final Place place) { + return Completable + .fromAction(() -> { + saveSynchronous(place); + }); + } +} diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/PlacesLocalDataSource.java b/app/src/main/java/fr/free/nrw/commons/nearby/PlacesLocalDataSource.java new file mode 100644 index 000000000..985331ee4 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/nearby/PlacesLocalDataSource.java @@ -0,0 +1,24 @@ +package fr.free.nrw.commons.nearby; + +import fr.free.nrw.commons.location.LatLng; +import io.reactivex.Completable; +import javax.inject.Inject; + +public class PlacesLocalDataSource { + + private final PlaceDao placeDao; + + @Inject + public PlacesLocalDataSource( + final PlaceDao placeDao) { + this.placeDao = placeDao; + } + + public Place fetchPlace(LatLng latLng){ + return placeDao.getPlace(latLng); + } + + public Completable savePlace(Place place) { + return placeDao.save(place); + } +} diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/PlacesRepository.java b/app/src/main/java/fr/free/nrw/commons/nearby/PlacesRepository.java new file mode 100644 index 000000000..e6a485ca9 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/nearby/PlacesRepository.java @@ -0,0 +1,25 @@ +package fr.free.nrw.commons.nearby; + +import fr.free.nrw.commons.contributions.Contribution; +import fr.free.nrw.commons.location.LatLng; +import io.reactivex.Completable; +import javax.inject.Inject; + +public class PlacesRepository { + + private PlacesLocalDataSource localDataSource; + + @Inject + public PlacesRepository(PlacesLocalDataSource localDataSource) { + this.localDataSource = localDataSource; + } + + public Completable save(Place place){ + return localDataSource.savePlace(place); + } + + public Place fetchPlace(LatLng latLng){ + return localDataSource.fetchPlace(latLng); + } + +} diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/fragments/NearbyParentFragment.java b/app/src/main/java/fr/free/nrw/commons/nearby/fragments/NearbyParentFragment.java index bcd5e469c..70a5fa2bc 100644 --- a/app/src/main/java/fr/free/nrw/commons/nearby/fragments/NearbyParentFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/nearby/fragments/NearbyParentFragment.java @@ -86,6 +86,7 @@ import fr.free.nrw.commons.nearby.NearbyController; import fr.free.nrw.commons.nearby.NearbyFilterSearchRecyclerViewAdapter; import fr.free.nrw.commons.nearby.NearbyFilterState; import fr.free.nrw.commons.nearby.Place; +import fr.free.nrw.commons.nearby.PlacesRepository; import fr.free.nrw.commons.nearby.contract.NearbyParentFragmentContract; import fr.free.nrw.commons.nearby.fragments.AdvanceQueryFragment.Callback; import fr.free.nrw.commons.nearby.presenter.NearbyParentFragmentPresenter; @@ -99,7 +100,6 @@ import fr.free.nrw.commons.utils.NetworkUtils; import fr.free.nrw.commons.utils.SystemThemeUtils; import fr.free.nrw.commons.utils.ViewUtil; import fr.free.nrw.commons.wikidata.WikidataEditListener; -import fr.free.nrw.commons.wikidata.WikidataEditListener.WikidataP18EditListener; import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.Disposable; @@ -107,14 +107,12 @@ import io.reactivex.schedulers.Schedulers; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; -import java.io.InterruptedIOException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Date; -import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; @@ -160,6 +158,8 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment @Inject BookmarkLocationsDao bookmarkLocationDao; @Inject + PlacesRepository placesRepository; + @Inject ContributionController controller; @Inject WikidataEditListener wikidataEditListener; @@ -1244,6 +1244,7 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment Place pl = updatedPlaceList.get(i); if (pl.location == updatedPlace.location){ updatedPlaceList.set(i, updatedPlace); + savePlaceToDB(place); } } Drawable icon = ContextCompat.getDrawable(getContext(), getIconFor(updatedPlace, isBookMarked)); @@ -1381,13 +1382,13 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment if (stopQuery) { return; } - if (!p.isEmpty()) { + if (!p.isEmpty() && p != updatedPlaceList) { synchronized (updatedPlaceList) { updatedPlaceList.clear(); updatedPlaceList.addAll((Collection) p); } - updateMapMarkers(new ArrayList<>(updatedPlaceList), curLatLng, false); } + updateMapMarkers(new ArrayList<>(updatedPlaceList), curLatLng, false); processBatchesSequentially(places, batchSize, updatedPlaceList, curLatLng, endIndex); }, throwable -> { Timber.e(throwable); @@ -1399,43 +1400,87 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment } private Observable> processBatch(List batch, List placeList) { - return Observable.fromCallable(() -> nearbyController.getPlaces(batch)) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .map(places -> { - if (stopQuery) { - return Collections.emptyList(); - } - if (places == null || places.isEmpty()) { - showErrorMessage(getString(R.string.no_nearby_places_around)); - return Collections.emptyList(); - } else { - List updatedPlaceList = new ArrayList<>(placeList); - for (Place place : places) { - for (Place foundPlace : placeList) { - if (place.siteLinks.getWikidataLink().equals(foundPlace.siteLinks.getWikidataLink())) { - place.location = foundPlace.location; - place.distance = foundPlace.distance; - place.setMonument(foundPlace.isMonument()); - int index = updatedPlaceList.indexOf(foundPlace); - if (index != -1) { - updatedPlaceList.set(index, place); - } + List toBeProcessed = new ArrayList<>(); + + List> placeObservables = new ArrayList<>(); + + for (Place place : batch) { + Observable placeObservable = Observable + .fromCallable(() -> { + Place fetchedPlace = placesRepository.fetchPlace(place.location); + return fetchedPlace != null ? fetchedPlace : place; + }) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .doOnNext(placeData -> { + if (placeData.equals(place)) { + toBeProcessed.add(place); + } else { + for (int i = 0; i < placeList.size(); i++) { + Place pl = placeList.get(i); + if (pl.location.equals(place.location)) { + placeList.set(i, placeData); break; } } } - return updatedPlaceList; + }); + + placeObservables.add(placeObservable); + } + + return Observable.zip(placeObservables, objects -> toBeProcessed) + .flatMap(processedList -> { + if (processedList.isEmpty()) { + return Observable.just(placeList); } - }) - .onErrorReturn(throwable -> { - Timber.e(throwable); - showErrorMessage(getString(R.string.error_fetching_nearby_places) + " " + throwable.getLocalizedMessage()); - setFilterState(); - return Collections.emptyList(); + return Observable.fromCallable(() -> nearbyController.getPlaces(processedList)) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .map(places -> { + if (stopQuery) { + return Collections.emptyList(); + } + if (places == null || places.isEmpty()) { + return Collections.emptyList(); + } else { + List updatedPlaceList = new ArrayList<>(placeList); + for (Place place : places) { + for (Place foundPlace : placeList) { + if (place.siteLinks.getWikidataLink() + .equals(foundPlace.siteLinks.getWikidataLink())) { + place.location = foundPlace.location; + place.distance = foundPlace.distance; + place.setMonument(foundPlace.isMonument()); + int index = updatedPlaceList.indexOf(foundPlace); + if (index != -1) { + updatedPlaceList.set(index, place); + savePlaceToDB(place); + } + break; + } + } + } + return updatedPlaceList; + } + }) + .onErrorReturn(throwable -> { + Timber.e(throwable); + showErrorMessage(getString(R.string.error_fetching_nearby_places) + " " + + throwable.getLocalizedMessage()); + setFilterState(); + return Collections.emptyList(); + }); }); } + private void savePlaceToDB(Place place) { + compositeDisposable.add(placesRepository + .save(place) + .subscribeOn(Schedulers.io()) + .subscribe()); + } + @Override public void stopQuery() { stopQuery = true; diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/presenter/NearbyParentFragmentPresenter.java b/app/src/main/java/fr/free/nrw/commons/nearby/presenter/NearbyParentFragmentPresenter.java index 40ea75adf..7da5d6714 100644 --- a/app/src/main/java/fr/free/nrw/commons/nearby/presenter/NearbyParentFragmentPresenter.java +++ b/app/src/main/java/fr/free/nrw/commons/nearby/presenter/NearbyParentFragmentPresenter.java @@ -14,8 +14,10 @@ import android.location.Location; import android.view.View; import androidx.annotation.MainThread; import androidx.annotation.Nullable; +import androidx.work.ExistingWorkPolicy; import fr.free.nrw.commons.BaseMarker; import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao; +import fr.free.nrw.commons.contributions.Contribution; import fr.free.nrw.commons.kvstore.JsonKvStore; import fr.free.nrw.commons.location.LatLng; import fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType; @@ -26,9 +28,12 @@ import fr.free.nrw.commons.nearby.MarkerPlaceGroup; import fr.free.nrw.commons.nearby.NearbyController; import fr.free.nrw.commons.nearby.NearbyFilterState; import fr.free.nrw.commons.nearby.Place; +import fr.free.nrw.commons.nearby.PlaceDao; import fr.free.nrw.commons.nearby.contract.NearbyParentFragmentContract; +import fr.free.nrw.commons.upload.worker.WorkRequestHelper; import fr.free.nrw.commons.utils.LocationUtils; import fr.free.nrw.commons.wikidata.WikidataEditListener; +import io.reactivex.disposables.CompositeDisposable; import java.lang.reflect.Proxy; import java.util.List; import timber.log.Timber;