package com.fsck.k9.mailstore;
import java.util.List;
import java.util.Locale;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import timber.log.Timber;
import com.fsck.k9.Account;
import com.fsck.k9.BuildConfig;
import com.fsck.k9.K9;
import com.fsck.k9.mail.Flag;
import com.fsck.k9.mail.Folder;
import com.fsck.k9.mailstore.migrations.Migrations;
import com.fsck.k9.mailstore.migrations.MigrationsHelper;
import com.fsck.k9.preferences.Storage;
import static com.fsck.k9.mailstore.LocalStore.DB_VERSION;
import static java.lang.String.format;
import static java.util.Locale.US;
class StoreSchemaDefinition implements LockableDatabase.SchemaDefinition {
private final LocalStore localStore;
StoreSchemaDefinition(LocalStore localStore) {
this.localStore = localStore;
}
@Override
public int getVersion() {
return LocalStore.DB_VERSION;
}
@Override
public void doDbUpgrade(final SQLiteDatabase db) {
try {
upgradeDatabase(db);
} catch (Exception e) {
if (BuildConfig.DEBUG) {
throw new Error("Exception while upgrading database", e);
}
Timber.e(e, "Exception while upgrading database. Resetting the DB to v0");
db.setVersion(0);
upgradeDatabase(db);
}
}
private void upgradeDatabase(final SQLiteDatabase db) {
Timber.i("Upgrading database from version %d to version %d", db.getVersion(), DB_VERSION);
db.beginTransaction();
try {
// schema version 29 was when we moved to incremental updates
// in the case of a new db or a < v29 db, we blow away and start from scratch
if (db.getVersion() < 29) {
dbCreateDatabaseFromScratch(db);
} else {
RealMigrationsHelper migrationsHelper = new RealMigrationsHelper(localStore);
Migrations.upgradeDatabase(db, migrationsHelper);
}
db.setVersion(LocalStore.DB_VERSION);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
if (db.getVersion() != LocalStore.DB_VERSION) {
throw new RuntimeException("Database upgrade failed!");
}
}
private static void dbCreateDatabaseFromScratch(SQLiteDatabase db) {
db.execSQL("DROP TABLE IF EXISTS folders");
db.execSQL("CREATE TABLE folders (" +
"id INTEGER PRIMARY KEY," +
"name TEXT, " +
"last_updated INTEGER, " +
"unread_count INTEGER, " +
"visible_limit INTEGER, " +
"status TEXT, " +
"push_state TEXT, " +
"last_pushed INTEGER, " +
"flagged_count INTEGER default 0, " +
"integrate INTEGER, " +
"top_group INTEGER, " +
"poll_class TEXT, " +
"push_class TEXT, " +
"display_class TEXT, " +
"notify_class TEXT default '"+ Folder.FolderClass.INHERITED.name() + "', " +
"more_messages TEXT default \"unknown\"" +
")");
db.execSQL("CREATE INDEX IF NOT EXISTS folder_name ON folders (name)");
db.execSQL("DROP TABLE IF EXISTS messages");
db.execSQL("CREATE TABLE messages (" +
"id INTEGER PRIMARY KEY, " +
"deleted INTEGER default 0, " +
"folder_id INTEGER, " +
"uid TEXT, " +
"subject TEXT, " +
"date INTEGER, " +
"flags TEXT, " +
"sender_list TEXT, " +
"to_list TEXT, " +
"cc_list TEXT, " +
"bcc_list TEXT, " +
"reply_to_list TEXT, " +
"attachment_count INTEGER, " +
"internal_date INTEGER, " +
"message_id TEXT, " +
"preview_type TEXT default \"none\", " +
"preview TEXT, " +
"mime_type TEXT, "+
"normalized_subject_hash INTEGER, " +
"empty INTEGER default 0, " +
"read INTEGER default 0, " +
"flagged INTEGER default 0, " +
"answered INTEGER default 0, " +
"forwarded INTEGER default 0, " +
"message_part_id INTEGER" +
")");
db.execSQL("DROP TABLE IF EXISTS message_parts");
db.execSQL("CREATE TABLE message_parts (" +
"id INTEGER PRIMARY KEY, " +
"type INTEGER NOT NULL, " +
"root INTEGER, " +
"parent INTEGER NOT NULL, " +
"seq INTEGER NOT NULL, " +
"mime_type TEXT, " +
"decoded_body_size INTEGER, " +
"display_name TEXT, " +
"header TEXT, " +
"encoding TEXT, " +
"charset TEXT, " +
"data_location INTEGER NOT NULL, " +
"data BLOB, " +
"preamble TEXT, " +
"epilogue TEXT, " +
"boundary TEXT, " +
"content_id TEXT, " +
"server_extra TEXT" +
")");
db.execSQL("CREATE TRIGGER set_message_part_root " +
"AFTER INSERT ON message_parts " +
"BEGIN " +
"UPDATE message_parts SET root=id WHERE root IS NULL AND ROWID = NEW.ROWID; " +
"END");
db.execSQL("CREATE INDEX IF NOT EXISTS msg_uid ON messages (uid, folder_id)");
db.execSQL("DROP INDEX IF EXISTS msg_folder_id");
db.execSQL("DROP INDEX IF EXISTS msg_folder_id_date");
db.execSQL("CREATE INDEX IF NOT EXISTS msg_folder_id_deleted_date ON messages (folder_id,deleted,internal_date)");
db.execSQL("DROP INDEX IF EXISTS msg_empty");
db.execSQL("CREATE INDEX IF NOT EXISTS msg_empty ON messages (empty)");
db.execSQL("DROP INDEX IF EXISTS msg_read");
db.execSQL("CREATE INDEX IF NOT EXISTS msg_read ON messages (read)");
db.execSQL("DROP INDEX IF EXISTS msg_flagged");
db.execSQL("CREATE INDEX IF NOT EXISTS msg_flagged ON messages (flagged)");
db.execSQL("DROP INDEX IF EXISTS msg_composite");
db.execSQL("CREATE INDEX IF NOT EXISTS msg_composite ON messages (deleted, empty,folder_id,flagged,read)");
db.execSQL("DROP TABLE IF EXISTS threads");
db.execSQL("CREATE TABLE threads (" +
"id INTEGER PRIMARY KEY, " +
"message_id INTEGER, " +
"root INTEGER, " +
"parent INTEGER" +
")");
db.execSQL("DROP INDEX IF EXISTS threads_message_id");
db.execSQL("CREATE INDEX IF NOT EXISTS threads_message_id ON threads (message_id)");
db.execSQL("DROP INDEX IF EXISTS threads_root");
db.execSQL("CREATE INDEX IF NOT EXISTS threads_root ON threads (root)");
db.execSQL("DROP INDEX IF EXISTS threads_parent");
db.execSQL("CREATE INDEX IF NOT EXISTS threads_parent ON threads (parent)");
db.execSQL("DROP TRIGGER IF EXISTS set_thread_root");
db.execSQL("CREATE TRIGGER set_thread_root " +
"AFTER INSERT ON threads " +
"BEGIN " +
"UPDATE threads SET root=id WHERE root IS NULL AND ROWID = NEW.ROWID; " +
"END");
db.execSQL("DROP TABLE IF EXISTS pending_commands");
db.execSQL("CREATE TABLE pending_commands " +
"(id INTEGER PRIMARY KEY, command TEXT, data TEXT)");
db.execSQL("DROP TRIGGER IF EXISTS delete_folder");
db.execSQL("CREATE TRIGGER delete_folder BEFORE DELETE ON folders BEGIN DELETE FROM messages WHERE old.id = folder_id; END;");
db.execSQL("DROP TRIGGER IF EXISTS delete_message");
db.execSQL("CREATE TRIGGER delete_message " +
"BEFORE DELETE ON messages " +
"BEGIN " +
"DELETE FROM message_parts WHERE root = OLD.message_part_id; " +
"DELETE FROM messages_fulltext WHERE docid = OLD.id; " +
"END");
db.execSQL("DROP TABLE IF EXISTS messages_fulltext");
db.execSQL("CREATE VIRTUAL TABLE messages_fulltext USING fts4 (fulltext)");
}
private static class RealMigrationsHelper implements MigrationsHelper {
private final LocalStore localStore;
public RealMigrationsHelper(LocalStore localStore) {
this.localStore = localStore;
}
@Override
public LocalStore getLocalStore() {
return localStore;
}
@Override
public Storage getStorage() {
return localStore.getStorage();
}
@Override
public Account getAccount() {
return localStore.getAccount();
}
@Override
public Context getContext() {
return localStore.getContext();
}
@Override
public String serializeFlags(List<Flag> flags) {
return LocalStore.serializeFlags(flags);
}
}
}