Moved Dabase changes into their own class.

This commit is contained in:
Fabian Schlenz 2016-07-13 06:56:00 +02:00
parent ac67818d28
commit 0b95b42607
2 changed files with 306 additions and 166 deletions

View File

@ -47,8 +47,8 @@ import de.fabianonline.telegram_backup.mediafilemanager.FileManagerFactory;
public class Database {
private Connection conn;
private Statement stmt;
private UserManager user_manager;
private TelegramClient client;
public UserManager user_manager;
public TelegramClient client;
public Database(UserManager user_manager, TelegramClient client) {
this(user_manager, client, true);
@ -81,176 +81,20 @@ public class Database {
private void init(boolean update_db) {
if (!update_db) return;
try {
int version;
ResultSet rs = stmt.executeQuery("SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='database_versions'");
rs.next();
if (rs.getInt(1)==0) {
version = 0;
} else {
rs.close();
rs = stmt.executeQuery("SELECT MAX(version) FROM database_versions");
rs.next();
version = rs.getInt(1);
rs.close();
}
System.out.println("Database version: " + version);
if (version==0) {
System.out.println(" Updating to version 1...");
stmt.executeUpdate("CREATE TABLE messages ("
+ "id INTEGER PRIMARY KEY ASC, "
+ "dialog_id INTEGER, "
+ "to_id INTEGER, "
+ "from_id INTEGER, "
+ "from_type TEXT, "
+ "text TEXT, "
+ "time TEXT, "
+ "has_media BOOLEAN, "
+ "sticker TEXT, "
+ "data BLOB,"
+ "type TEXT)");
stmt.executeUpdate("CREATE TABLE dialogs ("
+ "id INTEGER PRIMARY KEY ASC, "
+ "name TEXT, "
+ "type TEXT)");
stmt.executeUpdate("CREATE TABLE people ("
+ "id INTEGER PRIMARY KEY ASC, "
+ "first_name TEXT, "
+ "last_name TEXT, "
+ "username TEXT, "
+ "type TEXT)");
stmt.executeUpdate("CREATE TABLE database_versions ("
+ "version INTEGER)");
stmt.executeUpdate("INSERT INTO database_versions (version) VALUES (1)");
version = 1;
}
if (version==1) {
System.out.println(" Updating to version 2...");
stmt.executeUpdate("ALTER TABLE people RENAME TO 'users'");
stmt.executeUpdate("ALTER TABLE users ADD COLUMN phone TEXT");
stmt.executeUpdate("INSERT INTO database_versions (version) VALUES (2)");
version = 2;
}
if (version==2) {
System.out.println(" Updating to version 3...");
stmt.executeUpdate("ALTER TABLE dialogs RENAME TO 'chats'");
stmt.executeUpdate("INSERT INTO database_versions (version) VALUES (3)");
version = 3;
}
if (version==3) {
System.out.println(" Updating to version 4...");
stmt.executeUpdate("CREATE TABLE messages_new (id INTEGER PRIMARY KEY ASC, dialog_id INTEGER, to_id INTEGER, from_id INTEGER, from_type TEXT, text TEXT, time INTEGER, has_media BOOLEAN, sticker TEXT, data BLOB, type TEXT);");
stmt.executeUpdate("INSERT INTO messages_new SELECT * FROM messages");
stmt.executeUpdate("DROP TABLE messages");
stmt.executeUpdate("ALTER TABLE messages_new RENAME TO 'messages'");
stmt.executeUpdate("INSERT INTO database_versions (version) VALUES (4)");
version = 4;
}
if (version==4) {
System.out.println(" Updating to version 5...");
stmt.executeUpdate("CREATE TABLE runs (id INTEGER PRIMARY KEY ASC, time INTEGER, start_id INTEGER, end_id INTEGER, count_missing INTEGER)");
stmt.executeUpdate("INSERT INTO database_versions (version) VALUES (5)");
version = 5;
}
if (version==5) {
backupDatabase(5);
System.out.println(" Updating to version 6...");
stmt.executeUpdate(
"CREATE TABLE messages_new (\n" +
" id INTEGER PRIMARY KEY ASC,\n" +
" message_type TEXT,\n" +
" dialog_id INTEGER,\n" +
" chat_id INTEGER,\n" +
" sender_id INTEGER,\n" +
" fwd_from_id INTEGER,\n" +
" text TEXT,\n" +
" time INTEGER,\n" +
" has_media BOOLEAN,\n" +
" media_type TEXT,\n" +
" media_file TEXT,\n" +
" media_size INTEGER,\n" +
" media_json TEXT,\n" +
" markup_json TEXT,\n" +
" data BLOB)");
LinkedHashMap<String, String> mappings = new LinkedHashMap<String, String>();
mappings.put("id", "id");
mappings.put("message_type", "type");
mappings.put("dialog_id", "CASE from_type WHEN 'user' THEN dialog_id ELSE NULL END");
mappings.put("chat_id", "CASE from_type WHEN 'chat' THEN dialog_id ELSE NULL END");
mappings.put("sender_id", "from_id");
mappings.put("text", "text");
mappings.put("time", "time");
mappings.put("has_media", "has_media");
mappings.put("data", "data");
StringBuilder query = new StringBuilder("INSERT INTO messages_new\n(");
boolean first;
first = true;
for(String s : mappings.keySet()) {
if (!first) query.append(", ");
query.append(s);
first = false;
}
query.append(")\nSELECT \n");
first = true;
for (String s : mappings.values()) {
if (!first) query.append(", ");
query.append(s);
first = false;
}
query.append("\nFROM messages");
stmt.executeUpdate(query.toString());
System.out.println(" Updating the data (this might take some time)...");
rs = stmt.executeQuery("SELECT id, data FROM messages_new");
PreparedStatement ps = conn.prepareStatement("UPDATE messages_new SET fwd_from_id=?, media_type=?, media_file=?, media_size=? WHERE id=?");
while (rs.next()) {
ps.setInt(5, rs.getInt(1));
TLMessage msg = bytesToTLMessage(rs.getBytes(2));
if (msg==null || msg.getFwdFromId()==null || ! (msg.getFwdFromId() instanceof TLPeerUser)) {
ps.setNull(1, Types.INTEGER);
} else {
ps.setInt(1, ((TLPeerUser)msg.getFwdFromId()).getUserId());
}
AbstractMediaFileManager f = FileManagerFactory.getFileManager(msg, user_manager, client);
if (f==null) {
ps.setNull(2, Types.VARCHAR);
ps.setNull(3, Types.VARCHAR);
ps.setNull(4, Types.INTEGER);
} else {
ps.setString(2, f.getName());
ps.setString(3, f.getTargetFilename());
ps.setInt(4, f.getSize());
}
ps.addBatch();
}
rs.close();
conn.setAutoCommit(false);
ps.executeBatch();
conn.commit();
conn.setAutoCommit(true);
stmt.executeUpdate("DROP TABLE messages");
stmt.executeUpdate("ALTER TABLE messages_new RENAME TO messages");
stmt.executeUpdate("INSERT INTO database_versions (version) VALUES (6)");
version = 6;
}
} catch (SQLException e) {
System.out.println(e.getSQLState());
throw new RuntimeException(e);
}
DatabaseUpdates updates = new DatabaseUpdates(conn, this);
updates.doUpdates();
}
private void backupDatabase(int currentVersion) {
public void backupDatabase(int currentVersion) {
String filename = String.format(Config.FILE_NAME_DB_BACKUP, currentVersion);
System.out.println(" Creating a backup of your database as " + filename);
try {
String src = user_manager.getFileBase() + Config.FILE_NAME_DB;
String dst = user_manager.getFileBase() + filename;
Log.debug("Copying %s to %s", src, dst);
Files.copy(
new File(user_manager.getFileBase() + Config.FILE_NAME_DB).toPath(),
new File(user_manager.getFileBase() + filename).toPath(),
new File(src).toPath(),
new File(dst).toPath(),
StandardCopyOption.REPLACE_EXISTING);
} catch (IOException e) {
e.printStackTrace();

View File

@ -0,0 +1,296 @@
package de.fabianonline.telegram_backup;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Types;
import java.sql.ResultSet;
import java.sql.PreparedStatement;
import com.github.badoualy.telegram.tl.api.*;
import de.fabianonline.telegram_backup.mediafilemanager.FileManagerFactory;
import de.fabianonline.telegram_backup.mediafilemanager.AbstractMediaFileManager;
public class DatabaseUpdates {
protected Connection conn;
protected Database db;
private static LinkedList<DatabaseUpdate> updates = new LinkedList<DatabaseUpdate>();
public DatabaseUpdates(Connection conn, Database db) {
this.conn = conn;
this.db = db;
Log.debug("Registering Database Updates...");
Log.up();
register(new DB_Update_1(conn, db));
register(new DB_Update_2(conn, db));
register(new DB_Update_3(conn, db));
register(new DB_Update_4(conn, db));
register(new DB_Update_5(conn, db));
register(new DB_Update_6(conn, db));
Log.down();
}
public void doUpdates() {
try {
Statement stmt = conn.createStatement();
ResultSet rs;
Log.debug("DatabaseUpdate.doUpdates running");
Log.up();
Log.debug("Getting current database version");
Log.up();
int version;
Log.debug("Checking if table database_versions exists");
rs = stmt.executeQuery("SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='database_versions'");
rs.next();
if (rs.getInt(1)==0) {
Log.debug("Table does not exist");
version = 0;
} else {
Log.debug("Table exists. Checking max version");
rs.close();
rs = stmt.executeQuery("SELECT MAX(version) FROM database_versions");
rs.next();
version = rs.getInt(1);
rs.close();
}
Log.debug("version: %d", version);
Log.down();
System.out.println("Database version: " + version);
Log.debug("Max available database version is %d", getMaxPossibleVersion());
if (version < getMaxPossibleVersion()) {
Log.debug("Update is necessary. %d => %d.", version, getMaxPossibleVersion());
boolean backup = false;
for (int i=version+1; i<=getMaxPossibleVersion(); i++) {
if (getUpdateToVersion(i).needsBackup()) {
Log.debug("Update to version %d needs a backup", i);
backup=true;
}
}
if (backup) {
Log.debug("Performing backup");
Log.up();
db.backupDatabase(version);
Log.down();
}
Log.debug("Applying updates");
Log.up();
try {
for (int i=version+1; i<=getMaxPossibleVersion(); i++) {
Log.up();
getUpdateToVersion(i).doUpdate();
Log.down();
}
} catch (SQLException e) { throw new RuntimeException(e); }
Log.down();
} else {
Log.debug("No update necessary.");
}
Log.down();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
private DatabaseUpdate getUpdateToVersion(int i) { return updates.get(i); }
private int getMaxPossibleVersion() {
return updates.size();
}
private void register(DatabaseUpdate d) {
Log.debug("Registering %s as update to version %d", d.getClass().getName(), d.getVersion());
if (d.getVersion() != updates.size()+1) {
throw new RuntimeException("Tried to register DB update to version " + d.getVersion() + ", but would need update to version " + (updates.size()+1));
}
updates.add(d);
}
}
abstract class DatabaseUpdate {
protected Connection conn;
protected Statement stmt;
protected Database db;
public DatabaseUpdate(Connection conn, Database db) {
this.conn = conn;
try {
stmt = conn.createStatement();
} catch (SQLException e) { throw new RuntimeException(e); }
this.db = db;
}
public void doUpdate() throws SQLException {
Log.debug("Applying update to version %d", getVersion());
System.out.println(" Updating to version " + getVersion() + "...");
Log.up();
_doUpdate();
Log.down();
Log.debug("Saving current database version to the db");
stmt.executeUpdate("INSERT INTO database_versions (version) VALUES (" + getVersion() + ")");
}
protected abstract void _doUpdate() throws SQLException;
public abstract int getVersion();
public boolean needsBackup() { return false; }
}
class DB_Update_1 extends DatabaseUpdate {
public int getVersion() { return 1; }
public DB_Update_1(Connection conn, Database db) { super(conn, db); }
protected void _doUpdate() throws SQLException {
stmt.executeUpdate("CREATE TABLE messages ("
+ "id INTEGER PRIMARY KEY ASC, "
+ "dialog_id INTEGER, "
+ "to_id INTEGER, "
+ "from_id INTEGER, "
+ "from_type TEXT, "
+ "text TEXT, "
+ "time TEXT, "
+ "has_media BOOLEAN, "
+ "sticker TEXT, "
+ "data BLOB,"
+ "type TEXT)");
stmt.executeUpdate("CREATE TABLE dialogs ("
+ "id INTEGER PRIMARY KEY ASC, "
+ "name TEXT, "
+ "type TEXT)");
stmt.executeUpdate("CREATE TABLE people ("
+ "id INTEGER PRIMARY KEY ASC, "
+ "first_name TEXT, "
+ "last_name TEXT, "
+ "username TEXT, "
+ "type TEXT)");
stmt.executeUpdate("CREATE TABLE database_versions ("
+ "version INTEGER)");
}
}
class DB_Update_2 extends DatabaseUpdate {
public int getVersion() { return 2; }
public DB_Update_2(Connection conn, Database db) { super(conn, db); }
protected void _doUpdate() throws SQLException {
stmt.executeUpdate("ALTER TABLE people RENAME TO 'users'");
stmt.executeUpdate("ALTER TABLE users ADD COLUMN phone TEXT");
}
}
class DB_Update_3 extends DatabaseUpdate {
public int getVersion() { return 3; }
public DB_Update_3(Connection conn, Database db) { super(conn, db); }
protected void _doUpdate() throws SQLException {
stmt.executeUpdate("ALTER TABLE dialogs RENAME TO 'chats'");
}
}
class DB_Update_4 extends DatabaseUpdate {
public int getVersion() { return 4; }
public DB_Update_4(Connection conn, Database db) { super(conn, db); }
protected void _doUpdate() throws SQLException {
stmt.executeUpdate("CREATE TABLE messages_new (id INTEGER PRIMARY KEY ASC, dialog_id INTEGER, to_id INTEGER, from_id INTEGER, from_type TEXT, text TEXT, time INTEGER, has_media BOOLEAN, sticker TEXT, data BLOB, type TEXT);");
stmt.executeUpdate("INSERT INTO messages_new SELECT * FROM messages");
stmt.executeUpdate("DROP TABLE messages");
stmt.executeUpdate("ALTER TABLE messages_new RENAME TO 'messages'");
}
}
class DB_Update_5 extends DatabaseUpdate {
public int getVersion() { return 5; }
public DB_Update_5(Connection conn, Database db) { super(conn, db); }
protected void _doUpdate() throws SQLException {
stmt.executeUpdate("CREATE TABLE runs (id INTEGER PRIMARY KEY ASC, time INTEGER, start_id INTEGER, end_id INTEGER, count_missing INTEGER)");
}
}
class DB_Update_6 extends DatabaseUpdate {
public int getVersion() { return 6; }
public DB_Update_6(Connection conn, Database db) { super(conn, db); }
public boolean needsBackup() { return true; }
protected void _doUpdate() throws SQLException {
stmt.executeUpdate(
"CREATE TABLE messages_new (\n" +
" id INTEGER PRIMARY KEY ASC,\n" +
" message_type TEXT,\n" +
" dialog_id INTEGER,\n" +
" chat_id INTEGER,\n" +
" sender_id INTEGER,\n" +
" fwd_from_id INTEGER,\n" +
" text TEXT,\n" +
" time INTEGER,\n" +
" has_media BOOLEAN,\n" +
" media_type TEXT,\n" +
" media_file TEXT,\n" +
" media_size INTEGER,\n" +
" media_json TEXT,\n" +
" markup_json TEXT,\n" +
" data BLOB)");
LinkedHashMap<String, String> mappings = new LinkedHashMap<String, String>();
mappings.put("id", "id");
mappings.put("message_type", "type");
mappings.put("dialog_id", "CASE from_type WHEN 'user' THEN dialog_id ELSE NULL END");
mappings.put("chat_id", "CASE from_type WHEN 'chat' THEN dialog_id ELSE NULL END");
mappings.put("sender_id", "from_id");
mappings.put("text", "text");
mappings.put("time", "time");
mappings.put("has_media", "has_media");
mappings.put("data", "data");
StringBuilder query = new StringBuilder("INSERT INTO messages_new\n(");
boolean first;
first = true;
for(String s : mappings.keySet()) {
if (!first) query.append(", ");
query.append(s);
first = false;
}
query.append(")\nSELECT \n");
first = true;
for (String s : mappings.values()) {
if (!first) query.append(", ");
query.append(s);
first = false;
}
query.append("\nFROM messages");
stmt.executeUpdate(query.toString());
System.out.println(" Updating the data (this might take some time)...");
ResultSet rs = stmt.executeQuery("SELECT id, data FROM messages_new");
PreparedStatement ps = conn.prepareStatement("UPDATE messages_new SET fwd_from_id=?, media_type=?, media_file=?, media_size=? WHERE id=?");
while (rs.next()) {
ps.setInt(5, rs.getInt(1));
TLMessage msg = db.bytesToTLMessage(rs.getBytes(2));
if (msg==null || msg.getFwdFromId()==null || ! (msg.getFwdFromId() instanceof TLPeerUser)) {
ps.setNull(1, Types.INTEGER);
} else {
ps.setInt(1, ((TLPeerUser)msg.getFwdFromId()).getUserId());
}
AbstractMediaFileManager f = FileManagerFactory.getFileManager(msg, db.user_manager, db.client);
if (f==null) {
ps.setNull(2, Types.VARCHAR);
ps.setNull(3, Types.VARCHAR);
ps.setNull(4, Types.INTEGER);
} else {
ps.setString(2, f.getName());
ps.setString(3, f.getTargetFilename());
ps.setInt(4, f.getSize());
}
ps.addBatch();
}
rs.close();
conn.setAutoCommit(false);
ps.executeBatch();
conn.commit();
conn.setAutoCommit(true);
stmt.executeUpdate("DROP TABLE messages");
stmt.executeUpdate("ALTER TABLE messages_new RENAME TO messages");
}
}