Add blacklist functionality

Add the ability to block directories from indexing. Will add a UI interface for this later.
This commit is contained in:
OxygenCobalt 2021-03-07 11:53:25 -07:00
parent 8de09cd880
commit f5f67a0b76
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
2 changed files with 161 additions and 2 deletions

View file

@ -0,0 +1,142 @@
package org.oxycblt.auxio.database
import android.content.ContentValues
import android.content.Context
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
import org.oxycblt.auxio.logD
import java.io.File
import java.io.IOException
/**
* Database for storing blacklisted paths.
* Note that the paths stored here will not work with MediaStore unless you append a "%" at the
* end.
*/
class BlacklistDatabase(context: Context) : SQLiteOpenHelper(context, DB_NAME, null, DB_VERSION) {
override fun onCreate(db: SQLiteDatabase) {
db.execSQL("CREATE TABLE IF NOT EXISTS $TABLE_NAME ($COLUMN_PATH TEXT NOT NULL)")
}
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
db.execSQL("DROP TABLE IF EXISTS $TABLE_NAME")
onCreate(db)
}
override fun onDowngrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
db.execSQL("DROP TABLE IF EXISTS $TABLE_NAME")
onCreate(db)
}
/**
* Add a [File] to the the blacklist.
* @return Whether this file has been added to the database or not.
*/
fun addPath(file: File): Boolean {
val path = file.mediaStorePath
logD("Adding path $path to blacklist")
if (hasFile(path)) {
logD("Path already exists. Ignoring.")
return false
}
val database = writableDatabase
database.beginTransaction()
try {
val values = ContentValues(1)
values.put(COLUMN_PATH, path)
database.insert(TABLE_NAME, null, values)
database.setTransactionSuccessful()
} finally {
database.endTransaction()
return true
}
}
/**
* Remove a [File] from this blacklist.
*/
fun removePath(file: File) {
val database = writableDatabase
val path = file.mediaStorePath
database.beginTransaction()
database.delete(TABLE_NAME, "$COLUMN_PATH=?", arrayOf(path))
database.setTransactionSuccessful()
database.endTransaction()
}
fun getPaths(): List<String> {
val paths = mutableListOf<String>()
val pathsCursor = readableDatabase.query(
TABLE_NAME, arrayOf(COLUMN_PATH), null, null, null, null, null
)
pathsCursor?.use { cursor ->
while (cursor.moveToNext()) {
paths.add(cursor.getString(0))
}
}
return paths
}
private fun hasFile(path: String): Boolean {
val pathsCursor = readableDatabase.query(
TABLE_NAME,
arrayOf(COLUMN_PATH),
"$COLUMN_PATH=?",
arrayOf(path),
null, null, null, null
)
pathsCursor?.use { cursor ->
return cursor.moveToFirst()
}
return false
}
private val File.mediaStorePath: String get() {
return try {
canonicalPath
} catch (e: IOException) {
absolutePath
}
}
companion object {
const val DB_VERSION = 1
const val DB_NAME = "auxio_blacklist_database.db"
const val TABLE_NAME = "blacklist_dirs_table"
const val COLUMN_PATH = "COLUMN_PATH"
@Volatile
private var INSTANCE: BlacklistDatabase? = null
/**
* Get/Instantiate the single instance of [PlaybackStateDatabase].
*/
fun getInstance(context: Context): BlacklistDatabase {
val currentInstance = INSTANCE
if (currentInstance != null) {
return currentInstance
}
synchronized(this) {
val newInstance = BlacklistDatabase(context.applicationContext)
INSTANCE = newInstance
return newInstance
}
}
}
}

View file

@ -8,6 +8,7 @@ import android.provider.MediaStore.Audio.Genres
import android.provider.MediaStore.Audio.Media import android.provider.MediaStore.Audio.Media
import androidx.core.database.getStringOrNull import androidx.core.database.getStringOrNull
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.database.BlacklistDatabase
import org.oxycblt.auxio.logD import org.oxycblt.auxio.logD
/** /**
@ -22,11 +23,16 @@ class MusicLoader(private val context: Context) {
private val resolver = context.contentResolver private val resolver = context.contentResolver
private var selector = "${Media.IS_MUSIC}=1"
private var args = arrayOf<String>()
/** /**
* Begin the loading process. * Begin the loading process.
* Resulting models are pushed to [genres], [artists], [albums], and [songs]. * Resulting models are pushed to [genres], [artists], [albums], and [songs].
*/ */
fun load() { fun load() {
buildSelector()
loadGenres() loadGenres()
loadAlbums() loadAlbums()
loadSongs() loadSongs()
@ -36,6 +42,17 @@ class MusicLoader(private val context: Context) {
linkGenres() linkGenres()
} }
private fun buildSelector() {
val blacklistDatabase = BlacklistDatabase.getInstance(context)
val paths = blacklistDatabase.getPaths()
for (path in paths) {
selector += " AND ${Media.DATA} NOT LIKE ?"
args += "$path%" // Append % so that the selector properly detects children
}
}
private fun loadGenres() { private fun loadGenres() {
logD("Starting genre search...") logD("Starting genre search...")
@ -127,7 +144,7 @@ class MusicLoader(private val context: Context) {
Media.TRACK, // 4 Media.TRACK, // 4
Media.DURATION // 5 Media.DURATION // 5
), ),
"${Media.IS_MUSIC}=1", null, selector, args,
Media.DEFAULT_SORT_ORDER Media.DEFAULT_SORT_ORDER
) )
@ -222,7 +239,7 @@ class MusicLoader(private val context: Context) {
val songCursor = resolver.query( val songCursor = resolver.query(
Genres.Members.getContentUri("external", genre.id), Genres.Members.getContentUri("external", genre.id),
arrayOf(Genres.Members._ID), arrayOf(Genres.Members._ID),
null, null, null null, null, null // Dont even bother selecting here as useless iters are less expensive than IO
) )
songCursor?.use { cursor -> songCursor?.use { cursor ->