telegram_backup/src/main/java/de/fabianonline/telegram_backup/DatabaseUpdates.java

345 lines
12 KiB
Java

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 org.slf4j.LoggerFactory;
import org.slf4j.Logger;
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 final Logger logger = LoggerFactory.getLogger(DatabaseUpdates.class);
private static LinkedList<DatabaseUpdate> updates = new LinkedList<DatabaseUpdate>();
public DatabaseUpdates(Connection conn, Database db) {
this.conn = conn;
this.db = db;
logger.debug("Registering Database Updates...");
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));
register(new DB_Update_7(conn, db));
//register(new DB_Update_8(conn, db));
}
public void doUpdates() {
try {
Statement stmt = conn.createStatement();
ResultSet rs;
logger.debug("DatabaseUpdate.doUpdates running");
logger.debug("Getting current database version");
int version;
logger.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) {
logger.debug("Table does not exist");
version = 0;
} else {
logger.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();
logger.debug("version: {}", version);
System.out.println("Database version: " + version);
logger.debug("Max available database version is {}", getMaxPossibleVersion());
if (version < getMaxPossibleVersion()) {
logger.debug("Update is necessary. {} => {}.", version, getMaxPossibleVersion());
boolean backup = false;
for (int i=version+1; i<=getMaxPossibleVersion(); i++) {
if (getUpdateToVersion(i).needsBackup()) {
logger.debug("Update to version {} needs a backup", i);
backup=true;
}
}
if (backup) {
if (version > 0) {
logger.debug("Performing backup");
db.backupDatabase(version);
} else {
logger.debug("NOT performing a backup, because we are creating a fresh database and don't need a backup of that.");
}
}
logger.debug("Applying updates");
try {
for (int i=version+1; i<=getMaxPossibleVersion(); i++) {
getUpdateToVersion(i).doUpdate();
}
} catch (SQLException e) { throw new RuntimeException(e); }
} else {
logger.debug("No update necessary.");
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
private DatabaseUpdate getUpdateToVersion(int i) { return updates.get(i-1); }
private int getMaxPossibleVersion() {
return updates.size();
}
private void register(DatabaseUpdate d) {
logger.debug("Registering {} as update to version {}", 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;
protected static final Logger logger = LoggerFactory.getLogger(DatabaseUpdate.class);
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 {
logger.debug("Applying update to version {}", getVersion());
System.out.println(" Updating to version " + getVersion() + "...");
_doUpdate();
logger.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.getFwdFrom()==null) {
ps.setNull(1, Types.INTEGER);
} else {
ps.setInt(1, msg.getFwdFrom().getFromId());
}
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");
}
}
class DB_Update_7 extends DatabaseUpdate {
public int getVersion() { return 7; }
public boolean needsBackup() { return true; }
public DB_Update_7(Connection conn, Database db) { super(conn, db); }
protected void _doUpdate() throws SQLException {
stmt.executeUpdate("ALTER TABLE messages ADD COLUMN api_layer INTEGER");
stmt.executeUpdate("UPDATE messages SET api_layer=51");
}
}
class DB_Update_8 extends DatabaseUpdate {
public int getVersion() { return 8; }
public boolean needsBackup() { return true; }
public DB_Update_8(Connection conn, Database db) { super(conn, db); }
protected void _doUpdate() throws SQLException {
stmt.executeUpdate("CREATE TABLE users_new (id INTEGER PRIMARY KEY ASC, telegram_id INTEGER, is_current BOOLEAN, first_name TEXT, last_name TEXT, username TEXT, type TEXT, phone TEXT, photo_id INTEGER, photo_downloaded BOOLEAN, time INTEGER");
LinkedHashMap<String, String> mappings = new LinkedHashMap<String, String>();
mappings.put("telegram_id", "id");
mappings.put("is_current", "TRUE");
mappings.put("first_name", "first_name");
mappings.put("last_name", "last_name");
mappings.put("username", "username");
mappings.put("type", "type");
mappings.put("phone", "phone");
mappings.put("photo_id", "NULL");
mappings.put("photo_downloaded", "FALSE");
mappings.put("time", "0");
StringBuilder query = new StringBuilder("INSERT INTO users_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 users");
stmt.executeUpdate(query.toString());
stmt.executeUpdate("DROP TABLE users");
stmt.executeUpdate("ALTER TABLE users_new RENAME TO users");
}
}