Convert and cleanup content providers

This commit is contained in:
Paul Hawke 2025-07-12 07:44:54 -05:00
parent 8772a5a208
commit c0514798c8
13 changed files with 429 additions and 527 deletions

View file

@ -1,129 +0,0 @@
package fr.free.nrw.commons.bookmarks.items;
import static fr.free.nrw.commons.bookmarks.items.BookmarkItemsTable.COLUMN_ID;
import static fr.free.nrw.commons.bookmarks.items.BookmarkItemsTable.TABLE_NAME;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import fr.free.nrw.commons.BuildConfig;
import fr.free.nrw.commons.data.DBOpenHelper;
import fr.free.nrw.commons.di.CommonsDaggerContentProvider;
import javax.inject.Inject;
import timber.log.Timber;
/**
* Handles private storage for bookmarked items
*/
public class BookmarkItemsContentProvider extends CommonsDaggerContentProvider {
private static final String BASE_PATH = "bookmarksItems";
public static final Uri BASE_URI =
Uri.parse("content://" + BuildConfig.BOOKMARK_ITEMS_AUTHORITY + "/" + BASE_PATH);
/**
* Append bookmark items ID to the base uri
*/
public static Uri uriForName(final String id) {
return Uri.parse(BASE_URI + "/" + id);
}
@Inject
DBOpenHelper dbOpenHelper;
@Override
public String getType(@NonNull final Uri uri) {
return null;
}
/**
* Queries the SQLite database for the bookmark items
* @param uri : contains the uri for bookmark items
* @param projection : contains the all fields of the table
* @param selection : handles Where
* @param selectionArgs : the condition of Where clause
* @param sortOrder : ascending or descending
*/
@SuppressWarnings("ConstantConditions")
@Override
public Cursor query(@NonNull final Uri uri, final String[] projection, final String selection,
final String[] selectionArgs, final String sortOrder) {
final SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
queryBuilder.setTables(TABLE_NAME);
final SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
final Cursor cursor = queryBuilder.query(db, projection, selection,
selectionArgs, null, null, sortOrder);
cursor.setNotificationUri(getContext().getContentResolver(), uri);
return cursor;
}
/**
* Handles the update query of local SQLite Database
* @param uri : contains the uri for bookmark items
* @param contentValues : new values to be entered to db
* @param selection : handles Where
* @param selectionArgs : the condition of Where clause
*/
@SuppressWarnings("ConstantConditions")
@Override
public int update(@NonNull final Uri uri, final ContentValues contentValues,
final String selection, final String[] selectionArgs) {
final SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase();
final int rowsUpdated;
if (TextUtils.isEmpty(selection)) {
final int id = Integer.parseInt(uri.getLastPathSegment());
rowsUpdated = sqlDB.update(TABLE_NAME,
contentValues,
COLUMN_ID + " = ?",
new String[]{String.valueOf(id)});
} else {
throw new IllegalArgumentException(
"Parameter `selection` should be empty when updating an ID");
}
getContext().getContentResolver().notifyChange(uri, null);
return rowsUpdated;
}
/**
* Handles the insertion of new bookmark items record to local SQLite Database
* @param uri
* @param contentValues
* @return
*/
@SuppressWarnings("ConstantConditions")
@Override
public Uri insert(@NonNull final Uri uri, final ContentValues contentValues) {
final SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase();
final long id = sqlDB.insert(TABLE_NAME, null, contentValues);
getContext().getContentResolver().notifyChange(uri, null);
return Uri.parse(BASE_URI + "/" + id);
}
/**
* Handles the deletion of new bookmark items record to local SQLite Database
* @param uri
* @param s
* @param strings
* @return
*/
@SuppressWarnings("ConstantConditions")
@Override
public int delete(@NonNull final Uri uri, final String s, final String[] strings) {
final int rows;
final SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
Timber.d("Deleting bookmark name %s", uri.getLastPathSegment());
rows = db.delete(
TABLE_NAME,
"item_id = ?",
new String[]{uri.getLastPathSegment()}
);
getContext().getContentResolver().notifyChange(uri, null);
return rows;
}
}

View file

@ -0,0 +1,101 @@
package fr.free.nrw.commons.bookmarks.items
import android.content.ContentValues
import android.database.Cursor
import android.database.sqlite.SQLiteQueryBuilder
import android.net.Uri
import fr.free.nrw.commons.BuildConfig
import fr.free.nrw.commons.bookmarks.items.BookmarkItemsTable.TABLE_NAME
import fr.free.nrw.commons.di.CommonsDaggerContentProvider
import androidx.core.net.toUri
import fr.free.nrw.commons.bookmarks.items.BookmarkItemsTable.COLUMN_ID
/**
* Handles private storage for bookmarked items
*/
class BookmarkItemsContentProvider : CommonsDaggerContentProvider() {
override fun getType(uri: Uri): String? = null
/**
* Queries the SQLite database for the bookmark items
* @param uri : contains the uri for bookmark items
* @param projection : contains the all fields of the table
* @param selection : handles Where
* @param selectionArgs : the condition of Where clause
* @param sortOrder : ascending or descending
*/
override fun query(
uri: Uri, projection: Array<String>?, selection: String?,
selectionArgs: Array<String>?, sortOrder: String?
): Cursor {
val queryBuilder = SQLiteQueryBuilder().apply {
tables = TABLE_NAME
}
return queryBuilder.query(
requireDb(), projection, selection,
selectionArgs, null, null, sortOrder
).apply {
setNotificationUri(requireContext().contentResolver, uri)
}
}
/**
* Handles the update query of local SQLite Database
* @param uri : contains the uri for bookmark items
* @param contentValues : new values to be entered to db
* @param selection : handles Where
* @param selectionArgs : the condition of Where clause
*/
override fun update(
uri: Uri, contentValues: ContentValues?,
selection: String?, selectionArgs: Array<String>?
): Int {
val rowsUpdated: Int
if (selection.isNullOrEmpty()) {
val id = uri.lastPathSegment!!.toInt()
rowsUpdated = requireDb().update(
TABLE_NAME,
contentValues,
"$COLUMN_ID = ?",
arrayOf(id.toString())
)
} else {
throw IllegalArgumentException(
"Parameter `selection` should be empty when updating an ID"
)
}
requireContext().contentResolver.notifyChange(uri, null)
return rowsUpdated
}
/**
* Handles the insertion of new bookmark items record to local SQLite Database
*/
override fun insert(uri: Uri, contentValues: ContentValues?): Uri? {
val id = requireDb().insert(TABLE_NAME, null, contentValues)
requireContext().contentResolver.notifyChange(uri, null)
return "$BASE_URI/$id".toUri()
}
/**
* Handles the deletion of new bookmark items record to local SQLite Database
*/
override fun delete(uri: Uri, s: String?, strings: Array<String>?): Int {
val rows: Int = requireDb().delete(
TABLE_NAME,
"$COLUMN_ID = ?",
arrayOf(uri.lastPathSegment)
)
requireContext().contentResolver.notifyChange(uri, null)
return rows
}
companion object {
private const val BASE_PATH = "bookmarksItems"
val BASE_URI: Uri = "content://${BuildConfig.BOOKMARK_ITEMS_AUTHORITY}/$BASE_PATH".toUri()
fun uriForName(id: String) = "$BASE_URI/$id".toUri()
}
}

View file

@ -6,8 +6,8 @@ import android.content.ContentValues
import android.database.Cursor import android.database.Cursor
import android.os.RemoteException import android.os.RemoteException
import androidx.core.content.contentValuesOf import androidx.core.content.contentValuesOf
import fr.free.nrw.commons.bookmarks.items.BookmarkItemsContentProvider.BASE_URI import fr.free.nrw.commons.bookmarks.items.BookmarkItemsContentProvider.Companion.BASE_URI
import fr.free.nrw.commons.bookmarks.items.BookmarkItemsContentProvider.uriForName import fr.free.nrw.commons.bookmarks.items.BookmarkItemsContentProvider.Companion.uriForName
import fr.free.nrw.commons.bookmarks.items.BookmarkItemsTable.COLUMN_CATEGORIES_DESCRIPTION_LIST import fr.free.nrw.commons.bookmarks.items.BookmarkItemsTable.COLUMN_CATEGORIES_DESCRIPTION_LIST
import fr.free.nrw.commons.bookmarks.items.BookmarkItemsTable.COLUMN_CATEGORIES_NAME_LIST import fr.free.nrw.commons.bookmarks.items.BookmarkItemsTable.COLUMN_CATEGORIES_NAME_LIST
import fr.free.nrw.commons.bookmarks.items.BookmarkItemsTable.COLUMN_CATEGORIES_THUMBNAIL_LIST import fr.free.nrw.commons.bookmarks.items.BookmarkItemsTable.COLUMN_CATEGORIES_THUMBNAIL_LIST

View file

@ -1,120 +0,0 @@
package fr.free.nrw.commons.bookmarks.pictures;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteQueryBuilder;
// We can get uri using java.Net.Uri, but andoid implimentation is faster (but it's forgiving with handling exceptions though)
import android.net.Uri;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import javax.inject.Inject;
import fr.free.nrw.commons.BuildConfig;
import fr.free.nrw.commons.data.DBOpenHelper;
import fr.free.nrw.commons.di.CommonsDaggerContentProvider;
import timber.log.Timber;
import static fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesDao.Table.COLUMN_MEDIA_NAME;
import static fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesDao.Table.TABLE_NAME;
/**
* Handles private storage for Bookmark pictures
*/
public class BookmarkPicturesContentProvider extends CommonsDaggerContentProvider {
private static final String BASE_PATH = "bookmarks";
public static final Uri BASE_URI = Uri.parse("content://" + BuildConfig.BOOKMARK_AUTHORITY + "/" + BASE_PATH);
/**
* Append bookmark pictures name to the base uri
*/
public static Uri uriForName(String name) {
return Uri.parse(BASE_URI.toString() + "/" + name);
}
@Inject
DBOpenHelper dbOpenHelper;
@Override
public String getType(@NonNull Uri uri) {
return null;
}
/**
* Queries the SQLite database for the bookmark pictures
* @param uri : contains the uri for bookmark pictures
* @param projection
* @param selection : handles Where
* @param selectionArgs : the condition of Where clause
* @param sortOrder : ascending or descending
*/
@SuppressWarnings("ConstantConditions")
@Override
public Cursor query(@NonNull Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
queryBuilder.setTables(TABLE_NAME);
SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
Cursor cursor = queryBuilder.query(db, projection, selection, selectionArgs, null, null, sortOrder);
cursor.setNotificationUri(getContext().getContentResolver(), uri);
return cursor;
}
/**
* Handles the update query of local SQLite Database
* @param uri : contains the uri for bookmark pictures
* @param contentValues : new values to be entered to db
* @param selection : handles Where
* @param selectionArgs : the condition of Where clause
*/
@SuppressWarnings("ConstantConditions")
@Override
public int update(@NonNull Uri uri, ContentValues contentValues, String selection,
String[] selectionArgs) {
SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase();
int rowsUpdated;
if (TextUtils.isEmpty(selection)) {
int id = Integer.valueOf(uri.getLastPathSegment());
rowsUpdated = sqlDB.update(TABLE_NAME,
contentValues,
COLUMN_MEDIA_NAME + " = ?",
new String[]{String.valueOf(id)});
} else {
throw new IllegalArgumentException(
"Parameter `selection` should be empty when updating an ID");
}
getContext().getContentResolver().notifyChange(uri, null);
return rowsUpdated;
}
/**
* Handles the insertion of new bookmark pictures record to local SQLite Database
*/
@SuppressWarnings("ConstantConditions")
@Override
public Uri insert(@NonNull Uri uri, ContentValues contentValues) {
SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase();
long id = sqlDB.insert(BookmarkPicturesDao.Table.TABLE_NAME, null, contentValues);
getContext().getContentResolver().notifyChange(uri, null);
return Uri.parse(BASE_URI + "/" + id);
}
@SuppressWarnings("ConstantConditions")
@Override
public int delete(@NonNull Uri uri, String s, String[] strings) {
int rows;
SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
Timber.d("Deleting bookmark name %s", uri.getLastPathSegment());
rows = db.delete(TABLE_NAME,
"media_name = ?",
new String[]{uri.getLastPathSegment()}
);
getContext().getContentResolver().notifyChange(uri, null);
return rows;
}
}

View file

@ -0,0 +1,101 @@
package fr.free.nrw.commons.bookmarks.pictures
import android.content.ContentValues
import android.database.Cursor
import android.database.sqlite.SQLiteQueryBuilder
import android.net.Uri
import android.text.TextUtils
import fr.free.nrw.commons.BuildConfig
import fr.free.nrw.commons.di.CommonsDaggerContentProvider
import androidx.core.net.toUri
import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesDao.Table.COLUMN_MEDIA_NAME
import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesDao.Table.TABLE_NAME
/**
* Handles private storage for Bookmark pictures
*/
class BookmarkPicturesContentProvider : CommonsDaggerContentProvider() {
override fun getType(uri: Uri): String? = null
/**
* Queries the SQLite database for the bookmark pictures
* @param uri : contains the uri for bookmark pictures
* @param projection
* @param selection : handles Where
* @param selectionArgs : the condition of Where clause
* @param sortOrder : ascending or descending
*/
override fun query(
uri: Uri, projection: Array<String>?, selection: String?,
selectionArgs: Array<String>?, sortOrder: String?
): Cursor {
val queryBuilder = SQLiteQueryBuilder().apply {
tables = TABLE_NAME
}
val cursor = queryBuilder.query(
requireDb(), projection, selection,
selectionArgs, null, null, sortOrder
)
cursor.setNotificationUri(requireContext().contentResolver, uri)
return cursor
}
/**
* Handles the update query of local SQLite Database
* @param uri : contains the uri for bookmark pictures
* @param contentValues : new values to be entered to db
* @param selection : handles Where
* @param selectionArgs : the condition of Where clause
*/
override fun update(
uri: Uri, contentValues: ContentValues?, selection: String?,
selectionArgs: Array<String>?
): Int {
val rowsUpdated: Int
if (selection.isNullOrEmpty()) {
val id = uri.lastPathSegment!!.toInt()
rowsUpdated = requireDb().update(
TABLE_NAME,
contentValues,
"$COLUMN_MEDIA_NAME = ?",
arrayOf(id.toString())
)
} else {
throw IllegalArgumentException(
"Parameter `selection` should be empty when updating an ID"
)
}
requireContext().contentResolver.notifyChange(uri, null)
return rowsUpdated
}
/**
* Handles the insertion of new bookmark pictures record to local SQLite Database
*/
override fun insert(uri: Uri, contentValues: ContentValues?): Uri {
val id = requireDb().insert(TABLE_NAME, null, contentValues)
requireContext().contentResolver.notifyChange(uri, null)
return "$BASE_URI/$id".toUri()
}
override fun delete(uri: Uri, s: String?, strings: Array<String>?): Int {
val rows: Int = requireDb().delete(
TABLE_NAME,
"media_name = ?",
arrayOf(uri.lastPathSegment)
)
requireContext().contentResolver.notifyChange(uri, null)
return rows
}
companion object {
private const val BASE_PATH = "bookmarks"
@JvmField
val BASE_URI: Uri = "content://${BuildConfig.BOOKMARK_AUTHORITY}/$BASE_PATH".toUri()
@JvmStatic
fun uriForName(name: String): Uri = "$BASE_URI/$name".toUri()
}
}

View file

@ -9,12 +9,9 @@ import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteQueryBuilder import android.database.sqlite.SQLiteQueryBuilder
import android.net.Uri import android.net.Uri
import android.text.TextUtils import android.text.TextUtils
import androidx.annotation.NonNull
import fr.free.nrw.commons.BuildConfig import fr.free.nrw.commons.BuildConfig
import fr.free.nrw.commons.data.DBOpenHelper
import fr.free.nrw.commons.di.CommonsDaggerContentProvider import fr.free.nrw.commons.di.CommonsDaggerContentProvider
import timber.log.Timber import androidx.core.net.toUri
import javax.inject.Inject
class CategoryContentProvider : CommonsDaggerContentProvider() { class CategoryContentProvider : CommonsDaggerContentProvider() {
@ -23,9 +20,6 @@ class CategoryContentProvider : CommonsDaggerContentProvider() {
addURI(BuildConfig.CATEGORY_AUTHORITY, "${BASE_PATH}/#", CATEGORIES_ID) addURI(BuildConfig.CATEGORY_AUTHORITY, "${BASE_PATH}/#", CATEGORIES_ID)
} }
@Inject
lateinit var dbOpenHelper: DBOpenHelper
@SuppressWarnings("ConstantConditions") @SuppressWarnings("ConstantConditions")
override fun query(uri: Uri, projection: Array<String>?, selection: String?, override fun query(uri: Uri, projection: Array<String>?, selection: String?,
selectionArgs: Array<String>?, sortOrder: String?): Cursor? { selectionArgs: Array<String>?, sortOrder: String?): Cursor? {
@ -34,7 +28,7 @@ class CategoryContentProvider : CommonsDaggerContentProvider() {
} }
val uriType = uriMatcher.match(uri) val uriType = uriMatcher.match(uri)
val db = dbOpenHelper.readableDatabase val db = requireDb()
val cursor: Cursor? = when (uriType) { val cursor: Cursor? = when (uriType) {
CATEGORIES -> queryBuilder.query( CATEGORIES -> queryBuilder.query(
@ -58,45 +52,37 @@ class CategoryContentProvider : CommonsDaggerContentProvider() {
else -> throw IllegalArgumentException("Unknown URI $uri") else -> throw IllegalArgumentException("Unknown URI $uri")
} }
cursor?.setNotificationUri(context?.contentResolver, uri) cursor?.setNotificationUri(requireContext().contentResolver, uri)
return cursor return cursor
} }
override fun getType(uri: Uri): String? { override fun getType(uri: Uri): String? = null
return null
}
@SuppressWarnings("ConstantConditions") @SuppressWarnings("ConstantConditions")
override fun insert(uri: Uri, contentValues: ContentValues?): Uri? { override fun insert(uri: Uri, contentValues: ContentValues?): Uri {
val uriType = uriMatcher.match(uri) val uriType = uriMatcher.match(uri)
val sqlDB = dbOpenHelper.writableDatabase
val id: Long val id: Long
when (uriType) { when (uriType) {
CATEGORIES -> { CATEGORIES -> {
id = sqlDB.insert(TABLE_NAME, null, contentValues) id = requireDb().insert(TABLE_NAME, null, contentValues)
} }
else -> throw IllegalArgumentException("Unknown URI: $uri") else -> throw IllegalArgumentException("Unknown URI: $uri")
} }
context?.contentResolver?.notifyChange(uri, null) requireContext().contentResolver?.notifyChange(uri, null)
return Uri.parse("${Companion.BASE_URI}/$id") return "${BASE_URI}/$id".toUri()
} }
@SuppressWarnings("ConstantConditions") @SuppressWarnings("ConstantConditions")
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int { override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int = 0
// Not implemented
return 0
}
@SuppressWarnings("ConstantConditions") @SuppressWarnings("ConstantConditions")
override fun bulkInsert(uri: Uri, values: Array<ContentValues>): Int { override fun bulkInsert(uri: Uri, values: Array<ContentValues>): Int {
Timber.d("Hello, bulk insert! (CategoryContentProvider)")
val uriType = uriMatcher.match(uri) val uriType = uriMatcher.match(uri)
val sqlDB = dbOpenHelper.writableDatabase val sqlDB = requireDb()
sqlDB.beginTransaction() sqlDB.beginTransaction()
when (uriType) { when (uriType) {
CATEGORIES -> { CATEGORIES -> {
for (value in values) { for (value in values) {
Timber.d("Inserting! %s", value)
sqlDB.insert(TABLE_NAME, null, value) sqlDB.insert(TABLE_NAME, null, value)
} }
sqlDB.setTransactionSuccessful() sqlDB.setTransactionSuccessful()
@ -104,7 +90,7 @@ class CategoryContentProvider : CommonsDaggerContentProvider() {
else -> throw IllegalArgumentException("Unknown URI: $uri") else -> throw IllegalArgumentException("Unknown URI: $uri")
} }
sqlDB.endTransaction() sqlDB.endTransaction()
context?.contentResolver?.notifyChange(uri, null) requireContext().contentResolver?.notifyChange(uri, null)
return values.size return values.size
} }
@ -112,17 +98,18 @@ class CategoryContentProvider : CommonsDaggerContentProvider() {
override fun update(uri: Uri, contentValues: ContentValues?, selection: String?, override fun update(uri: Uri, contentValues: ContentValues?, selection: String?,
selectionArgs: Array<String>?): Int { selectionArgs: Array<String>?): Int {
val uriType = uriMatcher.match(uri) val uriType = uriMatcher.match(uri)
val sqlDB = dbOpenHelper.writableDatabase
val rowsUpdated: Int val rowsUpdated: Int
when (uriType) { when (uriType) {
CATEGORIES_ID -> { CATEGORIES_ID -> {
if (TextUtils.isEmpty(selection)) { if (TextUtils.isEmpty(selection)) {
val id = uri.lastPathSegment?.toInt() val id = uri.lastPathSegment?.toInt()
?: throw IllegalArgumentException("Invalid ID") ?: throw IllegalArgumentException("Invalid ID")
rowsUpdated = sqlDB.update(TABLE_NAME, rowsUpdated = requireDb().update(
TABLE_NAME,
contentValues, contentValues,
"$COLUMN_ID = ?", "$COLUMN_ID = ?",
arrayOf(id.toString())) arrayOf(id.toString())
)
} else { } else {
throw IllegalArgumentException( throw IllegalArgumentException(
"Parameter `selection` should be empty when updating an ID") "Parameter `selection` should be empty when updating an ID")
@ -130,7 +117,7 @@ class CategoryContentProvider : CommonsDaggerContentProvider() {
} }
else -> throw IllegalArgumentException("Unknown URI: $uri with type $uriType") else -> throw IllegalArgumentException("Unknown URI: $uri with type $uriType")
} }
context?.contentResolver?.notifyChange(uri, null) requireContext().contentResolver?.notifyChange(uri, null)
return rowsUpdated return rowsUpdated
} }
@ -165,13 +152,9 @@ class CategoryContentProvider : CommonsDaggerContentProvider() {
"$COLUMN_TIMES_USED INTEGER" + "$COLUMN_TIMES_USED INTEGER" +
");" ");"
fun uriForId(id: Int): Uri { fun uriForId(id: Int): Uri = Uri.parse("${BASE_URI}/$id")
return Uri.parse("${BASE_URI}/$id")
}
fun onCreate(db: SQLiteDatabase) { fun onCreate(db: SQLiteDatabase) = db.execSQL(CREATE_TABLE_STATEMENT)
db.execSQL(CREATE_TABLE_STATEMENT)
}
fun onDelete(db: SQLiteDatabase) { fun onDelete(db: SQLiteDatabase) {
db.execSQL(DROP_TABLE_STATEMENT) db.execSQL(DROP_TABLE_STATEMENT)
@ -200,6 +183,6 @@ class CategoryContentProvider : CommonsDaggerContentProvider() {
private const val CATEGORIES = 1 private const val CATEGORIES = 1
private const val CATEGORIES_ID = 2 private const val CATEGORIES_ID = 2
private const val BASE_PATH = "categories" private const val BASE_PATH = "categories"
val BASE_URI: Uri = Uri.parse("content://${BuildConfig.CATEGORY_AUTHORITY}/${Companion.BASE_PATH}") val BASE_URI: Uri = "content://${BuildConfig.CATEGORY_AUTHORITY}/${BASE_PATH}".toUri()
} }
} }

View file

@ -1,14 +1,25 @@
package fr.free.nrw.commons.di package fr.free.nrw.commons.di
import android.content.ContentProvider import android.content.ContentProvider
import android.database.sqlite.SQLiteDatabase
import fr.free.nrw.commons.data.DBOpenHelper
import fr.free.nrw.commons.di.ApplicationlessInjection.Companion.getInstance import fr.free.nrw.commons.di.ApplicationlessInjection.Companion.getInstance
import javax.inject.Inject
abstract class CommonsDaggerContentProvider : ContentProvider() { abstract class CommonsDaggerContentProvider : ContentProvider() {
@JvmField
@Inject
var dbOpenHelper: DBOpenHelper? = null
override fun onCreate(): Boolean { override fun onCreate(): Boolean {
inject() inject()
return true return true
} }
fun requireDbOpenHelper(): DBOpenHelper = dbOpenHelper!!
fun requireDb(): SQLiteDatabase = requireDbOpenHelper().writableDatabase!!
private fun inject() { private fun inject() {
val injection = getInstance(context!!) val injection = getInstance(context!!)

View file

@ -1,202 +0,0 @@
package fr.free.nrw.commons.explore.recentsearches;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import javax.inject.Inject;
import fr.free.nrw.commons.BuildConfig;
import fr.free.nrw.commons.data.DBOpenHelper;
import fr.free.nrw.commons.di.CommonsDaggerContentProvider;
import timber.log.Timber;
import static android.content.UriMatcher.NO_MATCH;
import static fr.free.nrw.commons.explore.recentsearches.RecentSearchesDao.Table.ALL_FIELDS;
import static fr.free.nrw.commons.explore.recentsearches.RecentSearchesDao.Table.COLUMN_ID;
import static fr.free.nrw.commons.explore.recentsearches.RecentSearchesDao.Table.TABLE_NAME;
/**
* This class contains functions for executing queries for
* inserting, searching, deleting, editing recent searches in SqLite DB
**/
public class RecentSearchesContentProvider extends CommonsDaggerContentProvider {
// For URI matcher
private static final int RECENT_SEARCHES = 1;
private static final int RECENT_SEARCHES_ID = 2;
private static final String BASE_PATH = "recent_searches";
public static final Uri BASE_URI = Uri.parse("content://" + BuildConfig.RECENT_SEARCH_AUTHORITY + "/" + BASE_PATH);
private static final UriMatcher uriMatcher = new UriMatcher(NO_MATCH);
static {
uriMatcher.addURI(BuildConfig.RECENT_SEARCH_AUTHORITY, BASE_PATH, RECENT_SEARCHES);
uriMatcher.addURI(BuildConfig.RECENT_SEARCH_AUTHORITY, BASE_PATH + "/#", RECENT_SEARCHES_ID);
}
public static Uri uriForId(int id) {
return Uri.parse(BASE_URI.toString() + "/" + id);
}
@Inject DBOpenHelper dbOpenHelper;
/**
* This functions executes query for searching recent searches in SqLite DB
**/
@SuppressWarnings("ConstantConditions")
@Override
public Cursor query(@NonNull Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
queryBuilder.setTables(TABLE_NAME);
int uriType = uriMatcher.match(uri);
SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
Cursor cursor;
switch (uriType) {
case RECENT_SEARCHES:
cursor = queryBuilder.query(db, projection, selection, selectionArgs,
null, null, sortOrder);
break;
case RECENT_SEARCHES_ID:
cursor = queryBuilder.query(db,
ALL_FIELDS,
"_id = ?",
new String[]{uri.getLastPathSegment()},
null,
null,
sortOrder
);
break;
default:
throw new IllegalArgumentException("Unknown URI" + uri);
}
cursor.setNotificationUri(getContext().getContentResolver(), uri);
return cursor;
}
@Override
public String getType(@NonNull Uri uri) {
return null;
}
/**
* This functions executes query for inserting a recentSearch object in SqLite DB
**/
@SuppressWarnings("ConstantConditions")
@Override
public Uri insert(@NonNull Uri uri, ContentValues contentValues) {
int uriType = uriMatcher.match(uri);
SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase();
long id;
switch (uriType) {
case RECENT_SEARCHES:
id = sqlDB.insert(TABLE_NAME, null, contentValues);
break;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return Uri.parse(BASE_URI + "/" + id);
}
/**
* This functions executes query for deleting a recentSearch object in SqLite DB
**/
@Override
public int delete(@NonNull Uri uri, String s, String[] strings) {
int rows;
int uriType = uriMatcher.match(uri);
SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
switch (uriType) {
case RECENT_SEARCHES_ID:
Timber.d("Deleting recent searches id %s", uri.getLastPathSegment());
rows = db.delete(RecentSearchesDao.Table.TABLE_NAME,
"_id = ?",
new String[]{uri.getLastPathSegment()}
);
break;
default:
throw new IllegalArgumentException("Unknown URI" + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return rows;
}
/**
* This functions executes query for inserting multiple recentSearch objects in SqLite DB
**/
@SuppressWarnings("ConstantConditions")
@Override
public int bulkInsert(@NonNull Uri uri, @NonNull ContentValues[] values) {
Timber.d("Hello, bulk insert! (RecentSearchesContentProvider)");
int uriType = uriMatcher.match(uri);
SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase();
sqlDB.beginTransaction();
switch (uriType) {
case RECENT_SEARCHES:
for (ContentValues value : values) {
Timber.d("Inserting! %s", value);
sqlDB.insert(TABLE_NAME, null, value);
}
break;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
sqlDB.setTransactionSuccessful();
sqlDB.endTransaction();
getContext().getContentResolver().notifyChange(uri, null);
return values.length;
}
/**
* This functions executes query for updating a particular recentSearch object in SqLite DB
**/
@SuppressWarnings("ConstantConditions")
@Override
public int update(@NonNull Uri uri, ContentValues contentValues, String selection,
String[] selectionArgs) {
/*
SQL Injection warnings: First, note that we're not exposing this to the
outside world (exported="false"). Even then, we should make sure to sanitize
all user input appropriately. Input that passes through ContentValues
should be fine. So only issues are those that pass in via concating.
In here, the only concat created argument is for id. It is cast to an int,
and will error out otherwise.
*/
int uriType = uriMatcher.match(uri);
SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase();
int rowsUpdated;
switch (uriType) {
case RECENT_SEARCHES_ID:
if (TextUtils.isEmpty(selection)) {
int id = Integer.valueOf(uri.getLastPathSegment());
rowsUpdated = sqlDB.update(TABLE_NAME,
contentValues,
COLUMN_ID + " = ?",
new String[]{String.valueOf(id)});
} else {
throw new IllegalArgumentException(
"Parameter `selection` should be empty when updating an ID");
}
break;
default:
throw new IllegalArgumentException("Unknown URI: " + uri + " with type " + uriType);
}
getContext().getContentResolver().notifyChange(uri, null);
return rowsUpdated;
}
}

View file

@ -0,0 +1,174 @@
package fr.free.nrw.commons.explore.recentsearches
import android.content.ContentValues
import android.content.UriMatcher
import android.database.Cursor
import android.database.sqlite.SQLiteQueryBuilder
import android.net.Uri
import androidx.core.net.toUri
import fr.free.nrw.commons.BuildConfig
import fr.free.nrw.commons.di.CommonsDaggerContentProvider
import fr.free.nrw.commons.explore.recentsearches.RecentSearchesDao.Table.ALL_FIELDS
import fr.free.nrw.commons.explore.recentsearches.RecentSearchesDao.Table.COLUMN_ID
import fr.free.nrw.commons.explore.recentsearches.RecentSearchesDao.Table.TABLE_NAME
/**
* This class contains functions for executing queries for
* inserting, searching, deleting, editing recent searches in SqLite DB
*/
class RecentSearchesContentProvider : CommonsDaggerContentProvider() {
/**
* This functions executes query for searching recent searches in SqLite DB
*/
override fun query(
uri: Uri, projection: Array<String>?, selection: String?,
selectionArgs: Array<String>?, sortOrder: String?
): Cursor {
val queryBuilder = SQLiteQueryBuilder().apply {
tables = TABLE_NAME
}
val uriType = uriMatcher.match(uri)
val cursor = when (uriType) {
RECENT_SEARCHES -> queryBuilder.query(
requireDb(), projection, selection, selectionArgs,
null, null, sortOrder
)
RECENT_SEARCHES_ID -> queryBuilder.query(
requireDb(),
ALL_FIELDS,
"$COLUMN_ID = ?",
arrayOf(uri.lastPathSegment),
null,
null,
sortOrder
)
else -> throw IllegalArgumentException("Unknown URI$uri")
}
cursor.setNotificationUri(requireContext().contentResolver, uri)
return cursor
}
override fun getType(uri: Uri): String? = null
/**
* This functions executes query for inserting a recentSearch object in SqLite DB
*/
override fun insert(uri: Uri, contentValues: ContentValues?): Uri? {
val uriType = uriMatcher.match(uri)
val id: Long = when (uriType) {
RECENT_SEARCHES -> requireDb().insert(TABLE_NAME, null, contentValues)
else -> throw IllegalArgumentException("Unknown URI: $uri")
}
requireContext().contentResolver.notifyChange(uri, null)
return "$BASE_URI/$id".toUri()
}
/**
* This functions executes query for deleting a recentSearch object in SqLite DB
*/
override fun delete(uri: Uri, s: String?, strings: Array<String>?): Int {
val rows: Int
val uriType = uriMatcher.match(uri)
when (uriType) {
RECENT_SEARCHES_ID -> {
rows = requireDb().delete(
TABLE_NAME,
"_id = ?",
arrayOf(uri.lastPathSegment)
)
}
else -> throw IllegalArgumentException("Unknown URI - $uri")
}
requireContext().contentResolver.notifyChange(uri, null)
return rows
}
/**
* This functions executes query for inserting multiple recentSearch objects in SqLite DB
*/
override fun bulkInsert(uri: Uri, values: Array<ContentValues>): Int {
val uriType = uriMatcher.match(uri)
val sqlDB = requireDb()
sqlDB.beginTransaction()
when (uriType) {
RECENT_SEARCHES -> for (value in values) {
sqlDB.insert(TABLE_NAME, null, value)
}
else -> throw IllegalArgumentException("Unknown URI: $uri")
}
sqlDB.setTransactionSuccessful()
sqlDB.endTransaction()
requireContext().contentResolver.notifyChange(uri, null)
return values.size
}
/**
* This functions executes query for updating a particular recentSearch object in SqLite DB
*/
override fun update(
uri: Uri, contentValues: ContentValues?, selection: String?,
selectionArgs: Array<String>?
): Int {
/*
SQL Injection warnings: First, note that we're not exposing this to the
outside world (exported="false"). Even then, we should make sure to sanitize
all user input appropriately. Input that passes through ContentValues
should be fine. So only issues are those that pass in via concating.
In here, the only concat created argument is for id. It is cast to an int,
and will error out otherwise.
*/
val uriType = uriMatcher.match(uri)
val rowsUpdated: Int
when (uriType) {
RECENT_SEARCHES_ID -> if (selection.isNullOrEmpty()) {
val id = uri.lastPathSegment!!.toInt()
rowsUpdated = requireDb().update(
TABLE_NAME,
contentValues,
"$COLUMN_ID = ?",
arrayOf(id.toString())
)
} else {
throw IllegalArgumentException(
"Parameter `selection` should be empty when updating an ID"
)
}
else -> throw IllegalArgumentException("Unknown URI: $uri with type $uriType")
}
requireContext().contentResolver.notifyChange(uri, null)
return rowsUpdated
}
companion object {
// For URI matcher
private const val RECENT_SEARCHES = 1
private const val RECENT_SEARCHES_ID = 2
private const val BASE_PATH = "recent_searches"
@JvmField
val BASE_URI: Uri = "content://${BuildConfig.RECENT_SEARCH_AUTHORITY}/$BASE_PATH".toUri()
private val uriMatcher = UriMatcher(UriMatcher.NO_MATCH)
init {
uriMatcher.addURI(BuildConfig.RECENT_SEARCH_AUTHORITY, BASE_PATH, RECENT_SEARCHES)
uriMatcher.addURI(BuildConfig.RECENT_SEARCH_AUTHORITY, "$BASE_PATH/#", RECENT_SEARCHES_ID)
}
@JvmStatic
fun uriForId(id: Int): Uri = "$BASE_URI/$id".toUri()
}
}

View file

@ -3,17 +3,13 @@ package fr.free.nrw.commons.recentlanguages
import android.content.ContentValues import android.content.ContentValues
import android.database.Cursor import android.database.Cursor
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteQueryBuilder import android.database.sqlite.SQLiteQueryBuilder
import android.net.Uri import android.net.Uri
import android.text.TextUtils
import fr.free.nrw.commons.BuildConfig import fr.free.nrw.commons.BuildConfig
import fr.free.nrw.commons.data.DBOpenHelper
import fr.free.nrw.commons.di.CommonsDaggerContentProvider import fr.free.nrw.commons.di.CommonsDaggerContentProvider
import fr.free.nrw.commons.recentlanguages.RecentLanguagesDao.Table.COLUMN_NAME import fr.free.nrw.commons.recentlanguages.RecentLanguagesDao.Table.COLUMN_NAME
import fr.free.nrw.commons.recentlanguages.RecentLanguagesDao.Table.TABLE_NAME import fr.free.nrw.commons.recentlanguages.RecentLanguagesDao.Table.TABLE_NAME
import javax.inject.Inject import androidx.core.net.toUri
import timber.log.Timber
/** /**
@ -23,27 +19,17 @@ class RecentLanguagesContentProvider : CommonsDaggerContentProvider() {
companion object { companion object {
private const val BASE_PATH = "recent_languages" private const val BASE_PATH = "recent_languages"
val BASE_URI: Uri = val BASE_URI: Uri = "content://${BuildConfig.RECENT_LANGUAGE_AUTHORITY}/$BASE_PATH".toUri()
Uri.parse(
"content://${BuildConfig.RECENT_LANGUAGE_AUTHORITY}/$BASE_PATH"
)
/** /**
* Append language code to the base URI * Append language code to the base URI
* @param languageCode Code of a language * @param languageCode Code of a language
*/ */
@JvmStatic @JvmStatic
fun uriForCode(languageCode: String): Uri { fun uriForCode(languageCode: String): Uri = "$BASE_URI/$languageCode".toUri()
return Uri.parse("$BASE_URI/$languageCode")
}
} }
@Inject override fun getType(uri: Uri): String? = null
lateinit var dbOpenHelper: DBOpenHelper
override fun getType(uri: Uri): String? {
return null
}
/** /**
* Queries the SQLite database for the recently used languages * Queries the SQLite database for the recently used languages
@ -60,11 +46,12 @@ class RecentLanguagesContentProvider : CommonsDaggerContentProvider() {
selectionArgs: Array<String>?, selectionArgs: Array<String>?,
sortOrder: String? sortOrder: String?
): Cursor? { ): Cursor? {
val queryBuilder = SQLiteQueryBuilder() val queryBuilder = SQLiteQueryBuilder().apply {
queryBuilder.tables = TABLE_NAME tables = TABLE_NAME
val db = dbOpenHelper.readableDatabase }
val cursor = queryBuilder.query( val cursor = queryBuilder.query(
db, requireDb(),
projection, projection,
selection, selection,
selectionArgs, selectionArgs,
@ -72,7 +59,7 @@ class RecentLanguagesContentProvider : CommonsDaggerContentProvider() {
null, null,
sortOrder sortOrder
) )
cursor.setNotificationUri(context?.contentResolver, uri) cursor.setNotificationUri(requireContext().contentResolver, uri)
return cursor return cursor
} }
@ -89,12 +76,11 @@ class RecentLanguagesContentProvider : CommonsDaggerContentProvider() {
selection: String?, selection: String?,
selectionArgs: Array<String>? selectionArgs: Array<String>?
): Int { ): Int {
val sqlDB = dbOpenHelper.writableDatabase
val rowsUpdated: Int val rowsUpdated: Int
if (selection.isNullOrEmpty()) { if (selection.isNullOrEmpty()) {
val id = uri.lastPathSegment?.toInt() val id = uri.lastPathSegment?.toInt()
?: throw IllegalArgumentException("Invalid URI: $uri") ?: throw IllegalArgumentException("Invalid URI: $uri")
rowsUpdated = sqlDB.update( rowsUpdated = requireDb().update(
TABLE_NAME, TABLE_NAME,
contentValues, contentValues,
"$COLUMN_NAME = ?", "$COLUMN_NAME = ?",
@ -104,7 +90,7 @@ class RecentLanguagesContentProvider : CommonsDaggerContentProvider() {
throw IllegalArgumentException("Parameter `selection` should be empty when updating an ID") throw IllegalArgumentException("Parameter `selection` should be empty when updating an ID")
} }
context?.contentResolver?.notifyChange(uri, null) requireContext().contentResolver?.notifyChange(uri, null)
return rowsUpdated return rowsUpdated
} }
@ -114,14 +100,13 @@ class RecentLanguagesContentProvider : CommonsDaggerContentProvider() {
* @param contentValues : new values to be entered to the database * @param contentValues : new values to be entered to the database
*/ */
override fun insert(uri: Uri, contentValues: ContentValues?): Uri? { override fun insert(uri: Uri, contentValues: ContentValues?): Uri? {
val sqlDB = dbOpenHelper.writableDatabase val id = requireDb().insert(
val id = sqlDB.insert(
TABLE_NAME, TABLE_NAME,
null, null,
contentValues contentValues
) )
context?.contentResolver?.notifyChange(uri, null) requireContext().contentResolver?.notifyChange(uri, null)
return Uri.parse("$BASE_URI/$id") return "$BASE_URI/$id".toUri()
} }
/** /**
@ -129,14 +114,12 @@ class RecentLanguagesContentProvider : CommonsDaggerContentProvider() {
* @param uri : contains the URI for recently used languages * @param uri : contains the URI for recently used languages
*/ */
override fun delete(uri: Uri, s: String?, strings: Array<String>?): Int { override fun delete(uri: Uri, s: String?, strings: Array<String>?): Int {
val db = dbOpenHelper.readableDatabase val rows = requireDb().delete(
Timber.d("Deleting recently used language %s", uri.lastPathSegment)
val rows = db.delete(
TABLE_NAME, TABLE_NAME,
"language_code = ?", "language_code = ?",
arrayOf(uri.lastPathSegment) arrayOf(uri.lastPathSegment)
) )
context?.contentResolver?.notifyChange(uri, null) requireContext().contentResolver?.notifyChange(uri, null)
return rows return rows
} }
} }

View file

@ -19,7 +19,7 @@ import com.nhaarman.mockitokotlin2.verify
import com.nhaarman.mockitokotlin2.whenever import com.nhaarman.mockitokotlin2.whenever
import fr.free.nrw.commons.TestCommonsApplication import fr.free.nrw.commons.TestCommonsApplication
import fr.free.nrw.commons.bookmarks.models.Bookmark import fr.free.nrw.commons.bookmarks.models.Bookmark
import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesContentProvider.BASE_URI import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesContentProvider.Companion.BASE_URI
import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesDao.Table.COLUMN_CREATOR import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesDao.Table.COLUMN_CREATOR
import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesDao.Table.COLUMN_MEDIA_NAME import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesDao.Table.COLUMN_MEDIA_NAME
import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesDao.Table.CREATE_TABLE_STATEMENT import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesDao.Table.CREATE_TABLE_STATEMENT

View file

@ -29,7 +29,7 @@ import fr.free.nrw.commons.category.CategoryDao.Table.DROP_TABLE_STATEMENT
import fr.free.nrw.commons.category.CategoryDao.Table.onCreate import fr.free.nrw.commons.category.CategoryDao.Table.onCreate
import fr.free.nrw.commons.category.CategoryDao.Table.onDelete import fr.free.nrw.commons.category.CategoryDao.Table.onDelete
import fr.free.nrw.commons.category.CategoryDao.Table.onUpdate import fr.free.nrw.commons.category.CategoryDao.Table.onUpdate
import fr.free.nrw.commons.explore.recentsearches.RecentSearchesContentProvider.uriForId import fr.free.nrw.commons.explore.recentsearches.RecentSearchesContentProvider.Companion.uriForId
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull import org.junit.Assert.assertNull

View file

@ -18,8 +18,8 @@ import com.nhaarman.mockitokotlin2.verify
import com.nhaarman.mockitokotlin2.whenever import com.nhaarman.mockitokotlin2.whenever
import fr.free.nrw.commons.TestCommonsApplication import fr.free.nrw.commons.TestCommonsApplication
import fr.free.nrw.commons.explore.models.RecentSearch import fr.free.nrw.commons.explore.models.RecentSearch
import fr.free.nrw.commons.explore.recentsearches.RecentSearchesContentProvider.BASE_URI import fr.free.nrw.commons.explore.recentsearches.RecentSearchesContentProvider.Companion.BASE_URI
import fr.free.nrw.commons.explore.recentsearches.RecentSearchesContentProvider.uriForId import fr.free.nrw.commons.explore.recentsearches.RecentSearchesContentProvider.Companion.uriForId
import fr.free.nrw.commons.explore.recentsearches.RecentSearchesDao.Table.ALL_FIELDS import fr.free.nrw.commons.explore.recentsearches.RecentSearchesDao.Table.ALL_FIELDS
import fr.free.nrw.commons.explore.recentsearches.RecentSearchesDao.Table.COLUMN_ID import fr.free.nrw.commons.explore.recentsearches.RecentSearchesDao.Table.COLUMN_ID
import fr.free.nrw.commons.explore.recentsearches.RecentSearchesDao.Table.COLUMN_LAST_USED import fr.free.nrw.commons.explore.recentsearches.RecentSearchesDao.Table.COLUMN_LAST_USED