package org.gscript.data; import java.io.ByteArrayInputStream; import java.io.IOException; import java.util.Properties; import org.gscript.data.ContentUri.LibraryPathSegments; import org.gscript.data.library.Library; import org.gscript.data.library.Library.LibraryNotificationListener; import org.gscript.data.library.LibraryItem; import android.content.ContentProvider; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteQueryBuilder; import android.net.Uri; import android.text.TextUtils; import android.util.Log; import android.util.SparseArray; public class LibraryProvider extends ContentProvider implements LibraryNotificationListener { static final String LOG_TAG = "LibraryProvider"; public static final String COLUMN_ID = "_id"; public static final String COLUMN_TITLE = "title"; public static final String COLUMN_TYPE = "type"; public static final String COLUMN_PROPS = "props"; public static final String COLUMN_LIBRARY = "library"; public static final String COLUMN_PATH = "path"; public static final String COLUMN_KEY = "key"; public static final String COLUMN_VALUE = "value"; LibraryDatabase mLibraryDatabase; SparseArray<Library> mLibraries = new SparseArray<Library>(); @Override public boolean onCreate() { mLibraryDatabase = new LibraryDatabase(getContext()); return true; } @Override public String getType(Uri uri) { return null; } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { int uriType = ContentUri.MATCHER.match(uri); SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder(); SQLiteTable table; switch (uriType) { case ContentUri.MATCH_LIBRARY: case ContentUri.MATCH_ITEM_CONDITIONS: case ContentUri.MATCH_ITEM_ATTRIBS: table = getTableForUriMatch(uriType); queryBuilder.setTables(table.getTableName()); break; case ContentUri.MATCH_LIBRARY_ITEM: table = getTableForUriMatch(uriType); queryBuilder.setTables(table.getTableName()); queryBuilder.appendWhere(table.getPrimaryKeyField() + "=" + uri.getLastPathSegment()); break; case ContentUri.MATCH_LIBRARY_PATH: int flags = 0; try { String flagsQuery = uri .getQueryParameter(ContentUri.QUERY_FLAGS); if (flagsQuery != null) { flags = Integer.parseInt(flagsQuery); } LibraryPathSegments segments = LibraryPathSegments.parse(uri); /* check if library has already been loaded else load it now */ Library library = getLibraryForId(segments.id); if (library != null) { Cursor cursor = LibraryItem.toCursor((library.query( segments.path, flags))); if (cursor != null) { Uri notificationUri = uri.buildUpon().clearQuery() .build(); cursor.setNotificationUri(getContext() .getContentResolver(), notificationUri); return cursor; } } } catch (Exception e) { Log.e(LOG_TAG, "MATCH_LIBRARY_PATH: " + e.getMessage()); } return LibraryItem.emptyCursor(); case ContentUri.MATCH_ITEM_ATTRIBS_PATH: case ContentUri.MATCH_ITEM_CONDITIONS_PATH: LibraryPathSegments segments = LibraryPathSegments.parse(uri); table = getTableForUriMatch(uriType); queryBuilder.setTables(table.getTableName()); queryBuilder.appendWhere("library" + "=" + segments.id); queryBuilder .appendWhere(" AND path" + "=\"" + segments.path + "\""); break; default: throw new IllegalArgumentException("Unknown URI: " + uri); } SQLiteDatabase db = mLibraryDatabase.getReadableDatabase(); Cursor cursor = queryBuilder.query(db, projection, selection, selectionArgs, null, null, sortOrder); cursor.setNotificationUri(getContext().getContentResolver(), uri); return cursor; } @Override public Uri insert(Uri uri, ContentValues values) { int uriType = ContentUri.MATCHER.match(uri); SQLiteDatabase db = mLibraryDatabase.getWritableDatabase(); SQLiteTable table = getTableForUriMatch(uriType); long id = 0; switch (uriType) { case ContentUri.MATCH_LIBRARY: id = db.insert(table.getTableName(), null, values); break; case ContentUri.MATCH_LIBRARY_PATH: /* TODO: handle insert of library item */ break; case ContentUri.MATCH_ITEM_ATTRIBS_PATH: case ContentUri.MATCH_ITEM_CONDITIONS_PATH: LibraryPathSegments seg = LibraryPathSegments.parse(uri); values.put("library", seg.id); values.put("path", seg.path); id = db.insert(table.getTableName(), null, values); break; default: throw new IllegalArgumentException("Unknown URI: " + uri); } getContext().getContentResolver().notifyChange(uri, null); return Uri.withAppendedPath(uri, String.valueOf(id)); } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { int uriType = ContentUri.MATCHER.match(uri); SQLiteDatabase db = mLibraryDatabase.getWritableDatabase(); SQLiteTable table = getTableForUriMatch(uriType); int rowsUpdated = 0; switch (uriType) { case ContentUri.MATCH_LIBRARY_ITEM: String id = uri.getLastPathSegment(); if (TextUtils.isEmpty(selection)) { rowsUpdated = db.update(table.getTableName(), values, table.getPrimaryKeyField() + "=" + id, null); } else { rowsUpdated = db.update(table.getTableName(), values, table.getPrimaryKeyField() + "=" + id + " and " + selection, selectionArgs); } /* unload library so that any property changes will be reflected */ Library library = mLibraries.get(Integer.valueOf(id)); if (library != null) { library.onDestroy(); mLibraries.remove(library.getId()); } break; case ContentUri.MATCH_LIBRARY_PATH: /* TODO: handle update of library item */ break; default: throw new IllegalArgumentException("Unknown URI: " + uri); } getContext().getContentResolver().notifyChange(uri, null); return rowsUpdated; } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { int uriType = ContentUri.MATCHER.match(uri); SQLiteDatabase db = mLibraryDatabase.getWritableDatabase(); SQLiteTable table = getTableForUriMatch(uriType); int rowsDeleted = 0; switch (uriType) { case ContentUri.MATCH_LIBRARY_ITEM: String id = uri.getLastPathSegment(); if (TextUtils.isEmpty(selection)) { rowsDeleted = db.delete(table.getTableName(), table.getPrimaryKeyField() + "=" + id, null); } else { rowsDeleted = db.delete(table.getTableName(), table.getPrimaryKeyField() + "=" + id + " and " + selection, selectionArgs); } if (rowsDeleted > 0) { /* remove all attributes and conditions */ db.delete(ItemAttributesTable.TABLE_NAME, "library=?", new String[] { id }); db.delete(ItemConditionsTable.TABLE_NAME, "library=?", new String[] { id }); /* destroy library if loaded */ Library library = mLibraries.get(Integer.valueOf(id)); if (library != null) { library.onDestroy(); mLibraries.remove(library.getId()); } } break; case ContentUri.MATCH_LIBRARY_PATH: /* TODO: handle delete of library item */ break; case ContentUri.MATCH_ITEM_ATTRIBS_PATH: case ContentUri.MATCH_ITEM_CONDITIONS_PATH: LibraryPathSegments segments = LibraryPathSegments.parse(uri); rowsDeleted = db .delete(table.getTableName(), "library=? and path=?", new String[] { String.valueOf(segments.id), segments.path }); break; default: throw new IllegalArgumentException("Unknown URI: " + uri); } getContext().getContentResolver().notifyChange(uri, null); return rowsDeleted; } SQLiteTable getTableForUriMatch(int uriType) { switch (uriType) { case ContentUri.MATCH_LIBRARY: case ContentUri.MATCH_LIBRARY_ITEM: return LibraryDatabase.TABLES[LibraryDatabase.TABLE_LIBRARY]; case ContentUri.MATCH_ITEM_ATTRIBS: case ContentUri.MATCH_ITEM_ATTRIBS_PATH: return LibraryDatabase.TABLES[LibraryDatabase.TABLE_ITEM_ATTRIBS]; case ContentUri.MATCH_ITEM_CONDITIONS: case ContentUri.MATCH_ITEM_CONDITIONS_PATH: return LibraryDatabase.TABLES[LibraryDatabase.TABLE_ITEM_CONDITIONS]; } return null; } /* library */ Library getLibraryForId(int id) { Library library = mLibraries.get(id); if (library == null) { /* try to load library */ Uri uri = Uri.withAppendedPath(ContentUri.URI_LIBRARY, String.valueOf(id)); Cursor c = this.query(uri, null, null, null, null); if (c.moveToFirst()) { String type = c.getString(c.getColumnIndex(COLUMN_TYPE)); if ((library = Library.forType(type)) != null) { byte[] blob = c.getBlob(c.getColumnIndex(COLUMN_PROPS)); ByteArrayInputStream is = new ByteArrayInputStream(blob); Properties props = new Properties(); try { props.load(is); } catch (IOException e) { Log.e(LOG_TAG, "failed to load library properties"); } library.create(this.getContext(), id, props, this); mLibraries.append(id, library); } } else { Log.d(LOG_TAG, String.format("no library with id %d", id)); } } return library; } @Override public void OnLibraryChanged(Library library, String path) { Uri uri = Uri.withAppendedPath(ContentUri.URI_LIBRARY, String.valueOf(library.getId()) + "/" + path); getContext().getContentResolver().notifyChange(uri, null); } static class LibraryDatabase extends SQLiteOpenHelper { public static final String DATABASE_NAME = "library"; public static final int DATABASE_VERSION = 1; static final SQLiteTable[] TABLES = new SQLiteTable[] { new LibraryTable(), new ItemAttributesTable(), new ItemConditionsTable() }; static final int TABLE_LIBRARY = 0; static final int TABLE_ITEM_ATTRIBS = 1; static final int TABLE_ITEM_CONDITIONS = 2; LibraryDatabase(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } @Override public void onCreate(SQLiteDatabase db) { for (SQLiteTable table : TABLES) { table.onCreate(db); } } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { for (SQLiteTable table : TABLES) { table.onUpgrade(db, oldVersion, newVersion); } } } static class LibraryTable extends SQLiteTable { public static final String TABLE_NAME = "library"; public static final String[] COLUMNS = { COLUMN_ID, COLUMN_TITLE, COLUMN_TYPE, COLUMN_PROPS }; static final String SQL_CREATE_TABLE = "create table " + TABLE_NAME + "(" + COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + COLUMN_TITLE + " TEXT NOT NULL, " + COLUMN_TYPE + " TEXT NOT NULL, " + COLUMN_PROPS + " BLOB" + ");"; public LibraryTable() { super(TABLE_NAME); } @Override void onCreate(SQLiteDatabase db) { db.execSQL(SQL_CREATE_TABLE); } @Override void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME); onCreate(db); } @Override String getPrimaryKeyField() { return COLUMN_ID; } } static class ItemAttributesTable extends SQLiteTable { public static final String TABLE_NAME = "attributes"; public static final String[] COLUMNS = { COLUMN_ID, COLUMN_LIBRARY, COLUMN_PATH, COLUMN_KEY, COLUMN_VALUE }; static final String SQL_CREATE_TABLE = "create table " + TABLE_NAME + "(" + COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + COLUMN_LIBRARY + " INTEGER, " + COLUMN_PATH + " TEXT NOT NULL, " + COLUMN_KEY + " TEXT NOT NULL, " + COLUMN_VALUE + " TEXT NOT NULL" + ");"; public ItemAttributesTable() { super(TABLE_NAME); } @Override void onCreate(SQLiteDatabase db) { db.execSQL(SQL_CREATE_TABLE); } @Override void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME); onCreate(db); } @Override String getPrimaryKeyField() { return COLUMN_ID; } } static class ItemConditionsTable extends SQLiteTable { public static final String TABLE_NAME = "conditions"; public static final String[] COLUMNS = { COLUMN_ID, COLUMN_LIBRARY, COLUMN_PATH, COLUMN_KEY, COLUMN_VALUE }; static final String SQL_CREATE_TABLE = "create table " + TABLE_NAME + "(" + COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + COLUMN_LIBRARY + " INTEGER, " + COLUMN_PATH + " TEXT NOT NULL, " + COLUMN_KEY + " TEXT NOT NULL, " + COLUMN_VALUE + " TEXT NOT NULL" + ");"; public ItemConditionsTable() { super(TABLE_NAME); } @Override void onCreate(SQLiteDatabase db) { db.execSQL(SQL_CREATE_TABLE); } @Override void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME); onCreate(db); } @Override String getPrimaryKeyField() { return COLUMN_ID; } } }