diff --git a/ExaltedDice/res/values/strings.xml b/ExaltedDice/res/values/strings.xml index eec08d3..4259cf6 100755 --- a/ExaltedDice/res/values/strings.xml +++ b/ExaltedDice/res/values/strings.xml @@ -1,12 +1,9 @@ - + Exalted Dice + Rolled: + twentycodes@gmail.com + Deleting… -Rolled: - - - - -twentycodes@gmail.com - + \ No newline at end of file diff --git a/ExaltedDice/src/com/TwentyCode/android/ExaltedDice/Database.java b/ExaltedDice/src/com/TwentyCode/android/ExaltedDice/Database.java new file mode 100644 index 0000000..450760c --- /dev/null +++ b/ExaltedDice/src/com/TwentyCode/android/ExaltedDice/Database.java @@ -0,0 +1,543 @@ +/** + * Database.java + * @date Feb 4, 2012 + * @author Twenty Codes, LLC + * @author ricky barrette + */ +package com.TwentyCode.android.ExaltedDice; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.channels.FileChannel; +import java.util.ArrayList; +import java.util.List; +import java.util.Map.Entry; + +import android.app.ProgressDialog; +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.DatabaseUtils; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.os.Environment; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.util.Log; + +/** + * This class will be the main interface between Exalted Dice and it's database + * @author ricky barrette + */ +public class Database { + + private static final String TAG = "Database"; + private Context mContext; + private SQLiteDatabase mDb; + public boolean isUpgrading = false; + private DatabaseListener mListener; + + /** + * database version. If this is increased, the database will be upgraded the next time it connects + */ + private final int DATABASE_VERSION = 1; + + /** + * database file name + */ + private final String DATABASE_NAME = "history.db"; + + /** + * database table for games + */ + private final String GAME_NAME_TABLE = "game_name"; + + /** + * Database table of history + */ + private final String GAME_HISTORY_TABLE = "game_history"; + + /* + * Database keys + */ + private static final String KEY = "key"; + private static final String KEY_VALUE = "value"; + + /* + * database value keys + */ + public final static String KEY_NAME = "name"; + public final static String KEY_D_TYPE = "d_type"; + public final static String KEY_NUMBER = "number"; + public final static String KEY_LOG = "log"; + public final static String KEY_ROLL_ID = "log_number"; + + + /** + * A helper class to manage database creation and version management. + * @author ricky barrette + */ + private class OpenHelper extends SQLiteOpenHelper { + + + /** + * Creates a new OpenHelper + * @param context + * @author ricky barrette + */ + public OpenHelper(Context context) { + super(context, DATABASE_NAME, null, DATABASE_VERSION); + } + + /** + * Creates the initial database structure + * @param db + * @author ricky barrette + */ + private void createDatabase(SQLiteDatabase db){ + db.execSQL("CREATE TABLE " + GAME_NAME_TABLE + + "(id INTEGER PRIMARY KEY, " + + KEY_NAME+" TEXT)"); + db.execSQL("CREATE TABLE " + GAME_HISTORY_TABLE + + "(id INTEGER PRIMARY KEY, " + + KEY_NAME+" TEXT, " + + KEY+" TEXT, " + + KEY_VALUE+" TEXT)"); + } + + /** + * called when the database is created for the first time. this will create our Ringer database + * (non-Javadoc) + * @see android.database.sqlite.SQLiteOpenHelper#onCreate(android.database.sqlite.SQLiteDatabase) + * @author ricky barrette + */ + @Override + public void onCreate(SQLiteDatabase db) { + createDatabase(db); + } + + /** + * called when the database needs to be updated + * (non-Javadoc) + * @see android.database.sqlite.SQLiteOpenHelper#onUpgrade(android.database.sqlite.SQLiteDatabase, int, int) + * @author ricky barrette + */ + @Override + public void onUpgrade(final SQLiteDatabase db, final int oldVersion, int newVersion) { + Log.w(TAG, "Upgrading database from version "+oldVersion+" to "+newVersion); + + if(Database.this.mListener != null) + Database.this.mListener.onDatabaseUpgrade(); + + Database.this.isUpgrading = true; + + final Handler handler = new Handler(){ + @Override + public void handleMessage(Message msg) { + if(Database.this.mListener != null) + Database.this.mListener.onDatabaseUpgradeComplete(); + } + }; + + //upgrade thread + new Thread( new Runnable(){ + @Override + public void run(){ + Looper.prepare(); + switch(oldVersion){ + case 1: + // upgrade from 1 to 2 + case 2: + //upgrade from 2 to 3 + case 3: + //upgrade from 4 to 4 + } + handler.sendEmptyMessage(0); + Database.this.isUpgrading = false; + } + }).start(); + } + } + + /** + * Parses a string boolean from the database + * @param bool + * @return true or false + * @author ricky barrette + */ + public static boolean parseBoolean(String bool){ + try { + return bool == null ? false : Integer.parseInt(bool) == 1 ? true : false; + } catch (NumberFormatException e) { + return false; + } + } + + /** + * Creates a new Database + * @param context + * @author ricky barrette + */ + public Database(Context context){ + this.mContext = context; + this.mDb = new OpenHelper(this.mContext).getWritableDatabase(); + } + + /** + * Creates a new Database + * @param context + * @param listener + * @author ricky barrette + */ + public Database(Context context, DatabaseListener listener){ + this.mListener = listener; + this.mContext = context; + this.mDb = new OpenHelper(this.mContext).getWritableDatabase(); + } + + /** + * Backs up the database to the user's external storage + * @return true if successful + * @author ricky barrette + */ + public boolean backup(){ + File dbFile = new File(Environment.getDataDirectory() + "/data/"+mContext.getPackageName()+"/databases/"+DATABASE_NAME); + + File exportDir = new File(Environment.getExternalStorageDirectory(), "/"+this.mContext.getString(R.string.app_name)); + if (!exportDir.exists()) { + exportDir.mkdirs(); + } + File file = new File(exportDir, dbFile.getName()); + + try { + file.createNewFile(); + this.copyFile(dbFile, file); + return true; + } catch (IOException e) { + e.printStackTrace(); + return false; + } + } + + /** + * Checks to see if this ringer name is original, if not it renames it + * @param name + * @return + */ + private String checkName(String name){ + List names = this.getAllGameTitles(); + String ringerName = name; + int count = 1; + for(int index = 0; index < names.size(); index++ ){ + if(ringerName.equals(names.get(index))){ + ringerName = name + count+++""; + index = 0; + } + } + return ringerName; + } + + /** + * Copies a file + * @param src file + * @param dst file + * @throws IOException + * @author ricky barrette + */ + private void copyFile(File src, File dst) throws IOException { + FileChannel inChannel = new FileInputStream(src).getChannel(); + FileChannel outChannel = new FileOutputStream(dst).getChannel(); + try { + inChannel.transferTo(0, inChannel.size(), outChannel); + } finally { + if (inChannel != null) + inChannel.close(); + if (outChannel != null) + outChannel.close(); + } + } + + /** + * deletes a game by its row id + * @param id + * @author ricky barrette + */ + public void deleteGame(final long id) { + + final ProgressDialog progress = ProgressDialog.show(Database.this.mContext, "", Database.this.mContext.getText(R.string.deleteing), true, true); + + final Handler handler = new Handler(){ + @Override + public void handleMessage(Message msg) { + if(Database.this.mListener != null) + Database.this.mListener.onDeletionComplete(); + progress.dismiss(); + } + }; + + //ringer deleting thread + new Thread( new Runnable(){ + @Override + public void run(){ + Looper.prepare(); + + /* + * get the game name from the id, and then delete all its information from the game histroy table + */ + Database.this.mDb.delete(GAME_HISTORY_TABLE, KEY_NAME +" = "+ DatabaseUtils.sqlEscapeString(Database.this.getGameName(id)), null); + + /* + * finally delete the ringer from the ringer table + */ + Database.this.mDb.delete(GAME_NAME_TABLE, "id = "+ id, null); + updateRowIds(id +1); + handler.sendEmptyMessage(0); + } + }).start(); + } + + /** + * @return a cursor containing all game names + * @author ricky barrette + */ + public Cursor getAllGames(){ + return this.mDb.query(GAME_NAME_TABLE, new String[] { KEY_NAME }, null, null, null, null, null); + } + + /** + * returns all game names in the database, where or not if they are enabled + * @return list of all strings in the database table + * @author ricky barrette + */ + public List getAllGameTitles() { + List list = new ArrayList(); + Cursor cursor = this.mDb.query(GAME_NAME_TABLE, new String[] { KEY_NAME }, null, null, null, null, null); + if (cursor.moveToFirst()) { + do { + list.add(cursor.getString(0)); + } while (cursor.moveToNext()); + } + if (cursor != null && !cursor.isClosed()) { + cursor.close(); + } + return list; + } + + /** + * gets a game name from a row id; + * @param id + * @return cursor containing the note + * @author ricky barrette + */ + public Cursor getGameFromId(long id) { + return this.mDb.query(GAME_NAME_TABLE, new String[]{ KEY_NAME }, "id = "+id, null, null, null, null); + } + + /** + * gets a games's histrory info from the supplied ringer name + * @param gameName + * @return + * @author ricky barrette + */ + public ContentValues getGameHistoryInfo(String gameName){ + ContentValues values = new ContentValues(); + Cursor info = this.mDb.query(GAME_HISTORY_TABLE, new String[]{ KEY, KEY_VALUE }, KEY_NAME +" = "+ DatabaseUtils.sqlEscapeString(gameName), null, null, null, null); + if (info.moveToFirst()) { + do { + values.put(info.getString(0), info.getString(1)); + } while (info.moveToNext()); + } + if (info != null && !info.isClosed()) { + info.close(); + } + return values; + } + + /** + * Retrieves the game's name form the game name table + * @param id + * @return game's name + * @author ricky barrette + */ + public String getGameName(long id) { + String name = null; + Cursor cursor = this.mDb.query(GAME_NAME_TABLE, new String[]{ KEY_NAME }, "id = "+id, null, null, null, null);; + if (cursor.moveToFirst()) { + name = cursor.getString(0); + } + if (cursor != null && !cursor.isClosed()) { + cursor.close(); + } + return name; + } + + /** + * Inserts a new game into the database + * @param game values + * @param gameHistory values + * @author ricky barrette + */ + public void insertRinger(String gameName, ContentValues gameHistory){ + ContentValues game = new ContentValues(); + game.put(Database.KEY_NAME, checkName(gameName)); + mDb.insert(GAME_NAME_TABLE, null, game); + String ringerName = game.getAsString(Database.KEY_NAME); + + //insert the information values + for(Entry item : gameHistory.valueSet()){ + ContentValues values = new ContentValues(); + values.put(KEY_NAME, ringerName); + values.put(KEY, item.getKey()); + /* + * Try get the value. + * If there is a class cast exception, try casting to the next object type. + * + * The following types are tried: + * String + * Integer + * Boolean + */ + try { + values.put(KEY_VALUE, (String) item.getValue()); + } catch (ClassCastException e) { + try { + values.put(KEY_VALUE, (Boolean) item.getValue() ? 1 : 0); + } catch (ClassCastException e1) { + values.put(KEY_VALUE, (Integer) item.getValue()); + } + } + mDb.insert(GAME_HISTORY_TABLE, null, values); + } + } + + /** + * Restores the database from external storage + * @return true if successful + * @author ricky barrette + */ + public void restore(){ + File dbFile = new File(Environment.getDataDirectory() + "/data/"+mContext.getPackageName()+"/databases/"+DATABASE_NAME); + + File exportDir = new File(Environment.getExternalStorageDirectory(), "/"+this.mContext.getString(R.string.app_name)); + if (!exportDir.exists()) { + exportDir.mkdirs(); + } + File file = new File(exportDir, dbFile.getName()); + + try { + file.createNewFile(); + this.copyFile(file, dbFile); + } catch (IOException e) { + e.printStackTrace(); + } + /* + * close and reopen the database to upgrade it. + */ + this.mDb.close(); + this.mDb = new OpenHelper(this.mContext).getWritableDatabase(); + if(this.mDb.isOpen() && ! this.isUpgrading) + if(this.mListener != null) + this.mListener.onRestoreComplete(); + } + + /** + * updates a ringer by it's id + * @param id + * @param ringer values + * @param gameHistory values + * @author ricky barrette + */ + public void updateGame(long id, String gameName, ContentValues gameHistory) throws NullPointerException{ + + ContentValues game= new ContentValues(); + + if(gameName == null || gameHistory == null) + throw new NullPointerException("game content was null"); + + String ringer_name = getGameName(id); + if(!ringer_name.equals(gameName)) + game.put(Database.KEY_NAME, checkName(gameName)); + + /* + * update the information values in the info table + */ + for(Entry item : gameHistory.valueSet()){ + ContentValues values = new ContentValues(); + values.put(KEY_NAME, game.getAsString(KEY_NAME)); + values.put(KEY, item.getKey()); + try { + values.put(KEY_VALUE, (String) item.getValue()); + } catch (ClassCastException e) { + try { + values.put(KEY_VALUE, (Boolean) item.getValue() ? 1 : 0); + } catch (ClassCastException e1) { + values.put(KEY_VALUE, (Integer) item.getValue()); + } + } + /* + * try to update, if update fails insert + */ + if(!(mDb.update(GAME_HISTORY_TABLE, values, KEY_NAME + "="+ DatabaseUtils.sqlEscapeString(ringer_name) +" AND " + KEY +"='"+ item.getKey()+"'", null) > 0)) + mDb.insert(GAME_HISTORY_TABLE, null, values); + } + + /* + * update the ringer table + */ + mDb.update(GAME_NAME_TABLE, game, "id" + "= "+ id, null); + } + + /** + * Updates the row ids after a row is deleted + * @param id of the row to start with + * @author ricky barrette + */ + private void updateRowIds(long id) { + long currentRow; + ContentValues values = new ContentValues(); + Cursor cursor = this.mDb.query(GAME_NAME_TABLE, new String[] { "id" },null, null, null, null, null); + if (cursor.moveToFirst()) { + do { + currentRow = cursor.getLong(0); + if(currentRow == id){ + id++; + values.clear(); + values.put("id", currentRow -1); + mDb.update(GAME_NAME_TABLE, values, "id" + "= "+ currentRow, null); + } + } while (cursor.moveToNext()); + } + if (cursor != null && !cursor.isClosed()) { + cursor.close(); + } + } + + /** + * Updates all the roll ids after a row is deleted + * @param gameName + * @param id of the roll to start with + * @author ricky barrette + */ + private void updateRollIds(String gameName, int rollId) { + long currentRow; + ContentValues values = new ContentValues(); + Cursor cursor = this.mDb.query(GAME_HISTORY_TABLE, new String[] { KEY_ROLL_ID },KEY_NAME+" = "+ gameName, null, null, null, null); + if (cursor.moveToFirst()) { + do { + currentRow = cursor.getLong(0); + if(currentRow == rollId){ + rollId++; + values.clear(); + values.put(KEY_ROLL_ID, currentRow -1); + mDb.update(GAME_NAME_TABLE, values, KEY_ROLL_ID + "= "+ currentRow, null); + } + } while (cursor.moveToNext()); + } + if (cursor != null && !cursor.isClosed()) { + cursor.close(); + } + } +} \ No newline at end of file diff --git a/ExaltedDice/src/com/TwentyCode/android/ExaltedDice/DatabaseListener.java b/ExaltedDice/src/com/TwentyCode/android/ExaltedDice/DatabaseListener.java new file mode 100644 index 0000000..7cecefa --- /dev/null +++ b/ExaltedDice/src/com/TwentyCode/android/ExaltedDice/DatabaseListener.java @@ -0,0 +1,40 @@ +/** + * DatabaseListener.java + * @date Feb 4, 2012 + * @author Twenty Codes, LLC + * @author ricky barrette + */ +package com.TwentyCode.android.ExaltedDice; + +/** + * This interface will be used to listen to see when the database events are complete + * @author ricky barrette + */ +public interface DatabaseListener { + + /** + * Called when a database upgrade is completed + * @author ricky barrette + */ + public void onDatabaseUpgradeComplete(); + + /** + * Called when a deletion is completed + * + * @author ricky barrette + */ + public void onDeletionComplete(); + + /** + * Called when a database restore is completed + * @author ricky barrette + */ + public void onRestoreComplete(); + + /** + * Called when a database is being upgraded + * @author ricky barrette + */ + public void onDatabaseUpgrade(); + +} \ No newline at end of file