diff options
Diffstat (limited to 'main/src/cgeo/geocaching/DataStore.java')
-rw-r--r-- | main/src/cgeo/geocaching/DataStore.java | 668 |
1 files changed, 327 insertions, 341 deletions
diff --git a/main/src/cgeo/geocaching/DataStore.java b/main/src/cgeo/geocaching/DataStore.java index e404b22..b7ca577 100644 --- a/main/src/cgeo/geocaching/DataStore.java +++ b/main/src/cgeo/geocaching/DataStore.java @@ -11,29 +11,34 @@ import cgeo.geocaching.enumerations.LoadFlags.SaveFlag; import cgeo.geocaching.enumerations.LogType; import cgeo.geocaching.enumerations.WaypointType; import cgeo.geocaching.files.LocalStorage; -import cgeo.geocaching.geopoint.Geopoint; -import cgeo.geocaching.geopoint.Viewport; import cgeo.geocaching.list.AbstractList; import cgeo.geocaching.list.PseudoList; import cgeo.geocaching.list.StoredList; +import cgeo.geocaching.location.Geopoint; +import cgeo.geocaching.location.Viewport; import cgeo.geocaching.search.SearchSuggestionCursor; import cgeo.geocaching.settings.Settings; import cgeo.geocaching.ui.dialog.Dialogs; import cgeo.geocaching.utils.FileUtils; import cgeo.geocaching.utils.Log; +import cgeo.geocaching.utils.Version; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.MapUtils; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; -import rx.android.observables.AndroidObservable; +import rx.Observable; +import rx.Observable.OnSubscribe; +import rx.Subscriber; +import rx.android.app.AppObservable; +import rx.functions.Action0; import rx.functions.Action1; import rx.functions.Func0; import rx.functions.Func1; import rx.schedulers.Schedulers; -import rx.util.async.Async; import android.app.Activity; import android.app.ProgressDialog; @@ -58,6 +63,7 @@ import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Date; +import java.util.EnumMap; import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; @@ -68,6 +74,7 @@ import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import java.util.Set; +import java.util.concurrent.TimeUnit; import java.util.regex.Pattern; public class DataStore { @@ -111,35 +118,32 @@ public class DataStore { "cg_caches.direction," + // 16 "cg_caches.distance," + // 17 "cg_caches.terrain," + // 18 - "cg_caches.latlon," + // 19 - "cg_caches.location," + // 20 - "cg_caches.personal_note," + // 21 - "cg_caches.shortdesc," + // 22 - "cg_caches.favourite_cnt," + // 23 - "cg_caches.rating," + // 24 - "cg_caches.votes," + // 25 - "cg_caches.myvote," + // 26 - "cg_caches.disabled," + // 27 - "cg_caches.archived," + // 28 - "cg_caches.members," + // 29 - "cg_caches.found," + // 30 - "cg_caches.favourite," + // 31 - "cg_caches.inventoryunknown," + // 32 - "cg_caches.onWatchlist," + // 33 - "cg_caches.reliable_latlon," + // 34 - "cg_caches.coordsChanged," + // 35 - "cg_caches.latitude," + // 36 - "cg_caches.longitude," + // 37 - "cg_caches.finalDefined," + // 38 - "cg_caches._id," + // 39 - "cg_caches.inventorycoins," + // 40 - "cg_caches.inventorytags," + // 41 - "cg_caches.logPasswordRequired"; // 42 - - //TODO: remove "latlon" field from cache table + "cg_caches.location," + // 19 + "cg_caches.personal_note," + // 20 + "cg_caches.shortdesc," + // 21 + "cg_caches.favourite_cnt," + // 22 + "cg_caches.rating," + // 23 + "cg_caches.votes," + // 24 + "cg_caches.myvote," + // 25 + "cg_caches.disabled," + // 26 + "cg_caches.archived," + // 27 + "cg_caches.members," + // 28 + "cg_caches.found," + // 29 + "cg_caches.favourite," + // 30 + "cg_caches.inventoryunknown," + // 31 + "cg_caches.onWatchlist," + // 32 + "cg_caches.reliable_latlon," + // 33 + "cg_caches.coordsChanged," + // 34 + "cg_caches.latitude," + // 35 + "cg_caches.longitude," + // 36 + "cg_caches.finalDefined," + // 37 + "cg_caches._id," + // 38 + "cg_caches.inventorycoins," + // 39 + "cg_caches.inventorytags," + // 40 + "cg_caches.logPasswordRequired"; // 41 /** The list of fields needed for mapping. */ - private static final String[] WAYPOINT_COLUMNS = new String[] { "_id", "geocode", "updated", "type", "prefix", "lookup", "name", "latlon", "latitude", "longitude", "note", "own", "visited" }; + private static final String[] WAYPOINT_COLUMNS = new String[] { "_id", "geocode", "updated", "type", "prefix", "lookup", "name", "latitude", "longitude", "note", "own", "visited" }; /** Number of days (as ms) after temporarily saved caches are deleted */ private final static long DAYS_AFTER_CACHE_IS_DELETED = 3 * 24 * 60 * 60 * 1000; @@ -147,7 +151,7 @@ public class DataStore { /** * holds the column indexes of the cache table to avoid lookups */ - private static CacheCache cacheCache = new CacheCache(); + private static final CacheCache cacheCache = new CacheCache(); private static SQLiteDatabase database = null; private static final int dbVersion = 68; public static final int customListIdOffset = 10; @@ -162,7 +166,7 @@ public class DataStore { private static final @NonNull String dbTableLogImages = "cg_logImages"; private static final @NonNull String dbTableLogsOffline = "cg_logs_offline"; private static final @NonNull String dbTableTrackables = "cg_trackables"; - private static final @NonNull String dbTableSearchDestionationHistory = "cg_search_destination_history"; + private static final @NonNull String dbTableSearchDestinationHistory = "cg_search_destination_history"; private static final @NonNull String dbCreateCaches = "" + "create table " + dbTableCaches + " (" + "_id integer primary key autoincrement, " @@ -183,7 +187,6 @@ public class DataStore { + "size text, " + "difficulty float, " + "terrain float, " - + "latlon text, " + "location text, " + "direction double, " + "distance double, " @@ -214,9 +217,7 @@ public class DataStore { + "create table " + dbTableLists + " (" + "_id integer primary key autoincrement, " + "title text not null, " - + "updated long not null, " - + "latitude double, " - + "longitude double " + + "updated long not null" + "); "; private static final String dbCreateAttributes = "" + "create table " + dbTableAttributes + " (" @@ -235,7 +236,6 @@ public class DataStore { + "prefix text, " + "lookup text, " + "name text, " - + "latlon text, " + "latitude double, " + "longitude double, " + "note text, " @@ -303,13 +303,23 @@ public class DataStore { + "); "; private static final String dbCreateSearchDestinationHistory = "" - + "create table " + dbTableSearchDestionationHistory + " (" + + "create table " + dbTableSearchDestinationHistory + " (" + "_id integer primary key autoincrement, " + "date long not null, " + "latitude double, " + "longitude double " + "); "; + private static final Observable<Integer> allCachesCountObservable = Observable.create(new OnSubscribe<Integer>() { + @Override + public void call(final Subscriber<? super Integer> subscriber) { + if (isInitialized()) { + subscriber.onNext(getAllCachesCount()); + subscriber.onCompleted(); + } + } + }).timeout(500, TimeUnit.MILLISECONDS).retry(10).subscribeOn(Schedulers.io()); + private static boolean newlyCreatedDatabase = false; private static boolean databaseCleaned = false; @@ -358,11 +368,12 @@ public class DataStore { } cacheCache.removeAllFromCache(); - PreparedStatements.clearPreparedStatements(); + PreparedStatement.clearPreparedStatements(); database.close(); database = null; } + @NonNull public static File getBackupFileInternal() { return new File(LocalStorage.getStorage(), "cgeo.sqlite"); } @@ -391,16 +402,15 @@ public class DataStore { * Move the database to/from external cgdata in a new thread, * showing a progress window * - * @param fromActivity */ public static void moveDatabase(final Activity fromActivity) { final ProgressDialog dialog = ProgressDialog.show(fromActivity, fromActivity.getString(R.string.init_dbmove_dbmove), fromActivity.getString(R.string.init_dbmove_running), true, false); - AndroidObservable.bindActivity(fromActivity, Async.fromCallable(new Func0<Boolean>() { + AppObservable.bindActivity(fromActivity, Observable.defer(new Func0<Observable<Boolean>>() { @Override - public Boolean call() { + public Observable<Boolean> call() { if (!LocalStorage.isExternalStorageAvailable()) { Log.w("Database was not moved: external memory not available"); - return false; + return Observable.just(false); } closeDb(); @@ -409,7 +419,7 @@ public class DataStore { if (!LocalStorage.copy(source, target)) { Log.e("Database could not be moved to " + target); init(); - return false; + return Observable.just(false); } if (!FileUtils.delete(source)) { Log.e("Original database could not be deleted during move"); @@ -418,7 +428,7 @@ public class DataStore { Log.i("Database was moved to " + target); init(); - return true; + return Observable.just(true); } })).subscribeOn(Schedulers.io()).subscribe(new Action1<Boolean>() { @Override @@ -430,14 +440,17 @@ public class DataStore { }); } + @NonNull private static File databasePath(final boolean internal) { return new File(internal ? LocalStorage.getInternalDbDirectory() : LocalStorage.getExternalDbDirectory(), dbName); } + @NonNull private static File databasePath() { return databasePath(!Settings.isDbOnSDCard()); } + @NonNull private static File databaseAlternatePath() { return databasePath(Settings.isDbOnSDCard()); } @@ -552,7 +565,7 @@ public class DataStore { try { db.execSQL(dbCreateSearchDestinationHistory); - Log.i("Added table " + dbTableSearchDestionationHistory + "."); + Log.i("Added table " + dbTableSearchDestinationHistory + "."); } catch (final Exception e) { Log.e("Failed to upgrade to ver. 52", e); } @@ -638,7 +651,6 @@ public class DataStore { + "size text, " + "difficulty float, " + "terrain float, " - + "latlon text, " + "location text, " + "direction double, " + "distance double, " @@ -665,7 +677,7 @@ public class DataStore { db.execSQL(dbCreateCachesTemp); db.execSQL("insert into " + dbTableCachesTemp + " select _id,updated,detailed,detailedupdate,visiteddate,geocode,reason,cacheid,guid,type,name,own,owner,owner_real," + - "hidden,hint,size,difficulty,terrain,latlon,location,direction,distance,latitude,longitude, 0," + + "hidden,hint,size,difficulty,terrain,location,direction,distance,latitude,longitude, 0," + "personal_note,shortdesc,description,favourite_cnt,rating,votes,myvote,disabled,archived,members,found,favourite,inventorycoins," + "inventorytags,inventoryunknown,onWatchlist from " + dbTableCaches); db.execSQL("drop table " + dbTableCaches); @@ -681,13 +693,12 @@ public class DataStore { + "prefix text, " + "lookup text, " + "name text, " - + "latlon text, " + "latitude double, " + "longitude double, " + "note text " + "); "; db.execSQL(dbCreateWaypointsTemp); - db.execSQL("insert into " + dbTableWaypointsTemp + " select _id, geocode, updated, type, prefix, lookup, name, latlon, latitude, longitude, note from " + dbTableWaypoints); + db.execSQL("insert into " + dbTableWaypointsTemp + " select _id, geocode, updated, type, prefix, lookup, name, latitude, longitude, note from " + dbTableWaypoints); db.execSQL("drop table " + dbTableWaypoints); db.execSQL("alter table " + dbTableWaypointsTemp + " rename to " + dbTableWaypoints); @@ -823,7 +834,7 @@ public class DataStore { private static void sanityChecks(final SQLiteDatabase db) { // Check that the history of searches is well formed as some dates seem to be missing according // to NPE traces. - final int staleHistorySearches = db.delete(dbTableSearchDestionationHistory, "date is null", null); + final int staleHistorySearches = db.delete(dbTableSearchDestinationHistory, "date is null", null); if (staleHistorySearches > 0) { Log.w(String.format(Locale.getDefault(), "DataStore.dbHelper.onOpen: removed %d bad search history entries", staleHistorySearches)); } @@ -871,15 +882,17 @@ public class DataStore { final File[] files = LocalStorage.getStorage().listFiles(); if (ArrayUtils.isNotEmpty(files)) { final Pattern oldFilePattern = Pattern.compile("^[GC|TB|EC|GK|O][A-Z0-9]{4,7}$"); - final SQLiteStatement select = db.compileStatement("select count(*) from " + dbTableCaches + " where geocode = ?"); + final SQLiteStatement select = PreparedStatement.CHECK_IF_PRESENT.getStatement(); final ArrayList<File> toRemove = new ArrayList<>(files.length); for (final File file : files) { if (file.isDirectory()) { final String geocode = file.getName(); if (oldFilePattern.matcher(geocode).find()) { - select.bindString(1, geocode); - if (select.simpleQueryForLong() == 0) { - toRemove.add(file); + synchronized (select) { + select.bindString(1, geocode); + if (select.simpleQueryForLong() == 0) { + toRemove.add(file); + } } } } @@ -887,15 +900,15 @@ public class DataStore { // Use a background thread for the real removal to avoid keeping the database locked // if we are called from within a transaction. - new Thread(new Runnable() { + Schedulers.io().createWorker().schedule(new Action0() { @Override - public void run() { + public void call() { for (final File dir : toRemove) { Log.i("Removing obsolete cache directory for " + dir.getName()); - LocalStorage.deleteDirectory(dir); + FileUtils.deleteDirectory(dir); } } - }).start(); + }); } } @@ -933,7 +946,7 @@ public class DataStore { int dataDetailed = 0; try { - Cursor cursor; + final Cursor cursor; if (StringUtils.isNotBlank(geocode)) { cursor = database.query( @@ -1000,18 +1013,18 @@ public class DataStore { final SQLiteStatement listId; final String value; if (StringUtils.isNotBlank(geocode)) { - listId = PreparedStatements.getListIdOfGeocode(); + listId = PreparedStatement.LIST_ID_OF_GEOCODE.getStatement(); value = geocode; } else { - listId = PreparedStatements.getListIdOfGuid(); + listId = PreparedStatement.LIST_ID_OF_GUID.getStatement(); value = guid; } synchronized (listId) { listId.bindString(1, value); - return listId.simpleQueryForLong() != StoredList.TEMPORARY_LIST_ID; + return listId.simpleQueryForLong() != StoredList.TEMPORARY_LIST.id; } - } catch (final SQLiteDoneException e) { + } catch (final SQLiteDoneException ignored) { // Do nothing, it only means we have no information on the cache } catch (final Exception e) { Log.e("DataStore.isOffline", e); @@ -1020,6 +1033,7 @@ public class DataStore { return false; } + @Nullable public static String getGeocodeForGuid(final String guid) { if (StringUtils.isBlank(guid)) { return null; @@ -1027,12 +1041,12 @@ public class DataStore { init(); try { - final SQLiteStatement description = PreparedStatements.getGeocodeOfGuid(); + final SQLiteStatement description = PreparedStatement.GEOCODE_OF_GUID.getStatement(); synchronized (description) { description.bindString(1, guid); return description.simpleQueryForString(); } - } catch (final SQLiteDoneException e) { + } catch (final SQLiteDoneException ignored) { // Do nothing, it only means we have no information on the cache } catch (final Exception e) { Log.e("DataStore.getGeocodeForGuid", e); @@ -1041,36 +1055,14 @@ public class DataStore { return null; } - public static String getCacheidForGeocode(final String geocode) { - if (StringUtils.isBlank(geocode)) { - return null; - } - init(); - - try { - final SQLiteStatement description = PreparedStatements.getCacheIdOfGeocode(); - synchronized (description) { - description.bindString(1, geocode); - return description.simpleQueryForString(); - } - } catch (final SQLiteDoneException e) { - // Do nothing, it only means we have no information on the cache - } catch (final Exception e) { - Log.e("DataStore.getCacheidForGeocode", e); - } - - return null; - } - /** * Save/store a cache to the CacheCache * * @param cache * the Cache to save in the CacheCache/DB - * @param saveFlags * */ - public static void saveCache(final Geocache cache, final EnumSet<LoadFlags.SaveFlag> saveFlags) { + public static void saveCache(final Geocache cache, final Set<LoadFlags.SaveFlag> saveFlags) { saveCaches(Collections.singletonList(cache), saveFlags); } @@ -1079,10 +1071,9 @@ public class DataStore { * * @param caches * the caches to save in the CacheCache/DB - * @param saveFlags * */ - public static void saveCaches(final Collection<Geocache> caches, final EnumSet<LoadFlags.SaveFlag> saveFlags) { + public static void saveCaches(final Collection<Geocache> caches, final Set<LoadFlags.SaveFlag> saveFlags) { if (CollectionUtils.isEmpty(caches)) { return; } @@ -1116,7 +1107,9 @@ public class DataStore { for (final Geocache cache : caches) { final String geocode = cache.getGeocode(); final Geocache existingCache = existingCaches.get(geocode); - final boolean dbUpdateRequired = !cache.gatherMissingFrom(existingCache) || cacheCache.getCacheFromCache(geocode) != null; + boolean dbUpdateRequired = !cache.gatherMissingFrom(existingCache) || cacheCache.getCacheFromCache(geocode) != null; + // parse the note AFTER merging the local information in + dbUpdateRequired |= cache.parseWaypointsFromNote(); cache.addStorageLocation(StorageLocation.CACHE); cacheCache.putCacheInCache(cache); @@ -1162,7 +1155,7 @@ public class DataStore { values.put("hidden", hiddenDate.getTime()); } values.put("hint", cache.getHint()); - values.put("size", cache.getSize() == null ? "" : cache.getSize().id); + values.put("size", cache.getSize().id); values.put("difficulty", cache.getDifficulty()); values.put("terrain", cache.getTerrain()); values.put("location", cache.getLocation()); @@ -1228,7 +1221,7 @@ public class DataStore { if (attributes.isEmpty()) { return; } - final SQLiteStatement statement = PreparedStatements.getInsertAttribute(); + final SQLiteStatement statement = PreparedStatement.INSERT_ATTRIBUTE.getStatement(); final long timestamp = System.currentTimeMillis(); for (final String attribute : attributes) { statement.bindString(1, geocode); @@ -1249,9 +1242,12 @@ public class DataStore { init(); database.beginTransaction(); - try { - final SQLiteStatement insertDestination = PreparedStatements.getInsertSearchDestination(destination); + final SQLiteStatement insertDestination = PreparedStatement.INSERT_SEARCH_DESTINATION.getStatement(); + insertDestination.bindLong(1, destination.getDate()); + final Geopoint coords = destination.getCoords(); + insertDestination.bindDouble(2, coords.getLatitude()); + insertDestination.bindDouble(3, coords.getLongitude()); insertDestination.executeInsert(); database.setTransactionSuccessful(); } catch (final Exception e) { @@ -1264,7 +1260,6 @@ public class DataStore { public static boolean saveWaypoints(final Geocache cache) { init(); database.beginTransaction(); - try { saveWaypointsWithoutTransaction(cache); database.setTransactionSuccessful(); @@ -1294,7 +1289,6 @@ public class DataStore { values.put("prefix", oneWaypoint.getPrefix()); values.put("lookup", oneWaypoint.getLookup()); values.put("name", oneWaypoint.getName()); - values.put("latlon", oneWaypoint.getLatlon()); putCoords(values, oneWaypoint.getCoords()); values.put("note", oneWaypoint.getNote()); values.put("own", oneWaypoint.isUserDefined() ? 1 : 0); @@ -1315,7 +1309,6 @@ public class DataStore { /** * remove all waypoints of the given cache, where the id is not in the given list * - * @param cache * @param remainingWaypointIds * ids of waypoints which shall not be deleted */ @@ -1329,7 +1322,7 @@ public class DataStore { * * @param values * a ContentValues to save coordinates in - * @param oneWaypoint + * @param coords * coordinates to save, or null to save empty coordinates */ private static void putCoords(final ContentValues values, final Geopoint coords) { @@ -1348,6 +1341,7 @@ public class DataStore { * index of the longitude column * @return the coordinates, or null if latitude or longitude is null or the coordinates are invalid */ + @Nullable private static Geopoint getCoords(final Cursor cursor, final int indexLat, final int indexLon) { if (cursor.isNull(indexLat) || cursor.isNull(indexLon)) { return null; @@ -1373,7 +1367,6 @@ public class DataStore { values.put("prefix", waypoint.getPrefix()); values.put("lookup", waypoint.getLookup()); values.put("name", waypoint.getName()); - values.put("latlon", waypoint.getLatlon()); putCoords(values, waypoint.getCoords()); values.put("note", waypoint.getNote()); values.put("own", waypoint.isUserDefined() ? 1 : 0); @@ -1410,7 +1403,7 @@ public class DataStore { final List<Image> spoilers = cache.getSpoilers(); if (CollectionUtils.isNotEmpty(spoilers)) { - final SQLiteStatement insertSpoiler = PreparedStatements.getInsertSpoiler(); + final SQLiteStatement insertSpoiler = PreparedStatement.INSERT_SPOILER.getStatement(); final long timestamp = System.currentTimeMillis(); for (final Image spoiler : spoilers) { insertSpoiler.bindString(1, geocode); @@ -1420,8 +1413,7 @@ public class DataStore { final String description = spoiler.getDescription(); if (description != null) { insertSpoiler.bindString(5, description); - } - else { + } else { insertSpoiler.bindNull(5); } insertSpoiler.executeInsert(); @@ -1429,11 +1421,21 @@ public class DataStore { } } - public static void saveLogsWithoutTransaction(final String geocode, final Iterable<LogEntry> logs) { + public static void saveLogs(final String geocode, final Iterable<LogEntry> logs) { + database.beginTransaction(); + try { + saveLogsWithoutTransaction(geocode, logs); + database.setTransactionSuccessful(); + } finally { + database.endTransaction(); + } + } + + private static void saveLogsWithoutTransaction(final String geocode, final Iterable<LogEntry> logs) { // TODO delete logimages referring these logs database.delete(dbTableLogs, "geocode = ?", new String[]{geocode}); - final SQLiteStatement insertLog = PreparedStatements.getInsertLog(); + final SQLiteStatement insertLog = PreparedStatement.INSERT_LOG.getStatement(); final long timestamp = System.currentTimeMillis(); for (final LogEntry log : logs) { insertLog.bindString(1, geocode); @@ -1446,7 +1448,7 @@ public class DataStore { insertLog.bindLong(8, log.friend ? 1 : 0); final long logId = insertLog.executeInsert(); if (log.hasLogImages()) { - final SQLiteStatement insertImage = PreparedStatements.getInsertLogImage(); + final SQLiteStatement insertImage = PreparedStatement.INSERT_LOG_IMAGE.getStatement(); for (final Image img : log.getLogImages()) { insertImage.bindLong(1, logId); insertImage.bindString(2, img.getTitle()); @@ -1464,7 +1466,7 @@ public class DataStore { final Map<LogType, Integer> logCounts = cache.getLogCounts(); if (MapUtils.isNotEmpty(logCounts)) { final Set<Entry<LogType, Integer>> logCountsItems = logCounts.entrySet(); - final SQLiteStatement insertLogCounts = PreparedStatements.getInsertLogCounts(); + final SQLiteStatement insertLogCounts = PreparedStatement.INSERT_LOG_COUNTS.getStatement(); final long timestamp = System.currentTimeMillis(); for (final Entry<LogType, Integer> pair : logCountsItems) { insertLogCounts.bindString(1, geocode); @@ -1526,6 +1528,7 @@ public class DataStore { } } + @Nullable public static Viewport getBounds(final Set<String> geocodes) { if (CollectionUtils.isEmpty(geocodes)) { return null; @@ -1542,6 +1545,7 @@ public class DataStore { * The Geocode GCXXXX * @return the loaded cache (if found). Can be null */ + @Nullable public static Geocache loadCache(final String geocode, final EnumSet<LoadFlag> loadFlags) { if (StringUtils.isBlank(geocode)) { throw new IllegalArgumentException("geocode must not be empty"); @@ -1554,15 +1558,15 @@ public class DataStore { /** * Load caches. * - * @param geocodes * @return Set of loaded caches. Never null. */ + @NonNull public static Set<Geocache> loadCaches(final Collection<String> geocodes, final EnumSet<LoadFlag> loadFlags) { if (CollectionUtils.isEmpty(geocodes)) { return new HashSet<>(); } - final Set<Geocache> result = new HashSet<>(); + final Set<Geocache> result = new HashSet<>(geocodes.size()); final Set<String> remaining = new HashSet<>(geocodes); if (loadFlags.contains(LoadFlag.CACHE_BEFORE)) { @@ -1609,10 +1613,9 @@ public class DataStore { /** * Load caches. * - * @param geocodes - * @param loadFlags * @return Set of loaded caches. Never null. */ + @NonNull private static Set<Geocache> loadCachesFromGeocodes(final Set<String> geocodes, final EnumSet<LoadFlag> loadFlags) { if (CollectionUtils.isEmpty(geocodes)) { return Collections.emptySet(); @@ -1640,7 +1643,7 @@ public class DataStore { int logIndex = -1; while (cursor.moveToNext()) { - final Geocache cache = DataStore.createCacheFromDatabaseContent(cursor); + final Geocache cache = createCacheFromDatabaseContent(cursor); if (loadFlags.contains(LoadFlag.ATTRIBUTES)) { cache.setAttributes(loadAttributes(cache.getGeocode())); @@ -1699,11 +1702,9 @@ public class DataStore { /** * Builds a where for a viewport with the size enhanced by 50%. * - * @param dbTable - * @param viewport - * @return */ + @NonNull private static StringBuilder buildCoordinateWhere(final String dbTable, final Viewport viewport) { return viewport.resize(1.5).sqlWhere(dbTable); } @@ -1711,9 +1712,9 @@ public class DataStore { /** * creates a Cache from the cursor. Doesn't next. * - * @param cursor * @return Cache from DB */ + @NonNull private static Geocache createCacheFromDatabaseContent(final Cursor cursor) { final Geocache cache = new Geocache(); @@ -1750,31 +1751,32 @@ public class DataStore { } cache.setTerrain(cursor.getFloat(18)); // do not set cache.location - cache.setCoords(getCoords(cursor, 36, 37)); - cache.setPersonalNote(cursor.getString(21)); + cache.setPersonalNote(cursor.getString(20)); // do not set cache.shortdesc // do not set cache.description - cache.setFavoritePoints(cursor.getInt(23)); - cache.setRating(cursor.getFloat(24)); - cache.setVotes(cursor.getInt(25)); - cache.setMyVote(cursor.getFloat(26)); - cache.setDisabled(cursor.getInt(27) == 1); - cache.setArchived(cursor.getInt(28) == 1); - cache.setPremiumMembersOnly(cursor.getInt(29) == 1); - cache.setFound(cursor.getInt(30) == 1); - cache.setFavorite(cursor.getInt(31) == 1); - cache.setInventoryItems(cursor.getInt(32)); - cache.setOnWatchlist(cursor.getInt(33) == 1); - cache.setReliableLatLon(cursor.getInt(34) > 0); - cache.setUserModifiedCoords(cursor.getInt(35) > 0); - cache.setFinalDefined(cursor.getInt(38) > 0); - cache.setLogPasswordRequired(cursor.getInt(42) > 0); + cache.setFavoritePoints(cursor.getInt(22)); + cache.setRating(cursor.getFloat(23)); + cache.setVotes(cursor.getInt(24)); + cache.setMyVote(cursor.getFloat(25)); + cache.setDisabled(cursor.getInt(26) == 1); + cache.setArchived(cursor.getInt(27) == 1); + cache.setPremiumMembersOnly(cursor.getInt(28) == 1); + cache.setFound(cursor.getInt(29) == 1); + cache.setFavorite(cursor.getInt(30) == 1); + cache.setInventoryItems(cursor.getInt(31)); + cache.setOnWatchlist(cursor.getInt(32) == 1); + cache.setReliableLatLon(cursor.getInt(33) > 0); + cache.setUserModifiedCoords(cursor.getInt(34) > 0); + cache.setCoords(getCoords(cursor, 35, 36)); + cache.setFinalDefined(cursor.getInt(37) > 0); + cache.setLogPasswordRequired(cursor.getInt(41) > 0); Log.d("Loading " + cache.toString() + " (" + cache.getListId() + ") from DB"); return cache; } + @Nullable public static List<String> loadAttributes(final String geocode) { if (StringUtils.isBlank(geocode)) { return null; @@ -1792,6 +1794,7 @@ public class DataStore { GET_STRING_0); } + @Nullable public static Waypoint loadWaypoint(final int id) { if (id == 0) { return null; @@ -1818,6 +1821,7 @@ public class DataStore { return waypoint; } + @Nullable public static List<Waypoint> loadWaypoints(final String geocode) { if (StringUtils.isBlank(geocode)) { return null; @@ -1840,6 +1844,7 @@ public class DataStore { }); } + @NonNull private static Waypoint createWaypointFromDatabaseContent(final Cursor cursor) { final String name = cursor.getString(cursor.getColumnIndex("name")); final WaypointType type = WaypointType.findById(cursor.getString(cursor.getColumnIndex("type"))); @@ -1850,13 +1855,13 @@ public class DataStore { waypoint.setGeocode(cursor.getString(cursor.getColumnIndex("geocode"))); waypoint.setPrefix(cursor.getString(cursor.getColumnIndex("prefix"))); waypoint.setLookup(cursor.getString(cursor.getColumnIndex("lookup"))); - waypoint.setLatlon(cursor.getString(cursor.getColumnIndex("latlon"))); waypoint.setCoords(getCoords(cursor, cursor.getColumnIndex("latitude"), cursor.getColumnIndex("longitude"))); waypoint.setNote(cursor.getString(cursor.getColumnIndex("note"))); return waypoint; } + @Nullable private static List<Image> loadSpoilers(final String geocode) { if (StringUtils.isBlank(geocode)) { return null; @@ -1885,8 +1890,9 @@ public class DataStore { * * @return A list of previously entered destinations or an empty list. */ + @NonNull public static List<Destination> loadHistoryOfSearchedLocations() { - return queryToColl(dbTableSearchDestionationHistory, + return queryToColl(dbTableSearchDestinationHistory, new String[]{"_id", "date", "latitude", "longitude"}, "latitude IS NOT NULL AND longitude IS NOT NULL", null, @@ -1908,7 +1914,7 @@ public class DataStore { database.beginTransaction(); try { - database.delete(dbTableSearchDestionationHistory, null, null); + database.delete(dbTableSearchDestinationHistory, null, null); database.setTransactionSuccessful(); return true; } catch (final Exception e) { @@ -1921,7 +1927,6 @@ public class DataStore { } /** - * @param geocode * @return an immutable, non null list of logs */ @NonNull @@ -1935,7 +1940,7 @@ public class DataStore { init(); final Cursor cursor = database.rawQuery( - /* 0 1 2 3 4 5 6 7 8 9 10 */ + // 0 1 2 3 4 5 6 7 8 9 10 "SELECT cg_logs._id as cg_logs_id, type, author, log, date, found, friend, " + dbTableLogImages + "._id as cg_logImages_id, log_id, title, url" + " FROM " + dbTableLogs + " LEFT OUTER JOIN " + dbTableLogImages + " ON ( cg_logs._id = log_id ) WHERE geocode = ? ORDER BY date desc, cg_logs._id asc", new String[]{geocode}); @@ -1963,6 +1968,7 @@ public class DataStore { return Collections.unmodifiableList(logs); } + @Nullable public static Map<LogType, Integer> loadLogCounts(final String geocode) { if (StringUtils.isBlank(geocode)) { return null; @@ -1970,7 +1976,7 @@ public class DataStore { init(); - final Map<LogType, Integer> logCounts = new HashMap<>(); + final Map<LogType, Integer> logCounts = new EnumMap<>(LogType.class); final Cursor cursor = database.query( dbTableLogCount, @@ -1991,6 +1997,7 @@ public class DataStore { return logCounts; } + @Nullable private static List<Trackable> loadInventory(final String geocode) { if (StringUtils.isBlank(geocode)) { return null; @@ -2019,6 +2026,7 @@ public class DataStore { return trackables; } + @Nullable public static Trackable loadTrackable(final String geocode) { if (StringUtils.isBlank(geocode)) { return null; @@ -2043,6 +2051,7 @@ public class DataStore { return trackable; } + @NonNull private static Trackable createTrackableFromDatabaseContent(final Cursor cursor) { final Trackable trackable = new Trackable(); trackable.setGeocode(cursor.getString(cursor.getColumnIndex("tbcode"))); @@ -2067,9 +2076,6 @@ public class DataStore { /** * Number of caches stored for a given type and/or list * - * @param cacheType - * @param list - * @return */ public static int getAllStoredCachesCount(final CacheType cacheType, final int list) { if (cacheType == null) { @@ -2081,37 +2087,29 @@ public class DataStore { init(); try { - final StringBuilder sql = new StringBuilder("select count(_id) from " + dbTableCaches + " where detailed = 1"); - String typeKey; - int reasonIndex; - if (cacheType != CacheType.ALL) { - sql.append(" and type = ?"); - typeKey = cacheType.id; - reasonIndex = 2; - } - else { - typeKey = "all_types"; - reasonIndex = 1; - } - String listKey; - if (list == PseudoList.ALL_LIST.id) { - sql.append(" and reason > 0"); - listKey = "all_list"; - } else { - sql.append(" and reason = ?"); - listKey = "list"; - } - - final String key = "CountCaches_" + typeKey + "_" + listKey; + final SQLiteStatement compiledStmnt; + synchronized (PreparedStatement.COUNT_TYPE_LIST) { + // All the statements here are used only once and are protected through the current synchronized block + if (list == PseudoList.ALL_LIST.id) { + if (cacheType == CacheType.ALL) { + compiledStmnt = PreparedStatement.COUNT_ALL_TYPES_ALL_LIST.getStatement(); + } else { + compiledStmnt = PreparedStatement.COUNT_TYPE_ALL_LIST.getStatement(); + compiledStmnt.bindString(1, cacheType.id); + } + } else { + if (cacheType == CacheType.ALL) { + compiledStmnt = PreparedStatement.COUNT_ALL_TYPES_LIST.getStatement(); + compiledStmnt.bindLong(1, list); + } else { + compiledStmnt = PreparedStatement.COUNT_TYPE_LIST.getStatement(); + compiledStmnt.bindString(1, cacheType.id); + compiledStmnt.bindLong(1, list); + } + } - final SQLiteStatement compiledStmnt = PreparedStatements.getStatement(key, sql.toString()); - if (cacheType != CacheType.ALL) { - compiledStmnt.bindString(1, cacheType.id); - } - if (list != PseudoList.ALL_LIST.id) { - compiledStmnt.bindLong(reasonIndex, list); + return (int) compiledStmnt.simpleQueryForLong(); } - return (int) compiledStmnt.simpleQueryForLong(); } catch (final Exception e) { Log.e("DataStore.loadAllStoredCachesCount", e); } @@ -2123,7 +2121,7 @@ public class DataStore { init(); try { - return (int) PreparedStatements.getCountHistoryCaches().simpleQueryForLong(); + return (int) PreparedStatement.HISTORY_COUNT.simpleQueryForLong(); } catch (final Exception e) { Log.e("DataStore.getAllHistoricCachesCount", e); } @@ -2131,6 +2129,7 @@ public class DataStore { return 0; } + @NonNull private static<T, U extends Collection<? super T>> U queryToColl(@NonNull final String table, final String[] columns, final String selection, @@ -2162,10 +2161,9 @@ public class DataStore { * * @param coords * the current coordinates to sort by distance, or null to sort by geocode - * @param cacheType - * @param listId * @return a non-null set of geocodes */ + @NonNull private static Set<String> loadBatchOfStoredGeocodes(final Geopoint coords, final CacheType cacheType, final int listId) { if (cacheType == null) { throw new IllegalArgumentException("cacheType must not be null"); @@ -2212,6 +2210,7 @@ public class DataStore { } } + @NonNull private static Set<String> loadBatchOfHistoricGeocodes(final boolean detailedOnly, final CacheType cacheType) { final StringBuilder selection = new StringBuilder("visiteddate > 0"); @@ -2243,11 +2242,13 @@ public class DataStore { } /** Retrieve all stored caches from DB */ + @NonNull public static SearchResult loadCachedInViewport(final Viewport viewport, final CacheType cacheType) { return loadInViewport(false, viewport, cacheType); } /** Retrieve stored caches from DB with listId >= 1 */ + @NonNull public static SearchResult loadStoredInViewport(final Viewport viewport, final CacheType cacheType) { return loadInViewport(true, viewport, cacheType); } @@ -2255,15 +2256,12 @@ public class DataStore { /** * Loads the geocodes of caches in a viewport from CacheCache and/or Database * - * @param stored - * True - query only stored caches, False - query cached ones as well - * @param centerLat - * @param centerLon - * @param spanLat - * @param spanLon - * @param cacheType - * @return Set with geocodes + * @param stored {@code true} to query caches stored in the database, {@code false} to also use the CacheCache + * @param viewport the viewport defining the area to scan + * @param cacheType the cache type + * @return the matching caches */ + @NonNull private static SearchResult loadInViewport(final boolean stored, final Viewport viewport, final CacheType cacheType) { final Set<String> geocodes = new HashSet<>(); @@ -2306,72 +2304,78 @@ public class DataStore { } /** - * Remove caches with listId = 0 - * - * @param more - * true = all caches false = caches stored 3 days or more before + * Remove caches with listId = 0 in the background. Once it has been executed once it will not do anything. + * This must be called from the UI thread to ensure synchronization of an internal variable. */ - public static void clean(final boolean more) { + public static void cleanIfNeeded(final Context context) { if (databaseCleaned) { return; } + databaseCleaned = true; - Log.d("Database clean: started"); + Schedulers.io().createWorker().schedule(new Action0() { + @Override + public void call() { + Log.d("Database clean: started"); + try { + final int version = Version.getVersionCode(context); + final Set<String> geocodes = new HashSet<>(); + if (version != Settings.getVersion()) { + queryToColl(dbTableCaches, + new String[]{"geocode"}, + "reason = 0", + null, + null, + null, + null, + null, + geocodes, + GET_STRING_0); + } else { + final long timestamp = System.currentTimeMillis() - DAYS_AFTER_CACHE_IS_DELETED; + final String timestampString = Long.toString(timestamp); + queryToColl(dbTableCaches, + new String[]{"geocode"}, + "reason = 0 and detailed < ? and detailedupdate < ? and visiteddate < ?", + new String[]{timestampString, timestampString, timestampString}, + null, + null, + null, + null, + geocodes, + GET_STRING_0); + } - try { - Set<String> geocodes = new HashSet<>(); - if (more) { - queryToColl(dbTableCaches, - new String[]{"geocode"}, - "reason = 0", - null, - null, - null, - null, - null, - geocodes, - GET_STRING_0); - } else { - final long timestamp = System.currentTimeMillis() - DAYS_AFTER_CACHE_IS_DELETED; - final String timestampString = Long.toString(timestamp); - queryToColl(dbTableCaches, - new String[]{"geocode"}, - "reason = 0 and detailed < ? and detailedupdate < ? and visiteddate < ?", - new String[]{timestampString, timestampString, timestampString}, - null, - null, - null, - null, - geocodes, - GET_STRING_0); - } + final Set<String> withoutOfflineLogs = exceptCachesWithOfflineLog(geocodes); + Log.d("Database clean: removing " + withoutOfflineLogs.size() + " geocaches from listId=0"); + removeCaches(withoutOfflineLogs, LoadFlags.REMOVE_ALL); - geocodes = exceptCachesWithOfflineLog(geocodes); + // This cleanup needs to be kept in place for about one year so that older log images records are + // cleaned. TO BE REMOVED AFTER 2015-03-24. + Log.d("Database clean: removing obsolete log images records"); + database.delete(dbTableLogImages, "log_id NOT IN (SELECT _id FROM " + dbTableLogs + ")", null); - if (!geocodes.isEmpty()) { - Log.d("Database clean: removing " + geocodes.size() + " geocaches from listId=0"); - removeCaches(geocodes, LoadFlags.REMOVE_ALL); - } + // Remove the obsolete "_others" directory where the user avatar used to be stored. + FileUtils.deleteDirectory(LocalStorage.getStorageDir("_others")); - // This cleanup needs to be kept in place for about one year so that older log images records are - // cleaned. TO BE REMOVED AFTER 2015-03-24. - Log.d("Database clean: removing obsolete log images records"); - database.delete(dbTableLogImages, "log_id NOT IN (SELECT _id FROM " + dbTableLogs + ")", null); - } catch (final Exception e) { - Log.w("DataStore.clean", e); - } + if (version > -1) { + Settings.setVersion(version); + } + } catch (final Exception e) { + Log.w("DataStore.clean", e); + } - Log.d("Database clean: finished"); - databaseCleaned = true; + Log.d("Database clean: finished"); + } + }); } /** * remove all geocodes from the given list of geocodes where an offline log exists * - * @param geocodes - * @return */ - private static Set<String> exceptCachesWithOfflineLog(final Set<String> geocodes) { + @NonNull + private static Set<String> exceptCachesWithOfflineLog(@NonNull final Set<String> geocodes) { if (geocodes.isEmpty()) { return geocodes; } @@ -2448,7 +2452,7 @@ public class DataStore { // Delete cache directories for (final String geocode : geocodes) { - LocalStorage.deleteDirectory(LocalStorage.getStorageDir(geocode)); + FileUtils.deleteDirectory(LocalStorage.getStorageDir(geocode)); } } } @@ -2480,6 +2484,7 @@ public class DataStore { return id != -1; } + @Nullable public static LogEntry loadLogOffline(final String geocode) { if (StringUtils.isBlank(geocode)) { return null; @@ -2528,13 +2533,11 @@ public class DataStore { init(); - final Set<String> geocodes = new HashSet<>(caches.size()); for (final Geocache cache : caches) { - geocodes.add(cache.getGeocode()); cache.setLogOffline(false); } - database.execSQL(String.format("DELETE FROM %s where %s", dbTableLogsOffline, whereGeocodeIn(geocodes))); + database.execSQL(String.format("DELETE FROM %s where %s", dbTableLogsOffline, whereGeocodeIn(Geocache.getGeocodes(caches)))); } public static boolean hasLogOffline(final String geocode) { @@ -2544,7 +2547,7 @@ public class DataStore { init(); try { - final SQLiteStatement logCount = PreparedStatements.getLogCountOfGeocode(); + final SQLiteStatement logCount = PreparedStatement.LOG_COUNT_OF_GEOCODE.getStatement(); synchronized (logCount) { logCount.bindString(1, geocode); return logCount.simpleQueryForLong() > 0; @@ -2565,8 +2568,7 @@ public class DataStore { database.beginTransaction(); try { - final SQLiteStatement setVisit = PreparedStatements.getUpdateVisitDate(); - + final SQLiteStatement setVisit = PreparedStatement.UPDATE_VISIT_DATE.getStatement(); for (final String geocode : geocodes) { setVisit.bindLong(1, visitedDate); setVisit.bindString(2, geocode); @@ -2584,7 +2586,7 @@ public class DataStore { final Resources res = CgeoApplication.getInstance().getResources(); final List<StoredList> lists = new ArrayList<>(); - lists.add(new StoredList(StoredList.STANDARD_LIST_ID, res.getString(R.string.list_inbox), (int) PreparedStatements.getCountCachesOnStandardList().simpleQueryForLong())); + lists.add(new StoredList(StoredList.STANDARD_LIST_ID, res.getString(R.string.list_inbox), (int) PreparedStatement.COUNT_CACHES_ON_STANDARD_LIST.simpleQueryForLong())); try { final String query = "SELECT l._id as _id, l.title as title, COUNT(c._id) as count" + @@ -2600,6 +2602,7 @@ public class DataStore { return lists; } + @NonNull private static ArrayList<StoredList> getListsFromCursor(final Cursor cursor) { final int indexId = cursor.getColumnIndex("_id"); final int indexTitle = cursor.getColumnIndex("title"); @@ -2613,6 +2616,7 @@ public class DataStore { }); } + @NonNull public static StoredList getList(final int id) { init(); if (id >= customListIdOffset) { @@ -2636,15 +2640,20 @@ public class DataStore { } // fall back to standard list in case of invalid list id - if (id == StoredList.STANDARD_LIST_ID || id >= customListIdOffset) { - return new StoredList(StoredList.STANDARD_LIST_ID, res.getString(R.string.list_inbox), (int) PreparedStatements.getCountCachesOnStandardList().simpleQueryForLong()); - } - - return null; + return new StoredList(StoredList.STANDARD_LIST_ID, res.getString(R.string.list_inbox), (int) PreparedStatement.COUNT_CACHES_ON_STANDARD_LIST.simpleQueryForLong()); } public static int getAllCachesCount() { - return (int) PreparedStatements.getCountAllCaches().simpleQueryForLong(); + return (int) PreparedStatement.COUNT_ALL_CACHES.simpleQueryForLong(); + } + + /** + * Count all caches in the background. + * + * @return an observable containing a unique element if the caches could be counted, or an error otherwise + */ + public static Observable<Integer> getAllCachesCountObservable() { + return allCachesCountObservable; } /** @@ -2710,7 +2719,6 @@ public class DataStore { /** * Remove a list. Caches in the list are moved to the standard list. * - * @param listId * @return true if the list got deleted, false else */ public static boolean removeList(final int listId) { @@ -2727,7 +2735,7 @@ public class DataStore { if (cnt > 0) { // move caches from deleted list to standard list - final SQLiteStatement moveToStandard = PreparedStatements.getMoveToStandardList(); + final SQLiteStatement moveToStandard = PreparedStatement.MOVE_TO_STANDARD_LIST.getStatement(); moveToStandard.bindLong(1, listId); moveToStandard.execute(); @@ -2759,7 +2767,7 @@ public class DataStore { } init(); - final SQLiteStatement move = PreparedStatements.getMoveToList(); + final SQLiteStatement move = PreparedStatement.MOVE_TO_LIST.getStatement(); database.beginTransaction(); try { @@ -2787,7 +2795,7 @@ public class DataStore { database.beginTransaction(); try { - database.delete(dbTableSearchDestionationHistory, "_id = " + destination.getId(), null); + database.delete(dbTableSearchDestinationHistory, "_id = " + destination.getId(), null); database.setTransactionSuccessful(); return true; } catch (final Exception e) { @@ -2802,9 +2810,8 @@ public class DataStore { /** * Load the lazily initialized fields of a cache and return them as partial cache (all other fields unset). * - * @param geocode - * @return */ + @NonNull public static Geocache loadCacheTexts(final String geocode) { final Geocache partial = new Geocache(); @@ -2835,7 +2842,7 @@ public class DataStore { } cursor.close(); - } catch (final SQLiteDoneException e) { + } catch (final SQLiteDoneException ignored) { // Do nothing, it only means we have no information on the cache } catch (final Exception e) { Log.e("DataStore.getCacheDescription", e); @@ -2862,11 +2869,12 @@ public class DataStore { * Creates the WHERE clause for matching multiple geocodes. This automatically converts all given codes to * UPPERCASE. */ + @NonNull private static StringBuilder whereGeocodeIn(final Collection<String> geocodes) { final StringBuilder whereExpr = new StringBuilder("geocode in ("); final Iterator<String> iterator = geocodes.iterator(); while (true) { - whereExpr.append(DatabaseUtils.sqlEscapeString(StringUtils.upperCase(iterator.next()))); + DatabaseUtils.appendEscapedSQLString(whereExpr, StringUtils.upperCase(iterator.next())); if (!iterator.hasNext()) { break; } @@ -2878,12 +2886,9 @@ public class DataStore { /** * Loads all Waypoints in the coordinate rectangle. * - * @param excludeDisabled - * @param excludeMine - * @param type - * @return */ + @NonNull public static Set<Waypoint> loadWaypoints(final Viewport viewport, final boolean excludeMine, final boolean excludeDisabled, final CacheType type) { final StringBuilder where = buildCoordinateWhere(dbTableWaypoints, viewport); if (excludeMine) { @@ -2914,103 +2919,71 @@ public class DataStore { } public static void saveChangedCache(final Geocache cache) { - DataStore.saveCache(cache, cache.getStorageLocation().contains(StorageLocation.DATABASE) ? LoadFlags.SAVE_ALL : EnumSet.of(SaveFlag.CACHE)); + DataStore.saveCache(cache, cache.inDatabase() ? LoadFlags.SAVE_ALL : EnumSet.of(SaveFlag.CACHE)); } - private static class PreparedStatements { - - private static HashMap<String, SQLiteStatement> statements = new HashMap<>(); - - public static SQLiteStatement getMoveToStandardList() { - return getStatement("MoveToStandardList", "UPDATE " + dbTableCaches + " SET reason = " + StoredList.STANDARD_LIST_ID + " WHERE reason = ?"); - } + private static enum PreparedStatement { - public static SQLiteStatement getMoveToList() { - return getStatement("MoveToList", "UPDATE " + dbTableCaches + " SET reason = ? WHERE geocode = ?"); - } + HISTORY_COUNT("SELECT COUNT(_id) FROM " + dbTableCaches + " WHERE visiteddate > 0"), + MOVE_TO_STANDARD_LIST("UPDATE " + dbTableCaches + " SET reason = " + StoredList.STANDARD_LIST_ID + " WHERE reason = ?"), + MOVE_TO_LIST("UPDATE " + dbTableCaches + " SET reason = ? WHERE geocode = ?"), + UPDATE_VISIT_DATE("UPDATE " + dbTableCaches + " SET visiteddate = ? WHERE geocode = ?"), + INSERT_LOG_IMAGE("INSERT INTO " + dbTableLogImages + " (log_id, title, url) VALUES (?, ?, ?)"), + INSERT_LOG_COUNTS("INSERT INTO " + dbTableLogCount + " (geocode, updated, type, count) VALUES (?, ?, ?, ?)"), + INSERT_SPOILER("INSERT INTO " + dbTableSpoilers + " (geocode, updated, url, title, description) VALUES (?, ?, ?, ?, ?)"), + LOG_COUNT_OF_GEOCODE("SELECT count(_id) FROM " + DataStore.dbTableLogsOffline + " WHERE geocode = ?"), + COUNT_CACHES_ON_STANDARD_LIST("SELECT count(_id) FROM " + dbTableCaches + " WHERE reason = " + StoredList.STANDARD_LIST_ID), + COUNT_ALL_CACHES("SELECT count(_id) FROM " + dbTableCaches + " WHERE reason >= " + StoredList.STANDARD_LIST_ID), + INSERT_LOG("INSERT INTO " + dbTableLogs + " (geocode, updated, type, author, log, date, found, friend) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"), + INSERT_ATTRIBUTE("INSERT INTO " + dbTableAttributes + " (geocode, updated, attribute) VALUES (?, ?, ?)"), + LIST_ID_OF_GEOCODE("SELECT reason FROM " + dbTableCaches + " WHERE geocode = ?"), + LIST_ID_OF_GUID("SELECT reason FROM " + dbTableCaches + " WHERE guid = ?"), + GEOCODE_OF_GUID("SELECT geocode FROM " + dbTableCaches + " WHERE guid = ?"), + INSERT_SEARCH_DESTINATION("INSERT INTO " + dbTableSearchDestinationHistory + " (date, latitude, longitude) VALUES (?, ?, ?)"), + COUNT_TYPE_ALL_LIST("SELECT COUNT(_id) FROM " + dbTableCaches + " WHERE detailed = 1 AND type = ? AND reason > 0"), // See use of COUNT_TYPE_LIST for synchronization + COUNT_ALL_TYPES_ALL_LIST("SELECT COUNT(_id) FROM " + dbTableCaches + " WHERE detailed = 1 AND reason > 0"), // See use of COUNT_TYPE_LIST for synchronization + COUNT_TYPE_LIST("SELECT COUNT(_id) FROM " + dbTableCaches + " WHERE detailed = 1 AND type = ? AND reason = ?"), + COUNT_ALL_TYPES_LIST("SELECT COUNT(_id) FROM " + dbTableCaches + " WHERE detailed = 1 AND reason = ?"), // See use of COUNT_TYPE_LIST for synchronization + CHECK_IF_PRESENT("SELECT COUNT(*) FROM " + dbTableCaches + " WHERE geocode = ?"); - public static SQLiteStatement getUpdateVisitDate() { - return getStatement("UpdateVisitDate", "UPDATE " + dbTableCaches + " SET visiteddate = ? WHERE geocode = ?"); - } + private static final List<PreparedStatement> statements = new ArrayList<>(); - public static SQLiteStatement getInsertLogImage() { - return getStatement("InsertLogImage", "INSERT INTO " + dbTableLogImages + " (log_id, title, url) VALUES (?, ?, ?)"); - } + @Nullable + private volatile SQLiteStatement statement = null; // initialized lazily + final String query; - public static SQLiteStatement getInsertLogCounts() { - return getStatement("InsertLogCounts", "INSERT INTO " + dbTableLogCount + " (geocode, updated, type, count) VALUES (?, ?, ?, ?)"); + PreparedStatement(final String query) { + this.query = query; } - public static SQLiteStatement getInsertSpoiler() { - return getStatement("InsertSpoiler", "INSERT INTO " + dbTableSpoilers + " (geocode, updated, url, title, description) VALUES (?, ?, ?, ?, ?)"); + public long simpleQueryForLong() { + return getStatement().simpleQueryForLong(); } - public static SQLiteStatement getInsertSearchDestination(final Destination destination) { - final SQLiteStatement statement = getStatement("InsertSearch", "INSERT INTO " + dbTableSearchDestionationHistory + " (date, latitude, longitude) VALUES (?, ?, ?)"); - statement.bindLong(1, destination.getDate()); - final Geopoint coords = destination.getCoords(); - statement.bindDouble(2, coords.getLatitude()); - statement.bindDouble(3, coords.getLongitude()); + private SQLiteStatement getStatement() { + if (statement == null) { + synchronized (statements) { + if (statement == null) { + init(); + statement = database.compileStatement(query); + statements.add(this); + } + } + } return statement; } private static void clearPreparedStatements() { - for (final SQLiteStatement statement : statements.values()) { - statement.close(); + for (final PreparedStatement preparedStatement : statements) { + final SQLiteStatement statement = preparedStatement.statement; + if (statement != null) { + statement.close(); + preparedStatement.statement = null; + } } statements.clear(); } - private static synchronized SQLiteStatement getStatement(final String key, final String query) { - SQLiteStatement statement = statements.get(key); - if (statement == null) { - init(); - statement = database.compileStatement(query); - statements.put(key, statement); - } - return statement; - } - - public static SQLiteStatement getCountHistoryCaches() { - return getStatement("HistoryCount", "select count(_id) from " + dbTableCaches + " where visiteddate > 0"); - } - - private static SQLiteStatement getLogCountOfGeocode() { - return getStatement("LogCountFromGeocode", "SELECT count(_id) FROM " + DataStore.dbTableLogsOffline + " WHERE geocode = ?"); - } - - private static SQLiteStatement getCountCachesOnStandardList() { - return getStatement("CountStandardList", "SELECT count(_id) FROM " + dbTableCaches + " WHERE reason = " + StoredList.STANDARD_LIST_ID); - } - - private static SQLiteStatement getCountAllCaches() { - return getStatement("CountAllLists", "SELECT count(_id) FROM " + dbTableCaches + " WHERE reason >= " + StoredList.STANDARD_LIST_ID); - } - - private static SQLiteStatement getInsertLog() { - return getStatement("InsertLog", "INSERT INTO " + dbTableLogs + " (geocode, updated, type, author, log, date, found, friend) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"); - } - - private static SQLiteStatement getInsertAttribute() { - return getStatement("InsertAttribute", "INSERT INTO " + dbTableAttributes + " (geocode, updated, attribute) VALUES (?, ?, ?)"); - } - - private static SQLiteStatement getListIdOfGeocode() { - return getStatement("listFromGeocode", "SELECT reason FROM " + dbTableCaches + " WHERE geocode = ?"); - } - - private static SQLiteStatement getListIdOfGuid() { - return getStatement("listFromGeocode", "SELECT reason FROM " + dbTableCaches + " WHERE guid = ?"); - } - - private static SQLiteStatement getCacheIdOfGeocode() { - return getStatement("cacheIdFromGeocode", "SELECT cacheid FROM " + dbTableCaches + " WHERE geocode = ?"); - } - - private static SQLiteStatement getGeocodeOfGuid() { - return getStatement("geocodeFromGuid", "SELECT geocode FROM " + dbTableCaches + " WHERE guid = ?"); - } - } public static void saveVisitDate(final String geocode) { @@ -3018,9 +2991,10 @@ public class DataStore { } public static void markDropped(final List<Geocache> caches) { - moveToList(caches, StoredList.TEMPORARY_LIST_ID); + moveToList(caches, StoredList.TEMPORARY_LIST.id); } + @Nullable public static Viewport getBounds(final String geocode) { if (geocode == null) { return null; @@ -3033,11 +3007,13 @@ public class DataStore { setVisitDate(Arrays.asList(selected), 0); } + @NonNull public static SearchResult getBatchOfStoredCaches(final Geopoint coords, final CacheType cacheType, final int listId) { final Set<String> geocodes = DataStore.loadBatchOfStoredGeocodes(coords, cacheType, listId); return new SearchResult(geocodes, DataStore.getAllStoredCachesCount(cacheType, listId)); } + @NonNull public static SearchResult getHistoryOfCaches(final boolean detailedOnly, final CacheType cacheType) { final Set<String> geocodes = DataStore.loadBatchOfHistoricGeocodes(detailedOnly, cacheType); return new SearchResult(geocodes, DataStore.getAllHistoryCachesCount()); @@ -3051,6 +3027,7 @@ public class DataStore { return false; } + @NonNull public static Set<String> getCachedMissingFromSearch(final SearchResult searchResult, final Set<Tile> tiles, final IConnector connector, final int maxZoom) { // get cached CacheListActivity @@ -3081,6 +3058,7 @@ public class DataStore { return missingFromSearch; } + @Nullable public static Cursor findSuggestions(final String searchTerm) { // require 3 characters, otherwise there are to many results if (StringUtils.length(searchTerm) < 3) { @@ -3116,6 +3094,7 @@ public class DataStore { cursor.close(); } + @NonNull private static String getSuggestionArgument(final String input) { return "%" + StringUtils.trim(input) + "%"; } @@ -3143,6 +3122,7 @@ public class DataStore { cursor.close(); } + @NonNull public static String[] getSuggestions(final String table, final String column, final String input) { try { final Cursor cursor = database.rawQuery("SELECT DISTINCT " + column @@ -3152,26 +3132,31 @@ public class DataStore { return cursorToColl(cursor, new LinkedList<String>(), GET_STRING_0).toArray(new String[cursor.getCount()]); } catch (final RuntimeException e) { Log.e("cannot get suggestions from " + table + "->" + column + " for input '" + input + "'", e); - return new String[0]; + return ArrayUtils.EMPTY_STRING_ARRAY; } } + @NonNull public static String[] getSuggestionsOwnerName(final String input) { return getSuggestions(dbTableCaches, "owner_real", input); } + @NonNull public static String[] getSuggestionsTrackableCode(final String input) { return getSuggestions(dbTableTrackables, "tbcode", input); } + @NonNull public static String[] getSuggestionsFinderName(final String input) { return getSuggestions(dbTableLogs, "author", input); } + @NonNull public static String[] getSuggestionsGeocode(final String input) { return getSuggestions(dbTableCaches, "geocode", input); } + @NonNull public static String[] getSuggestionsKeyword(final String input) { return getSuggestions(dbTableCaches, "name", input); } @@ -3180,6 +3165,7 @@ public class DataStore { * * @return list of last caches opened in the details view, ordered by most recent first */ + @NonNull public static ArrayList<Geocache> getLastOpenedCaches() { final List<String> geocodes = Settings.getLastOpenedCaches(); final Set<Geocache> cachesSet = DataStore.loadCaches(geocodes, LoadFlags.LOAD_CACHE_OR_DB); |