Implemented caching of places

This commit is contained in:
Kanahia 2024-06-30 13:24:05 +05:30
parent a11003a2fb
commit 411ef31c5e
9 changed files with 206 additions and 43 deletions

View file

@ -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

View file

@ -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<List<DepictedItem>>() {});
}
@TypeConverter
public static Sitelinks sitelinksFromString(String value) {
Type type = new TypeToken<Sitelinks>() {}.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);
}

View file

@ -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.
*/

View file

@ -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;
}
}

View file

@ -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);
});
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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<? extends Place>) 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<List<?>> processBatch(List<Place> batch, List<Place> 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<Place> 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<Place> toBeProcessed = new ArrayList<>();
List<Observable<Place>> placeObservables = new ArrayList<>();
for (Place place : batch) {
Observable<Place> 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<Place> 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;

View file

@ -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;