diff --git a/src/main/java/org/mtransit/android/commons/data/Schedule.java b/src/main/java/org/mtransit/android/commons/data/Schedule.java index 99881f32..6f1f9b18 100644 --- a/src/main/java/org/mtransit/android/commons/data/Schedule.java +++ b/src/main/java/org/mtransit/android/commons/data/Schedule.java @@ -4,6 +4,7 @@ import android.database.Cursor; import android.text.TextUtils; +import androidx.annotation.Discouraged; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; @@ -464,6 +465,17 @@ public void setResetHeadsign() { this.headsignValue = null; } + @Discouraged(message = "should call getHeadsign()") + @Nullable + public String getHeadsignValue() { + return headsignValue; + } + + @Discouraged(message = "should call setHeadsign()") + public void setHeadsignValue(@Nullable String headsignValue) { + this.headsignValue = headsignValue; + } + public boolean hasHeadsign() { if (this.headsignType == Direction.HEADSIGN_TYPE_NO_PICKUP) { return true; diff --git a/src/main/java/org/mtransit/android/commons/provider/GTFSPOIProvider.java b/src/main/java/org/mtransit/android/commons/provider/GTFSPOIProvider.java index 1487920a..a87b0a93 100644 --- a/src/main/java/org/mtransit/android/commons/provider/GTFSPOIProvider.java +++ b/src/main/java/org/mtransit/android/commons/provider/GTFSPOIProvider.java @@ -23,12 +23,12 @@ @SuppressWarnings("WeakerAccess") public class GTFSPOIProvider implements MTLog.Loggable { - private static final String TAG = GTFSPOIProvider.class.getSimpleName(); + private static final String LOG_TAG = GTFSPOIProvider.class.getSimpleName(); @NonNull @Override public String getLogTag() { - return TAG; + return LOG_TAG; } public static void append(@NonNull UriMatcher uriMatcher, @NonNull String authority) { @@ -125,15 +125,15 @@ public static Cursor getPOIFromDB(@NonNull GTFSProvider provider, @Nullable POIP poiProjection = ArrayUtils.addAllNonNull(poiProjection, new String[]{POIProviderContract.Columns.T_POI_K_SCORE_META_OPT}); } if (poiProjection.length != poiProjectionMap.size()) { - MTLog.w(TAG, "getPOIFromDB() > different projection sizes (%d VS %d)", poiProjection.length, poiProjectionMap.size()); + MTLog.w(LOG_TAG, "getPOIFromDB() > different projection sizes (%d VS %d)", poiProjection.length, poiProjectionMap.size()); if (Constants.DEBUG) { - MTLog.w(TAG, "getPOIFromDB() > poiProjection: %d", poiProjection.length); + MTLog.w(LOG_TAG, "getPOIFromDB() > poiProjection: %d", poiProjection.length); for (String string : poiProjection) { - MTLog.w(TAG, "getPOIFromDB() > poiProjection: - %s.", string); + MTLog.w(LOG_TAG, "getPOIFromDB() > poiProjection: - %s.", string); } - MTLog.w(TAG, "getPOIFromDB() > poiProjectionMap: %d", poiProjectionMap.size()); + MTLog.w(LOG_TAG, "getPOIFromDB() > poiProjectionMap: %d", poiProjectionMap.size()); for (Map.Entry keyValue : poiProjectionMap.entrySet()) { - MTLog.w(TAG, "getPOIFromDB() > poiProjectionMap: - %s: %s.", keyValue.getKey(), keyValue.getValue()); + MTLog.w(LOG_TAG, "getPOIFromDB() > poiProjectionMap: - %s: %s.", keyValue.getKey(), keyValue.getValue()); } } } @@ -147,7 +147,7 @@ public static Cursor getPOIFromDB(@NonNull GTFSProvider provider, @Nullable POIP } return qb.query(provider.getReadDB(), poiProjection, selection, null, groupBy, null, sortOrder, null); } catch (Exception e) { - MTLog.w(TAG, e, "Error while loading POIs '%s'!", poiFilter); + MTLog.w(LOG_TAG, e, "Error while loading POIs '%s'!", poiFilter); return null; } } diff --git a/src/main/java/org/mtransit/android/commons/provider/GTFSProviderDbHelper.java b/src/main/java/org/mtransit/android/commons/provider/GTFSProviderDbHelper.java index d0fbd930..bd25ba6b 100644 --- a/src/main/java/org/mtransit/android/commons/provider/GTFSProviderDbHelper.java +++ b/src/main/java/org/mtransit/android/commons/provider/GTFSProviderDbHelper.java @@ -1,5 +1,7 @@ package org.mtransit.android.commons.provider; +import static org.mtransit.android.commons.provider.gtfs.GTFSProviderDBHelperUtils.initDbTableWithRetry; + import android.annotation.SuppressLint; import android.content.Context; import android.database.sqlite.SQLiteDatabase; @@ -8,7 +10,6 @@ import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationManagerCompat; -import org.mtransit.android.commons.FileUtils; import org.mtransit.android.commons.MTLog; import org.mtransit.android.commons.NotificationUtils; import org.mtransit.android.commons.PackageManagerUtils; @@ -19,9 +20,10 @@ import org.mtransit.commons.GTFSCommons; import org.mtransit.commons.sql.SQLUtils; -import java.io.BufferedReader; -import java.io.InputStream; -import java.io.InputStreamReader; +import java.util.HashMap; +import java.util.Map; + +import kotlin.Unit; public class GTFSProviderDbHelper extends MTSQLiteOpenHelper { @@ -38,6 +40,13 @@ public String getLogTag() { */ public static final String DB_NAME = "gtfs_rts.db"; // do not change to avoid breaking compat w/ old modules + static final String T_STRINGS = GTFSCommons.T_STRINGS; + static final String T_STRINGS_K_ID = GTFSCommons.T_STRINGS_K_ID; + static final String T_STRINGS_K_STRING = GTFSCommons.T_STRINGS_K_STRING; + private static final String T_STRINGS_SQL_CREATE = GTFSCommons.getT_STRINGS_SQL_CREATE(); + private static final String T_STRINGS_SQL_INSERT = GTFSCommons.getT_STRINGS_SQL_INSERT(); + private static final String T_STRINGS_SQL_DROP = GTFSCommons.getT_STRINGS_SQL_DROP(); + static final String T_ROUTE = GTFSCommons.T_ROUTE; static final String T_ROUTE_K_ID = GTFSCommons.T_ROUTE_K_ID; static final String T_ROUTE_K_SHORT_NAME = GTFSCommons.T_ROUTE_K_SHORT_NAME; @@ -45,6 +54,7 @@ public String getLogTag() { static final String T_ROUTE_K_COLOR = GTFSCommons.T_ROUTE_K_COLOR; static final String T_ROUTE_K_ORIGINAL_ID_HASH = GTFSCommons.T_ROUTE_K_ORIGINAL_ID_HASH; static final String T_ROUTE_K_TYPE = GTFSCommons.T_ROUTE_K_TYPE; + private static final int[] T_ROUTE_STRINGS_COLUMN_IDX = GTFSCommons.T_ROUTE_STRINGS_COLUMN_IDX; private static final String T_ROUTE_SQL_CREATE = GTFSCommons.getT_ROUTE_SQL_CREATE(); private static final String T_ROUTE_SQL_INSERT = GTFSCommons.getT_ROUTE_SQL_INSERT(); private static final String T_ROUTE_SQL_DROP = GTFSCommons.getT_ROUTE_SQL_DROP(); @@ -54,6 +64,7 @@ public String getLogTag() { static final String T_DIRECTION_K_HEADSIGN_TYPE = GTFSCommons.T_DIRECTION_K_HEADSIGN_TYPE; static final String T_DIRECTION_K_HEADSIGN_VALUE = GTFSCommons.T_DIRECTION_K_HEADSIGN_VALUE; // really? static final String T_DIRECTION_K_ROUTE_ID = GTFSCommons.T_DIRECTION_K_ROUTE_ID; + private static final int[] T_DIRECTION_STRINGS_COLUMN_IDX = GTFSCommons.T_DIRECTION_STRINGS_COLUMN_IDX; private static final String T_DIRECTION_SQL_CREATE = GTFSCommons.getT_DIRECTION_SQL_CREATE(); private static final String T_DIRECTION_SQL_INSERT = GTFSCommons.getT_DIRECTION_SQL_INSERT(); private static final String T_DIRECTION_SQL_DROP = GTFSCommons.getT_DIRECTION_SQL_DROP(); @@ -66,6 +77,7 @@ public String getLogTag() { static final String T_STOP_K_LNG = GTFSCommons.T_STOP_K_LNG; static final String T_STOP_K_ACCESSIBLE = GTFSCommons.T_STOP_K_ACCESSIBLE; static final String T_STOP_K_ORIGINAL_ID_HASH = GTFSCommons.T_STOP_K_ORIGINAL_ID_HASH; + private static final int[] T_STOP_STRINGS_COLUMN_IDX = GTFSCommons.T_STOP_STRINGS_COLUMN_IDX; private static final String T_STOP_SQL_CREATE = GTFSCommons.getT_STOP_SQL_CREATE(); private static final String T_STOP_SQL_INSERT = GTFSCommons.getT_STOP_SQL_INSERT(); private static final String T_STOP_SQL_DROP = GTFSCommons.getT_STOP_SQL_DROP(); @@ -147,11 +159,12 @@ public boolean isDbExist(@NonNull Context context) { return SqlUtils.isDbExist(context, DB_NAME); } - @SuppressLint("MissingPermission") // no notification if not permitted (not requesting permission) + @SuppressLint("MissingPermission") // no notification if not permitted (not requesting permission for that) private void initAllDbTables(@NonNull SQLiteDatabase db, boolean upgrade) { MTLog.i(this, "Data: deploying DB..."); - int nId = TimeUtils.currentTimeSec(); - int nbTotalOperations = 7; + final int nId = TimeUtils.currentTimeSec(); + final int nbTotalOperations = 8; + int progress = 0; final NotificationManagerCompat nm = NotificationManagerCompat.from(this.context); final boolean notifEnabled = nm.areNotificationsEnabled(); final NotificationCompat.Builder nb; @@ -167,96 +180,40 @@ private void initAllDbTables(@NonNull SQLiteDatabase db, boolean upgrade) { nb = null; } db.execSQL(SQLUtils.PRAGMA_AUTO_VACUUM_NONE); - if (notifEnabled) { - NotificationUtils.setProgressAndNotify(nm, nb, nId, nbTotalOperations, 0); - } - initDbTableWithRetry(db, T_ROUTE, T_ROUTE_SQL_CREATE, T_ROUTE_SQL_INSERT, T_ROUTE_SQL_DROP, getRouteFiles()); - if (notifEnabled) { - NotificationUtils.setProgressAndNotify(nm, nb, nId, nbTotalOperations, 1); - } - initDbTableWithRetry(db, T_DIRECTION, T_DIRECTION_SQL_CREATE, T_DIRECTION_SQL_INSERT, T_DIRECTION_SQL_DROP, getDirectionFiles()); - if (notifEnabled) { - NotificationUtils.setProgressAndNotify(nm, nb, nId, nbTotalOperations, 2); - } - initDbTableWithRetry(db, T_STOP, T_STOP_SQL_CREATE, T_STOP_SQL_INSERT, T_STOP_SQL_DROP, getStopFiles()); - if (notifEnabled) { - NotificationUtils.setProgressAndNotify(nm, nb, nId, nbTotalOperations, 3); - } - initDbTableWithRetry(db, T_DIRECTION_STOPS, T_DIRECTION_STOPS_SQL_CREATE, T_DIRECTION_STOPS_SQL_INSERT, T_DIRECTION_STOPS_SQL_DROP, getDirectionStopsFiles()); - if (notifEnabled) { - NotificationUtils.setProgressAndNotify(nm, nb, nId, nbTotalOperations, 4); - } - initDbTableWithRetry(db, T_SERVICE_IDS, T_SERVICE_IDS_SQL_CREATE, T_SERVICE_IDS_SQL_INSERT, T_SERVICE_IDS_SQL_DROP, getServiceIdsFiles()); - if (notifEnabled) { - NotificationUtils.setProgressAndNotify(nm, nb, nId, nbTotalOperations, 5); + if (notifEnabled) NotificationUtils.setProgressAndNotify(nm, nb, nId, nbTotalOperations, progress++); + final Map allStrings = new HashMap<>(); + if (FeatureFlags.F_EXPORT_STRINGS) { + initDbTableWithRetry(context, db, T_STRINGS, T_STRINGS_SQL_CREATE, T_STRINGS_SQL_INSERT, T_STRINGS_SQL_DROP, getStringsFiles(), null, null, + (id, string) -> { + allStrings.put(id, string); + return Unit.INSTANCE; + } + ); // 1st } - initDbTableWithRetry(db, T_SERVICE_DATES, T_SERVICE_DATES_SQL_CREATE, T_SERVICE_DATES_SQL_INSERT, T_SERVICE_DATES_SQL_DROP, getServiceDatesFiles()); - if (notifEnabled) { - NotificationUtils.setProgressAndNotify(nm, nb, nId, nbTotalOperations, 6); + if (notifEnabled) NotificationUtils.setProgressAndNotify(nm, nb, nId, nbTotalOperations, progress++); + initDbTableWithRetry(context, db, T_ROUTE, T_ROUTE_SQL_CREATE, T_ROUTE_SQL_INSERT, T_ROUTE_SQL_DROP, getRouteFiles(), allStrings, T_ROUTE_STRINGS_COLUMN_IDX); + if (notifEnabled) NotificationUtils.setProgressAndNotify(nm, nb, nId, nbTotalOperations, progress++); + initDbTableWithRetry(context, db, T_DIRECTION, T_DIRECTION_SQL_CREATE, T_DIRECTION_SQL_INSERT, T_DIRECTION_SQL_DROP, getDirectionFiles(), allStrings, T_DIRECTION_STRINGS_COLUMN_IDX); + if (notifEnabled) NotificationUtils.setProgressAndNotify(nm, nb, nId, nbTotalOperations, progress++); + initDbTableWithRetry(context, db, T_STOP, T_STOP_SQL_CREATE, T_STOP_SQL_INSERT, T_STOP_SQL_DROP, getStopFiles(), allStrings, T_STOP_STRINGS_COLUMN_IDX); + if (notifEnabled) NotificationUtils.setProgressAndNotify(nm, nb, nId, nbTotalOperations, progress++); + initDbTableWithRetry(context, db, T_DIRECTION_STOPS, T_DIRECTION_STOPS_SQL_CREATE, T_DIRECTION_STOPS_SQL_INSERT, T_DIRECTION_STOPS_SQL_DROP, getDirectionStopsFiles()); + if (notifEnabled) NotificationUtils.setProgressAndNotify(nm, nb, nId, nbTotalOperations, progress++); + if (FeatureFlags.F_EXPORT_SERVICE_ID_INTS) { + initDbTableWithRetry(context, db, T_SERVICE_IDS, T_SERVICE_IDS_SQL_CREATE, T_SERVICE_IDS_SQL_INSERT, T_SERVICE_IDS_SQL_DROP, getServiceIdsFiles()); } + if (notifEnabled) NotificationUtils.setProgressAndNotify(nm, nb, nId, nbTotalOperations, progress++); + initDbTableWithRetry(context, db, T_SERVICE_DATES, T_SERVICE_DATES_SQL_CREATE, T_SERVICE_DATES_SQL_INSERT, T_SERVICE_DATES_SQL_DROP, getServiceDatesFiles()); + if (notifEnabled) NotificationUtils.setProgressAndNotify(nm, nb, nId, nbTotalOperations, progress++); db.execSQL(T_ROUTE_DIRECTION_STOP_STATUS_SQL_CREATE); if (notifEnabled) { nb.setSmallIcon(android.R.drawable.stat_notify_sync_noanim); // - NotificationUtils.setProgressAndNotify(nm, nb, nId, nbTotalOperations, 7); + NotificationUtils.setProgressAndNotify(nm, nb, nId, nbTotalOperations, progress); nm.cancel(nId); } MTLog.i(this, "Data: deploying DB... DONE"); } - private void initDbTableWithRetry(@NonNull SQLiteDatabase db, String table, String sqlCreate, String sqlInsert, String sqlDrop, int[] files) { - boolean success; - do { - try { - success = initDbTable(db, table, sqlCreate, sqlInsert, sqlDrop, files); - } catch (Exception e) { - MTLog.w(this, e, "Error while deploying DB table %s!", table); - success = false; - } - } while (!success); - } - - private boolean initDbTable(@NonNull SQLiteDatabase db, String table, String sqlCreate, String sqlInsert, String sqlDrop, int[] files) { - try { - db.beginTransaction(); - db.execSQL(sqlDrop); // drop if exists - db.execSQL(sqlCreate); // create if not exists - String line; - BufferedReader br = null; - InputStreamReader isr = null; - InputStream is = null; - for (int file : files) { - try { - is = this.context.getResources().openRawResource(file); - isr = new InputStreamReader(is, FileUtils.getUTF8()); - br = new BufferedReader(isr, 8192); - while ((line = br.readLine()) != null) { - String sql = String.format(sqlInsert, line); - try { - db.execSQL(sql); - } catch (Exception e) { - MTLog.w(this, e, "ERROR while executing '%s' on database '%s' table '%s' file '%s'!", sql, DB_NAME, table, file); - throw e; - } - } - } catch (Exception e) { - MTLog.w(this, e, "ERROR while copying the database '%s' table '%s' file '%s'!", DB_NAME, table, file); - return false; - } finally { - FileUtils.closeQuietly(br); - FileUtils.closeQuietly(isr); - FileUtils.closeQuietly(is); - } - } - db.setTransactionSuccessful(); - return true; - } catch (Exception e) { - MTLog.w(this, e, "ERROR while copying the database '%s' table '%s' file!", DB_NAME, table); - return false; - } finally { - SqlUtils.endTransactionQuietly(db); - } - } - /** * Override if multiple {@link GTFSProviderDbHelper} implementations in same app. */ @@ -272,6 +229,21 @@ private int[] getServiceIdsFiles() { } } + /** + * Override if multiple {@link GTFSProviderDbHelper} implementations in same app. + */ + private int[] getStringsFiles() { + if (GTFSCurrentNextProvider.hasCurrentData(context)) { + if (GTFSCurrentNextProvider.isNextData(context)) { + return new int[]{R.raw.next_gtfs_strings}; + } else { // CURRENT = default + return new int[]{R.raw.current_gtfs_strings}; + } + } else { + return new int[]{R.raw.gtfs_strings}; + } + } + /** * Override if multiple {@link GTFSProviderDbHelper} implementations in same app. */ diff --git a/src/main/java/org/mtransit/android/commons/provider/GTFSRDSProvider.java b/src/main/java/org/mtransit/android/commons/provider/GTFSRDSProvider.java index 5367ac7e..9614c705 100644 --- a/src/main/java/org/mtransit/android/commons/provider/GTFSRDSProvider.java +++ b/src/main/java/org/mtransit/android/commons/provider/GTFSRDSProvider.java @@ -20,12 +20,12 @@ public class GTFSRDSProvider implements MTLog.Loggable { - private static final String TAG = GTFSRDSProvider.class.getSimpleName(); + private static final String LOG_TAG = GTFSRDSProvider.class.getSimpleName(); @NonNull @Override public String getLogTag() { - return TAG; + return LOG_TAG; } protected static final int ROUTES = 1; @@ -223,13 +223,13 @@ public static Cursor queryS(@NonNull GTFSProvider provider, @NonNull Uri uri, @N if (TextUtils.isEmpty(sortOrder)) { sortOrder = provider.getSortOrder(uri); } - Cursor cursor = qb.query(provider.getReadDB(), projection, selection, selectionArgs, null, null, sortOrder, null); + final Cursor cursor = qb.query(provider.getReadDB(), projection, selection, selectionArgs, null, null, sortOrder, null); if (cursor != null) { cursor.setNotificationUri(provider.requireContextCompat().getContentResolver(), uri); } return cursor; } catch (Exception e) { - MTLog.w(TAG, e, "Error while resolving query '%s'!", uri); + MTLog.w(LOG_TAG, e, "Error while resolving query '%s'!", uri); return null; } } diff --git a/src/main/java/org/mtransit/android/commons/provider/GTFSScheduleTimestampsProvider.java b/src/main/java/org/mtransit/android/commons/provider/GTFSScheduleTimestampsProvider.java index 9ea8c06c..a94b2368 100644 --- a/src/main/java/org/mtransit/android/commons/provider/GTFSScheduleTimestampsProvider.java +++ b/src/main/java/org/mtransit/android/commons/provider/GTFSScheduleTimestampsProvider.java @@ -15,10 +15,11 @@ import org.mtransit.android.commons.data.Schedule; import org.mtransit.android.commons.data.ScheduleTimestamps; import org.mtransit.android.commons.provider.agency.AgencyUtils; +import org.mtransit.commons.FeatureFlags; import java.util.ArrayList; import java.util.Calendar; -import java.util.HashSet; +import java.util.Set; import java.util.TimeZone; import java.util.concurrent.TimeUnit; @@ -48,7 +49,7 @@ static ScheduleTimestamps getScheduleTimestamps(@NonNull GTFSProvider provider, final TimeZone timeZone = TimeZone.getTimeZone(AgencyUtils.getRDSAgencyTimeZone(context)); final Calendar startsAt = TimeUtils.getNewCalendar(timeZone, startsAtInMs); startsAt.add(Calendar.DATE, -1); // starting yesterday - HashSet dayTimestamps; + Set dayTimestamps; String lookupDayTime; String lookupDayDate; int dataRequests = 0; @@ -106,6 +107,9 @@ static ScheduleTimestamps getScheduleTimestamps(@NonNull GTFSProvider provider, } startsAt.add(Calendar.DATE, +1); // NEXT DAY } + if (FeatureFlags.F_EXPORT_STRINGS) { + allTimestamps = GTFSStringsUtils.updateStrings(allTimestamps, provider); + } ScheduleTimestamps scheduleTimestamps = new ScheduleTimestamps(rds.getUUID(), startsAtInMs, endsAtInMs); scheduleTimestamps.setSourceLabel(GTFSProvider.getSOURCE_LABEL(provider.requireContextCompat())); scheduleTimestamps.setTimestampsAndSort(allTimestamps); diff --git a/src/main/java/org/mtransit/android/commons/provider/GTFSStatusProvider.java b/src/main/java/org/mtransit/android/commons/provider/GTFSStatusProvider.java index 6b7b064e..2913e306 100644 --- a/src/main/java/org/mtransit/android/commons/provider/GTFSStatusProvider.java +++ b/src/main/java/org/mtransit/android/commons/provider/GTFSStatusProvider.java @@ -252,7 +252,7 @@ private static ArrayList findTimestamps(@NonNull GTFSProvide } } now.add(Calendar.DATE, -1); // starting yesterday - HashSet dayTimestamps; + Set dayTimestamps; String lookupDayTime; String lookupDayDate; int nbTimestamps = 0; @@ -310,6 +310,9 @@ private static ArrayList findTimestamps(@NonNull GTFSProvide } now.add(Calendar.DATE, +1); // NEXT DAY } + if (FeatureFlags.F_EXPORT_STRINGS) { + allTimestamps = GTFSStringsUtils.updateStrings(allTimestamps, provider); + } return allTimestamps; } @@ -349,14 +352,16 @@ private static String getSTOP_SCHEDULE_RAW_FILE_FORMAT(@NonNull Context context) private static final int GTFS_SCHEDULE_STOP_FILE_COL_ACCESSIBLE_IDX = 5; @NonNull - static HashSet findScheduleList(@NonNull GTFSProvider provider, - @SuppressWarnings("unused") long routeId, - long directionId, - int stopId, - String dateS, String timeS, - long diffWithRealityInMs) { + static Set findScheduleList( + @NonNull GTFSProvider provider, + @SuppressWarnings("unused") long routeId, + long directionId, + int stopId, + String dateS, String timeS, + long diffWithRealityInMs + ) { final int timeI = Integer.parseInt(timeS); - HashSet result = new HashSet<>(); + Set result = new HashSet<>(); final Set> serviceIdOrIntAndExceptionTypes = findServicesAndExceptionTypes(provider, dateS); final Set serviceIdOrInts = filterServiceIdOrInts(serviceIdOrIntAndExceptionTypes, diffWithRealityInMs > 0L); BufferedReader br = null; diff --git a/src/main/java/org/mtransit/android/commons/provider/GTFSStringsUtils.kt b/src/main/java/org/mtransit/android/commons/provider/GTFSStringsUtils.kt new file mode 100644 index 00000000..b3fa7b2a --- /dev/null +++ b/src/main/java/org/mtransit/android/commons/provider/GTFSStringsUtils.kt @@ -0,0 +1,107 @@ +package org.mtransit.android.commons.provider + +import android.database.Cursor +import org.mtransit.android.commons.MTLog +import org.mtransit.android.commons.data.Schedule +import org.mtransit.commons.GTFSCommons +import org.mtransit.commons.sql.SQLUtils +import org.mtransit.commons.sql.SQLUtils.quotes +import org.mtransit.commons.sql.SQLUtils.unquotes + +object GTFSStringsUtils : MTLog.Loggable { + + private val LOG_TAG: String = GTFSStringsUtils::class.java.simpleName + + override fun getLogTag() = LOG_TAG + + @Suppress("DiscouragedApi") + @JvmStatic + fun > updateStrings(timestamps: T, gtfsProvider: GTFSProvider): T { + val stringIds = timestamps + .mapNotNull { it.headsignValue?.split(GTFSCommons.STRINGS_SEPARATOR) } + .flatten() + .distinct() + .takeIf { it.isNotEmpty() } + ?: return timestamps + val idToStringMap = loadStrings(gtfsProvider, stringIds) + timestamps.forEach { timestamp -> + timestamp.headsignValue?.let { headsignValue -> + timestamp.headsignValue = headsignValue + .split(GTFSCommons.STRINGS_SEPARATOR) + .mapNotNull { idToStringMap[it.toIntOrNull()] } + .joinToString(GTFSCommons.STRINGS_SEPARATOR) + } + } + return timestamps + } + + private fun loadStrings(gtfsProvider: GTFSProvider, stringIds: List): Map { + if (stringIds.isEmpty()) return emptyMap() + val placeholders = stringIds.joinToString(",") { "?" } + return gtfsProvider.readDB.query( + GTFSProviderDbHelper.T_STRINGS, + arrayOf(GTFSProviderDbHelper.T_STRINGS_K_ID, GTFSProviderDbHelper.T_STRINGS_K_STRING), + "${GTFSProviderDbHelper.T_STRINGS_K_ID} IN ($placeholders)", + stringIds.toTypedArray(), + null, + null, + null + ).use { cursor -> + cursorToStrings(cursor) + } + } + + private fun cursorToStrings(cursor: Cursor) = buildMap { + while (cursor.moveToNext()) { + try { + put( + cursor.getInt(cursor.getColumnIndexOrThrow(GTFSProviderDbHelper.T_STRINGS_K_ID)), + cursor.getString(cursor.getColumnIndexOrThrow(GTFSProviderDbHelper.T_STRINGS_K_STRING)) ?: continue + ) + } catch (e: Exception) { + MTLog.w(this@GTFSStringsUtils, e, "Cannot parse strings cursor: '$cursor'!") + } + } + } + + @JvmStatic + fun fromFileLine(line: String) = + line.split(SQLUtils.COLUMN_SEPARATOR) + .takeIf { it.size == 2 } + ?.let { columns -> + columns[0].toIntOrNull()?.let { it to columns[1].unquotes() } + } + ?: run { + MTLog.w(this@GTFSStringsUtils, "Invalid string line: '$line'!") + null + } + + @JvmStatic + fun replaceLineStrings(line: String, allStrings: Map?, stringsColumnIdx: IntArray): String { + return line + .takeUnless { allStrings.isNullOrEmpty() || stringsColumnIdx.isEmpty() } + ?.split(SQLUtils.COLUMN_SEPARATOR) + ?.takeIf { it.isNotEmpty() } + ?.toMutableList() + ?.apply { + for (idx in stringsColumnIdx) { + this.getOrNull(idx)?.unquotes()?.let { replaceStrings(it, allStrings) }?.quotes()?.let { this[idx] = it } + } + }?.joinToString(SQLUtils.COLUMN_SEPARATOR) + ?: line + } + + fun replaceStrings(stringIds: String, allStrings: Map?): String { + return stringIds + .takeUnless { allStrings.isNullOrEmpty() } + ?.split(GTFSCommons.STRINGS_SEPARATOR) + ?.takeIf { it.isNotEmpty() } + ?.toMutableList() + ?.apply { + indices.forEach { idx -> + allStrings?.get(this[idx].toIntOrNull())?.let { this[idx] = it } + } + }?.joinToString(GTFSCommons.STRINGS_SEPARATOR) + ?: stringIds + } +} diff --git a/src/main/java/org/mtransit/android/commons/provider/gtfs/GTFSProviderDBHelperUtils.kt b/src/main/java/org/mtransit/android/commons/provider/gtfs/GTFSProviderDBHelperUtils.kt new file mode 100644 index 00000000..691963f3 --- /dev/null +++ b/src/main/java/org/mtransit/android/commons/provider/gtfs/GTFSProviderDBHelperUtils.kt @@ -0,0 +1,105 @@ +package org.mtransit.android.commons.provider.gtfs + +import android.content.Context +import android.database.sqlite.SQLiteDatabase +import org.mtransit.android.commons.FileUtils +import org.mtransit.android.commons.MTLog +import org.mtransit.android.commons.SqlUtils +import org.mtransit.android.commons.provider.GTFSProviderDbHelper +import org.mtransit.android.commons.provider.GTFSStringsUtils +import org.mtransit.commons.FeatureFlags +import org.mtransit.commons.GTFSCommons +import java.io.BufferedReader +import java.io.InputStreamReader + +object GTFSProviderDBHelperUtils: MTLog.Loggable { + + private val LOG_TAG: String = GTFSProviderDBHelperUtils::class.java.simpleName + + override fun getLogTag() = LOG_TAG + + private const val MAX_DB_INIT_RETRIES = 3 + + @JvmStatic + @JvmOverloads + fun initDbTableWithRetry( + context: Context, + db: SQLiteDatabase, + table: String, + sqlCreate: String?, + sqlInsert: String, + sqlDrop: String?, + files: IntArray, + allStrings: Map? = null, + stringsColumnIdx: IntArray? = null, + addStrings: (Int, String) -> Unit = { _, _ -> }, + ) { + var tried = 0 + var success: Boolean + do { + try { + success = initDbTable(context, db, table, sqlCreate, sqlInsert, sqlDrop, files, allStrings, stringsColumnIdx, addStrings) + } catch (e: Exception) { + MTLog.w(this, e, "Error while deploying DB table '$table'!") + success = false + } + tried++ + } while (!success && tried < MAX_DB_INIT_RETRIES) + } + + private fun initDbTable( + context: Context, + db: SQLiteDatabase, + table: String, + sqlCreate: String?, + sqlInsert: String, + sqlDrop: String?, + files: IntArray, + allStrings: Map?, + stringsColumnIdx: IntArray?, + addStrings: (Int, String) -> Unit, + ): Boolean { + try { + db.beginTransaction() + db.execSQL(sqlDrop) // drop if exists + db.execSQL(sqlCreate) // create if not exists + for (file in files) { + try { + context.resources.openRawResource(file).use { inputStream -> + InputStreamReader(inputStream, FileUtils.getUTF8()).use { inputStreamReader -> + BufferedReader(inputStreamReader).forEachLine { + var line = it + if (FeatureFlags.F_EXPORT_STRINGS) { + if (allStrings != null && stringsColumnIdx != null && stringsColumnIdx.isNotEmpty()) { + line = GTFSStringsUtils.replaceLineStrings(line, allStrings, stringsColumnIdx) + } else if (table == GTFSCommons.T_STRINGS) { + GTFSStringsUtils.fromFileLine(line)?.let { (id, string) -> + addStrings(id, string) + } + } + } + val sql = String.format(sqlInsert, line) + try { + db.execSQL(sql) + } catch (e: Exception) { + MTLog.w(this, e, "ERROR while executing '$sql' on database '${GTFSProviderDbHelper.DB_NAME}' table '$table' file '$file'!") + throw e + } + } + } + } + } catch (e: Exception) { + MTLog.w(this, e, "ERROR while copying the database '${GTFSProviderDbHelper.DB_NAME}' table '$table' file '$file'!") + return false + } + } + db.setTransactionSuccessful() + return true + } catch (e: Exception) { + MTLog.w(this, e, "ERROR while copying the database '${GTFSProviderDbHelper.DB_NAME}' table '$table' file!") + return false + } finally { + SqlUtils.endTransactionQuietly(db) + } + } +} diff --git a/src/main/res-current/raw/current_gtfs_strings b/src/main/res-current/raw/current_gtfs_strings new file mode 100644 index 00000000..e69de29b diff --git a/src/main/res-next/raw/next_gtfs_strings b/src/main/res-next/raw/next_gtfs_strings new file mode 100644 index 00000000..e69de29b diff --git a/src/main/res/raw/gtfs_strings b/src/main/res/raw/gtfs_strings new file mode 100644 index 00000000..e69de29b