/**
* Copyright (c) 2013, Redsolution LTD. All rights reserved.
*
* This file is part of Xabber project; you can redistribute it and/or
* modify it under the terms of the GNU General Public License, Version 3.
*
* Xabber is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License,
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
package com.xabber.android.data.database;
import android.database.DatabaseUtils;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteOpenHelper;
import com.xabber.android.data.Application;
import com.xabber.android.data.OnClearListener;
import com.xabber.android.data.OnLoadListener;
import com.xabber.android.data.OnMigrationListener;
import com.xabber.android.data.database.sqlite.AbstractAccountTable;
import com.xabber.android.data.database.sqlite.AccountTable;
import com.xabber.android.data.database.sqlite.AvatarTable;
import com.xabber.android.data.database.sqlite.DatabaseTable;
import com.xabber.android.data.database.sqlite.GroupTable;
import com.xabber.android.data.database.sqlite.MessageTable;
import com.xabber.android.data.database.sqlite.NotificationTable;
import com.xabber.android.data.database.sqlite.NotifyVisibleTable;
import com.xabber.android.data.database.sqlite.OTRTable;
import com.xabber.android.data.database.sqlite.PhraseTable;
import com.xabber.android.data.database.sqlite.PrivateChatTable;
import com.xabber.android.data.database.sqlite.RoomTable;
import com.xabber.android.data.database.sqlite.ShowTextTable;
import com.xabber.android.data.database.sqlite.SoundTable;
import com.xabber.android.data.database.sqlite.StatusTable;
import com.xabber.android.data.database.sqlite.Suppress100Table;
import com.xabber.android.data.database.sqlite.VCardTable;
import com.xabber.android.data.database.sqlite.VibroTable;
import com.xabber.android.data.entity.AccountJid;
import com.xabber.android.data.log.LogManager;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
/**
* Helps to open, create, and upgrade the database file.
* <p/>
* All requests to database / file system MUST be called from background thread.
*
* @author alexander.ivanov
*/
public class DatabaseManager extends SQLiteOpenHelper implements
OnLoadListener, OnClearListener {
private static final String DATABASE_NAME = "xabber.db";
private static final int DATABASE_VERSION = 70;
private static final SQLiteException DOWNGRADE_EXCEPTION = new SQLiteException(
"Database file was deleted");
private static DatabaseManager instance;
private final ArrayList<DatabaseTable> registeredTables;
public static DatabaseManager getInstance() {
if (instance == null) {
instance = new DatabaseManager();
}
return instance;
}
private DatabaseManager() {
super(Application.getInstance(), DATABASE_NAME, null, DATABASE_VERSION);
registeredTables = new ArrayList<>();
MessageDatabaseManager.getInstance();
RealmManager.getInstance();
}
public void addTables() {
addTable(AccountTable.getInstance());
addTable(RoomTable.getInstance());
addTable(MessageTable.getInstance());
addTable(GroupTable.getInstance());
addTable(PrivateChatTable.getInstance());
addTable(NotifyVisibleTable.getInstance());
addTable(ShowTextTable.getInstance());
addTable(SoundTable.getInstance());
addTable(VCardTable.getInstance());
addTable(AvatarTable.getInstance());
addTable(StatusTable.getInstance());
addTable(OTRTable.getInstance());
addTable(VibroTable.getInstance());
addTable(NotificationTable.getInstance());
addTable(Suppress100Table.getInstance());
addTable(PhraseTable.getInstance());
}
/**
* Builds IN statement for specified collection of values.
*
* @param <T>
* @param column
* @param values
* @return "column IN (value1, ... valueN)" or
* "(column IS NULL AND column IS NOT NULL)" if ids is empty.
*/
public static <T> String in(String column, Collection<T> values) {
if (values.isEmpty())
return new StringBuilder("(").append(column)
.append(" IS NULL AND ").append(column)
.append(" IS NOT NULL)").toString();
StringBuilder builder = new StringBuilder(column);
builder.append(" IN (");
Iterator<T> iterator = values.iterator();
while (iterator.hasNext()) {
T value = iterator.next();
if (value instanceof String)
builder.append(DatabaseUtils.sqlEscapeString((String) value));
else
builder.append(value.toString());
if (iterator.hasNext())
builder.append(",");
}
builder.append(")");
return builder.toString();
}
public static void execSQL(SQLiteDatabase db, String sql) {
LogManager.iString(DatabaseManager.class.getName(), sql);
db.execSQL(sql);
}
public static void dropTable(SQLiteDatabase db, String table) {
execSQL(db, "DROP TABLE IF EXISTS " + table + ";");
}
public static void renameTable(SQLiteDatabase db, String table,
String newTable) {
execSQL(db, "ALTER TABLE " + table + " RENAME TO " + newTable + ";");
}
public static String commaSeparatedFromCollection(Collection<String> strings) {
StringBuilder builder = new StringBuilder();
for (String value : strings) {
if (builder.length() > 0)
builder.append(",");
builder.append(value.replace("\\", "\\\\").replace(",", "\\,"));
}
return builder.toString();
}
public static Collection<String> collectionFromCommaSeparated(String value) {
Collection<String> collection = new ArrayList<String>();
boolean escape = false;
StringBuilder builder = new StringBuilder();
for (int index = 0; index < value.length(); index++) {
char chr = value.charAt(index);
if (!escape) {
if (chr == '\\') {
escape = true;
continue;
} else if (chr == ',') {
collection.add(builder.toString());
builder = new StringBuilder();
continue;
}
}
escape = false;
builder.append(chr);
}
collection.add(builder.toString());
return Collections.unmodifiableCollection(collection);
}
/**
* Register new table.
*
* @param table
*/
private void addTable(DatabaseTable table) {
registeredTables.add(table);
}
@Override
public void onLoad() {
try {
getWritableDatabase(); // Force onCreate or onUpgrade
RealmManager.getInstance().copyDataFromSqliteToRealm();
MessageDatabaseManager.getInstance().copyDataFromSqliteToRealm();
} catch (SQLiteException e) {
if (e == DOWNGRADE_EXCEPTION) {
// Downgrade occured
} else {
throw e;
}
}
}
@Override
public void onCreate(SQLiteDatabase db) {
for (DatabaseTable table : registeredTables)
table.create(db);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if (oldVersion > newVersion) {
LogManager.i(this, "Downgrading database from version "
+ oldVersion + " to " + newVersion);
File file = new File(db.getPath());
file.delete();
LogManager.i(this, "Database file was deleted");
throw DOWNGRADE_EXCEPTION;
// This will interrupt getWritableDatabase() call from
// DatabaseManager's constructor.
} else {
LogManager.i(this, "Upgrading database from version " + oldVersion
+ " to " + newVersion);
while (oldVersion < newVersion) {
oldVersion += 1;
LogManager.i(this, "Migrate to version " + oldVersion);
migrate(db, oldVersion);
for (DatabaseTable table : registeredTables)
table.migrate(db, oldVersion);
for (OnMigrationListener listener : Application.getInstance()
.getManagers(OnMigrationListener.class))
listener.onMigrate(oldVersion);
}
}
}
/**
* Called on database migration.
*
* @param db
* @param toVersion
*/
private void migrate(SQLiteDatabase db, int toVersion) {
switch (toVersion) {
case 42:
dropTable(db, "geolocs");
dropTable(db, "locations");
break;
default:
break;
}
}
@Override
public void onClear() {
for (DatabaseTable table : registeredTables) {
table.clear();
}
MessageDatabaseManager.getInstance().deleteRealm();
RealmManager.getInstance().deleteRealm();
}
public void removeAccount(final AccountJid account) {
// TODO: replace with constraint.
for (DatabaseTable table : registeredTables) {
if (table instanceof AbstractAccountTable) {
((AbstractAccountTable) table).removeAccount(account);
}
}
MessageDatabaseManager.getInstance().removeAccountMessages(account);
}
}