From 94a74ebcf8837ade64ba8b531d5eeb6f45bbb146 Mon Sep 17 00:00:00 2001 From: Alexander Capehart Date: Sun, 25 Sep 2022 15:00:48 -0600 Subject: [PATCH] music: re-add escaped parsing Re-add parsing by escaped separators. Previously I removed it becaue the regex parsing was not being cooperative. Turns out we really do have to write our own parser code. Fun. --- .../java/org/oxycblt/auxio/music/Music.kt | 4 +- .../music/extractor/MediaStoreExtractor.kt | 6 +- .../auxio/music/extractor/ParsingUtil.kt | 43 ++++++-- .../playback/state/PlaybackStateDatabase.kt | 102 +++++++++--------- 4 files changed, 87 insertions(+), 68 deletions(-) diff --git a/app/src/main/java/org/oxycblt/auxio/music/Music.kt b/app/src/main/java/org/oxycblt/auxio/music/Music.kt index d5ac7811a..112341741 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/Music.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/Music.kt @@ -247,7 +247,7 @@ class Song constructor(raw: Raw, settings: Settings) : Music() { */ val path = Path( - name = requireNotNull(raw.displayName) { "Invalid raw: No display name" }, + name = requireNotNull(raw.fileName) { "Invalid raw: No display name" }, parent = requireNotNull(raw.directory) { "Invalid raw: No parent directory" } ) @@ -387,8 +387,8 @@ class Song constructor(raw: Raw, settings: Settings) : Music() { var mediaStoreId: Long? = null, var musicBrainzId: String? = null, var name: String? = null, + var fileName: String? = null, var sortName: String? = null, - var displayName: String? = null, var directory: Directory? = null, var extensionMimeType: String? = null, var formatMimeType: String? = null, diff --git a/app/src/main/java/org/oxycblt/auxio/music/extractor/MediaStoreExtractor.kt b/app/src/main/java/org/oxycblt/auxio/music/extractor/MediaStoreExtractor.kt index b2709cc9f..dbc66508b 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/extractor/MediaStoreExtractor.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/extractor/MediaStoreExtractor.kt @@ -314,7 +314,7 @@ abstract class MediaStoreExtractor(private val context: Context, private val cac // Try to use the DISPLAY_NAME field to obtain a (probably sane) file name // from the android system. - raw.displayName = cursor.getStringOrNull(displayNameIndex) + raw.fileName = cursor.getStringOrNull(displayNameIndex) raw.durationMs = cursor.getLong(durationIndex) raw.date = cursor.getIntOrNull(yearIndex)?.toDate() @@ -411,8 +411,8 @@ class Api21MediaStoreExtractor(context: Context, cacheDatabase: CacheDatabase) : // that this only applies to below API 29, as beyond API 29, this field not being // present would completely break the scoped storage system. Fill it in with DATA // if it's not available. - if (raw.displayName == null) { - raw.displayName = data.substringAfterLast(File.separatorChar, "").ifEmpty { null } + if (raw.fileName == null) { + raw.fileName = data.substringAfterLast(File.separatorChar, "").ifEmpty { null } } // Find the volume that transforms the DATA field into a relative path. This is diff --git a/app/src/main/java/org/oxycblt/auxio/music/extractor/ParsingUtil.kt b/app/src/main/java/org/oxycblt/auxio/music/extractor/ParsingUtil.kt index cefa9f327..b0e0e7e74 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/extractor/ParsingUtil.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/extractor/ParsingUtil.kt @@ -51,7 +51,39 @@ fun String.parseYear() = toIntOrNull()?.toDate() /** Parse an ISO-8601 time-stamp from this field into a [Date]. */ fun String.parseTimestamp() = Date.from(this) -private val SEPARATOR_REGEX_CACHE = mutableMapOf() +/** + * Parse a string by [selector], also handling string escaping. + */ +inline fun String.splitEscaped(selector: (Char) -> Boolean): MutableList { + val split = mutableListOf() + var currentString = "" + var i = 0 + while (i < length) { + val a = get(i) + val b = getOrNull(i + 1) + + if (selector(a)) { + split.add(currentString.trim()) + currentString = "" + i++ + continue + } + + if (b != null && a == '\\' && selector(b)) { + currentString += b + i += 2 + } else { + currentString += a + i++ + } + } + + if (currentString.isNotEmpty()) { + split.add(currentString.trim()) + } + + return split +} /** * Fully parse a multi-value tag. @@ -75,14 +107,7 @@ fun List.parseMultiValue(settings: Settings) = fun String.maybeParseSeparators(settings: Settings): List { // Get the separators the user desires. If null, we don't parse any. val separators = settings.separators ?: return listOf(this) - - // Try to cache compiled regexes for particular separator combinations. - val regex = - synchronized(SEPARATOR_REGEX_CACHE) { - SEPARATOR_REGEX_CACHE.getOrPut(separators) { Regex("[$separators]") } - } - - return regex.split(this).map { it.trim() } + return splitEscaped { separators.contains(it) } } /** Parse a multi-value tag into a [ReleaseType], handling separators in the process. */ diff --git a/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateDatabase.kt b/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateDatabase.kt index 252550f83..c6597ed5a 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateDatabase.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateDatabase.kt @@ -39,8 +39,8 @@ class PlaybackStateDatabase private constructor(context: Context) : SQLiteOpenHelper(context, DB_NAME, null, DB_VERSION) { override fun onCreate(db: SQLiteDatabase) { - createTable(db, TABLE_NAME_STATE) - createTable(db, TABLE_NAME_QUEUE) + createTable(db, TABLE_STATE) + createTable(db, TABLE_QUEUE) } override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) = nuke(db) @@ -49,8 +49,8 @@ class PlaybackStateDatabase private constructor(context: Context) : private fun nuke(db: SQLiteDatabase) { logD("Nuking database") db.apply { - execSQL("DROP TABLE IF EXISTS $TABLE_NAME_STATE") - execSQL("DROP TABLE IF EXISTS $TABLE_NAME_QUEUE") + execSQL("DROP TABLE IF EXISTS $TABLE_STATE") + execSQL("DROP TABLE IF EXISTS $TABLE_QUEUE") onCreate(this) } } @@ -58,42 +58,36 @@ class PlaybackStateDatabase private constructor(context: Context) : // --- DATABASE CONSTRUCTION FUNCTIONS --- /** Create a table for this database. */ - private fun createTable(database: SQLiteDatabase, tableName: String) { + private fun createTable(db: SQLiteDatabase, name: String) { val command = StringBuilder() - command.append("CREATE TABLE IF NOT EXISTS $tableName(") + command.append("CREATE TABLE IF NOT EXISTS $name(") - if (tableName == TABLE_NAME_STATE) { + if (name == TABLE_STATE) { constructStateTable(command) - } else if (tableName == TABLE_NAME_QUEUE) { + } else if (name == TABLE_QUEUE) { constructQueueTable(command) } - database.execSQL(command.toString()) + db.execSQL(command.toString()) } /** Construct a [StateColumns] table */ - private fun constructStateTable(command: StringBuilder): StringBuilder { + private fun constructStateTable(command: StringBuilder) = command - .append("${StateColumns.COLUMN_ID} LONG PRIMARY KEY,") - .append("${StateColumns.COLUMN_SONG_UID} STRING,") - .append("${StateColumns.COLUMN_POSITION} LONG NOT NULL,") - .append("${StateColumns.COLUMN_PARENT_UID} STRING,") - .append("${StateColumns.COLUMN_INDEX} INTEGER NOT NULL,") - .append("${StateColumns.COLUMN_IS_SHUFFLED} BOOLEAN NOT NULL,") - .append("${StateColumns.COLUMN_REPEAT_MODE} INTEGER NOT NULL)") - - return command - } + .append("${StateColumns.ID} LONG PRIMARY KEY,") + .append("${StateColumns.SONG_UID} STRING,") + .append("${StateColumns.POSITION} LONG NOT NULL,") + .append("${StateColumns.PARENT_UID} STRING,") + .append("${StateColumns.INDEX} INTEGER NOT NULL,") + .append("${StateColumns.IS_SHUFFLED} BOOLEAN NOT NULL,") + .append("${StateColumns.REPEAT_MODE} INTEGER NOT NULL)") /** Construct a [QueueColumns] table */ - private fun constructQueueTable(command: StringBuilder): StringBuilder { + private fun constructQueueTable(command: StringBuilder) = command .append("${QueueColumns.ID} LONG PRIMARY KEY,") .append("${QueueColumns.SONG_UID} STRING NOT NULL)") - return command - } - // --- INTERFACE FUNCTIONS --- fun read(library: MusicStore.Library): SavedState? { @@ -121,18 +115,18 @@ class PlaybackStateDatabase private constructor(context: Context) : } private fun readRawState(): RawState? { - return readableDatabase.queryAll(TABLE_NAME_STATE) { cursor -> + return readableDatabase.queryAll(TABLE_STATE) { cursor -> if (cursor.count == 0) { return@queryAll null } - val indexIndex = cursor.getColumnIndexOrThrow(StateColumns.COLUMN_INDEX) + val indexIndex = cursor.getColumnIndexOrThrow(StateColumns.INDEX) - val posIndex = cursor.getColumnIndexOrThrow(StateColumns.COLUMN_POSITION) - val repeatModeIndex = cursor.getColumnIndexOrThrow(StateColumns.COLUMN_REPEAT_MODE) - val shuffleIndex = cursor.getColumnIndexOrThrow(StateColumns.COLUMN_IS_SHUFFLED) - val songUidIndex = cursor.getColumnIndexOrThrow(StateColumns.COLUMN_SONG_UID) - val parentUidIndex = cursor.getColumnIndexOrThrow(StateColumns.COLUMN_PARENT_UID) + val posIndex = cursor.getColumnIndexOrThrow(StateColumns.POSITION) + val repeatModeIndex = cursor.getColumnIndexOrThrow(StateColumns.REPEAT_MODE) + val shuffleIndex = cursor.getColumnIndexOrThrow(StateColumns.IS_SHUFFLED) + val songUidIndex = cursor.getColumnIndexOrThrow(StateColumns.SONG_UID) + val parentUidIndex = cursor.getColumnIndexOrThrow(StateColumns.PARENT_UID) cursor.moveToFirst() @@ -154,7 +148,7 @@ class PlaybackStateDatabase private constructor(context: Context) : val queue = mutableListOf() - readableDatabase.queryAll(TABLE_NAME_QUEUE) { cursor -> + readableDatabase.queryAll(TABLE_QUEUE) { cursor -> if (cursor.count == 0) return@queryAll val songIndex = cursor.getColumnIndexOrThrow(QueueColumns.SONG_UID) while (cursor.moveToNext()) { @@ -196,21 +190,21 @@ class PlaybackStateDatabase private constructor(context: Context) : private fun writeRawState(rawState: RawState?) { writableDatabase.transaction { - delete(TABLE_NAME_STATE, null, null) + delete(TABLE_STATE, null, null) if (rawState != null) { val stateData = - ContentValues(10).apply { - put(StateColumns.COLUMN_ID, 0) - put(StateColumns.COLUMN_SONG_UID, rawState.songUid.toString()) - put(StateColumns.COLUMN_POSITION, rawState.positionMs) - put(StateColumns.COLUMN_PARENT_UID, rawState.parentUid?.toString()) - put(StateColumns.COLUMN_INDEX, rawState.index) - put(StateColumns.COLUMN_IS_SHUFFLED, rawState.isShuffled) - put(StateColumns.COLUMN_REPEAT_MODE, rawState.repeatMode.intCode) + ContentValues(7).apply { + put(StateColumns.ID, 0) + put(StateColumns.SONG_UID, rawState.songUid.toString()) + put(StateColumns.POSITION, rawState.positionMs) + put(StateColumns.PARENT_UID, rawState.parentUid?.toString()) + put(StateColumns.INDEX, rawState.index) + put(StateColumns.IS_SHUFFLED, rawState.isShuffled) + put(StateColumns.REPEAT_MODE, rawState.repeatMode.intCode) } - insert(TABLE_NAME_STATE, null, stateData) + insert(TABLE_STATE, null, stateData) } } } @@ -218,7 +212,7 @@ class PlaybackStateDatabase private constructor(context: Context) : /** Write a queue to the database. */ private fun writeQueue(queue: List?) { val database = writableDatabase - database.transaction { delete(TABLE_NAME_QUEUE, null, null) } + database.transaction { delete(TABLE_QUEUE, null, null) } logD("Wiped queue db") @@ -236,12 +230,12 @@ class PlaybackStateDatabase private constructor(context: Context) : i++ val itemData = - ContentValues(4).apply { + ContentValues(2).apply { put(QueueColumns.ID, idStart + i) put(QueueColumns.SONG_UID, song.uid.toString()) } - insert(TABLE_NAME_QUEUE, null, itemData) + insert(TABLE_QUEUE, null, itemData) } } @@ -273,13 +267,13 @@ class PlaybackStateDatabase private constructor(context: Context) : ) private object StateColumns { - const val COLUMN_ID = "id" - const val COLUMN_SONG_UID = "song_uid" - const val COLUMN_POSITION = "position" - const val COLUMN_PARENT_UID = "parent" - const val COLUMN_INDEX = "queue_index" - const val COLUMN_IS_SHUFFLED = "is_shuffling" - const val COLUMN_REPEAT_MODE = "repeat_mode" + const val ID = "id" + const val SONG_UID = "song_uid" + const val POSITION = "position" + const val PARENT_UID = "parent" + const val INDEX = "queue_index" + const val IS_SHUFFLED = "is_shuffling" + const val REPEAT_MODE = "repeat_mode" } private object QueueColumns { @@ -291,8 +285,8 @@ class PlaybackStateDatabase private constructor(context: Context) : const val DB_NAME = "auxio_state_database.db" const val DB_VERSION = 8 - const val TABLE_NAME_STATE = "playback_state_table" - const val TABLE_NAME_QUEUE = "queue_table" + const val TABLE_STATE = "playback_state_table" + const val TABLE_QUEUE = "queue_table" @Volatile private var INSTANCE: PlaybackStateDatabase? = null