From bb180f95bee6bb2047dabc1850299f0e459233a5 Mon Sep 17 00:00:00 2001 From: Fabian Schlenz Date: Tue, 12 Dec 2017 22:04:20 +0100 Subject: [PATCH] Kotlin: Reformatted code. --- .../telegram_backup/ApiStorage.kt | 194 +-- .../telegram_backup/CommandLineController.kt | 598 ++++---- .../CommandLineDownloadProgress.kt | 106 +- .../telegram_backup/CommandLineOptions.kt | 159 +- .../telegram_backup/CommandLineRunner.kt | 102 +- .../de/fabianonline/telegram_backup/Config.kt | 56 +- .../fabianonline/telegram_backup/Database.kt | 1342 ++++++++--------- .../telegram_backup/DatabaseUpdates.kt | 584 +++---- .../telegram_backup/DownloadManager.kt | 730 ++++----- .../DownloadProgressInterface.kt | 18 +- .../telegram_backup/GUIController.kt | 100 +- .../telegram_backup/StickerConverter.kt | 52 +- ...equestAccountGetPasswordWithCurrentSalt.kt | 26 +- .../telegram_backup/TelegramUpdateHandler.kt | 238 +-- .../telegram_backup/TestFeatures.kt | 86 +- .../telegram_backup/UserManager.kt | 176 +-- .../de/fabianonline/telegram_backup/Utils.kt | 270 ++-- .../telegram_backup/exporter/HTMLExporter.kt | 236 +-- .../AbstractMediaFileManager.kt | 70 +- .../mediafilemanager/DocumentFileManager.kt | 100 +- .../mediafilemanager/FileManagerFactory.kt | 54 +- .../mediafilemanager/GeoFileManager.kt | 60 +- .../mediafilemanager/PhotoFileManager.kt | 70 +- .../mediafilemanager/StickerFileManager.kt | 96 +- .../UnsupportedFileManager.kt | 22 +- 25 files changed, 2750 insertions(+), 2795 deletions(-) diff --git a/src/main/kotlin/de/fabianonline/telegram_backup/ApiStorage.kt b/src/main/kotlin/de/fabianonline/telegram_backup/ApiStorage.kt index 0ce5886..ce99e44 100644 --- a/src/main/kotlin/de/fabianonline/telegram_backup/ApiStorage.kt +++ b/src/main/kotlin/de/fabianonline/telegram_backup/ApiStorage.kt @@ -28,120 +28,120 @@ import java.io.FileNotFoundException import java.io.IOException internal class ApiStorage(prefix: String?) : TelegramApiStorage { - private var prefix: String? = null - private var do_save = false - private var auth_key: AuthKey? = null - private var dc: DataCenter? = null - private var file_auth_key: File? = null - private var file_dc: File? = null + private var prefix: String? = null + private var do_save = false + private var auth_key: AuthKey? = null + private var dc: DataCenter? = null + private var file_auth_key: File? = null + private var file_dc: File? = null - init { - this.setPrefix(prefix) - } + init { + this.setPrefix(prefix) + } - fun setPrefix(prefix: String?) { - this.prefix = prefix - this.do_save = this.prefix != null - if (this.do_save) { - val base = Config.FILE_BASE + - File.separatorChar + - this.prefix + - File.separatorChar - this.file_auth_key = File(base + Config.FILE_NAME_AUTH_KEY) - this.file_dc = File(base + Config.FILE_NAME_DC) - this._saveAuthKey() - this._saveDc() - } else { - this.file_auth_key = null - this.file_dc = null - } - } + fun setPrefix(prefix: String?) { + this.prefix = prefix + this.do_save = this.prefix != null + if (this.do_save) { + val base = Config.FILE_BASE + + File.separatorChar + + this.prefix + + File.separatorChar + this.file_auth_key = File(base + Config.FILE_NAME_AUTH_KEY) + this.file_dc = File(base + Config.FILE_NAME_DC) + this._saveAuthKey() + this._saveDc() + } else { + this.file_auth_key = null + this.file_dc = null + } + } - override fun saveAuthKey(authKey: AuthKey) { - this.auth_key = authKey - this._saveAuthKey() - } + override fun saveAuthKey(authKey: AuthKey) { + this.auth_key = authKey + this._saveAuthKey() + } - private fun _saveAuthKey() { - if (this.do_save && this.auth_key != null) { - try { - FileUtils.writeByteArrayToFile(this.file_auth_key, this.auth_key!!.key) - } catch (e: IOException) { - e.printStackTrace() - } + private fun _saveAuthKey() { + if (this.do_save && this.auth_key != null) { + try { + FileUtils.writeByteArrayToFile(this.file_auth_key, this.auth_key!!.key) + } catch (e: IOException) { + e.printStackTrace() + } - } - } + } + } - override fun loadAuthKey(): AuthKey? { - if (this.auth_key != null) return this.auth_key - if (this.file_auth_key != null) { - try { - return AuthKey(FileUtils.readFileToByteArray(this.file_auth_key)) - } catch (e: IOException) { - if (e !is FileNotFoundException) e.printStackTrace() - } + override fun loadAuthKey(): AuthKey? { + if (this.auth_key != null) return this.auth_key + if (this.file_auth_key != null) { + try { + return AuthKey(FileUtils.readFileToByteArray(this.file_auth_key)) + } catch (e: IOException) { + if (e !is FileNotFoundException) e.printStackTrace() + } - } + } - return null - } + return null + } - override fun saveDc(dataCenter: DataCenter) { - this.dc = dataCenter - this._saveDc() - } + override fun saveDc(dataCenter: DataCenter) { + this.dc = dataCenter + this._saveDc() + } - private fun _saveDc() { - if (this.do_save && this.dc != null) { - try { - FileUtils.write(this.file_dc, this.dc!!.toString()) - } catch (e: IOException) { - e.printStackTrace() - } + private fun _saveDc() { + if (this.do_save && this.dc != null) { + try { + FileUtils.write(this.file_dc, this.dc!!.toString()) + } catch (e: IOException) { + e.printStackTrace() + } - } - } + } + } - override fun loadDc(): DataCenter? { - if (this.dc != null) return this.dc - if (this.file_dc != null) { - try { - val infos = FileUtils.readFileToString(this.file_dc).split(":") - return DataCenter(infos[0], Integer.parseInt(infos[1])) - } catch (e: IOException) { - if (e !is FileNotFoundException) e.printStackTrace() - } + override fun loadDc(): DataCenter? { + if (this.dc != null) return this.dc + if (this.file_dc != null) { + try { + val infos = FileUtils.readFileToString(this.file_dc).split(":") + return DataCenter(infos[0], Integer.parseInt(infos[1])) + } catch (e: IOException) { + if (e !is FileNotFoundException) e.printStackTrace() + } - } - return null - } + } + return null + } - override fun deleteAuthKey() { - if (this.do_save) { - try { - FileUtils.forceDelete(this.file_auth_key) - } catch (e: IOException) { - e.printStackTrace() - } + override fun deleteAuthKey() { + if (this.do_save) { + try { + FileUtils.forceDelete(this.file_auth_key) + } catch (e: IOException) { + e.printStackTrace() + } - } - } + } + } - override fun deleteDc() { - if (this.do_save) { - try { - FileUtils.forceDelete(this.file_dc) - } catch (e: IOException) { - e.printStackTrace() - } + override fun deleteDc() { + if (this.do_save) { + try { + FileUtils.forceDelete(this.file_dc) + } catch (e: IOException) { + e.printStackTrace() + } - } - } + } + } - override fun saveSession(session: MTSession?) {} + override fun saveSession(session: MTSession?) {} - override fun loadSession(): MTSession? { - return null - } + override fun loadSession(): MTSession? { + return null + } } diff --git a/src/main/kotlin/de/fabianonline/telegram_backup/CommandLineController.kt b/src/main/kotlin/de/fabianonline/telegram_backup/CommandLineController.kt index e31192e..45600bd 100644 --- a/src/main/kotlin/de/fabianonline/telegram_backup/CommandLineController.kt +++ b/src/main/kotlin/de/fabianonline/telegram_backup/CommandLineController.kt @@ -14,6 +14,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package de.fabianonline.telegram_backup + import de.fabianonline.telegram_backup.TelegramUpdateHandler import de.fabianonline.telegram_backup.exporter.HTMLExporter import com.github.badoualy.telegram.api.Kotlogram @@ -27,327 +28,282 @@ import java.util.Vector import java.util.HashMap import org.slf4j.LoggerFactory import org.slf4j.Logger + class CommandLineController { - private val storage:ApiStorage - var app:TelegramApp - - private fun getLine():String { - if (System.console() != null) { - return System.console().readLine("> ") - } else { - print("> ") - return Scanner(System.`in`).nextLine() - } - } - - private fun getPassword():String { - if (System.console() != null) { - return String(System.console().readPassword("> ")) - } else { - return getLine() - } - } - - init{ - logger.info("CommandLineController started. App version {}", Config.APP_APPVER) - this.printHeader() - if (CommandLineOptions.cmd_version) - { - System.exit(0) - } - else if (CommandLineOptions.cmd_help) - { - this.show_help() - System.exit(0) - } - else if (CommandLineOptions.cmd_license) - { - CommandLineController.show_license() - System.exit(0) - } - this.setupFileBase() - if (CommandLineOptions.cmd_list_accounts) - { - this.list_accounts() - System.exit(0) - } - logger.debug("Initializing TelegramApp") - app = TelegramApp(Config.APP_ID, Config.APP_HASH, Config.APP_MODEL, Config.APP_SYSVER, Config.APP_APPVER, Config.APP_LANG) - logger.trace("Checking accounts") - val account = this.selectAccount() - logger.debug("CommandLineOptions.cmd_login: {}", CommandLineOptions.cmd_login) - logger.info("Initializing ApiStorage") - storage = ApiStorage(account) - logger.info("Initializing TelegramUpdateHandler") - val handler = TelegramUpdateHandler() - logger.info("Creating Client") - val client = Kotlogram.getDefaultClient(app, storage, Kotlogram.PROD_DC4, handler) - try - { - logger.info("Initializing UserManager") - UserManager.init(client) - val user = UserManager.getInstance() - if (!CommandLineOptions.cmd_login && !user.loggedIn) - { - println("Your authorization data is invalid or missing. You will have to login with Telegram again.") - CommandLineOptions.cmd_login = true - } - if (account != null && user.loggedIn) - { - if (account != "+" + user.user!!.getPhone()) - { - logger.error("Account: {}, user.user!!.getPhone(): +{}", Utils.anonymize(account), Utils.anonymize(user.user!!.getPhone())) - throw RuntimeException("Account / User mismatch") - } - } - logger.debug("CommandLineOptions.cmd_login: {}", CommandLineOptions.cmd_login) - if (CommandLineOptions.cmd_login) - { - cmd_login(account) - System.exit(0) - } - // If we reach this point, we can assume that there is an account and a database can be loaded / created. - Database.init(client) - if (CommandLineOptions.cmd_stats) - { - cmd_stats() - System.exit(0) - } - if (CommandLineOptions.val_test != null) - { - if (CommandLineOptions.val_test == 1) - { - TestFeatures.test1() - } - else if (CommandLineOptions.val_test == 2) - { - TestFeatures.test2() - } - else - { - System.out.println("Unknown test " + CommandLineOptions.val_test) - } - System.exit(1) - } - logger.debug("CommandLineOptions.val_export: {}", CommandLineOptions.val_export) - if (CommandLineOptions.val_export != null) - { - if (CommandLineOptions.val_export!!.toLowerCase().equals("html")) - { - (HTMLExporter()).export() - System.exit(0) - } - else - { - show_error("Unknown export format.") - } - } - if (user.loggedIn) - { - System.out.println("You are logged in as " + Utils.anonymize(user.userString)) - } - else - { - println("You are not logged in.") - System.exit(1) - } - logger.info("Initializing Download Manager") - val d = DownloadManager(client, CommandLineDownloadProgress()) - logger.debug("Calling DownloadManager.downloadMessages with limit {}", CommandLineOptions.val_limit_messages) - d.downloadMessages(CommandLineOptions.val_limit_messages) - logger.debug("CommandLineOptions.cmd_no_media: {}", CommandLineOptions.cmd_no_media) - if (!CommandLineOptions.cmd_no_media) - { - logger.debug("Calling DownloadManager.downloadMedia") - d.downloadMedia() - } - else - { - println("Skipping media download because --no-media is set.") - } - } - catch (e:Exception) { - e.printStackTrace() - logger.error("Exception caught!", e) - } - finally - { - if (CommandLineOptions.cmd_daemon) - { - handler.activate() - println("DAEMON mode requested - keeping running.") - } - else - { - client.close() - println() - println("----- EXIT -----") - System.exit(0) - } - } - } - private fun printHeader() { - System.out.println("Telegram_Backup version " + Config.APP_APPVER + ", Copyright (C) 2016, 2017 Fabian Schlenz") - println() - println("Telegram_Backup comes with ABSOLUTELY NO WARRANTY. This is free software, and you are") - println("welcome to redistribute it under certain conditions; run it with '--license' for details.") - println() - } - private fun setupFileBase() { - logger.debug("Target dir at startup: {}", Utils.anonymize(Config.FILE_BASE)) - if (CommandLineOptions.val_target != null) - { - Config.FILE_BASE = CommandLineOptions.val_target!! - } - logger.debug("Target dir after options: {}", Utils.anonymize(Config.FILE_BASE)) - System.out.println("Base directory for files: " + Utils.anonymize(Config.FILE_BASE)) - } - private fun selectAccount():String? { - var account = "none" - val accounts = Utils.getAccounts() - if (CommandLineOptions.cmd_login) - { - logger.debug("Login requested, doing nothing.") - // do nothing - } - else if (CommandLineOptions.val_account != null) - { - logger.debug("Account requested: {}", Utils.anonymize(CommandLineOptions.val_account!!)) - logger.trace("Checking accounts for match.") - var found = false - for (acc in accounts) - { - logger.trace("Checking {}", Utils.anonymize(acc)) - if (acc == CommandLineOptions.val_account) - { - found = true - logger.trace("Matches.") - break - } - } - if (!found) - { - show_error("Couldn't find account '" + Utils.anonymize(CommandLineOptions.val_account!!) + "'. Maybe you want to use '--login' first?") - } - account = CommandLineOptions.val_account!! - } - else if (accounts.size == 0) - { - println("No accounts found. Starting login process...") - CommandLineOptions.cmd_login = true - return null - } - else if (accounts.size == 1) - { - account = accounts.firstElement() - System.out.println("Using only available account: " + Utils.anonymize(account)) - } - else - { - show_error(("You didn't specify which account to use.\n" + - "Use '--account ' to use account .\n" + - "Use '--list-accounts' to see all available accounts.")) - System.exit(1) - } - logger.debug("accounts.size: {}", accounts.size) - logger.debug("account: {}", Utils.anonymize(account)) - return account - } - private fun cmd_stats() { - println() - println("Stats:") - val format = "%40s: %d%n" - System.out.format(format, "Number of accounts", Utils.getAccounts().size) - System.out.format(format, "Number of messages", Database.getInstance().getMessageCount()) - System.out.format(format, "Number of chats", Database.getInstance().getChatCount()) - System.out.format(format, "Number of users", Database.getInstance().getUserCount()) - System.out.format(format, "Top message ID", Database.getInstance().getTopMessageID()) - println() - println("Media Types:") - for ((key, value) in Database.getInstance().getMessageMediaTypesWithCount()) - { - System.out.format(format, key, value) - } - println() - println("Api layers of messages:") - for ((key, value) in Database.getInstance().getMessageApiLayerWithCount()) - { - System.out.format(format, key, value) - } - } - @Throws(RpcErrorException::class, IOException::class) - private fun cmd_login(phoneToUse:String?) { - val user = UserManager.getInstance() - val phone: String - if (phoneToUse == null) - { - println("Please enter your phone number in international format.") - println("Example: +4917077651234") - phone = getLine() - } else { - phone = phoneToUse + private val storage: ApiStorage + var app: TelegramApp + + private fun getLine(): String { + if (System.console() != null) { + return System.console().readLine("> ") + } else { + print("> ") + return Scanner(System.`in`).nextLine() + } + } + + private fun getPassword(): String { + if (System.console() != null) { + return String(System.console().readPassword("> ")) + } else { + return getLine() + } + } + + init { + logger.info("CommandLineController started. App version {}", Config.APP_APPVER) + this.printHeader() + if (CommandLineOptions.cmd_version) { + System.exit(0) + } else if (CommandLineOptions.cmd_help) { + this.show_help() + System.exit(0) + } else if (CommandLineOptions.cmd_license) { + CommandLineController.show_license() + System.exit(0) + } + this.setupFileBase() + if (CommandLineOptions.cmd_list_accounts) { + this.list_accounts() + System.exit(0) + } + logger.debug("Initializing TelegramApp") + app = TelegramApp(Config.APP_ID, Config.APP_HASH, Config.APP_MODEL, Config.APP_SYSVER, Config.APP_APPVER, Config.APP_LANG) + logger.trace("Checking accounts") + val account = this.selectAccount() + logger.debug("CommandLineOptions.cmd_login: {}", CommandLineOptions.cmd_login) + logger.info("Initializing ApiStorage") + storage = ApiStorage(account) + logger.info("Initializing TelegramUpdateHandler") + val handler = TelegramUpdateHandler() + logger.info("Creating Client") + val client = Kotlogram.getDefaultClient(app, storage, Kotlogram.PROD_DC4, handler) + try { + logger.info("Initializing UserManager") + UserManager.init(client) + val user = UserManager.getInstance() + if (!CommandLineOptions.cmd_login && !user.loggedIn) { + println("Your authorization data is invalid or missing. You will have to login with Telegram again.") + CommandLineOptions.cmd_login = true + } + if (account != null && user.loggedIn) { + if (account != "+" + user.user!!.getPhone()) { + logger.error("Account: {}, user.user!!.getPhone(): +{}", Utils.anonymize(account), Utils.anonymize(user.user!!.getPhone())) + throw RuntimeException("Account / User mismatch") + } + } + logger.debug("CommandLineOptions.cmd_login: {}", CommandLineOptions.cmd_login) + if (CommandLineOptions.cmd_login) { + cmd_login(account) + System.exit(0) + } + // If we reach this point, we can assume that there is an account and a database can be loaded / created. + Database.init(client) + if (CommandLineOptions.cmd_stats) { + cmd_stats() + System.exit(0) + } + if (CommandLineOptions.val_test != null) { + if (CommandLineOptions.val_test == 1) { + TestFeatures.test1() + } else if (CommandLineOptions.val_test == 2) { + TestFeatures.test2() + } else { + System.out.println("Unknown test " + CommandLineOptions.val_test) + } + System.exit(1) + } + logger.debug("CommandLineOptions.val_export: {}", CommandLineOptions.val_export) + if (CommandLineOptions.val_export != null) { + if (CommandLineOptions.val_export!!.toLowerCase().equals("html")) { + (HTMLExporter()).export() + System.exit(0) + } else { + show_error("Unknown export format.") + } + } + if (user.loggedIn) { + System.out.println("You are logged in as " + Utils.anonymize(user.userString)) + } else { + println("You are not logged in.") + System.exit(1) + } + logger.info("Initializing Download Manager") + val d = DownloadManager(client, CommandLineDownloadProgress()) + logger.debug("Calling DownloadManager.downloadMessages with limit {}", CommandLineOptions.val_limit_messages) + d.downloadMessages(CommandLineOptions.val_limit_messages) + logger.debug("CommandLineOptions.cmd_no_media: {}", CommandLineOptions.cmd_no_media) + if (!CommandLineOptions.cmd_no_media) { + logger.debug("Calling DownloadManager.downloadMedia") + d.downloadMedia() + } else { + println("Skipping media download because --no-media is set.") + } + } catch (e: Exception) { + e.printStackTrace() + logger.error("Exception caught!", e) + } finally { + if (CommandLineOptions.cmd_daemon) { + handler.activate() + println("DAEMON mode requested - keeping running.") + } else { + client.close() + println() + println("----- EXIT -----") + System.exit(0) + } + } + } + + private fun printHeader() { + System.out.println("Telegram_Backup version " + Config.APP_APPVER + ", Copyright (C) 2016, 2017 Fabian Schlenz") + println() + println("Telegram_Backup comes with ABSOLUTELY NO WARRANTY. This is free software, and you are") + println("welcome to redistribute it under certain conditions; run it with '--license' for details.") + println() + } + + private fun setupFileBase() { + logger.debug("Target dir at startup: {}", Utils.anonymize(Config.FILE_BASE)) + if (CommandLineOptions.val_target != null) { + Config.FILE_BASE = CommandLineOptions.val_target!! + } + logger.debug("Target dir after options: {}", Utils.anonymize(Config.FILE_BASE)) + System.out.println("Base directory for files: " + Utils.anonymize(Config.FILE_BASE)) + } + + private fun selectAccount(): String? { + var account = "none" + val accounts = Utils.getAccounts() + if (CommandLineOptions.cmd_login) { + logger.debug("Login requested, doing nothing.") + // do nothing + } else if (CommandLineOptions.val_account != null) { + logger.debug("Account requested: {}", Utils.anonymize(CommandLineOptions.val_account!!)) + logger.trace("Checking accounts for match.") + var found = false + for (acc in accounts) { + logger.trace("Checking {}", Utils.anonymize(acc)) + if (acc == CommandLineOptions.val_account) { + found = true + logger.trace("Matches.") + break + } + } + if (!found) { + show_error("Couldn't find account '" + Utils.anonymize(CommandLineOptions.val_account!!) + "'. Maybe you want to use '--login' first?") + } + account = CommandLineOptions.val_account!! + } else if (accounts.size == 0) { + println("No accounts found. Starting login process...") + CommandLineOptions.cmd_login = true + return null + } else if (accounts.size == 1) { + account = accounts.firstElement() + System.out.println("Using only available account: " + Utils.anonymize(account)) + } else { + show_error(("You didn't specify which account to use.\n" + + "Use '--account ' to use account .\n" + + "Use '--list-accounts' to see all available accounts.")) + System.exit(1) + } + logger.debug("accounts.size: {}", accounts.size) + logger.debug("account: {}", Utils.anonymize(account)) + return account + } + + private fun cmd_stats() { + println() + println("Stats:") + val format = "%40s: %d%n" + System.out.format(format, "Number of accounts", Utils.getAccounts().size) + System.out.format(format, "Number of messages", Database.getInstance().getMessageCount()) + System.out.format(format, "Number of chats", Database.getInstance().getChatCount()) + System.out.format(format, "Number of users", Database.getInstance().getUserCount()) + System.out.format(format, "Top message ID", Database.getInstance().getTopMessageID()) + println() + println("Media Types:") + for ((key, value) in Database.getInstance().getMessageMediaTypesWithCount()) { + System.out.format(format, key, value) + } + println() + println("Api layers of messages:") + for ((key, value) in Database.getInstance().getMessageApiLayerWithCount()) { + System.out.format(format, key, value) + } + } + + @Throws(RpcErrorException::class, IOException::class) + private fun cmd_login(phoneToUse: String?) { + val user = UserManager.getInstance() + val phone: String + if (phoneToUse == null) { + println("Please enter your phone number in international format.") + println("Example: +4917077651234") + phone = getLine() + } else { + phone = phoneToUse + } + user.sendCodeToPhoneNumber(phone) + println("Telegram sent you a code. Please enter it here.") + val code = getLine() + user.verifyCode(code) + if (user.isPasswordNeeded) { + println("We also need your account password. Please enter it now. It should not be printed, so it's okay if you see nothing while typing it.") + val pw = getPassword() + user.verifyPassword(pw) + } + storage.setPrefix("+" + user.user!!.getPhone()) + System.out.println("Everything seems fine. Please run this tool again with '--account +" + Utils.anonymize(user.user!!.getPhone()) + " to use this account.") + } + + private fun show_help() { + println("Valid options are:") + println(" -h, --help Shows this help.") + println(" -a, --account Use account .") + println(" -l, --login Login to an existing telegram account.") + println(" --debug Shows some debug information.") + println(" --trace Shows lots of debug information. Overrides --debug.") + println(" --trace-telegram Shows lots of debug messages from the library used to access Telegram.") + println(" -A, --list-accounts List all existing accounts ") + println(" --limit-messages Downloads at most the most recent messages.") + println(" --no-media Do not download media files.") + println(" -t, --target Target directory for the files.") + println(" -e, --export Export the database. Valid formats are:") + println(" html - Creates HTML files.") + println(" --license Displays the license of this program.") + println(" -d, --daemon Keep running and automatically save new messages.") + println(" --anonymize (Try to) Remove all sensitive information from output. Useful for requesting support.") + println(" --stats Print some usage statistics.") + println(" --with-channels Backup channels as well.") + println(" --with-supergroups Backup supergroups as well.") + } + + private fun list_accounts() { + println("List of available accounts:") + val accounts = Utils.getAccounts() + if (accounts.size > 0) { + for (str in accounts) { + System.out.println(" " + Utils.anonymize(str)) + } + println("Use '--account ' to use one of those accounts.") + } else { + println("NO ACCOUNTS FOUND") + println("Use '--login' to login to a telegram account.") + } + } + + companion object { + private val logger = LoggerFactory.getLogger(CommandLineController::class.java) + + public fun show_error(error: String) { + logger.error(error) + println("ERROR: " + error) + System.exit(1) + } + + fun show_license() { + println("TODO: Print the GPL.") + } } - user.sendCodeToPhoneNumber(phone) - println("Telegram sent you a code. Please enter it here.") - val code = getLine() - user.verifyCode(code) - if (user.isPasswordNeeded) - { - println("We also need your account password. Please enter it now. It should not be printed, so it's okay if you see nothing while typing it.") - val pw = getPassword() - user.verifyPassword(pw) - } - storage.setPrefix("+" + user.user!!.getPhone()) - System.out.println("Everything seems fine. Please run this tool again with '--account +" + Utils.anonymize(user.user!!.getPhone()) + " to use this account.") - } - private fun show_help() { - println("Valid options are:") - println(" -h, --help Shows this help.") - println(" -a, --account Use account .") - println(" -l, --login Login to an existing telegram account.") - println(" --debug Shows some debug information.") - println(" --trace Shows lots of debug information. Overrides --debug.") - println(" --trace-telegram Shows lots of debug messages from the library used to access Telegram.") - println(" -A, --list-accounts List all existing accounts ") - println(" --limit-messages Downloads at most the most recent messages.") - println(" --no-media Do not download media files.") - println(" -t, --target Target directory for the files.") - println(" -e, --export Export the database. Valid formats are:") - println(" html - Creates HTML files.") - println(" --license Displays the license of this program.") - println(" -d, --daemon Keep running and automatically save new messages.") - println(" --anonymize (Try to) Remove all sensitive information from output. Useful for requesting support.") - println(" --stats Print some usage statistics.") - println(" --with-channels Backup channels as well.") - println(" --with-supergroups Backup supergroups as well.") - } - private fun list_accounts() { - println("List of available accounts:") - val accounts = Utils.getAccounts() - if (accounts.size > 0) - { - for (str in accounts) - { - System.out.println(" " + Utils.anonymize(str)) - } - println("Use '--account ' to use one of those accounts.") - } - else - { - println("NO ACCOUNTS FOUND") - println("Use '--login' to login to a telegram account.") - } - } - companion object { - private val logger = LoggerFactory.getLogger(CommandLineController::class.java) - - public fun show_error(error:String) { - logger.error(error) - println("ERROR: " + error) - System.exit(1) - } - fun show_license() { - println("TODO: Print the GPL.") - } -} } diff --git a/src/main/kotlin/de/fabianonline/telegram_backup/CommandLineDownloadProgress.kt b/src/main/kotlin/de/fabianonline/telegram_backup/CommandLineDownloadProgress.kt index 6d89e56..7646ff4 100644 --- a/src/main/kotlin/de/fabianonline/telegram_backup/CommandLineDownloadProgress.kt +++ b/src/main/kotlin/de/fabianonline/telegram_backup/CommandLineDownloadProgress.kt @@ -20,68 +20,68 @@ import de.fabianonline.telegram_backup.DownloadProgressInterface import de.fabianonline.telegram_backup.mediafilemanager.AbstractMediaFileManager internal class CommandLineDownloadProgress : DownloadProgressInterface { - private var mediaCount = 0 - private var i = 0 + private var mediaCount = 0 + private var i = 0 - override fun onMessageDownloadStart(count: Int, source: String?) { - i = 0 - if (source == null) { - System.out.println("Downloading $count messages.") - } else { - System.out.println("Downloading " + count + " messages from " + Utils.anonymize(source)) - } - } + override fun onMessageDownloadStart(count: Int, source: String?) { + i = 0 + if (source == null) { + System.out.println("Downloading $count messages.") + } else { + System.out.println("Downloading " + count + " messages from " + Utils.anonymize(source)) + } + } - override fun onMessageDownloaded(number: Int) { - i += number - print("..." + i) - } + override fun onMessageDownloaded(number: Int) { + i += number + print("..." + i) + } - override fun onMessageDownloadFinished() { - println(" done.") - } + override fun onMessageDownloadFinished() { + println(" done.") + } - override fun onMediaDownloadStart(count: Int) { - i = 0 - mediaCount = count - println("Checking and downloading media.") - println("Legend:") - println("'V' - Video 'P' - Photo 'D' - Document") - println("'S' - Sticker 'A' - Audio 'G' - Geolocation") - println("'.' - Previously downloaded file 'e' - Empty file") - println("' ' - Ignored media type (weblinks or contacts, for example)") - println("'x' - File skipped because of timeout errors") - println("" + count + " Files to check / download") - } + override fun onMediaDownloadStart(count: Int) { + i = 0 + mediaCount = count + println("Checking and downloading media.") + println("Legend:") + println("'V' - Video 'P' - Photo 'D' - Document") + println("'S' - Sticker 'A' - Audio 'G' - Geolocation") + println("'.' - Previously downloaded file 'e' - Empty file") + println("' ' - Ignored media type (weblinks or contacts, for example)") + println("'x' - File skipped because of timeout errors") + println("" + count + " Files to check / download") + } - override fun onMediaDownloaded(file_manager: AbstractMediaFileManager) { - show(file_manager.letter.toUpperCase()) - } + override fun onMediaDownloaded(file_manager: AbstractMediaFileManager) { + show(file_manager.letter.toUpperCase()) + } - override fun onMediaDownloadedEmpty() { - show("e") - } + override fun onMediaDownloadedEmpty() { + show("e") + } - override fun onMediaAlreadyPresent(file_manager: AbstractMediaFileManager) { - show(".") - } + override fun onMediaAlreadyPresent(file_manager: AbstractMediaFileManager) { + show(".") + } - override fun onMediaSkipped() { - show("x") - } + override fun onMediaSkipped() { + show("x") + } - override fun onMediaDownloadFinished() { - showNewLine() - println("Done.") - } + override fun onMediaDownloadFinished() { + showNewLine() + println("Done.") + } - private fun show(letter: String) { - print(letter) - i++ - if (i % 100 == 0) showNewLine() - } + private fun show(letter: String) { + print(letter) + i++ + if (i % 100 == 0) showNewLine() + } - private fun showNewLine() { - println(" - $i/$mediaCount") - } + private fun showNewLine() { + println(" - $i/$mediaCount") + } } diff --git a/src/main/kotlin/de/fabianonline/telegram_backup/CommandLineOptions.kt b/src/main/kotlin/de/fabianonline/telegram_backup/CommandLineOptions.kt index 5c1bace..ba371a0 100644 --- a/src/main/kotlin/de/fabianonline/telegram_backup/CommandLineOptions.kt +++ b/src/main/kotlin/de/fabianonline/telegram_backup/CommandLineOptions.kt @@ -14,85 +14,84 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package de.fabianonline.telegram_backup + internal object CommandLineOptions { - public var cmd_console = false - public var cmd_help = false - public var cmd_login = false - var cmd_debug = false - var cmd_trace = false - var cmd_trace_telegram = false - var cmd_list_accounts = false - var cmd_version = false - var cmd_license = false - var cmd_daemon = false - var cmd_no_media = false - var cmd_anonymize = false - var cmd_stats = false - var cmd_channels = false - var cmd_supergroups = false - var val_account:String? = null - var val_limit_messages:Int? = null - var val_target:String? = null - var val_export:String? = null - var val_test:Int? = null - @JvmStatic fun parseOptions(args:Array) { - var last_cmd:String? = null - loop@ for (arg in args) - { - if (last_cmd != null) - { - when (last_cmd) { - "--account" -> val_account = arg - "--limit-messages" -> val_limit_messages = Integer.parseInt(arg) - "--target" -> val_target = arg - "--export" -> val_export = arg - "--test" -> val_test = Integer.parseInt(arg) - } - last_cmd = null - continue - } - when (arg) { - "-a", "--account" -> { - last_cmd = "--account" - continue@loop - } - "-h", "--help" -> cmd_help = true - "-l", "--login" -> cmd_login = true - "--debug" -> cmd_debug = true - "--trace" -> cmd_trace = true - "--trace-telegram" -> cmd_trace_telegram = true - "-A", "--list-accounts" -> cmd_list_accounts = true - "--limit-messages" -> { - last_cmd = arg - continue@loop - } - "--console" -> cmd_console = true - "-t", "--target" -> { - last_cmd = "--target" - continue@loop - } - "-V", "--version" -> cmd_version = true - "-e", "--export" -> { - last_cmd = "--export" - continue@loop - } - "--license" -> cmd_license = true - "-d", "--daemon" -> cmd_daemon = true - "--no-media" -> cmd_no_media = true - "--test" -> { - last_cmd = "--test" - continue@loop - } - "--anonymize" -> cmd_anonymize = true - "--stats" -> cmd_stats = true - "--with-channels" -> cmd_channels = true - "--with-supergroups" -> cmd_supergroups = true - else -> throw RuntimeException("Unknown command " + arg) - } - } - if (last_cmd != null) - { - CommandLineController.show_error("Command $last_cmd had no parameter set.") - } - } + public var cmd_console = false + public var cmd_help = false + public var cmd_login = false + var cmd_debug = false + var cmd_trace = false + var cmd_trace_telegram = false + var cmd_list_accounts = false + var cmd_version = false + var cmd_license = false + var cmd_daemon = false + var cmd_no_media = false + var cmd_anonymize = false + var cmd_stats = false + var cmd_channels = false + var cmd_supergroups = false + var val_account: String? = null + var val_limit_messages: Int? = null + var val_target: String? = null + var val_export: String? = null + var val_test: Int? = null + @JvmStatic + fun parseOptions(args: Array) { + var last_cmd: String? = null + loop@ for (arg in args) { + if (last_cmd != null) { + when (last_cmd) { + "--account" -> val_account = arg + "--limit-messages" -> val_limit_messages = Integer.parseInt(arg) + "--target" -> val_target = arg + "--export" -> val_export = arg + "--test" -> val_test = Integer.parseInt(arg) + } + last_cmd = null + continue + } + when (arg) { + "-a", "--account" -> { + last_cmd = "--account" + continue@loop + } + "-h", "--help" -> cmd_help = true + "-l", "--login" -> cmd_login = true + "--debug" -> cmd_debug = true + "--trace" -> cmd_trace = true + "--trace-telegram" -> cmd_trace_telegram = true + "-A", "--list-accounts" -> cmd_list_accounts = true + "--limit-messages" -> { + last_cmd = arg + continue@loop + } + "--console" -> cmd_console = true + "-t", "--target" -> { + last_cmd = "--target" + continue@loop + } + "-V", "--version" -> cmd_version = true + "-e", "--export" -> { + last_cmd = "--export" + continue@loop + } + "--license" -> cmd_license = true + "-d", "--daemon" -> cmd_daemon = true + "--no-media" -> cmd_no_media = true + "--test" -> { + last_cmd = "--test" + continue@loop + } + "--anonymize" -> cmd_anonymize = true + "--stats" -> cmd_stats = true + "--with-channels" -> cmd_channels = true + "--with-supergroups" -> cmd_supergroups = true + else -> throw RuntimeException("Unknown command " + arg) + } + } + if (last_cmd != null) { + CommandLineController.show_error("Command $last_cmd had no parameter set.") + } + } } diff --git a/src/main/kotlin/de/fabianonline/telegram_backup/CommandLineRunner.kt b/src/main/kotlin/de/fabianonline/telegram_backup/CommandLineRunner.kt index 128a556..b8d6801 100644 --- a/src/main/kotlin/de/fabianonline/telegram_backup/CommandLineRunner.kt +++ b/src/main/kotlin/de/fabianonline/telegram_backup/CommandLineRunner.kt @@ -27,67 +27,67 @@ import ch.qos.logback.classic.spi.ILoggingEvent import ch.qos.logback.core.ConsoleAppender import ch.qos.logback.classic.Level - fun main(args: Array) { - CommandLineOptions.parseOptions(args) +fun main(args: Array) { + CommandLineOptions.parseOptions(args) - CommandLineRunner.setupLogging() - CommandLineRunner.checkVersion() + CommandLineRunner.setupLogging() + CommandLineRunner.checkVersion() - if (true || CommandLineOptions.cmd_console) { - // Always use the console for now. - CommandLineController() - } else { - GUIController() - } - } + if (true || CommandLineOptions.cmd_console) { + // Always use the console for now. + CommandLineController() + } else { + GUIController() + } +} object CommandLineRunner { - fun setupLogging() { - val logger = LoggerFactory.getLogger(CommandLineRunner::class.java) as Logger - logger.trace("Setting up Loggers...") - val rootLogger = LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME) as Logger - val rootContext = rootLogger.getLoggerContext() - rootContext.reset() + fun setupLogging() { + val logger = LoggerFactory.getLogger(CommandLineRunner::class.java) as Logger + logger.trace("Setting up Loggers...") + val rootLogger = LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME) as Logger + val rootContext = rootLogger.getLoggerContext() + rootContext.reset() - val encoder = PatternLayoutEncoder() - encoder.setContext(rootContext) - encoder.setPattern("%d{HH:mm:ss.SSS} %-5level %-35.-35(%logger{0}.%method): %message%n") - encoder.start() + val encoder = PatternLayoutEncoder() + encoder.setContext(rootContext) + encoder.setPattern("%d{HH:mm:ss.SSS} %-5level %-35.-35(%logger{0}.%method): %message%n") + encoder.start() - val appender = ConsoleAppender() - appender.setContext(rootContext) - appender.setEncoder(encoder) - appender.start() + val appender = ConsoleAppender() + appender.setContext(rootContext) + appender.setEncoder(encoder) + appender.start() - rootLogger.addAppender(appender) - rootLogger.setLevel(Level.OFF) + rootLogger.addAppender(appender) + rootLogger.setLevel(Level.OFF) - if (CommandLineOptions.cmd_trace) { - (LoggerFactory.getLogger("de.fabianonline.telegram_backup") as Logger).setLevel(Level.TRACE) - } else if (CommandLineOptions.cmd_debug) { - (LoggerFactory.getLogger("de.fabianonline.telegram_backup") as Logger).setLevel(Level.DEBUG) - } + if (CommandLineOptions.cmd_trace) { + (LoggerFactory.getLogger("de.fabianonline.telegram_backup") as Logger).setLevel(Level.TRACE) + } else if (CommandLineOptions.cmd_debug) { + (LoggerFactory.getLogger("de.fabianonline.telegram_backup") as Logger).setLevel(Level.DEBUG) + } - if (CommandLineOptions.cmd_trace_telegram) { - (LoggerFactory.getLogger("com.github.badoualy") as Logger).setLevel(Level.TRACE) - } - } + if (CommandLineOptions.cmd_trace_telegram) { + (LoggerFactory.getLogger("com.github.badoualy") as Logger).setLevel(Level.TRACE) + } + } - fun checkVersion(): Boolean { - val v = Utils.getNewestVersion() - if (v != null && v.isNewer) { - System.out.println("A newer version is vailable!") - System.out.println("You are using: " + Config.APP_APPVER) - System.out.println("Available: " + v.version) - System.out.println("Get it here: " + v.url) - System.out.println() - System.out.println("Changes in this version:") - System.out.println(v.body) - System.out.println() - return false - } - return true - } + fun checkVersion(): Boolean { + val v = Utils.getNewestVersion() + if (v != null && v.isNewer) { + System.out.println("A newer version is vailable!") + System.out.println("You are using: " + Config.APP_APPVER) + System.out.println("Available: " + v.version) + System.out.println("Get it here: " + v.url) + System.out.println() + System.out.println("Changes in this version:") + System.out.println(v.body) + System.out.println() + return false + } + return true + } } diff --git a/src/main/kotlin/de/fabianonline/telegram_backup/Config.kt b/src/main/kotlin/de/fabianonline/telegram_backup/Config.kt index c496650..c0828d4 100644 --- a/src/main/kotlin/de/fabianonline/telegram_backup/Config.kt +++ b/src/main/kotlin/de/fabianonline/telegram_backup/Config.kt @@ -22,39 +22,39 @@ import java.io.FileInputStream import java.util.Properties object Config { - val APP_ID = 32860 - val APP_HASH = "16e4ff955cd0adfc058f95ca564f562d" - val APP_MODEL = "Desktop" - val APP_SYSVER = "1.0" - val APP_APPVER: String - val APP_LANG = "en" + val APP_ID = 32860 + val APP_HASH = "16e4ff955cd0adfc058f95ca564f562d" + val APP_MODEL = "Desktop" + val APP_SYSVER = "1.0" + val APP_APPVER: String + val APP_LANG = "en" - var FILE_BASE = System.getProperty("user.home") + File.separatorChar + ".telegram_backup" - val FILE_NAME_AUTH_KEY = "auth.dat" - val FILE_NAME_DC = "dc.dat" - val FILE_NAME_DB = "database.sqlite" - val FILE_NAME_DB_BACKUP = "database.version_%d.backup.sqlite" - val FILE_FILES_BASE = "files" - val FILE_STICKER_BASE = "stickers" + var FILE_BASE = System.getProperty("user.home") + File.separatorChar + ".telegram_backup" + val FILE_NAME_AUTH_KEY = "auth.dat" + val FILE_NAME_DC = "dc.dat" + val FILE_NAME_DB = "database.sqlite" + val FILE_NAME_DB_BACKUP = "database.version_%d.backup.sqlite" + val FILE_FILES_BASE = "files" + val FILE_STICKER_BASE = "stickers" - var DELAY_AFTER_GET_MESSAGES: Long = 400 - var DELAY_AFTER_GET_FILE: Long = 100 - var GET_MESSAGES_BATCH_SIZE = 200 + var DELAY_AFTER_GET_MESSAGES: Long = 400 + var DELAY_AFTER_GET_FILE: Long = 100 + var GET_MESSAGES_BATCH_SIZE = 200 - var RENAMING_MAX_TRIES = 5 - var RENAMING_DELAY: Long = 1000 + var RENAMING_MAX_TRIES = 5 + var RENAMING_DELAY: Long = 1000 - val SECRET_GMAPS = "AIzaSyBEtUDhCQKEH6i2Mn1GAiQ9M_tLN0vxHIs" + val SECRET_GMAPS = "AIzaSyBEtUDhCQKEH6i2Mn1GAiQ9M_tLN0vxHIs" - init { - val p = Properties() - try { - p.load(Config::class.java.getResourceAsStream("/build.properties")) - APP_APPVER = p.getProperty("version") - } catch (e: IOException) { - throw RuntimeException(e) - } + init { + val p = Properties() + try { + p.load(Config::class.java.getResourceAsStream("/build.properties")) + APP_APPVER = p.getProperty("version") + } catch (e: IOException) { + throw RuntimeException(e) + } - } + } } diff --git a/src/main/kotlin/de/fabianonline/telegram_backup/Database.kt b/src/main/kotlin/de/fabianonline/telegram_backup/Database.kt index f99774b..8869a17 100644 --- a/src/main/kotlin/de/fabianonline/telegram_backup/Database.kt +++ b/src/main/kotlin/de/fabianonline/telegram_backup/Database.kt @@ -48,675 +48,675 @@ import de.fabianonline.telegram_backup.mediafilemanager.AbstractMediaFileManager import de.fabianonline.telegram_backup.mediafilemanager.FileManagerFactory class Database private constructor(var client: TelegramClient) { - private var conn: Connection? = null - private var stmt: Statement? = null - var user_manager: UserManager - - fun getTopMessageID(): Int { - try { - val rs = stmt!!.executeQuery("SELECT MAX(message_id) FROM messages WHERE source_type IN ('group', 'dialog')") - rs.next() - return rs.getInt(1) - } catch (e: SQLException) { - return 0 - } - - } - - fun getMessageCount(): Int = queryInt("SELECT COUNT(*) FROM messages") - fun getChatCount(): Int = queryInt("SELECT COUNT(*) FROM chats") - fun getUserCount(): Int = queryInt("SELECT COUNT(*) FROM users") - - val missingIDs: LinkedList - get() { - try { - val missing = LinkedList() - val max = getTopMessageID() - val rs = stmt!!.executeQuery("SELECT message_id FROM messages WHERE source_type IN ('group', 'dialog') ORDER BY id") - rs.next() - var id = rs.getInt(1) - for (i in 1..max) { - if (i == id) { - rs.next() - if (rs.isClosed()) { - id = Integer.MAX_VALUE - } else { - id = rs.getInt(1) - } - } else if (i < id) { - missing.add(i) - } - } - return missing - } catch (e: SQLException) { - e.printStackTrace() - throw RuntimeException("Could not get list of ids.") - } - - } - - fun getMessagesWithMedia(): LinkedList { - try { - val list = LinkedList() - val rs = stmt!!.executeQuery("SELECT data FROM messages WHERE has_media=1") - while (rs.next()) { - list.add(bytesToTLMessage(rs.getBytes(1))) - } - rs.close() - return list - } catch (e: Exception) { - e.printStackTrace() - throw RuntimeException("Exception occured. See above.") - } - - } - - fun getMessagesFromUserCount(): Int { - try { - val rs = stmt!!.executeQuery("SELECT COUNT(*) FROM messages WHERE sender_id=" + user_manager.user!!.getId()) - rs.next() - return rs.getInt(1) - } catch (e: SQLException) { - throw RuntimeException(e) - } - - } - - fun getMessageTypesWithCount(): HashMap = getMessageTypesWithCount(GlobalChat()) - - fun getMessageMediaTypesWithCount(): HashMap = getMessageMediaTypesWithCount(GlobalChat()) - - fun getMessageApiLayerWithCount(): HashMap { - val map = HashMap() - try { - val rs = stmt!!.executeQuery("SELECT COUNT(id), api_layer FROM messages GROUP BY api_layer ORDER BY api_layer") - while (rs.next()) { - var layer = rs.getInt(2) - map.put("count.messages.api_layer.$layer", rs.getInt(1)) - } - rs.close() - return map - } catch (e: Exception) { - throw RuntimeException(e) - } - } - - fun getMessageAuthorsWithCount(): HashMap = getMessageAuthorsWithCount(GlobalChat()) - - fun getMessageTimesMatrix(): Array = getMessageTimesMatrix(GlobalChat()) - - fun getEncoding(): String { - try { - val rs = stmt!!.executeQuery("PRAGMA encoding") - rs.next() - return rs.getString(1) - } catch (e: SQLException) { - logger.debug("SQLException: {}", e) - return "unknown" - } - - } - - - fun getListOfChatsForExport(): LinkedList { - val list = LinkedList() - try { - val rs = stmt!!.executeQuery("SELECT chats.id, chats.name, COUNT(messages.id) as c " + - "FROM chats, messages WHERE messages.source_type IN('group', 'supergroup', 'channel') AND messages.source_id=chats.id " + - "GROUP BY chats.id ORDER BY c DESC") - while (rs.next()) { - list.add(Chat(rs.getInt(1), rs.getString(2), rs.getInt(3))) - } - rs.close() - return list - } catch (e: Exception) { - e.printStackTrace() - throw RuntimeException("Exception above!") - } - - } - - - fun getListOfDialogsForExport(): LinkedList { - val list = LinkedList() - try { - val rs = stmt!!.executeQuery( - "SELECT users.id, first_name, last_name, username, COUNT(messages.id) as c " + - "FROM users, messages WHERE messages.source_type='dialog' AND messages.source_id=users.id " + - "GROUP BY users.id ORDER BY c DESC") - while (rs.next()) { - list.add(Dialog(rs.getInt(1), rs.getString(2), rs.getString(3), rs.getString(4), rs.getInt(5))) - } - rs.close() - return list - } catch (e: Exception) { - e.printStackTrace() - throw RuntimeException("Exception above!") - } - - } - - init { - this.user_manager = UserManager.getInstance() - System.out.println("Opening database...") - try { - Class.forName("org.sqlite.JDBC") - } catch (e: ClassNotFoundException) { - CommandLineController.show_error("Could not load jdbc-sqlite class.") - } - - val path = "jdbc:sqlite:${user_manager.fileBase}${Config.FILE_NAME_DB}" - - try { - conn = DriverManager.getConnection(path) - stmt = conn!!.createStatement() - } catch (e: SQLException) { - CommandLineController.show_error("Could not connect to SQLITE database.") - } - - // Run updates - val updates = DatabaseUpdates(conn!!, this) - updates.doUpdates() - - System.out.println("Database is ready.") - } - - fun backupDatabase(currentVersion: Int) { - val filename = String.format(Config.FILE_NAME_DB_BACKUP, currentVersion) - System.out.println(" Creating a backup of your database as " + filename) - try { - val src = user_manager.fileBase + Config.FILE_NAME_DB - val dst = user_manager.fileBase + filename - logger.debug("Copying {} to {}", src, dst) - Files.copy( - File(src).toPath(), - File(dst).toPath()) - } catch (e: FileAlreadyExistsException) { - logger.warn("Backup already exists:", e) - } catch (e: IOException) { - e.printStackTrace() - throw RuntimeException("Could not create backup.") - } - - } - - fun getTopMessageIDForChannel(id: Int): Int { - return queryInt("SELECT MAX(message_id) FROM messages WHERE source_id=$id AND source_type IN('channel', 'supergroup')") - } - - fun logRun(start_id: Int, end_id: Int, count: Int) { - try { - val ps = conn!!.prepareStatement("INSERT INTO runs " + - "(time, start_id, end_id, count_missing) " + - "VALUES " + - "(DateTime('now'), ?, ?, ? )") - ps.setInt(1, start_id) - ps.setInt(2, end_id) - ps.setInt(3, count) - ps.execute() - } catch (e: SQLException) { - } - - } - - fun queryInt(query: String): Int { - try { - val rs = stmt!!.executeQuery(query) - rs.next() - return rs.getInt(1) - } catch (e: SQLException) { - throw RuntimeException("Could not get count of messages.") - } - - } - - @Synchronized - fun saveMessages(all: TLVector, api_layer: Int) { - try { - //"(id, dialog_id, from_id, from_type, text, time, has_media, data, sticker, type) " + - //"VALUES " + - //"(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); - val columns = "(message_id, message_type, source_type, source_id, sender_id, fwd_from_id, text, time, has_media, media_type, media_file, media_size, data, api_layer) " + - "VALUES " + - "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" - //1 2 3 4 5 6 7 8 9 10 11 12 13 14 - val ps = conn!!.prepareStatement("INSERT OR REPLACE INTO messages " + columns) - val ps_insert_or_ignore = conn!!.prepareStatement("INSERT OR IGNORE INTO messages " + columns) - - for (abs in all) { - if (abs is TLMessage) { - val msg = abs - ps.setInt(1, msg.getId()) - ps.setString(2, "message") - val peer = msg.getToId() - if (peer is TLPeerChat) { - ps.setString(3, "group") - ps.setInt(4, peer.getChatId()) - } else if (peer is TLPeerUser) { - var id = peer.getUserId() - if (id == this.user_manager.user!!.getId()) { - id = msg.getFromId() - } - ps.setString(3, "dialog") - ps.setInt(4, id) - } else if (peer is TLPeerChannel) { - ps.setString(3, "channel") - ps.setInt(4, peer.getChannelId()) - } else { - throw RuntimeException("Unexpected Peer type: " + peer.javaClass) - } - - if (peer is TLPeerChannel) { - // Message in a channel don't have a sender -> insert a null - ps.setNull(5, Types.INTEGER) - } else { - ps.setInt(5, msg.getFromId()) - } - - if (msg.getFwdFrom() != null && msg.getFwdFrom().getFromId() != null) { - ps.setInt(6, msg.getFwdFrom().getFromId()) - } else { - ps.setNull(6, Types.INTEGER) - } - - var text = msg.getMessage() - if ((text == null || text.equals("")) && msg.getMedia() != null) { - val media = msg.getMedia() - if (media is TLMessageMediaDocument) { - text = media.getCaption() - } else if (media is TLMessageMediaPhoto) { - text = media.getCaption() - } - } - ps.setString(7, text) - ps.setString(8, "" + msg.getDate()) - val f = FileManagerFactory.getFileManager(msg, user_manager, client) - if (f == null) { - ps.setNull(9, Types.BOOLEAN) - ps.setNull(10, Types.VARCHAR) - ps.setNull(11, Types.VARCHAR) - ps.setNull(12, Types.INTEGER) - } else { - ps.setBoolean(9, true) - ps.setString(10, f.name) - ps.setString(11, f.targetFilename) - ps.setInt(12, f.size) - } - val stream = ByteArrayOutputStream() - msg.serializeBody(stream) - ps.setBytes(13, stream.toByteArray()) - ps.setInt(14, api_layer) - ps.addBatch() - } else if (abs is TLMessageService) { - ps_insert_or_ignore.setInt(1, abs.getId()) - ps_insert_or_ignore.setString(2, "service_message") - ps_insert_or_ignore.setNull(3, Types.INTEGER) - ps_insert_or_ignore.setNull(4, Types.INTEGER) - ps_insert_or_ignore.setNull(5, Types.INTEGER) - ps_insert_or_ignore.setNull(6, Types.INTEGER) - ps_insert_or_ignore.setNull(7, Types.VARCHAR) - ps_insert_or_ignore.setNull(8, Types.INTEGER) - ps_insert_or_ignore.setNull(9, Types.BOOLEAN) - ps_insert_or_ignore.setNull(10, Types.VARCHAR) - ps_insert_or_ignore.setNull(11, Types.VARCHAR) - ps_insert_or_ignore.setNull(12, Types.INTEGER) - ps_insert_or_ignore.setNull(13, Types.BLOB) - ps_insert_or_ignore.setInt(14, api_layer) - ps_insert_or_ignore.addBatch() - } else if (abs is TLMessageEmpty) { - ps_insert_or_ignore.setInt(1, abs.getId()) - ps_insert_or_ignore.setString(2, "empty_message") - ps_insert_or_ignore.setNull(3, Types.INTEGER) - ps_insert_or_ignore.setNull(4, Types.INTEGER) - ps_insert_or_ignore.setNull(5, Types.INTEGER) - ps_insert_or_ignore.setNull(6, Types.INTEGER) - ps_insert_or_ignore.setNull(7, Types.VARCHAR) - ps_insert_or_ignore.setNull(8, Types.INTEGER) - ps_insert_or_ignore.setNull(9, Types.BOOLEAN) - ps_insert_or_ignore.setNull(10, Types.VARCHAR) - ps_insert_or_ignore.setNull(11, Types.VARCHAR) - ps_insert_or_ignore.setNull(12, Types.INTEGER) - ps_insert_or_ignore.setNull(13, Types.BLOB) - ps_insert_or_ignore.setInt(14, api_layer) - ps_insert_or_ignore.addBatch() - } else { - throw RuntimeException("Unexpected Message type: " + abs.javaClass) - } - } - conn!!.setAutoCommit(false) - ps.executeBatch() - ps.clearBatch() - ps_insert_or_ignore.executeBatch() - ps_insert_or_ignore.clearBatch() - conn!!.commit() - conn!!.setAutoCommit(true) - } catch (e: Exception) { - e.printStackTrace() - throw RuntimeException("Exception shown above happened.") - } - - } - - @Synchronized - fun saveChats(all: TLVector) { - try { - val ps_insert_or_replace = conn!!.prepareStatement( - "INSERT OR REPLACE INTO chats " + - "(id, name, type) " + - "VALUES " + - "(?, ?, ?)") - val ps_insert_or_ignore = conn!!.prepareStatement( - "INSERT OR IGNORE INTO chats " + - "(id, name, type) " + - "VALUES " + - "(?, ?, ?)") - - for (abs in all) { - ps_insert_or_replace.setInt(1, abs.getId()) - ps_insert_or_ignore.setInt(1, abs.getId()) - if (abs is TLChatEmpty) { - ps_insert_or_ignore.setNull(2, Types.VARCHAR) - ps_insert_or_ignore.setString(3, "empty_chat") - ps_insert_or_ignore.addBatch() - } else if (abs is TLChatForbidden) { - ps_insert_or_replace.setString(2, abs.getTitle()) - ps_insert_or_replace.setString(3, "chat") - ps_insert_or_replace.addBatch() - } else if (abs is TLChannelForbidden) { - ps_insert_or_replace.setString(2, abs.getTitle()) - ps_insert_or_replace.setString(3, "channel") - ps_insert_or_replace.addBatch() - } else if (abs is TLChat) { - ps_insert_or_replace.setString(2, abs.getTitle()) - ps_insert_or_replace.setString(3, "chat") - ps_insert_or_replace.addBatch() - } else if (abs is TLChannel) { - ps_insert_or_replace.setString(2, abs.getTitle()) - ps_insert_or_replace.setString(3, "channel") - ps_insert_or_replace.addBatch() - } else { - throw RuntimeException("Unexpected " + abs.javaClass) - } - } - conn!!.setAutoCommit(false) - ps_insert_or_ignore.executeBatch() - ps_insert_or_ignore.clearBatch() - ps_insert_or_replace.executeBatch() - ps_insert_or_replace.clearBatch() - conn!!.commit() - conn!!.setAutoCommit(true) - } catch (e: Exception) { - e.printStackTrace() - throw RuntimeException("Exception shown above happened.") - } - - } - - @Synchronized - fun saveUsers(all: TLVector) { - try { - val ps_insert_or_replace = conn!!.prepareStatement( - "INSERT OR REPLACE INTO users " + - "(id, first_name, last_name, username, type, phone) " + - "VALUES " + - "(?, ?, ?, ?, ?, ?)") - val ps_insert_or_ignore = conn!!.prepareStatement( - "INSERT OR IGNORE INTO users " + - "(id, first_name, last_name, username, type, phone) " + - "VALUES " + - "(?, ?, ?, ?, ?, ?)") - for (abs in all) { - if (abs is TLUser) { - val user = abs - ps_insert_or_replace.setInt(1, user.getId()) - ps_insert_or_replace.setString(2, user.getFirstName()) - ps_insert_or_replace.setString(3, user.getLastName()) - ps_insert_or_replace.setString(4, user.getUsername()) - ps_insert_or_replace.setString(5, "user") - ps_insert_or_replace.setString(6, user.getPhone()) - ps_insert_or_replace.addBatch() - } else if (abs is TLUserEmpty) { - ps_insert_or_ignore.setInt(1, abs.getId()) - ps_insert_or_ignore.setNull(2, Types.VARCHAR) - ps_insert_or_ignore.setNull(3, Types.VARCHAR) - ps_insert_or_ignore.setNull(4, Types.VARCHAR) - ps_insert_or_ignore.setString(5, "empty_user") - ps_insert_or_ignore.setNull(6, Types.VARCHAR) - ps_insert_or_ignore.addBatch() - } else { - throw RuntimeException("Unexpected " + abs.javaClass) - } - } - conn!!.setAutoCommit(false) - ps_insert_or_ignore.executeBatch() - ps_insert_or_ignore.clearBatch() - ps_insert_or_replace.executeBatch() - ps_insert_or_replace.clearBatch() - conn!!.commit() - conn!!.setAutoCommit(true) - } catch (e: Exception) { - e.printStackTrace() - throw RuntimeException("Exception shown above happened.") - } - - } - - fun getIdsFromQuery(query: String): LinkedList { - try { - val list = LinkedList() - val rs = stmt!!.executeQuery(query) - while (rs.next()) { - list.add(rs.getInt(1)) - } - rs.close() - return list - } catch (e: SQLException) { - throw RuntimeException(e) - } - - } - - fun getMessageTypesWithCount(c: AbstractChat): HashMap { - val map = HashMap() - try { - val rs = stmt!!.executeQuery("SELECT message_type, COUNT(message_id) FROM messages WHERE " + c.query + " GROUP BY message_type") - while (rs.next()) { - map.put("count.messages.type." + rs.getString(1), rs.getInt(2)) - } - return map - } catch (e: Exception) { - throw RuntimeException(e) - } - - } - - fun getMessageMediaTypesWithCount(c: AbstractChat): HashMap { - val map = HashMap() - try { - var count = 0 - val rs = stmt!!.executeQuery("SELECT media_type, COUNT(message_id) FROM messages WHERE " + c.query + " GROUP BY media_type") - while (rs.next()) { - var s = rs.getString(1) - if (s == null) { - s = "null" - } else { - count += rs.getInt(2) - } - map.put("count.messages.media_type.$s", rs.getInt(2)) - } - map.put("count.messages.media_type.any", count) - return map - } catch (e: Exception) { - throw RuntimeException(e) - } - - } - - fun getMessageAuthorsWithCount(c: AbstractChat): HashMap { - val map = HashMap() - val user_map = HashMap() - var count_others = 0 - // Set a default value for 'me' to fix the charts for channels - cause I - // possibly didn't send any messages there. - map.put("authors.count.me", 0) - try { - val rs = stmt!!.executeQuery("SELECT users.id, users.first_name, users.last_name, users.username, COUNT(messages.id) " + - "FROM messages " + - "LEFT JOIN users ON users.id=messages.sender_id " + - "WHERE " + c.query + " GROUP BY sender_id") - while (rs.next()) { - val u: User - if (rs.getString(2) != null || rs.getString(3) != null || rs.getString(4) != null) { - u = User(rs.getInt(1), rs.getString(2), rs.getString(3)) - } else { - u = User(rs.getInt(1), "Unknown", "") - } - if (u.isMe) { - map.put("authors.count.me", rs.getInt(5)) - } else { - user_map.put(u, rs.getInt(5)) - count_others += rs.getInt(5) - } - } - map.put("authors.count.others", count_others) - map.put("authors.all", user_map) - return map - } catch (e: Exception) { - throw RuntimeException(e) - } - - } - - fun getMessageTimesMatrix(c: AbstractChat): Array { - val result = Array(7) { IntArray(24) } - try { - val rs = stmt!!.executeQuery("SELECT STRFTIME('%w', time, 'unixepoch') as DAY, " + - "STRFTIME('%H', time, 'unixepoch') AS hour, " + - "COUNT(id) FROM messages WHERE " + c.query + " GROUP BY hour, day " + - "ORDER BY hour, day") - while (rs.next()) { - result[if (rs.getInt(1) == 0) 6 else rs.getInt(1) - 1][rs.getInt(2)] = rs.getInt(3) - } - return result - } catch (e: Exception) { - throw RuntimeException(e) - } - - } - - fun getMessagesForExport(c: AbstractChat): LinkedList> { - try { - - val rs = stmt!!.executeQuery("SELECT messages.message_id as message_id, text, time*1000 as time, has_media, " + - "media_type, media_file, media_size, users.first_name as user_first_name, users.last_name as user_last_name, " + - "users.username as user_username, users.id as user_id, " + - "users_fwd.first_name as user_fwd_first_name, users_fwd.last_name as user_fwd_last_name, users_fwd.username as user_fwd_username " + - "FROM messages " + - "LEFT JOIN users ON users.id=messages.sender_id " + - "LEFT JOIN users AS users_fwd ON users_fwd.id=fwd_from_id WHERE " + - c.query + " " + - "ORDER BY messages.message_id") - val format_time = SimpleDateFormat("HH:mm:ss") - val format_date = SimpleDateFormat("d MMM yy") - val meta = rs.getMetaData() - val columns = meta.getColumnCount() - val list = LinkedList>() - - var count = 0 - var old_date: String? = null - var old_user = 0 - while (rs.next()) { - val h = HashMap(columns) - for (i in 1..columns) { - h.put(meta.getColumnName(i), rs.getObject(i)) - } - // Additional values to make up for Mustache's inability to format dates - val d = rs.getTime("time") - val date = format_date.format(d) - h.put("formatted_time", format_time.format(d)) - h.put("formatted_date", date) - if (rs.getString("media_type") != null) { - h.put("media_" + rs.getString("media_type"), true) - } - h.put("from_me", rs.getInt("user_id") == user_manager.user!!.getId()) - h.put("is_new_date", !date.equals(old_date)) - h.put("odd_even", if (count % 2 == 0) "even" else "odd") - h.put("same_user", old_user != 0 && rs.getInt("user_id") == old_user) - old_user = rs.getInt("user_id") - old_date = date - - list.add(h) - count++ - } - rs.close() - return list - } catch (e: Exception) { - e.printStackTrace() - throw RuntimeException("Exception above!") - } - - } - - - abstract inner class AbstractChat { - abstract val query: String - } - - inner class Dialog(var id: Int, var first_name: String?, var last_name: String?, var username: String?, var count: Int?) : AbstractChat() { - - override val query: String - get() = "source_type='dialog' AND source_id=" + id - } - - inner class Chat(var id: Int, var name: String?, var count: Int?) : AbstractChat() { - - override val query: String - get() = "source_type IN('group', 'supergroup', 'channel') AND source_id=" + id - } - - inner class User(id: Int, first_name: String?, last_name: String?) { - var name: String - var isMe: Boolean = false - - init { - isMe = id == user_manager.user!!.getId() - val s = StringBuilder() - if (first_name != null) s.append(first_name + " ") - if (last_name != null) s.append(last_name) - name = s.toString().trim() - } - } - - inner class GlobalChat : AbstractChat() { - override val query: String - get() = "1=1" - } - - companion object { - private val logger = LoggerFactory.getLogger(Database::class.java) - internal var instance: Database? = null - - fun init(c: TelegramClient) { - instance = Database(c) - } - - fun getInstance(): Database { - if (instance == null) throw RuntimeException("Database is not initialized but getInstance() was called.") - return instance!! - } - - fun bytesToTLMessage(b: ByteArray?): TLMessage? { - try { - if (b == null) return null - val stream = ByteArrayInputStream(b) - val msg = TLMessage() - msg.deserializeBody(stream, TLApiContext.getInstance()) - return msg - } catch (e: IOException) { - e.printStackTrace() - throw RuntimeException("Could not deserialize message.") - } - - } - } + private var conn: Connection? = null + private var stmt: Statement? = null + var user_manager: UserManager + + fun getTopMessageID(): Int { + try { + val rs = stmt!!.executeQuery("SELECT MAX(message_id) FROM messages WHERE source_type IN ('group', 'dialog')") + rs.next() + return rs.getInt(1) + } catch (e: SQLException) { + return 0 + } + + } + + fun getMessageCount(): Int = queryInt("SELECT COUNT(*) FROM messages") + fun getChatCount(): Int = queryInt("SELECT COUNT(*) FROM chats") + fun getUserCount(): Int = queryInt("SELECT COUNT(*) FROM users") + + val missingIDs: LinkedList + get() { + try { + val missing = LinkedList() + val max = getTopMessageID() + val rs = stmt!!.executeQuery("SELECT message_id FROM messages WHERE source_type IN ('group', 'dialog') ORDER BY id") + rs.next() + var id = rs.getInt(1) + for (i in 1..max) { + if (i == id) { + rs.next() + if (rs.isClosed()) { + id = Integer.MAX_VALUE + } else { + id = rs.getInt(1) + } + } else if (i < id) { + missing.add(i) + } + } + return missing + } catch (e: SQLException) { + e.printStackTrace() + throw RuntimeException("Could not get list of ids.") + } + + } + + fun getMessagesWithMedia(): LinkedList { + try { + val list = LinkedList() + val rs = stmt!!.executeQuery("SELECT data FROM messages WHERE has_media=1") + while (rs.next()) { + list.add(bytesToTLMessage(rs.getBytes(1))) + } + rs.close() + return list + } catch (e: Exception) { + e.printStackTrace() + throw RuntimeException("Exception occured. See above.") + } + + } + + fun getMessagesFromUserCount(): Int { + try { + val rs = stmt!!.executeQuery("SELECT COUNT(*) FROM messages WHERE sender_id=" + user_manager.user!!.getId()) + rs.next() + return rs.getInt(1) + } catch (e: SQLException) { + throw RuntimeException(e) + } + + } + + fun getMessageTypesWithCount(): HashMap = getMessageTypesWithCount(GlobalChat()) + + fun getMessageMediaTypesWithCount(): HashMap = getMessageMediaTypesWithCount(GlobalChat()) + + fun getMessageApiLayerWithCount(): HashMap { + val map = HashMap() + try { + val rs = stmt!!.executeQuery("SELECT COUNT(id), api_layer FROM messages GROUP BY api_layer ORDER BY api_layer") + while (rs.next()) { + var layer = rs.getInt(2) + map.put("count.messages.api_layer.$layer", rs.getInt(1)) + } + rs.close() + return map + } catch (e: Exception) { + throw RuntimeException(e) + } + } + + fun getMessageAuthorsWithCount(): HashMap = getMessageAuthorsWithCount(GlobalChat()) + + fun getMessageTimesMatrix(): Array = getMessageTimesMatrix(GlobalChat()) + + fun getEncoding(): String { + try { + val rs = stmt!!.executeQuery("PRAGMA encoding") + rs.next() + return rs.getString(1) + } catch (e: SQLException) { + logger.debug("SQLException: {}", e) + return "unknown" + } + + } + + + fun getListOfChatsForExport(): LinkedList { + val list = LinkedList() + try { + val rs = stmt!!.executeQuery("SELECT chats.id, chats.name, COUNT(messages.id) as c " + + "FROM chats, messages WHERE messages.source_type IN('group', 'supergroup', 'channel') AND messages.source_id=chats.id " + + "GROUP BY chats.id ORDER BY c DESC") + while (rs.next()) { + list.add(Chat(rs.getInt(1), rs.getString(2), rs.getInt(3))) + } + rs.close() + return list + } catch (e: Exception) { + e.printStackTrace() + throw RuntimeException("Exception above!") + } + + } + + + fun getListOfDialogsForExport(): LinkedList { + val list = LinkedList() + try { + val rs = stmt!!.executeQuery( + "SELECT users.id, first_name, last_name, username, COUNT(messages.id) as c " + + "FROM users, messages WHERE messages.source_type='dialog' AND messages.source_id=users.id " + + "GROUP BY users.id ORDER BY c DESC") + while (rs.next()) { + list.add(Dialog(rs.getInt(1), rs.getString(2), rs.getString(3), rs.getString(4), rs.getInt(5))) + } + rs.close() + return list + } catch (e: Exception) { + e.printStackTrace() + throw RuntimeException("Exception above!") + } + + } + + init { + this.user_manager = UserManager.getInstance() + System.out.println("Opening database...") + try { + Class.forName("org.sqlite.JDBC") + } catch (e: ClassNotFoundException) { + CommandLineController.show_error("Could not load jdbc-sqlite class.") + } + + val path = "jdbc:sqlite:${user_manager.fileBase}${Config.FILE_NAME_DB}" + + try { + conn = DriverManager.getConnection(path) + stmt = conn!!.createStatement() + } catch (e: SQLException) { + CommandLineController.show_error("Could not connect to SQLITE database.") + } + + // Run updates + val updates = DatabaseUpdates(conn!!, this) + updates.doUpdates() + + System.out.println("Database is ready.") + } + + fun backupDatabase(currentVersion: Int) { + val filename = String.format(Config.FILE_NAME_DB_BACKUP, currentVersion) + System.out.println(" Creating a backup of your database as " + filename) + try { + val src = user_manager.fileBase + Config.FILE_NAME_DB + val dst = user_manager.fileBase + filename + logger.debug("Copying {} to {}", src, dst) + Files.copy( + File(src).toPath(), + File(dst).toPath()) + } catch (e: FileAlreadyExistsException) { + logger.warn("Backup already exists:", e) + } catch (e: IOException) { + e.printStackTrace() + throw RuntimeException("Could not create backup.") + } + + } + + fun getTopMessageIDForChannel(id: Int): Int { + return queryInt("SELECT MAX(message_id) FROM messages WHERE source_id=$id AND source_type IN('channel', 'supergroup')") + } + + fun logRun(start_id: Int, end_id: Int, count: Int) { + try { + val ps = conn!!.prepareStatement("INSERT INTO runs " + + "(time, start_id, end_id, count_missing) " + + "VALUES " + + "(DateTime('now'), ?, ?, ? )") + ps.setInt(1, start_id) + ps.setInt(2, end_id) + ps.setInt(3, count) + ps.execute() + } catch (e: SQLException) { + } + + } + + fun queryInt(query: String): Int { + try { + val rs = stmt!!.executeQuery(query) + rs.next() + return rs.getInt(1) + } catch (e: SQLException) { + throw RuntimeException("Could not get count of messages.") + } + + } + + @Synchronized + fun saveMessages(all: TLVector, api_layer: Int) { + try { + //"(id, dialog_id, from_id, from_type, text, time, has_media, data, sticker, type) " + + //"VALUES " + + //"(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + val columns = "(message_id, message_type, source_type, source_id, sender_id, fwd_from_id, text, time, has_media, media_type, media_file, media_size, data, api_layer) " + + "VALUES " + + "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" + //1 2 3 4 5 6 7 8 9 10 11 12 13 14 + val ps = conn!!.prepareStatement("INSERT OR REPLACE INTO messages " + columns) + val ps_insert_or_ignore = conn!!.prepareStatement("INSERT OR IGNORE INTO messages " + columns) + + for (abs in all) { + if (abs is TLMessage) { + val msg = abs + ps.setInt(1, msg.getId()) + ps.setString(2, "message") + val peer = msg.getToId() + if (peer is TLPeerChat) { + ps.setString(3, "group") + ps.setInt(4, peer.getChatId()) + } else if (peer is TLPeerUser) { + var id = peer.getUserId() + if (id == this.user_manager.user!!.getId()) { + id = msg.getFromId() + } + ps.setString(3, "dialog") + ps.setInt(4, id) + } else if (peer is TLPeerChannel) { + ps.setString(3, "channel") + ps.setInt(4, peer.getChannelId()) + } else { + throw RuntimeException("Unexpected Peer type: " + peer.javaClass) + } + + if (peer is TLPeerChannel) { + // Message in a channel don't have a sender -> insert a null + ps.setNull(5, Types.INTEGER) + } else { + ps.setInt(5, msg.getFromId()) + } + + if (msg.getFwdFrom() != null && msg.getFwdFrom().getFromId() != null) { + ps.setInt(6, msg.getFwdFrom().getFromId()) + } else { + ps.setNull(6, Types.INTEGER) + } + + var text = msg.getMessage() + if ((text == null || text.equals("")) && msg.getMedia() != null) { + val media = msg.getMedia() + if (media is TLMessageMediaDocument) { + text = media.getCaption() + } else if (media is TLMessageMediaPhoto) { + text = media.getCaption() + } + } + ps.setString(7, text) + ps.setString(8, "" + msg.getDate()) + val f = FileManagerFactory.getFileManager(msg, user_manager, client) + if (f == null) { + ps.setNull(9, Types.BOOLEAN) + ps.setNull(10, Types.VARCHAR) + ps.setNull(11, Types.VARCHAR) + ps.setNull(12, Types.INTEGER) + } else { + ps.setBoolean(9, true) + ps.setString(10, f.name) + ps.setString(11, f.targetFilename) + ps.setInt(12, f.size) + } + val stream = ByteArrayOutputStream() + msg.serializeBody(stream) + ps.setBytes(13, stream.toByteArray()) + ps.setInt(14, api_layer) + ps.addBatch() + } else if (abs is TLMessageService) { + ps_insert_or_ignore.setInt(1, abs.getId()) + ps_insert_or_ignore.setString(2, "service_message") + ps_insert_or_ignore.setNull(3, Types.INTEGER) + ps_insert_or_ignore.setNull(4, Types.INTEGER) + ps_insert_or_ignore.setNull(5, Types.INTEGER) + ps_insert_or_ignore.setNull(6, Types.INTEGER) + ps_insert_or_ignore.setNull(7, Types.VARCHAR) + ps_insert_or_ignore.setNull(8, Types.INTEGER) + ps_insert_or_ignore.setNull(9, Types.BOOLEAN) + ps_insert_or_ignore.setNull(10, Types.VARCHAR) + ps_insert_or_ignore.setNull(11, Types.VARCHAR) + ps_insert_or_ignore.setNull(12, Types.INTEGER) + ps_insert_or_ignore.setNull(13, Types.BLOB) + ps_insert_or_ignore.setInt(14, api_layer) + ps_insert_or_ignore.addBatch() + } else if (abs is TLMessageEmpty) { + ps_insert_or_ignore.setInt(1, abs.getId()) + ps_insert_or_ignore.setString(2, "empty_message") + ps_insert_or_ignore.setNull(3, Types.INTEGER) + ps_insert_or_ignore.setNull(4, Types.INTEGER) + ps_insert_or_ignore.setNull(5, Types.INTEGER) + ps_insert_or_ignore.setNull(6, Types.INTEGER) + ps_insert_or_ignore.setNull(7, Types.VARCHAR) + ps_insert_or_ignore.setNull(8, Types.INTEGER) + ps_insert_or_ignore.setNull(9, Types.BOOLEAN) + ps_insert_or_ignore.setNull(10, Types.VARCHAR) + ps_insert_or_ignore.setNull(11, Types.VARCHAR) + ps_insert_or_ignore.setNull(12, Types.INTEGER) + ps_insert_or_ignore.setNull(13, Types.BLOB) + ps_insert_or_ignore.setInt(14, api_layer) + ps_insert_or_ignore.addBatch() + } else { + throw RuntimeException("Unexpected Message type: " + abs.javaClass) + } + } + conn!!.setAutoCommit(false) + ps.executeBatch() + ps.clearBatch() + ps_insert_or_ignore.executeBatch() + ps_insert_or_ignore.clearBatch() + conn!!.commit() + conn!!.setAutoCommit(true) + } catch (e: Exception) { + e.printStackTrace() + throw RuntimeException("Exception shown above happened.") + } + + } + + @Synchronized + fun saveChats(all: TLVector) { + try { + val ps_insert_or_replace = conn!!.prepareStatement( + "INSERT OR REPLACE INTO chats " + + "(id, name, type) " + + "VALUES " + + "(?, ?, ?)") + val ps_insert_or_ignore = conn!!.prepareStatement( + "INSERT OR IGNORE INTO chats " + + "(id, name, type) " + + "VALUES " + + "(?, ?, ?)") + + for (abs in all) { + ps_insert_or_replace.setInt(1, abs.getId()) + ps_insert_or_ignore.setInt(1, abs.getId()) + if (abs is TLChatEmpty) { + ps_insert_or_ignore.setNull(2, Types.VARCHAR) + ps_insert_or_ignore.setString(3, "empty_chat") + ps_insert_or_ignore.addBatch() + } else if (abs is TLChatForbidden) { + ps_insert_or_replace.setString(2, abs.getTitle()) + ps_insert_or_replace.setString(3, "chat") + ps_insert_or_replace.addBatch() + } else if (abs is TLChannelForbidden) { + ps_insert_or_replace.setString(2, abs.getTitle()) + ps_insert_or_replace.setString(3, "channel") + ps_insert_or_replace.addBatch() + } else if (abs is TLChat) { + ps_insert_or_replace.setString(2, abs.getTitle()) + ps_insert_or_replace.setString(3, "chat") + ps_insert_or_replace.addBatch() + } else if (abs is TLChannel) { + ps_insert_or_replace.setString(2, abs.getTitle()) + ps_insert_or_replace.setString(3, "channel") + ps_insert_or_replace.addBatch() + } else { + throw RuntimeException("Unexpected " + abs.javaClass) + } + } + conn!!.setAutoCommit(false) + ps_insert_or_ignore.executeBatch() + ps_insert_or_ignore.clearBatch() + ps_insert_or_replace.executeBatch() + ps_insert_or_replace.clearBatch() + conn!!.commit() + conn!!.setAutoCommit(true) + } catch (e: Exception) { + e.printStackTrace() + throw RuntimeException("Exception shown above happened.") + } + + } + + @Synchronized + fun saveUsers(all: TLVector) { + try { + val ps_insert_or_replace = conn!!.prepareStatement( + "INSERT OR REPLACE INTO users " + + "(id, first_name, last_name, username, type, phone) " + + "VALUES " + + "(?, ?, ?, ?, ?, ?)") + val ps_insert_or_ignore = conn!!.prepareStatement( + "INSERT OR IGNORE INTO users " + + "(id, first_name, last_name, username, type, phone) " + + "VALUES " + + "(?, ?, ?, ?, ?, ?)") + for (abs in all) { + if (abs is TLUser) { + val user = abs + ps_insert_or_replace.setInt(1, user.getId()) + ps_insert_or_replace.setString(2, user.getFirstName()) + ps_insert_or_replace.setString(3, user.getLastName()) + ps_insert_or_replace.setString(4, user.getUsername()) + ps_insert_or_replace.setString(5, "user") + ps_insert_or_replace.setString(6, user.getPhone()) + ps_insert_or_replace.addBatch() + } else if (abs is TLUserEmpty) { + ps_insert_or_ignore.setInt(1, abs.getId()) + ps_insert_or_ignore.setNull(2, Types.VARCHAR) + ps_insert_or_ignore.setNull(3, Types.VARCHAR) + ps_insert_or_ignore.setNull(4, Types.VARCHAR) + ps_insert_or_ignore.setString(5, "empty_user") + ps_insert_or_ignore.setNull(6, Types.VARCHAR) + ps_insert_or_ignore.addBatch() + } else { + throw RuntimeException("Unexpected " + abs.javaClass) + } + } + conn!!.setAutoCommit(false) + ps_insert_or_ignore.executeBatch() + ps_insert_or_ignore.clearBatch() + ps_insert_or_replace.executeBatch() + ps_insert_or_replace.clearBatch() + conn!!.commit() + conn!!.setAutoCommit(true) + } catch (e: Exception) { + e.printStackTrace() + throw RuntimeException("Exception shown above happened.") + } + + } + + fun getIdsFromQuery(query: String): LinkedList { + try { + val list = LinkedList() + val rs = stmt!!.executeQuery(query) + while (rs.next()) { + list.add(rs.getInt(1)) + } + rs.close() + return list + } catch (e: SQLException) { + throw RuntimeException(e) + } + + } + + fun getMessageTypesWithCount(c: AbstractChat): HashMap { + val map = HashMap() + try { + val rs = stmt!!.executeQuery("SELECT message_type, COUNT(message_id) FROM messages WHERE " + c.query + " GROUP BY message_type") + while (rs.next()) { + map.put("count.messages.type." + rs.getString(1), rs.getInt(2)) + } + return map + } catch (e: Exception) { + throw RuntimeException(e) + } + + } + + fun getMessageMediaTypesWithCount(c: AbstractChat): HashMap { + val map = HashMap() + try { + var count = 0 + val rs = stmt!!.executeQuery("SELECT media_type, COUNT(message_id) FROM messages WHERE " + c.query + " GROUP BY media_type") + while (rs.next()) { + var s = rs.getString(1) + if (s == null) { + s = "null" + } else { + count += rs.getInt(2) + } + map.put("count.messages.media_type.$s", rs.getInt(2)) + } + map.put("count.messages.media_type.any", count) + return map + } catch (e: Exception) { + throw RuntimeException(e) + } + + } + + fun getMessageAuthorsWithCount(c: AbstractChat): HashMap { + val map = HashMap() + val user_map = HashMap() + var count_others = 0 + // Set a default value for 'me' to fix the charts for channels - cause I + // possibly didn't send any messages there. + map.put("authors.count.me", 0) + try { + val rs = stmt!!.executeQuery("SELECT users.id, users.first_name, users.last_name, users.username, COUNT(messages.id) " + + "FROM messages " + + "LEFT JOIN users ON users.id=messages.sender_id " + + "WHERE " + c.query + " GROUP BY sender_id") + while (rs.next()) { + val u: User + if (rs.getString(2) != null || rs.getString(3) != null || rs.getString(4) != null) { + u = User(rs.getInt(1), rs.getString(2), rs.getString(3)) + } else { + u = User(rs.getInt(1), "Unknown", "") + } + if (u.isMe) { + map.put("authors.count.me", rs.getInt(5)) + } else { + user_map.put(u, rs.getInt(5)) + count_others += rs.getInt(5) + } + } + map.put("authors.count.others", count_others) + map.put("authors.all", user_map) + return map + } catch (e: Exception) { + throw RuntimeException(e) + } + + } + + fun getMessageTimesMatrix(c: AbstractChat): Array { + val result = Array(7) { IntArray(24) } + try { + val rs = stmt!!.executeQuery("SELECT STRFTIME('%w', time, 'unixepoch') as DAY, " + + "STRFTIME('%H', time, 'unixepoch') AS hour, " + + "COUNT(id) FROM messages WHERE " + c.query + " GROUP BY hour, day " + + "ORDER BY hour, day") + while (rs.next()) { + result[if (rs.getInt(1) == 0) 6 else rs.getInt(1) - 1][rs.getInt(2)] = rs.getInt(3) + } + return result + } catch (e: Exception) { + throw RuntimeException(e) + } + + } + + fun getMessagesForExport(c: AbstractChat): LinkedList> { + try { + + val rs = stmt!!.executeQuery("SELECT messages.message_id as message_id, text, time*1000 as time, has_media, " + + "media_type, media_file, media_size, users.first_name as user_first_name, users.last_name as user_last_name, " + + "users.username as user_username, users.id as user_id, " + + "users_fwd.first_name as user_fwd_first_name, users_fwd.last_name as user_fwd_last_name, users_fwd.username as user_fwd_username " + + "FROM messages " + + "LEFT JOIN users ON users.id=messages.sender_id " + + "LEFT JOIN users AS users_fwd ON users_fwd.id=fwd_from_id WHERE " + + c.query + " " + + "ORDER BY messages.message_id") + val format_time = SimpleDateFormat("HH:mm:ss") + val format_date = SimpleDateFormat("d MMM yy") + val meta = rs.getMetaData() + val columns = meta.getColumnCount() + val list = LinkedList>() + + var count = 0 + var old_date: String? = null + var old_user = 0 + while (rs.next()) { + val h = HashMap(columns) + for (i in 1..columns) { + h.put(meta.getColumnName(i), rs.getObject(i)) + } + // Additional values to make up for Mustache's inability to format dates + val d = rs.getTime("time") + val date = format_date.format(d) + h.put("formatted_time", format_time.format(d)) + h.put("formatted_date", date) + if (rs.getString("media_type") != null) { + h.put("media_" + rs.getString("media_type"), true) + } + h.put("from_me", rs.getInt("user_id") == user_manager.user!!.getId()) + h.put("is_new_date", !date.equals(old_date)) + h.put("odd_even", if (count % 2 == 0) "even" else "odd") + h.put("same_user", old_user != 0 && rs.getInt("user_id") == old_user) + old_user = rs.getInt("user_id") + old_date = date + + list.add(h) + count++ + } + rs.close() + return list + } catch (e: Exception) { + e.printStackTrace() + throw RuntimeException("Exception above!") + } + + } + + + abstract inner class AbstractChat { + abstract val query: String + } + + inner class Dialog(var id: Int, var first_name: String?, var last_name: String?, var username: String?, var count: Int?) : AbstractChat() { + + override val query: String + get() = "source_type='dialog' AND source_id=" + id + } + + inner class Chat(var id: Int, var name: String?, var count: Int?) : AbstractChat() { + + override val query: String + get() = "source_type IN('group', 'supergroup', 'channel') AND source_id=" + id + } + + inner class User(id: Int, first_name: String?, last_name: String?) { + var name: String + var isMe: Boolean = false + + init { + isMe = id == user_manager.user!!.getId() + val s = StringBuilder() + if (first_name != null) s.append(first_name + " ") + if (last_name != null) s.append(last_name) + name = s.toString().trim() + } + } + + inner class GlobalChat : AbstractChat() { + override val query: String + get() = "1=1" + } + + companion object { + private val logger = LoggerFactory.getLogger(Database::class.java) + internal var instance: Database? = null + + fun init(c: TelegramClient) { + instance = Database(c) + } + + fun getInstance(): Database { + if (instance == null) throw RuntimeException("Database is not initialized but getInstance() was called.") + return instance!! + } + + fun bytesToTLMessage(b: ByteArray?): TLMessage? { + try { + if (b == null) return null + val stream = ByteArrayInputStream(b) + val msg = TLMessage() + msg.deserializeBody(stream, TLApiContext.getInstance()) + return msg + } catch (e: IOException) { + e.printStackTrace() + throw RuntimeException("Could not deserialize message.") + } + + } + } } diff --git a/src/main/kotlin/de/fabianonline/telegram_backup/DatabaseUpdates.kt b/src/main/kotlin/de/fabianonline/telegram_backup/DatabaseUpdates.kt index 1423320..186d0b9 100644 --- a/src/main/kotlin/de/fabianonline/telegram_backup/DatabaseUpdates.kt +++ b/src/main/kotlin/de/fabianonline/telegram_backup/DatabaseUpdates.kt @@ -17,355 +17,355 @@ import de.fabianonline.telegram_backup.mediafilemanager.AbstractMediaFileManager class DatabaseUpdates(protected var conn: Connection, protected var db: Database) { - private val maxPossibleVersion: Int - get() = updates.size + private val maxPossibleVersion: Int + get() = updates.size - init { - logger.debug("Registering Database Updates...") - register(DB_Update_1(conn, db)) - register(DB_Update_2(conn, db)) - register(DB_Update_3(conn, db)) - register(DB_Update_4(conn, db)) - register(DB_Update_5(conn, db)) - register(DB_Update_6(conn, db)) - register(DB_Update_7(conn, db)) - register(DB_Update_8(conn, db)) - } + init { + logger.debug("Registering Database Updates...") + register(DB_Update_1(conn, db)) + register(DB_Update_2(conn, db)) + register(DB_Update_3(conn, db)) + register(DB_Update_4(conn, db)) + register(DB_Update_5(conn, db)) + register(DB_Update_6(conn, db)) + register(DB_Update_7(conn, db)) + register(DB_Update_8(conn, db)) + } - fun doUpdates() { - try { - val stmt = conn.createStatement() - var rs: ResultSet - logger.debug("DatabaseUpdate.doUpdates running") + fun doUpdates() { + try { + val stmt = conn.createStatement() + var rs: ResultSet + logger.debug("DatabaseUpdate.doUpdates running") - logger.debug("Getting current database version") - val version: Int - 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 {}", maxPossibleVersion) + logger.debug("Getting current database version") + val version: Int + 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 {}", maxPossibleVersion) - if (version < maxPossibleVersion) { - logger.debug("Update is necessary. {} => {}.", version, maxPossibleVersion) - var backup = false - for (i in version + 1..maxPossibleVersion) { - 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.") - } - } + if (version < maxPossibleVersion) { + logger.debug("Update is necessary. {} => {}.", version, maxPossibleVersion) + var backup = false + for (i in version + 1..maxPossibleVersion) { + 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 (i in version + 1..maxPossibleVersion) { - getUpdateToVersion(i).doUpdate() - } - } catch (e: SQLException) { - throw RuntimeException(e) - } + logger.debug("Applying updates") + try { + for (i in version + 1..maxPossibleVersion) { + getUpdateToVersion(i).doUpdate() + } + } catch (e: SQLException) { + throw RuntimeException(e) + } - } else { - logger.debug("No update necessary.") - } + } else { + logger.debug("No update necessary.") + } - } catch (e: SQLException) { - throw RuntimeException(e) - } + } catch (e: SQLException) { + throw RuntimeException(e) + } - } + } - private fun getUpdateToVersion(i: Int): DatabaseUpdate { - return updates.get(i - 1) - } + private fun getUpdateToVersion(i: Int): DatabaseUpdate { + return updates.get(i - 1) + } - private fun register(d: DatabaseUpdate) { - logger.debug("Registering {} as update to version {}", d.javaClass, d.version) - if (d.version != updates.size + 1) { - throw RuntimeException("Tried to register DB update to version ${d.version}, but would need update to version ${updates.size + 1}") - } - updates.add(d) - } + private fun register(d: DatabaseUpdate) { + logger.debug("Registering {} as update to version {}", d.javaClass, d.version) + if (d.version != updates.size + 1) { + throw RuntimeException("Tried to register DB update to version ${d.version}, but would need update to version ${updates.size + 1}") + } + updates.add(d) + } - companion object { - private val logger = LoggerFactory.getLogger(DatabaseUpdates::class.java) - private val updates = LinkedList() - } + companion object { + private val logger = LoggerFactory.getLogger(DatabaseUpdates::class.java) + private val updates = LinkedList() + } } internal abstract class DatabaseUpdate(protected var conn: Connection, protected var db: Database) { - protected var stmt: Statement - abstract val version: Int + protected var stmt: Statement + abstract val version: Int - init { - try { - stmt = conn.createStatement() - } catch (e: SQLException) { - throw RuntimeException(e) - } + init { + try { + stmt = conn.createStatement() + } catch (e: SQLException) { + throw RuntimeException(e) + } - } + } - @Throws(SQLException::class) - fun doUpdate() { - logger.debug("Applying update to version {}", version) - System.out.println(" Updating to version $version...") - _doUpdate() - logger.debug("Saving current database version to the db") - stmt.executeUpdate("INSERT INTO database_versions (version) VALUES ($version)") - } + @Throws(SQLException::class) + fun doUpdate() { + logger.debug("Applying update to version {}", version) + System.out.println(" Updating to version $version...") + _doUpdate() + logger.debug("Saving current database version to the db") + stmt.executeUpdate("INSERT INTO database_versions (version) VALUES ($version)") + } - @Throws(SQLException::class) - protected abstract fun _doUpdate() + @Throws(SQLException::class) + protected abstract fun _doUpdate() - open val needsBackup = false + open val needsBackup = false - @Throws(SQLException::class) - protected fun execute(sql: String) { - logger.debug("Executing: {}", sql) - stmt.executeUpdate(sql) - } + @Throws(SQLException::class) + protected fun execute(sql: String) { + logger.debug("Executing: {}", sql) + stmt.executeUpdate(sql) + } - companion object { - protected val logger = LoggerFactory.getLogger(DatabaseUpdate::class.java) - } + companion object { + protected val logger = LoggerFactory.getLogger(DatabaseUpdate::class.java) + } } internal class DB_Update_1(conn: Connection, db: Database) : DatabaseUpdate(conn, db) { - override val version: Int - get() = 1 + override val version: Int + get() = 1 - @Throws(SQLException::class) - override fun _doUpdate() { - 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)") - } + @Throws(SQLException::class) + override fun _doUpdate() { + 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)") + } } internal class DB_Update_2(conn: Connection, db: Database) : DatabaseUpdate(conn, db) { - override val version: Int - get() = 2 + override val version: Int + get() = 2 - @Throws(SQLException::class) - override fun _doUpdate() { - stmt.executeUpdate("ALTER TABLE people RENAME TO 'users'") - stmt.executeUpdate("ALTER TABLE users ADD COLUMN phone TEXT") - } + @Throws(SQLException::class) + override fun _doUpdate() { + stmt.executeUpdate("ALTER TABLE people RENAME TO 'users'") + stmt.executeUpdate("ALTER TABLE users ADD COLUMN phone TEXT") + } } internal class DB_Update_3(conn: Connection, db: Database) : DatabaseUpdate(conn, db) { - override val version: Int - get() = 3 + override val version: Int + get() = 3 - @Throws(SQLException::class) - override fun _doUpdate() { - stmt.executeUpdate("ALTER TABLE dialogs RENAME TO 'chats'") - } + @Throws(SQLException::class) + override fun _doUpdate() { + stmt.executeUpdate("ALTER TABLE dialogs RENAME TO 'chats'") + } } internal class DB_Update_4(conn: Connection, db: Database) : DatabaseUpdate(conn, db) { - override val version: Int - get() = 4 + override val version: Int + get() = 4 - @Throws(SQLException::class) - override fun _doUpdate() { - 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'") - } + @Throws(SQLException::class) + override fun _doUpdate() { + 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'") + } } internal class DB_Update_5(conn: Connection, db: Database) : DatabaseUpdate(conn, db) { - override val version: Int - get() = 5 + override val version: Int + get() = 5 - @Throws(SQLException::class) - override fun _doUpdate() { - stmt.executeUpdate("CREATE TABLE runs (id INTEGER PRIMARY KEY ASC, time INTEGER, start_id INTEGER, end_id INTEGER, count_missing INTEGER)") - } + @Throws(SQLException::class) + override fun _doUpdate() { + stmt.executeUpdate("CREATE TABLE runs (id INTEGER PRIMARY KEY ASC, time INTEGER, start_id INTEGER, end_id INTEGER, count_missing INTEGER)") + } } internal class DB_Update_6(conn: Connection, db: Database) : DatabaseUpdate(conn, db) { - override val version: Int - get() = 6 + override val version: Int + get() = 6 - override val needsBackup = true + override val needsBackup = true - @Throws(SQLException::class) - override fun _doUpdate() { - 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)") - val mappings = LinkedHashMap() - 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") - val query = StringBuilder("INSERT INTO messages_new\n(") - var first: Boolean - first = true - for (s in mappings.keys) { - if (!first) query.append(", ") - query.append(s) - first = false - } - query.append(")\nSELECT \n") - first = true - for (s in mappings.values) { - if (!first) query.append(", ") - query.append(s) - first = false - } - query.append("\nFROM messages") - stmt.executeUpdate(query.toString()) + @Throws(SQLException::class) + override fun _doUpdate() { + 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)") + val mappings = LinkedHashMap() + 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") + val query = StringBuilder("INSERT INTO messages_new\n(") + var first: Boolean + first = true + for (s in mappings.keys) { + if (!first) query.append(", ") + query.append(s) + first = false + } + query.append(")\nSELECT \n") + first = true + for (s in 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)...") - val rs = stmt.executeQuery("SELECT id, data FROM messages_new") - val 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)) - val msg = Database.bytesToTLMessage(rs.getBytes(2)) - if (msg == null || msg.getFwdFrom() == null) { - ps.setNull(1, Types.INTEGER) - } else { - ps.setInt(1, msg.getFwdFrom().getFromId()) - } - val 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.name) - ps.setString(3, f.targetFilename) - ps.setInt(4, f.size) - } - 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") - } + System.out.println(" Updating the data (this might take some time)...") + val rs = stmt.executeQuery("SELECT id, data FROM messages_new") + val 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)) + val msg = Database.bytesToTLMessage(rs.getBytes(2)) + if (msg == null || msg.getFwdFrom() == null) { + ps.setNull(1, Types.INTEGER) + } else { + ps.setInt(1, msg.getFwdFrom().getFromId()) + } + val 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.name) + ps.setString(3, f.targetFilename) + ps.setInt(4, f.size) + } + 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") + } } internal class DB_Update_7(conn: Connection, db: Database) : DatabaseUpdate(conn, db) { - override val version: Int - get() = 7 + override val version: Int + get() = 7 - override val needsBackup = true + override val needsBackup = true - @Throws(SQLException::class) - override fun _doUpdate() { - stmt.executeUpdate("ALTER TABLE messages ADD COLUMN api_layer INTEGER") + @Throws(SQLException::class) + override fun _doUpdate() { + stmt.executeUpdate("ALTER TABLE messages ADD COLUMN api_layer INTEGER") - stmt.executeUpdate("UPDATE messages SET api_layer=51") - } + stmt.executeUpdate("UPDATE messages SET api_layer=51") + } } internal class DB_Update_8(conn: Connection, db: Database) : DatabaseUpdate(conn, db) { - override val version: Int - get() = 8 + override val version: Int + get() = 8 - override val needsBackup = true + override val needsBackup = true - @Throws(SQLException::class) - override fun _doUpdate() { - execute("ALTER TABLE messages ADD COLUMN source_type TEXT") - execute("ALTER TABLE messages ADD COLUMN source_id INTEGER") - execute("update messages set source_type='dialog', source_id=dialog_id where dialog_id is not null") - execute("update messages set source_type='group', source_id=chat_id where chat_id is not null") + @Throws(SQLException::class) + override fun _doUpdate() { + execute("ALTER TABLE messages ADD COLUMN source_type TEXT") + execute("ALTER TABLE messages ADD COLUMN source_id INTEGER") + execute("update messages set source_type='dialog', source_id=dialog_id where dialog_id is not null") + execute("update messages set source_type='group', source_id=chat_id where chat_id is not null") - execute("CREATE TABLE messages_new (" + - "id INTEGER PRIMARY KEY AUTOINCREMENT," + - "message_id INTEGER," + - "message_type TEXT," + - "source_type TEXT," + - "source_id INTEGER," + - "sender_id INTEGER," + - "fwd_from_id INTEGER," + - "text TEXT," + - "time INTEGER," + - "has_media BOOLEAN," + - "media_type TEXT," + - "media_file TEXT," + - "media_size INTEGER," + - "media_json TEXT," + - "markup_json TEXT," + - "data BLOB," + - "api_layer INTEGER)") - execute("INSERT INTO messages_new" + - "(message_id, message_type, source_type, source_id, sender_id, fwd_from_id, text, time, has_media, media_type," + - "media_file, media_size, media_json, markup_json, data, api_layer)" + - "SELECT " + - "id, message_type, source_type, source_id, sender_id, fwd_from_id, text, time, has_media, media_type," + - "media_file, media_size, media_json, markup_json, data, api_layer FROM messages") - execute("DROP TABLE messages") - execute("ALTER TABLE messages_new RENAME TO 'messages'") - execute("CREATE UNIQUE INDEX unique_messages ON messages (source_type, source_id, message_id)") - } + execute("CREATE TABLE messages_new (" + + "id INTEGER PRIMARY KEY AUTOINCREMENT," + + "message_id INTEGER," + + "message_type TEXT," + + "source_type TEXT," + + "source_id INTEGER," + + "sender_id INTEGER," + + "fwd_from_id INTEGER," + + "text TEXT," + + "time INTEGER," + + "has_media BOOLEAN," + + "media_type TEXT," + + "media_file TEXT," + + "media_size INTEGER," + + "media_json TEXT," + + "markup_json TEXT," + + "data BLOB," + + "api_layer INTEGER)") + execute("INSERT INTO messages_new" + + "(message_id, message_type, source_type, source_id, sender_id, fwd_from_id, text, time, has_media, media_type," + + "media_file, media_size, media_json, markup_json, data, api_layer)" + + "SELECT " + + "id, message_type, source_type, source_id, sender_id, fwd_from_id, text, time, has_media, media_type," + + "media_file, media_size, media_json, markup_json, data, api_layer FROM messages") + execute("DROP TABLE messages") + execute("ALTER TABLE messages_new RENAME TO 'messages'") + execute("CREATE UNIQUE INDEX unique_messages ON messages (source_type, source_id, message_id)") + } } diff --git a/src/main/kotlin/de/fabianonline/telegram_backup/DownloadManager.kt b/src/main/kotlin/de/fabianonline/telegram_backup/DownloadManager.kt index 64856ae..014a942 100644 --- a/src/main/kotlin/de/fabianonline/telegram_backup/DownloadManager.kt +++ b/src/main/kotlin/de/fabianonline/telegram_backup/DownloadManager.kt @@ -53,102 +53,102 @@ import java.nio.file.StandardCopyOption import org.apache.commons.io.FileUtils class DownloadManager(internal var client: TelegramClient?, p: DownloadProgressInterface) { - internal var user: UserManager? = null - internal var db: Database? = null - internal var prog: DownloadProgressInterface? = null - internal var has_seen_flood_wait_message = false + internal var user: UserManager? = null + internal var db: Database? = null + internal var prog: DownloadProgressInterface? = null + internal var has_seen_flood_wait_message = false - init { - this.user = UserManager.getInstance() - this.prog = p - this.db = Database.getInstance() - } + init { + this.user = UserManager.getInstance() + this.prog = p + this.db = Database.getInstance() + } - @Throws(RpcErrorException::class, IOException::class) - fun downloadMessages(limit: Int?) { - var completed: Boolean - do { - completed = true - try { - _downloadMessages(limit) - } catch (e: RpcErrorException) { - if (e.getCode() == 420) { // FLOOD_WAIT - completed = false - Utils.obeyFloodWaitException(e) - } else { - throw e - } - } catch (e: TimeoutException) { - completed = false - System.out.println("") - System.out.println("Telegram took too long to respond to our request.") - System.out.println("I'm going to wait a minute and then try again.") - try { - TimeUnit.MINUTES.sleep(1) - } catch (e2: InterruptedException) { - } + @Throws(RpcErrorException::class, IOException::class) + fun downloadMessages(limit: Int?) { + var completed: Boolean + do { + completed = true + try { + _downloadMessages(limit) + } catch (e: RpcErrorException) { + if (e.getCode() == 420) { // FLOOD_WAIT + completed = false + Utils.obeyFloodWaitException(e) + } else { + throw e + } + } catch (e: TimeoutException) { + completed = false + System.out.println("") + System.out.println("Telegram took too long to respond to our request.") + System.out.println("I'm going to wait a minute and then try again.") + try { + TimeUnit.MINUTES.sleep(1) + } catch (e2: InterruptedException) { + } - System.out.println("") - } + System.out.println("") + } - } while (!completed) - } + } while (!completed) + } - @Throws(RpcErrorException::class, IOException::class, TimeoutException::class) - fun _downloadMessages(limit: Int?) { - logger.info("This is _downloadMessages with limit {}", limit) - val dialog_limit = 100 - logger.info("Downloading the last {} dialogs", dialog_limit) - System.out.println("Downloading most recent dialogs... ") - var max_message_id = 0 - val dialogs = client!!.messagesGetDialogs( - 0, - 0, - TLInputPeerEmpty(), - dialog_limit) - logger.debug("Got {} dialogs", dialogs.getDialogs().size) + @Throws(RpcErrorException::class, IOException::class, TimeoutException::class) + fun _downloadMessages(limit: Int?) { + logger.info("This is _downloadMessages with limit {}", limit) + val dialog_limit = 100 + logger.info("Downloading the last {} dialogs", dialog_limit) + System.out.println("Downloading most recent dialogs... ") + var max_message_id = 0 + val dialogs = client!!.messagesGetDialogs( + 0, + 0, + TLInputPeerEmpty(), + dialog_limit) + logger.debug("Got {} dialogs", dialogs.getDialogs().size) - for (d in dialogs.getDialogs()) { - if (d.getTopMessage() > max_message_id && d.getPeer() !is TLPeerChannel) { - logger.trace("Updating top message id: {} => {}. Dialog type: {}", max_message_id, d.getTopMessage(), d.getPeer().javaClass) - max_message_id = d.getTopMessage() - } - } - System.out.println("Top message ID is " + max_message_id) - var max_database_id = db!!.getTopMessageID() - System.out.println("Top message ID in database is " + max_database_id) - if (limit != null) { - System.out.println("Limit is set to " + limit) - max_database_id = Math.max(max_database_id, max_message_id - limit) - System.out.println("New top message id 'in database' is " + max_database_id) - } - if (max_message_id - max_database_id > 1000000) { - System.out.println("Would have to load more than 1 million messages which is not supported by telegram. Capping the list.") - logger.debug("max_message_id={}, max_database_id={}, difference={}", max_message_id, max_database_id, max_message_id - max_database_id) - max_database_id = Math.max(0, max_message_id - 1000000) - logger.debug("new max_database_id: {}", max_database_id) - } + for (d in dialogs.getDialogs()) { + if (d.getTopMessage() > max_message_id && d.getPeer() !is TLPeerChannel) { + logger.trace("Updating top message id: {} => {}. Dialog type: {}", max_message_id, d.getTopMessage(), d.getPeer().javaClass) + max_message_id = d.getTopMessage() + } + } + System.out.println("Top message ID is " + max_message_id) + var max_database_id = db!!.getTopMessageID() + System.out.println("Top message ID in database is " + max_database_id) + if (limit != null) { + System.out.println("Limit is set to " + limit) + max_database_id = Math.max(max_database_id, max_message_id - limit) + System.out.println("New top message id 'in database' is " + max_database_id) + } + if (max_message_id - max_database_id > 1000000) { + System.out.println("Would have to load more than 1 million messages which is not supported by telegram. Capping the list.") + logger.debug("max_message_id={}, max_database_id={}, difference={}", max_message_id, max_database_id, max_message_id - max_database_id) + max_database_id = Math.max(0, max_message_id - 1000000) + logger.debug("new max_database_id: {}", max_database_id) + } - if (max_database_id == max_message_id) { - System.out.println("No new messages to download.") - } else if (max_database_id > max_message_id) { - throw RuntimeException("max_database_id is bigger then max_message_id. This shouldn't happen. But the telegram api nonetheless does that sometimes. Just ignore this error, wait a few seconds and then try again.") - } else { - val start_id = max_database_id + 1 - val end_id = max_message_id + if (max_database_id == max_message_id) { + System.out.println("No new messages to download.") + } else if (max_database_id > max_message_id) { + throw RuntimeException("max_database_id is bigger then max_message_id. This shouldn't happen. But the telegram api nonetheless does that sometimes. Just ignore this error, wait a few seconds and then try again.") + } else { + val start_id = max_database_id + 1 + val end_id = max_message_id - val ids = makeIdList(start_id, end_id) - downloadMessages(ids, null, null) - } + val ids = makeIdList(start_id, end_id) + downloadMessages(ids, null, null) + } - logger.info("Searching for missing messages in the db") - System.out.println("Checking message database for completeness...") - val db_count = db!!.getMessageCount() - val db_max = db!!.getTopMessageID() - logger.debug("db_count: {}", db_count) - logger.debug("db_max: {}", db_max) + logger.info("Searching for missing messages in the db") + System.out.println("Checking message database for completeness...") + val db_count = db!!.getMessageCount() + val db_max = db!!.getTopMessageID() + logger.debug("db_count: {}", db_count) + logger.debug("db_max: {}", db_max) - /*if (db_count != db_max) { + /*if (db_count != db_max) { if (limit != null) { System.out.println("You are missing messages in your database. But since you're using '--limit-messages', I won't download these now."); } else { @@ -169,138 +169,138 @@ class DownloadManager(internal var client: TelegramClient?, p: DownloadProgressI } */ - if (CommandLineOptions.cmd_channels || CommandLineOptions.cmd_supergroups) { - System.out.println("Processing channels and/or supergroups...") - System.out.println("Please note that only channels/supergroups in the last 100 active chats are processed.") + if (CommandLineOptions.cmd_channels || CommandLineOptions.cmd_supergroups) { + System.out.println("Processing channels and/or supergroups...") + System.out.println("Please note that only channels/supergroups in the last 100 active chats are processed.") - val channel_access_hashes = HashMap() - val channel_names = HashMap() - val channels = LinkedList() - val supergroups = LinkedList() + val channel_access_hashes = HashMap() + val channel_names = HashMap() + val channels = LinkedList() + val supergroups = LinkedList() - // TODO Add chat title (and other stuff?) to the database - for (c in dialogs.getChats()) { - if (c is TLChannel) { - channel_access_hashes.put(c.getId(), c.getAccessHash()) - channel_names.put(c.getId(), c.getTitle()) - if (c.getMegagroup()) { - supergroups.add(c.getId()) - } else { - channels.add(c.getId()) - } - // Channel: TLChannel - // Supergroup: getMegagroup()==true - } - } + // TODO Add chat title (and other stuff?) to the database + for (c in dialogs.getChats()) { + if (c is TLChannel) { + channel_access_hashes.put(c.getId(), c.getAccessHash()) + channel_names.put(c.getId(), c.getTitle()) + if (c.getMegagroup()) { + supergroups.add(c.getId()) + } else { + channels.add(c.getId()) + } + // Channel: TLChannel + // Supergroup: getMegagroup()==true + } + } - for (d in dialogs.getDialogs()) { - if (d.getPeer() is TLPeerChannel) { - val channel_id = (d.getPeer() as TLPeerChannel).getChannelId() + for (d in dialogs.getDialogs()) { + if (d.getPeer() is TLPeerChannel) { + val channel_id = (d.getPeer() as TLPeerChannel).getChannelId() - // If this is a channel and we don't want to download channels OR - // it is a supergroups and we don't want to download supergroups, then - if (channels.contains(channel_id) && !CommandLineOptions.cmd_channels || supergroups.contains(channel_id) && !CommandLineOptions.cmd_supergroups) { - // Skip this chat. - continue - } - val max_known_id = db!!.getTopMessageIDForChannel(channel_id) - if (d.getTopMessage() > max_known_id) { - val ids = makeIdList(max_known_id + 1, d.getTopMessage()) - val access_hash = channel_access_hashes.get(channel_id) ?: throw RuntimeException("AccessHash for Channel missing.") - var channel_name = channel_names.get(channel_id) - if (channel_name == null) { - channel_name = "?" - } - val channel = TLInputChannel(channel_id, access_hash) - downloadMessages(ids, channel, "channel $channel_name") - } - } - } - } - } + // If this is a channel and we don't want to download channels OR + // it is a supergroups and we don't want to download supergroups, then + if (channels.contains(channel_id) && !CommandLineOptions.cmd_channels || supergroups.contains(channel_id) && !CommandLineOptions.cmd_supergroups) { + // Skip this chat. + continue + } + val max_known_id = db!!.getTopMessageIDForChannel(channel_id) + if (d.getTopMessage() > max_known_id) { + val ids = makeIdList(max_known_id + 1, d.getTopMessage()) + val access_hash = channel_access_hashes.get(channel_id) ?: throw RuntimeException("AccessHash for Channel missing.") + var channel_name = channel_names.get(channel_id) + if (channel_name == null) { + channel_name = "?" + } + val channel = TLInputChannel(channel_id, access_hash) + downloadMessages(ids, channel, "channel $channel_name") + } + } + } + } + } - @Throws(RpcErrorException::class, IOException::class) - private fun downloadMessages(ids: MutableList, channel: TLInputChannel?, source_string: String?) { - prog!!.onMessageDownloadStart(ids.size, source_string) + @Throws(RpcErrorException::class, IOException::class) + private fun downloadMessages(ids: MutableList, channel: TLInputChannel?, source_string: String?) { + prog!!.onMessageDownloadStart(ids.size, source_string) - logger.debug("Entering download loop") - while (ids.size > 0) { - logger.trace("Loop") - val vector = TLIntVector() - val download_count = Config.GET_MESSAGES_BATCH_SIZE - logger.trace("download_count: {}", download_count) - for (i in 0 until download_count) { - if (ids.size == 0) break - vector.add(ids.removeAt(0)) - } - logger.trace("vector.size(): {}", vector.size) - logger.trace("ids.size(): {}", ids.size) + logger.debug("Entering download loop") + while (ids.size > 0) { + logger.trace("Loop") + val vector = TLIntVector() + val download_count = Config.GET_MESSAGES_BATCH_SIZE + logger.trace("download_count: {}", download_count) + for (i in 0 until download_count) { + if (ids.size == 0) break + vector.add(ids.removeAt(0)) + } + logger.trace("vector.size(): {}", vector.size) + logger.trace("ids.size(): {}", ids.size) - var response: TLAbsMessages - var tries = 0 - while (true) { - logger.trace("Trying getMessages(), tries={}", tries) - if (tries >= 5) { - CommandLineController.show_error("Couldn't getMessages after 5 tries. Quitting.") - } - tries++ - try { - if (channel == null) { - response = client!!.messagesGetMessages(vector) - } else { - response = client!!.channelsGetMessages(channel, vector) - } - break - } catch (e: RpcErrorException) { - if (e.getCode() == 420) { // FLOOD_WAIT - Utils.obeyFloodWaitException(e, has_seen_flood_wait_message) - has_seen_flood_wait_message = true - } else { - throw e - } - } + var response: TLAbsMessages + var tries = 0 + while (true) { + logger.trace("Trying getMessages(), tries={}", tries) + if (tries >= 5) { + CommandLineController.show_error("Couldn't getMessages after 5 tries. Quitting.") + } + tries++ + try { + if (channel == null) { + response = client!!.messagesGetMessages(vector) + } else { + response = client!!.channelsGetMessages(channel, vector) + } + break + } catch (e: RpcErrorException) { + if (e.getCode() == 420) { // FLOOD_WAIT + Utils.obeyFloodWaitException(e, has_seen_flood_wait_message) + has_seen_flood_wait_message = true + } else { + throw e + } + } - } - logger.trace("response.getMessages().size(): {}", response.getMessages().size) - if (response.getMessages().size != vector.size) { - CommandLineController.show_error("Requested ${vector.size} messages, but got ${response.getMessages().size}. That is unexpected. Quitting.") - } + } + logger.trace("response.getMessages().size(): {}", response.getMessages().size) + if (response.getMessages().size != vector.size) { + CommandLineController.show_error("Requested ${vector.size} messages, but got ${response.getMessages().size}. That is unexpected. Quitting.") + } - prog!!.onMessageDownloaded(response.getMessages().size) - db!!.saveMessages(response.getMessages(), Kotlogram.API_LAYER) - db!!.saveChats(response.getChats()) - db!!.saveUsers(response.getUsers()) - logger.trace("Sleeping") - try { - TimeUnit.MILLISECONDS.sleep(Config.DELAY_AFTER_GET_MESSAGES) - } catch (e: InterruptedException) { - } + prog!!.onMessageDownloaded(response.getMessages().size) + db!!.saveMessages(response.getMessages(), Kotlogram.API_LAYER) + db!!.saveChats(response.getChats()) + db!!.saveUsers(response.getUsers()) + logger.trace("Sleeping") + try { + TimeUnit.MILLISECONDS.sleep(Config.DELAY_AFTER_GET_MESSAGES) + } catch (e: InterruptedException) { + } - } - logger.debug("Finished.") + } + logger.debug("Finished.") - prog!!.onMessageDownloadFinished() - } + prog!!.onMessageDownloadFinished() + } - @Throws(RpcErrorException::class, IOException::class) - fun downloadMedia() { - download_client = client!!.getDownloaderClient() - var completed: Boolean - do { - completed = true - try { - _downloadMedia() - } catch (e: RpcErrorException) { - if (e.getTag().startsWith("420: FLOOD_WAIT_")) { - completed = false - Utils.obeyFloodWaitException(e) - } else { - throw e - } - } - /*catch (TimeoutException e) { + @Throws(RpcErrorException::class, IOException::class) + fun downloadMedia() { + download_client = client!!.getDownloaderClient() + var completed: Boolean + do { + completed = true + try { + _downloadMedia() + } catch (e: RpcErrorException) { + if (e.getTag().startsWith("420: FLOOD_WAIT_")) { + completed = false + Utils.obeyFloodWaitException(e) + } else { + throw e + } + } + /*catch (TimeoutException e) { completed = false; System.out.println(""); System.out.println("Telegram took too long to respond to our request."); @@ -309,181 +309,181 @@ class DownloadManager(internal var client: TelegramClient?, p: DownloadProgressI try { TimeUnit.MINUTES.sleep(1); } catch(InterruptedException e2) {} System.out.println(""); }*/ - } while (!completed) - } + } while (!completed) + } - @Throws(RpcErrorException::class, IOException::class) - private fun _downloadMedia() { - logger.info("This is _downloadMedia") - logger.info("Checking if there are messages in the DB with a too old API layer") - val ids = db!!.getIdsFromQuery("SELECT id FROM messages WHERE has_media=1 AND api_layer<" + Kotlogram.API_LAYER) - if (ids.size > 0) { - System.out.println("You have ${ids.size} messages in your db that need an update. Doing that now.") - logger.debug("Found {} messages", ids.size) - downloadMessages(ids, null, null) - } + @Throws(RpcErrorException::class, IOException::class) + private fun _downloadMedia() { + logger.info("This is _downloadMedia") + logger.info("Checking if there are messages in the DB with a too old API layer") + val ids = db!!.getIdsFromQuery("SELECT id FROM messages WHERE has_media=1 AND api_layer<" + Kotlogram.API_LAYER) + if (ids.size > 0) { + System.out.println("You have ${ids.size} messages in your db that need an update. Doing that now.") + logger.debug("Found {} messages", ids.size) + downloadMessages(ids, null, null) + } - val messages = this.db!!.getMessagesWithMedia() - logger.debug("Database returned {} messages with media", messages.size) - prog!!.onMediaDownloadStart(messages.size) - for (msg in messages) { - if (msg==null) continue - val m = FileManagerFactory.getFileManager(msg, user!!, client!!) - logger.trace("message {}, {}, {}, {}, {}", - msg.getId(), - msg.getMedia().javaClass.getSimpleName().replace("TLMessageMedia", "…"), - m!!.javaClass.getSimpleName(), - if (m.isEmpty) "empty" else "non-empty", - if (m.downloaded) "downloaded" else "not downloaded") - if (m.isEmpty) { - prog!!.onMediaDownloadedEmpty() - } else if (m.downloaded) { - prog!!.onMediaAlreadyPresent(m) - } else { - try { - m.download() - prog!!.onMediaDownloaded(m) - } catch (e: TimeoutException) { - // do nothing - skip this file - prog!!.onMediaSkipped() - } + val messages = this.db!!.getMessagesWithMedia() + logger.debug("Database returned {} messages with media", messages.size) + prog!!.onMediaDownloadStart(messages.size) + for (msg in messages) { + if (msg == null) continue + val m = FileManagerFactory.getFileManager(msg, user!!, client!!) + logger.trace("message {}, {}, {}, {}, {}", + msg.getId(), + msg.getMedia().javaClass.getSimpleName().replace("TLMessageMedia", "…"), + m!!.javaClass.getSimpleName(), + if (m.isEmpty) "empty" else "non-empty", + if (m.downloaded) "downloaded" else "not downloaded") + if (m.isEmpty) { + prog!!.onMediaDownloadedEmpty() + } else if (m.downloaded) { + prog!!.onMediaAlreadyPresent(m) + } else { + try { + m.download() + prog!!.onMediaDownloaded(m) + } catch (e: TimeoutException) { + // do nothing - skip this file + prog!!.onMediaSkipped() + } - } - } - prog!!.onMediaDownloadFinished() - } + } + } + prog!!.onMediaDownloadFinished() + } - private fun makeIdList(start: Int, end: Int): MutableList { - val a = LinkedList() - for (i in start..end) a.add(i) - return a - } + private fun makeIdList(start: Int, end: Int): MutableList { + val a = LinkedList() + for (i in start..end) a.add(i) + return a + } - companion object { - internal var download_client: TelegramClient? = null - internal var last_download_succeeded = true - internal val logger = LoggerFactory.getLogger(DownloadManager::class.java) + companion object { + internal var download_client: TelegramClient? = null + internal var last_download_succeeded = true + internal val logger = LoggerFactory.getLogger(DownloadManager::class.java) - @Throws(RpcErrorException::class, IOException::class, TimeoutException::class) - fun downloadFile(targetFilename: String, size: Int, dcId: Int, volumeId: Long, localId: Int, secret: Long) { - val loc = TLInputFileLocation(volumeId, localId, secret) - downloadFileFromDc(targetFilename, loc, dcId, size) - } + @Throws(RpcErrorException::class, IOException::class, TimeoutException::class) + fun downloadFile(targetFilename: String, size: Int, dcId: Int, volumeId: Long, localId: Int, secret: Long) { + val loc = TLInputFileLocation(volumeId, localId, secret) + downloadFileFromDc(targetFilename, loc, dcId, size) + } - @Throws(RpcErrorException::class, IOException::class, TimeoutException::class) - fun downloadFile(targetFilename: String, size: Int, dcId: Int, id: Long, accessHash: Long) { - val loc = TLInputDocumentFileLocation(id, accessHash) - downloadFileFromDc(targetFilename, loc, dcId, size) - } + @Throws(RpcErrorException::class, IOException::class, TimeoutException::class) + fun downloadFile(targetFilename: String, size: Int, dcId: Int, id: Long, accessHash: Long) { + val loc = TLInputDocumentFileLocation(id, accessHash) + downloadFileFromDc(targetFilename, loc, dcId, size) + } - @Throws(RpcErrorException::class, IOException::class, TimeoutException::class) - private fun downloadFileFromDc(target: String, loc: TLAbsInputFileLocation, dcID: Int, size: Int): Boolean { - var fos: FileOutputStream? = null - try { - val temp_filename = target + ".downloading" - logger.debug("Downloading file {}", target) - logger.trace("Temporary filename: {}", temp_filename) + @Throws(RpcErrorException::class, IOException::class, TimeoutException::class) + private fun downloadFileFromDc(target: String, loc: TLAbsInputFileLocation, dcID: Int, size: Int): Boolean { + var fos: FileOutputStream? = null + try { + val temp_filename = target + ".downloading" + logger.debug("Downloading file {}", target) + logger.trace("Temporary filename: {}", temp_filename) - var offset = 0 - if (File(temp_filename).isFile()) { - logger.info("Temporary filename already exists; continuing this file") - offset = File(temp_filename).length().toInt() - if (offset >= size) { - logger.warn("Temporary file size is >= the target size. Assuming corrupt file & deleting it") - File(temp_filename).delete() - offset = 0 - } - } - logger.trace("offset before the loop is {}", offset) - fos = FileOutputStream(temp_filename, true) - var response: TLFile? = null - var try_again: Boolean - do { - try_again = false - logger.trace("offset: {} block_size: {} size: {}", offset, size, size) - val req = TLRequestUploadGetFile(loc, offset, size) - try { - response = download_client!!.executeRpcQuery(req, dcID) as TLFile - } catch (e: RpcErrorException) { - if (e.getTag().startsWith("420: FLOOD_WAIT_")) { - try_again = true - Utils.obeyFloodWaitException(e) - } else if (e.getCode() == 400) { - //Somehow this file is broken. No idea why. Let's skip it for now - return false - } else { - throw e - } - } + var offset = 0 + if (File(temp_filename).isFile()) { + logger.info("Temporary filename already exists; continuing this file") + offset = File(temp_filename).length().toInt() + if (offset >= size) { + logger.warn("Temporary file size is >= the target size. Assuming corrupt file & deleting it") + File(temp_filename).delete() + offset = 0 + } + } + logger.trace("offset before the loop is {}", offset) + fos = FileOutputStream(temp_filename, true) + var response: TLFile? = null + var try_again: Boolean + do { + try_again = false + logger.trace("offset: {} block_size: {} size: {}", offset, size, size) + val req = TLRequestUploadGetFile(loc, offset, size) + try { + response = download_client!!.executeRpcQuery(req, dcID) as TLFile + } catch (e: RpcErrorException) { + if (e.getTag().startsWith("420: FLOOD_WAIT_")) { + try_again = true + Utils.obeyFloodWaitException(e) + } else if (e.getCode() == 400) { + //Somehow this file is broken. No idea why. Let's skip it for now + return false + } else { + throw e + } + } - offset += response!!.getBytes().getData().size - logger.trace("response: {} total size: {}", response.getBytes().getData().size, offset) + offset += response!!.getBytes().getData().size + logger.trace("response: {} total size: {}", response.getBytes().getData().size, offset) - fos.write(response.getBytes().getData()) - fos.flush() - try { - TimeUnit.MILLISECONDS.sleep(Config.DELAY_AFTER_GET_FILE) - } catch (e: InterruptedException) { - } + fos.write(response.getBytes().getData()) + fos.flush() + try { + TimeUnit.MILLISECONDS.sleep(Config.DELAY_AFTER_GET_FILE) + } catch (e: InterruptedException) { + } - } while (offset < size && (response!!.getBytes().getData().size > 0 || try_again)) - fos.close() - if (offset < size) { - System.out.println("Requested file $target with $size bytes, but got only $offset bytes.") - File(temp_filename).delete() - System.exit(1) - } - logger.trace("Renaming {} to {}", temp_filename, target) - var rename_tries = 0 - var last_exception: IOException? = null - while (rename_tries <= Config.RENAMING_MAX_TRIES) { - rename_tries++ - try { - Files.move(File(temp_filename).toPath(), File(target).toPath(), StandardCopyOption.REPLACE_EXISTING) - last_exception = null - break - } catch (e: IOException) { - logger.debug("Exception during move. rename_tries: {}. Exception: {}", rename_tries, e) - last_exception = e - try { - TimeUnit.MILLISECONDS.sleep(Config.RENAMING_DELAY) - } catch (e2: InterruptedException) { - } + } while (offset < size && (response!!.getBytes().getData().size > 0 || try_again)) + fos.close() + if (offset < size) { + System.out.println("Requested file $target with $size bytes, but got only $offset bytes.") + File(temp_filename).delete() + System.exit(1) + } + logger.trace("Renaming {} to {}", temp_filename, target) + var rename_tries = 0 + var last_exception: IOException? = null + while (rename_tries <= Config.RENAMING_MAX_TRIES) { + rename_tries++ + try { + Files.move(File(temp_filename).toPath(), File(target).toPath(), StandardCopyOption.REPLACE_EXISTING) + last_exception = null + break + } catch (e: IOException) { + logger.debug("Exception during move. rename_tries: {}. Exception: {}", rename_tries, e) + last_exception = e + try { + TimeUnit.MILLISECONDS.sleep(Config.RENAMING_DELAY) + } catch (e2: InterruptedException) { + } - } + } - } - if (last_exception != null) { - throw last_exception - } - last_download_succeeded = true - return true - } catch (ex: java.io.IOException) { - if (fos != null) fos.close() - System.out.println("IOException happened while downloading " + target) - throw ex - } catch (ex: RpcErrorException) { - if (fos != null) fos.close() - if (ex.getCode() == 500) { - if (!last_download_succeeded) { - System.out.println("Got an Internal Server Error from Telegram. Since the file downloaded before also happened to get this error, we will stop downloading now. Please try again later.") - throw ex - } - last_download_succeeded = false - System.out.println("Got an Internal Server Error from Telegram. Skipping this file for now. Next run of telegram_backup will continue to download this file.") - logger.warn(ex.toString()) - return false - } - System.out.println("RpcErrorException happened while downloading " + target) - throw ex - } + } + if (last_exception != null) { + throw last_exception + } + last_download_succeeded = true + return true + } catch (ex: java.io.IOException) { + if (fos != null) fos.close() + System.out.println("IOException happened while downloading " + target) + throw ex + } catch (ex: RpcErrorException) { + if (fos != null) fos.close() + if (ex.getCode() == 500) { + if (!last_download_succeeded) { + System.out.println("Got an Internal Server Error from Telegram. Since the file downloaded before also happened to get this error, we will stop downloading now. Please try again later.") + throw ex + } + last_download_succeeded = false + System.out.println("Got an Internal Server Error from Telegram. Skipping this file for now. Next run of telegram_backup will continue to download this file.") + logger.warn(ex.toString()) + return false + } + System.out.println("RpcErrorException happened while downloading " + target) + throw ex + } - } + } - @Throws(IOException::class) - fun downloadExternalFile(target: String, url: String): Boolean { - FileUtils.copyURLToFile(URL(url), File(target), 5000, 5000) - return true - } - } + @Throws(IOException::class) + fun downloadExternalFile(target: String, url: String): Boolean { + FileUtils.copyURLToFile(URL(url), File(target), 5000, 5000) + return true + } + } } diff --git a/src/main/kotlin/de/fabianonline/telegram_backup/DownloadProgressInterface.kt b/src/main/kotlin/de/fabianonline/telegram_backup/DownloadProgressInterface.kt index be12e5e..ecd7895 100644 --- a/src/main/kotlin/de/fabianonline/telegram_backup/DownloadProgressInterface.kt +++ b/src/main/kotlin/de/fabianonline/telegram_backup/DownloadProgressInterface.kt @@ -19,14 +19,14 @@ package de.fabianonline.telegram_backup import de.fabianonline.telegram_backup.mediafilemanager.AbstractMediaFileManager interface DownloadProgressInterface { - fun onMessageDownloadStart(count: Int, source: String?) - fun onMessageDownloaded(number: Int) - fun onMessageDownloadFinished() + fun onMessageDownloadStart(count: Int, source: String?) + fun onMessageDownloaded(number: Int) + fun onMessageDownloadFinished() - fun onMediaDownloadStart(count: Int) - fun onMediaDownloaded(file_manager: AbstractMediaFileManager) - fun onMediaDownloadedEmpty() - fun onMediaSkipped() - fun onMediaAlreadyPresent(file_manager: AbstractMediaFileManager) - fun onMediaDownloadFinished() + fun onMediaDownloadStart(count: Int) + fun onMediaDownloaded(file_manager: AbstractMediaFileManager) + fun onMediaDownloadedEmpty() + fun onMediaSkipped() + fun onMediaAlreadyPresent(file_manager: AbstractMediaFileManager) + fun onMediaDownloadFinished() } diff --git a/src/main/kotlin/de/fabianonline/telegram_backup/GUIController.kt b/src/main/kotlin/de/fabianonline/telegram_backup/GUIController.kt index 7151266..b88acf6 100644 --- a/src/main/kotlin/de/fabianonline/telegram_backup/GUIController.kt +++ b/src/main/kotlin/de/fabianonline/telegram_backup/GUIController.kt @@ -25,61 +25,61 @@ import java.awt.event.ActionListener import java.util.Vector class GUIController() { - init { - showAccountChooserDialog() - } - - private fun showAccountChooserDialog() { - val accountChooser = JDialog() - accountChooser.setTitle("Choose account") - accountChooser.setSize(400, 200) - val vert = JPanel() - vert.setLayout(BorderLayout()) - vert.add(JLabel("Please select the account to use or create a new one."), BorderLayout.NORTH) - val accounts = Utils.getAccounts() - val list = JList(accounts) - list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION) - vert.add(list, BorderLayout.CENTER) - val bottom = JPanel(GridLayout(1, 2)) - val btnAddAccount = JButton("Add account") - bottom.add(btnAddAccount) - val btnLogin = JButton("Login") - btnLogin.setEnabled(false) - bottom.add(btnLogin) - vert.add(bottom, BorderLayout.SOUTH) - accountChooser.add(vert) - accountChooser.setVisible(true) - accountChooser.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE) + init { + showAccountChooserDialog() } - - private fun addAccountDialog() { - val loginDialog = JDialog() - loginDialog.setTitle("Add an account") - loginDialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE) - val sections = JPanel() - sections.setLayout(BoxLayout(sections, BoxLayout.Y_AXIS)) + private fun showAccountChooserDialog() { + val accountChooser = JDialog() + accountChooser.setTitle("Choose account") + accountChooser.setSize(400, 200) + val vert = JPanel() + vert.setLayout(BorderLayout()) + vert.add(JLabel("Please select the account to use or create a new one."), BorderLayout.NORTH) + val accounts = Utils.getAccounts() + val list = JList(accounts) + list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION) + vert.add(list, BorderLayout.CENTER) + val bottom = JPanel(GridLayout(1, 2)) + val btnAddAccount = JButton("Add account") + bottom.add(btnAddAccount) + val btnLogin = JButton("Login") + btnLogin.setEnabled(false) + bottom.add(btnLogin) + vert.add(bottom, BorderLayout.SOUTH) + accountChooser.add(vert) + accountChooser.setVisible(true) + accountChooser.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE) + } - val top = JPanel() - top.setLayout(BoxLayout(top, BoxLayout.Y_AXIS)) - top.add(JLabel("Please enter your phone number in international format:")) - top.add(JTextField("+49123773212")) + private fun addAccountDialog() { + val loginDialog = JDialog() + loginDialog.setTitle("Add an account") + loginDialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE) - sections.add(top) - sections.add(Box.createVerticalStrut(5)) - sections.add(JSeparator(SwingConstants.HORIZONTAL)) + val sections = JPanel() + sections.setLayout(BoxLayout(sections, BoxLayout.Y_AXIS)) - val middle = JPanel() - middle.setLayout(BoxLayout(middle, BoxLayout.Y_AXIS)) - middle.add(JLabel("Telegram sent you a code. Enter it here:")) - middle.add(JTextField()) - middle.setEnabled(false) + val top = JPanel() + top.setLayout(BoxLayout(top, BoxLayout.Y_AXIS)) + top.add(JLabel("Please enter your phone number in international format:")) + top.add(JTextField("+49123773212")) - sections.add(middle) - sections.add(Box.createVerticalStrut(5)) - sections.add(JSeparator(SwingConstants.HORIZONTAL)) + sections.add(top) + sections.add(Box.createVerticalStrut(5)) + sections.add(JSeparator(SwingConstants.HORIZONTAL)) - loginDialog.add(sections) - loginDialog.setVisible(true) - } + val middle = JPanel() + middle.setLayout(BoxLayout(middle, BoxLayout.Y_AXIS)) + middle.add(JLabel("Telegram sent you a code. Enter it here:")) + middle.add(JTextField()) + middle.setEnabled(false) + + sections.add(middle) + sections.add(Box.createVerticalStrut(5)) + sections.add(JSeparator(SwingConstants.HORIZONTAL)) + + loginDialog.add(sections) + loginDialog.setVisible(true) + } } diff --git a/src/main/kotlin/de/fabianonline/telegram_backup/StickerConverter.kt b/src/main/kotlin/de/fabianonline/telegram_backup/StickerConverter.kt index bd9d7cb..ad77560 100644 --- a/src/main/kotlin/de/fabianonline/telegram_backup/StickerConverter.kt +++ b/src/main/kotlin/de/fabianonline/telegram_backup/StickerConverter.kt @@ -21,32 +21,32 @@ import java.lang.StringBuilder import java.io.File object StickerConverter { - fun makeFilenameWithPath(attr: TLDocumentAttributeSticker): String { - val file = StringBuilder() - file.append(makePath()) - file.append(makeFilename(attr)) - return file.toString() - } + fun makeFilenameWithPath(attr: TLDocumentAttributeSticker): String { + val file = StringBuilder() + file.append(makePath()) + file.append(makeFilename(attr)) + return file.toString() + } - fun makeFilename(attr: TLDocumentAttributeSticker): String { - val file = StringBuilder() - if (attr.getStickerset() is TLInputStickerSetShortName) { - file.append((attr.getStickerset() as TLInputStickerSetShortName).getShortName()) - } else if (attr.getStickerset() is TLInputStickerSetID) { - file.append((attr.getStickerset() as TLInputStickerSetID).getId()) - } - file.append("_") - file.append(attr.getAlt().hashCode()) - file.append(".webp") - return file.toString() - } + fun makeFilename(attr: TLDocumentAttributeSticker): String { + val file = StringBuilder() + if (attr.getStickerset() is TLInputStickerSetShortName) { + file.append((attr.getStickerset() as TLInputStickerSetShortName).getShortName()) + } else if (attr.getStickerset() is TLInputStickerSetID) { + file.append((attr.getStickerset() as TLInputStickerSetID).getId()) + } + file.append("_") + file.append(attr.getAlt().hashCode()) + file.append(".webp") + return file.toString() + } - fun makePath(): String { - val path = Config.FILE_BASE + - File.separatorChar + - Config.FILE_STICKER_BASE + - File.separatorChar - File(path).mkdirs() - return path - } + fun makePath(): String { + val path = Config.FILE_BASE + + File.separatorChar + + Config.FILE_STICKER_BASE + + File.separatorChar + File(path).mkdirs() + return path + } } diff --git a/src/main/kotlin/de/fabianonline/telegram_backup/TLRequestAccountGetPasswordWithCurrentSalt.kt b/src/main/kotlin/de/fabianonline/telegram_backup/TLRequestAccountGetPasswordWithCurrentSalt.kt index cd6f782..48664cb 100644 --- a/src/main/kotlin/de/fabianonline/telegram_backup/TLRequestAccountGetPasswordWithCurrentSalt.kt +++ b/src/main/kotlin/de/fabianonline/telegram_backup/TLRequestAccountGetPasswordWithCurrentSalt.kt @@ -27,20 +27,20 @@ import java.io.InputStream import com.github.badoualy.telegram.tl.StreamUtils.readTLObject class TLRequestAccountGetPasswordWithCurrentSalt : TLMethod() { - private val _constructor = "account.getPassword#548a30f5" - override fun getConstructorId(): Int = CONSTRUCTOR_ID + private val _constructor = "account.getPassword#548a30f5" + override fun getConstructorId(): Int = CONSTRUCTOR_ID - @Throws(IOException::class) - override fun deserializeResponse(stream: InputStream, context: TLContext): TLPassword { - val response = (readTLObject(stream, context) ?: throw IOException("Unable to parse response")) as? TLPassword ?: throw IOException("Incorrect response type, expected getClass().getCanonicalName(), found response.getClass().getCanonicalName()") - return response - } + @Throws(IOException::class) + override fun deserializeResponse(stream: InputStream, context: TLContext): TLPassword { + val response = (readTLObject(stream, context) ?: throw IOException("Unable to parse response")) as? TLPassword ?: throw IOException("Incorrect response type, expected getClass().getCanonicalName(), found response.getClass().getCanonicalName()") + return response + } - override fun toString(): String { - return _constructor - } + override fun toString(): String { + return _constructor + } - companion object { - val CONSTRUCTOR_ID = 0x548a30f5 - } + companion object { + val CONSTRUCTOR_ID = 0x548a30f5 + } } diff --git a/src/main/kotlin/de/fabianonline/telegram_backup/TelegramUpdateHandler.kt b/src/main/kotlin/de/fabianonline/telegram_backup/TelegramUpdateHandler.kt index 6c60e59..ef42d13 100644 --- a/src/main/kotlin/de/fabianonline/telegram_backup/TelegramUpdateHandler.kt +++ b/src/main/kotlin/de/fabianonline/telegram_backup/TelegramUpdateHandler.kt @@ -28,133 +28,133 @@ import de.fabianonline.telegram_backup.mediafilemanager.AbstractMediaFileManager import de.fabianonline.telegram_backup.mediafilemanager.FileManagerFactory internal class TelegramUpdateHandler : UpdateCallback { - private var user: UserManager? = null - private var db: Database? = null - var debug = false + private var user: UserManager? = null + private var db: Database? = null + var debug = false - fun activate() { - this.user = UserManager.getInstance() - this.db = Database.getInstance() - } + fun activate() { + this.user = UserManager.getInstance() + this.db = Database.getInstance() + } - override fun onUpdates(client: TelegramClient, updates: TLUpdates) { - if (db == null) return - if (debug) System.out.println("onUpdates - " + updates.getUpdates().size + " Updates, " + updates.getUsers().size + " Users, " + updates.getChats().size + " Chats") - for (update in updates.getUpdates()) { - processUpdate(update, client) - if (debug) System.out.println(" " + update.javaClass.getName()) - } - db!!.saveUsers(updates.getUsers()) - db!!.saveChats(updates.getChats()) - } + override fun onUpdates(client: TelegramClient, updates: TLUpdates) { + if (db == null) return + if (debug) System.out.println("onUpdates - " + updates.getUpdates().size + " Updates, " + updates.getUsers().size + " Users, " + updates.getChats().size + " Chats") + for (update in updates.getUpdates()) { + processUpdate(update, client) + if (debug) System.out.println(" " + update.javaClass.getName()) + } + db!!.saveUsers(updates.getUsers()) + db!!.saveChats(updates.getChats()) + } - override fun onUpdatesCombined(client: TelegramClient, updates: TLUpdatesCombined) { - if (db == null) return - if (debug) System.out.println("onUpdatesCombined") - for (update in updates.getUpdates()) { - processUpdate(update, client) - } - db!!.saveUsers(updates.getUsers()) - db!!.saveChats(updates.getChats()) - } + override fun onUpdatesCombined(client: TelegramClient, updates: TLUpdatesCombined) { + if (db == null) return + if (debug) System.out.println("onUpdatesCombined") + for (update in updates.getUpdates()) { + processUpdate(update, client) + } + db!!.saveUsers(updates.getUsers()) + db!!.saveChats(updates.getChats()) + } - override fun onUpdateShort(client: TelegramClient, update: TLUpdateShort) { - if (db == null) return - if (debug) System.out.println("onUpdateShort") - processUpdate(update.getUpdate(), client) - if (debug) System.out.println(" " + update.getUpdate().javaClass.getName()) - } + override fun onUpdateShort(client: TelegramClient, update: TLUpdateShort) { + if (db == null) return + if (debug) System.out.println("onUpdateShort") + processUpdate(update.getUpdate(), client) + if (debug) System.out.println(" " + update.getUpdate().javaClass.getName()) + } - override fun onShortChatMessage(client: TelegramClient, message: TLUpdateShortChatMessage) { - if (db == null) return - if (debug) System.out.println("onShortChatMessage - " + message.getMessage()) - val msg = TLMessage( - message.getOut(), - message.getMentioned(), - message.getMediaUnread(), - message.getSilent(), - false, - message.getId(), - message.getFromId(), - TLPeerChat(message.getChatId()), - message.getFwdFrom(), - message.getViaBotId(), - message.getReplyToMsgId(), - message.getDate(), - message.getMessage(), null, null, - message.getEntities(), null, null) - val vector = TLVector(TLAbsMessage::class.java) - vector.add(msg) - db!!.saveMessages(vector, Kotlogram.API_LAYER) - System.out.print('.') - } + override fun onShortChatMessage(client: TelegramClient, message: TLUpdateShortChatMessage) { + if (db == null) return + if (debug) System.out.println("onShortChatMessage - " + message.getMessage()) + val msg = TLMessage( + message.getOut(), + message.getMentioned(), + message.getMediaUnread(), + message.getSilent(), + false, + message.getId(), + message.getFromId(), + TLPeerChat(message.getChatId()), + message.getFwdFrom(), + message.getViaBotId(), + message.getReplyToMsgId(), + message.getDate(), + message.getMessage(), null, null, + message.getEntities(), null, null) + val vector = TLVector(TLAbsMessage::class.java) + vector.add(msg) + db!!.saveMessages(vector, Kotlogram.API_LAYER) + System.out.print('.') + } - override fun onShortMessage(client: TelegramClient, message: TLUpdateShortMessage) { - val m = message - if (db == null) return - if (debug) System.out.println("onShortMessage - " + m.getOut() + " - " + m.getUserId() + " - " + m.getMessage()) - val from_id: Int - val to_id: Int - if (m.getOut() == true) { - from_id = user!!.user!!.getId() - to_id = m.getUserId() - } else { - to_id = user!!.user!!.getId() - from_id = m.getUserId() - } - val msg = TLMessage( - m.getOut(), - m.getMentioned(), - m.getMediaUnread(), - m.getSilent(), - false, - m.getId(), - from_id, - TLPeerUser(to_id), - m.getFwdFrom(), - m.getViaBotId(), - m.getReplyToMsgId(), - m.getDate(), - m.getMessage(), null, null, - m.getEntities(), null, null) - val vector = TLVector(TLAbsMessage::class.java) - vector.add(msg) - db!!.saveMessages(vector, Kotlogram.API_LAYER) - System.out.print('.') - } + override fun onShortMessage(client: TelegramClient, message: TLUpdateShortMessage) { + val m = message + if (db == null) return + if (debug) System.out.println("onShortMessage - " + m.getOut() + " - " + m.getUserId() + " - " + m.getMessage()) + val from_id: Int + val to_id: Int + if (m.getOut() == true) { + from_id = user!!.user!!.getId() + to_id = m.getUserId() + } else { + to_id = user!!.user!!.getId() + from_id = m.getUserId() + } + val msg = TLMessage( + m.getOut(), + m.getMentioned(), + m.getMediaUnread(), + m.getSilent(), + false, + m.getId(), + from_id, + TLPeerUser(to_id), + m.getFwdFrom(), + m.getViaBotId(), + m.getReplyToMsgId(), + m.getDate(), + m.getMessage(), null, null, + m.getEntities(), null, null) + val vector = TLVector(TLAbsMessage::class.java) + vector.add(msg) + db!!.saveMessages(vector, Kotlogram.API_LAYER) + System.out.print('.') + } - override fun onShortSentMessage(client: TelegramClient, message: TLUpdateShortSentMessage) { - if (db == null) return - System.out.println("onShortSentMessage") - } + override fun onShortSentMessage(client: TelegramClient, message: TLUpdateShortSentMessage) { + if (db == null) return + System.out.println("onShortSentMessage") + } - override fun onUpdateTooLong(client: TelegramClient) { - if (db == null) return - System.out.println("onUpdateTooLong") - } + override fun onUpdateTooLong(client: TelegramClient) { + if (db == null) return + System.out.println("onUpdateTooLong") + } - private fun processUpdate(update: TLAbsUpdate, client: TelegramClient) { - if (update is TLUpdateNewMessage) { - val abs_msg = update.getMessage() - val vector = TLVector(TLAbsMessage::class.java) - vector.add(abs_msg) - db!!.saveMessages(vector, Kotlogram.API_LAYER) - System.out.print('.') - if (abs_msg is TLMessage) { - val fm = FileManagerFactory.getFileManager(abs_msg, user!!, client) - if (fm != null && !fm.isEmpty && !fm.downloaded) { - try { - fm.download() - } catch (e: Exception) { - System.out.println("We got an exception while downloading media, but we're going to ignore it.") - System.out.println("Here it is anyway:") - e.printStackTrace() - } + private fun processUpdate(update: TLAbsUpdate, client: TelegramClient) { + if (update is TLUpdateNewMessage) { + val abs_msg = update.getMessage() + val vector = TLVector(TLAbsMessage::class.java) + vector.add(abs_msg) + db!!.saveMessages(vector, Kotlogram.API_LAYER) + System.out.print('.') + if (abs_msg is TLMessage) { + val fm = FileManagerFactory.getFileManager(abs_msg, user!!, client) + if (fm != null && !fm.isEmpty && !fm.downloaded) { + try { + fm.download() + } catch (e: Exception) { + System.out.println("We got an exception while downloading media, but we're going to ignore it.") + System.out.println("Here it is anyway:") + e.printStackTrace() + } - } - } - } else { - // ignore everything else... - } - } + } + } + } else { + // ignore everything else... + } + } } diff --git a/src/main/kotlin/de/fabianonline/telegram_backup/TestFeatures.kt b/src/main/kotlin/de/fabianonline/telegram_backup/TestFeatures.kt index a617aa7..cc79f64 100644 --- a/src/main/kotlin/de/fabianonline/telegram_backup/TestFeatures.kt +++ b/src/main/kotlin/de/fabianonline/telegram_backup/TestFeatures.kt @@ -11,55 +11,55 @@ import java.io.IOException import java.nio.charset.Charset internal object TestFeatures { - fun test1() { - // Tests entries in a cache4.db in the current working directory for compatibility - try { - Class.forName("org.sqlite.JDBC") - } catch (e: ClassNotFoundException) { - CommandLineController.show_error("Could not load jdbc-sqlite class.") - } + fun test1() { + // Tests entries in a cache4.db in the current working directory for compatibility + try { + Class.forName("org.sqlite.JDBC") + } catch (e: ClassNotFoundException) { + CommandLineController.show_error("Could not load jdbc-sqlite class.") + } - val path = "jdbc:sqlite:cache4.db" + val path = "jdbc:sqlite:cache4.db" - var conn: Connection - var stmt: Statement? = null + var conn: Connection + var stmt: Statement? = null - try { - conn = DriverManager.getConnection(path) - stmt = conn.createStatement() - } catch (e: SQLException) { - CommandLineController.show_error("Could not connect to SQLITE database.") - } + try { + conn = DriverManager.getConnection(path) + stmt = conn.createStatement() + } catch (e: SQLException) { + CommandLineController.show_error("Could not connect to SQLITE database.") + } - var unsupported_constructor = 0 - var success = 0 + var unsupported_constructor = 0 + var success = 0 - try { - val rs = stmt!!.executeQuery("SELECT data FROM messages") - while (rs.next()) { - try { - TLApiContext.getInstance().deserializeMessage(rs.getBytes(1)) - } catch (e: com.github.badoualy.telegram.tl.exception.UnsupportedConstructorException) { - unsupported_constructor++ - } catch (e: IOException) { - System.out.println("IOException: " + e) - } + try { + val rs = stmt!!.executeQuery("SELECT data FROM messages") + while (rs.next()) { + try { + TLApiContext.getInstance().deserializeMessage(rs.getBytes(1)) + } catch (e: com.github.badoualy.telegram.tl.exception.UnsupportedConstructorException) { + unsupported_constructor++ + } catch (e: IOException) { + System.out.println("IOException: " + e) + } - success++ - } - } catch (e: SQLException) { - System.out.println("SQL exception: " + e) - } + success++ + } + } catch (e: SQLException) { + System.out.println("SQL exception: " + e) + } - System.out.println("Success: " + success) - System.out.println("Unsupported constructor: " + unsupported_constructor) - } + System.out.println("Success: " + success) + System.out.println("Unsupported constructor: " + unsupported_constructor) + } - fun test2() { - // Prints system.encoding and default charset - System.out.println("Default Charset: " + Charset.defaultCharset()) - System.out.println("file.encoding: " + System.getProperty("file.encoding")) - val db = Database.getInstance() - System.out.println("Database encoding: " + db.getEncoding()) - } + fun test2() { + // Prints system.encoding and default charset + System.out.println("Default Charset: " + Charset.defaultCharset()) + System.out.println("file.encoding: " + System.getProperty("file.encoding")) + val db = Database.getInstance() + System.out.println("Database encoding: " + db.getEncoding()) + } } diff --git a/src/main/kotlin/de/fabianonline/telegram_backup/UserManager.kt b/src/main/kotlin/de/fabianonline/telegram_backup/UserManager.kt index ea4a34c..a89b5fd 100644 --- a/src/main/kotlin/de/fabianonline/telegram_backup/UserManager.kt +++ b/src/main/kotlin/de/fabianonline/telegram_backup/UserManager.kt @@ -36,105 +36,105 @@ import org.slf4j.Logger class UserManager @Throws(IOException::class) private constructor(c: TelegramClient) { - var user: TLUser? = null - var phone: String? = null - private var code: String? = null - private val client: TelegramClient - private var sent_code: TLSentCode? = null - private var auth: TLAuthorization? = null - var isPasswordNeeded = false - private set + var user: TLUser? = null + var phone: String? = null + private var code: String? = null + private val client: TelegramClient + private var sent_code: TLSentCode? = null + private var auth: TLAuthorization? = null + var isPasswordNeeded = false + private set - val loggedIn: Boolean - get() = user != null + val loggedIn: Boolean + get() = user != null - val userString: String - get() { - if (this.user == null) return "Not logged in" - val sb = StringBuilder() - if (this.user!!.getFirstName() != null) { - sb.append(this.user!!.getFirstName()) - } - if (this.user!!.getLastName() != null) { - sb.append(" ") - sb.append(this.user!!.getLastName()) - } - if (this.user!!.getUsername() != null) { - sb.append(" (@") - sb.append(this.user!!.getUsername()) - sb.append(")") - } - return sb.toString() - } + val userString: String + get() { + if (this.user == null) return "Not logged in" + val sb = StringBuilder() + if (this.user!!.getFirstName() != null) { + sb.append(this.user!!.getFirstName()) + } + if (this.user!!.getLastName() != null) { + sb.append(" ") + sb.append(this.user!!.getLastName()) + } + if (this.user!!.getUsername() != null) { + sb.append(" (@") + sb.append(this.user!!.getUsername()) + sb.append(")") + } + return sb.toString() + } - val fileBase: String - get() = Config.FILE_BASE + File.separatorChar + "+" + this.user!!.getPhone() + File.separatorChar + val fileBase: String + get() = Config.FILE_BASE + File.separatorChar + "+" + this.user!!.getPhone() + File.separatorChar - init { - this.client = c - logger.debug("Calling getFullUser") - try { - val full_user = this.client.usersGetFullUser(TLInputUserSelf()) - this.user = full_user.getUser().getAsUser() - } catch (e: RpcErrorException) { - // This may happen. Ignoring it. - logger.debug("Ignoring exception:", e) - } + init { + this.client = c + logger.debug("Calling getFullUser") + try { + val full_user = this.client.usersGetFullUser(TLInputUserSelf()) + this.user = full_user.getUser().getAsUser() + } catch (e: RpcErrorException) { + // This may happen. Ignoring it. + logger.debug("Ignoring exception:", e) + } - } + } - @Throws(RpcErrorException::class, IOException::class) - fun sendCodeToPhoneNumber(number: String) { - this.phone = number - this.sent_code = this.client.authSendCode(false, number, true) - } + @Throws(RpcErrorException::class, IOException::class) + fun sendCodeToPhoneNumber(number: String) { + this.phone = number + this.sent_code = this.client.authSendCode(false, number, true) + } - @Throws(RpcErrorException::class, IOException::class) - fun verifyCode(code: String) { - this.code = code - try { - this.auth = client.authSignIn(phone, this.sent_code!!.getPhoneCodeHash(), this.code) - this.user = auth!!.getUser().getAsUser() - } catch (e: RpcErrorException) { - if (e.getCode() != 401 || !e.getTag().equals("SESSION_PASSWORD_NEEDED")) throw e - this.isPasswordNeeded = true - } + @Throws(RpcErrorException::class, IOException::class) + fun verifyCode(code: String) { + this.code = code + try { + this.auth = client.authSignIn(phone, this.sent_code!!.getPhoneCodeHash(), this.code) + this.user = auth!!.getUser().getAsUser() + } catch (e: RpcErrorException) { + if (e.getCode() != 401 || !e.getTag().equals("SESSION_PASSWORD_NEEDED")) throw e + this.isPasswordNeeded = true + } - } + } - @Throws(RpcErrorException::class, IOException::class) - fun verifyPassword(pw: String) { - val password = pw.toByteArray(charset=Charsets.UTF_8) - val salt = (client.accountGetPassword() as TLPassword).getCurrentSalt().getData() - var md: MessageDigest - try { - md = MessageDigest.getInstance("SHA-256") - } catch (e: NoSuchAlgorithmException) { - e.printStackTrace() - return - } + @Throws(RpcErrorException::class, IOException::class) + fun verifyPassword(pw: String) { + val password = pw.toByteArray(charset = Charsets.UTF_8) + val salt = (client.accountGetPassword() as TLPassword).getCurrentSalt().getData() + var md: MessageDigest + try { + md = MessageDigest.getInstance("SHA-256") + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + return + } - val salted = ByteArray(2 * salt.size + password.size) - System.arraycopy(salt, 0, salted, 0, salt.size) - System.arraycopy(password, 0, salted, salt.size, password.size) - System.arraycopy(salt, 0, salted, salt.size + password.size, salt.size) - val hash = md.digest(salted) - auth = client.authCheckPassword(TLBytes(hash)) - this.user = auth!!.getUser().getAsUser() - } + val salted = ByteArray(2 * salt.size + password.size) + System.arraycopy(salt, 0, salted, 0, salt.size) + System.arraycopy(password, 0, salted, salt.size, password.size) + System.arraycopy(salt, 0, salted, salt.size + password.size, salt.size) + val hash = md.digest(salted) + auth = client.authCheckPassword(TLBytes(hash)) + this.user = auth!!.getUser().getAsUser() + } - companion object { - private val logger = LoggerFactory.getLogger(UserManager::class.java) - internal var instance: UserManager? = null + companion object { + private val logger = LoggerFactory.getLogger(UserManager::class.java) + internal var instance: UserManager? = null - @Throws(IOException::class) - fun init(c: TelegramClient) { - instance = UserManager(c) - } + @Throws(IOException::class) + fun init(c: TelegramClient) { + instance = UserManager(c) + } - fun getInstance(): UserManager { - if (instance == null) throw RuntimeException("UserManager is not yet initialized.") - return instance!! - } - } + fun getInstance(): UserManager { + if (instance == null) throw RuntimeException("UserManager is not yet initialized.") + return instance!! + } + } } diff --git a/src/main/kotlin/de/fabianonline/telegram_backup/Utils.kt b/src/main/kotlin/de/fabianonline/telegram_backup/Utils.kt index c9d9cd1..d4334e7 100644 --- a/src/main/kotlin/de/fabianonline/telegram_backup/Utils.kt +++ b/src/main/kotlin/de/fabianonline/telegram_backup/Utils.kt @@ -28,156 +28,156 @@ import org.slf4j.Logger import org.slf4j.LoggerFactory object Utils { - @JvmField public val VERSIONS_EQUAL = 0 - @JvmField public val VERSION_1_NEWER = 1 - @JvmField public val VERSION_2_NEWER = 2 + @JvmField public val VERSIONS_EQUAL = 0 + @JvmField public val VERSION_1_NEWER = 1 + @JvmField public val VERSION_2_NEWER = 2 - private val logger = LoggerFactory.getLogger(Utils::class.java) as Logger + private val logger = LoggerFactory.getLogger(Utils::class.java) as Logger - fun getAccounts(): Vector { - val accounts = Vector() - val folder = File(Config.FILE_BASE) - val files = folder.listFiles() - if (files != null) - for (f in files) { - if (f.isDirectory() && f.getName().startsWith("+")) { - accounts.add(f.getName()) - } - } - return accounts - } + fun getAccounts(): Vector { + val accounts = Vector() + val folder = File(Config.FILE_BASE) + val files = folder.listFiles() + if (files != null) + for (f in files) { + if (f.isDirectory() && f.getName().startsWith("+")) { + accounts.add(f.getName()) + } + } + return accounts + } - fun getNewestVersion(): Version? { - try { - val data_url = "https://api.github.com/repos/fabianonline/telegram_backup/releases" - logger.debug("Requesting current release info from {}", data_url) - val json = IOUtils.toString(URL(data_url)) - val parser = JsonParser() - val root_elm = parser.parse(json) - if (root_elm.isJsonArray()) { - val root = root_elm.getAsJsonArray() - var newest_version: JsonObject? = null - for (e in root) - if (e.isJsonObject()) { - val version = e.getAsJsonObject() - if (version.getAsJsonPrimitive("prerelease").getAsBoolean() == false) { - newest_version = version - break - } - } - if (newest_version == null) return null - val new_v = newest_version.getAsJsonPrimitive("tag_name").getAsString() - logger.debug("Found current release version {}", new_v) - val cur_v = Config.APP_APPVER + fun getNewestVersion(): Version? { + try { + val data_url = "https://api.github.com/repos/fabianonline/telegram_backup/releases" + logger.debug("Requesting current release info from {}", data_url) + val json = IOUtils.toString(URL(data_url)) + val parser = JsonParser() + val root_elm = parser.parse(json) + if (root_elm.isJsonArray()) { + val root = root_elm.getAsJsonArray() + var newest_version: JsonObject? = null + for (e in root) + if (e.isJsonObject()) { + val version = e.getAsJsonObject() + if (version.getAsJsonPrimitive("prerelease").getAsBoolean() == false) { + newest_version = version + break + } + } + if (newest_version == null) return null + val new_v = newest_version.getAsJsonPrimitive("tag_name").getAsString() + logger.debug("Found current release version {}", new_v) + val cur_v = Config.APP_APPVER - val result = compareVersions(cur_v, new_v) + val result = compareVersions(cur_v, new_v) - return Version(new_v, newest_version.getAsJsonPrimitive("html_url").getAsString(), newest_version.getAsJsonPrimitive("body").getAsString(), result == VERSION_2_NEWER) - } - return null - } catch (e: Exception) { - return null - } + return Version(new_v, newest_version.getAsJsonPrimitive("html_url").getAsString(), newest_version.getAsJsonPrimitive("body").getAsString(), result == VERSION_2_NEWER) + } + return null + } catch (e: Exception) { + return null + } - } + } - @Throws(RpcErrorException::class) - @JvmOverloads internal fun obeyFloodWaitException(e: RpcErrorException?, silent: Boolean = false) { - if (e == null || e.getCode() != 420) return + @Throws(RpcErrorException::class) + @JvmOverloads internal fun obeyFloodWaitException(e: RpcErrorException?, silent: Boolean = false) { + if (e == null || e.getCode() != 420) return - val delay: Long = e.getTagInteger()!!.toLong() - if (!silent) { - System.out.println("") - System.out.println( - "Telegram complained about us (okay, me) making too many requests in too short time by\n" + - "sending us \"" + e.getTag() + "\" as an error. So we now have to wait a bit. Telegram\n" + - "asked us to wait for " + delay + " seconds.\n" + - "\n" + - "So I'm going to do just that for now. If you don't want to wait, you can quit by pressing\n" + - "Ctrl+C. You can restart me at any time and I will just continue to download your\n" + - "messages and media. But be advised that just restarting me is not going to change\n" + - "the fact that Telegram won't talk to me until then.") - System.out.println("") - } - try { - TimeUnit.SECONDS.sleep(delay + 1) - } catch (e2: InterruptedException) { - } + val delay: Long = e.getTagInteger()!!.toLong() + if (!silent) { + System.out.println("") + System.out.println( + "Telegram complained about us (okay, me) making too many requests in too short time by\n" + + "sending us \"" + e.getTag() + "\" as an error. So we now have to wait a bit. Telegram\n" + + "asked us to wait for " + delay + " seconds.\n" + + "\n" + + "So I'm going to do just that for now. If you don't want to wait, you can quit by pressing\n" + + "Ctrl+C. You can restart me at any time and I will just continue to download your\n" + + "messages and media. But be advised that just restarting me is not going to change\n" + + "the fact that Telegram won't talk to me until then.") + System.out.println("") + } + try { + TimeUnit.SECONDS.sleep(delay + 1) + } catch (e2: InterruptedException) { + } - } + } - @JvmStatic - fun compareVersions(v1: String, v2: String): Int { - logger.debug("Comparing versions {} and {}.", v1, v2) - if (v1.equals(v2)) return VERSIONS_EQUAL + @JvmStatic + fun compareVersions(v1: String, v2: String): Int { + logger.debug("Comparing versions {} and {}.", v1, v2) + if (v1.equals(v2)) return VERSIONS_EQUAL - val v1_p = v1.split("-", limit=2) - val v2_p = v2.split("-", limit=2) + val v1_p = v1.split("-", limit = 2) + val v2_p = v2.split("-", limit = 2) - logger.trace("Parts to compare without suffixes: {} and {}.", v1_p[0], v2_p[0]) + logger.trace("Parts to compare without suffixes: {} and {}.", v1_p[0], v2_p[0]) - val v1_p2 = v1_p[0].split(".") - val v2_p2 = v2_p[0].split(".") + val v1_p2 = v1_p[0].split(".") + val v2_p2 = v2_p[0].split(".") - logger.trace("Length of the parts without suffixes: {} and {}.", v1_p2.size, v2_p2.size) + logger.trace("Length of the parts without suffixes: {} and {}.", v1_p2.size, v2_p2.size) - var i: Int - i = 0 - while (i < v1_p2.size && i < v2_p2.size) { - val i_1 = Integer.parseInt(v1_p2[i]) - val i_2 = Integer.parseInt(v2_p2[i]) - logger.trace("Comparing parts: {} and {}.", i_1, i_2) - if (i_1 > i_2) { - logger.debug("v1 is newer") - return VERSION_1_NEWER - } else if (i_2 > i_1) { - logger.debug("v2 is newer") - return VERSION_2_NEWER - } - i++ - } - logger.trace("At least one of the versions has run out of parts.") - if (v1_p2.size > v2_p2.size) { - logger.debug("v1 is longer, so it is newer") - return VERSION_1_NEWER - } else if (v2_p2.size > v1_p2.size) { - logger.debug("v2 is longer, so it is newer") - return VERSION_2_NEWER - } + var i: Int + i = 0 + while (i < v1_p2.size && i < v2_p2.size) { + val i_1 = Integer.parseInt(v1_p2[i]) + val i_2 = Integer.parseInt(v2_p2[i]) + logger.trace("Comparing parts: {} and {}.", i_1, i_2) + if (i_1 > i_2) { + logger.debug("v1 is newer") + return VERSION_1_NEWER + } else if (i_2 > i_1) { + logger.debug("v2 is newer") + return VERSION_2_NEWER + } + i++ + } + logger.trace("At least one of the versions has run out of parts.") + if (v1_p2.size > v2_p2.size) { + logger.debug("v1 is longer, so it is newer") + return VERSION_1_NEWER + } else if (v2_p2.size > v1_p2.size) { + logger.debug("v2 is longer, so it is newer") + return VERSION_2_NEWER + } - // startsWith - if (v1_p.size > 1 && v2_p.size == 1) { - logger.debug("v1 has a suffix, v2 not.") - if (v1_p[1].startsWith("pre")) { - logger.debug("v1 is a pre version, so v1 is newer") - return VERSION_2_NEWER - } else { - return VERSION_1_NEWER - } - } else if (v1_p.size == 1 && v2_p.size > 1) { - logger.debug("v1 has no suffix, but v2 has") - if (v2_p[1].startsWith("pre")) { - logger.debug("v2 is a pre version, so v1 is better") - return VERSION_1_NEWER - } else { - return VERSION_2_NEWER - } - } else if (v1_p.size > 1 && v2_p.size > 1) { - logger.debug("Both have a suffix") - if (v1_p[1].startsWith("pre") && !v2_p[1].startsWith("pre")) { - logger.debug("v1 is a 'pre' version, v2 not.") - return VERSION_2_NEWER - } else if (!v1_p[1].startsWith("pre") && v2_p[1].startsWith("pre")) { - logger.debug("v2 is a 'pre' version, v2 not.") - return VERSION_1_NEWER - } - return VERSIONS_EQUAL - } - logger.debug("We couldn't find a real difference, so we're assuming the versions are equal-ish.") - return VERSIONS_EQUAL - } + // startsWith + if (v1_p.size > 1 && v2_p.size == 1) { + logger.debug("v1 has a suffix, v2 not.") + if (v1_p[1].startsWith("pre")) { + logger.debug("v1 is a pre version, so v1 is newer") + return VERSION_2_NEWER + } else { + return VERSION_1_NEWER + } + } else if (v1_p.size == 1 && v2_p.size > 1) { + logger.debug("v1 has no suffix, but v2 has") + if (v2_p[1].startsWith("pre")) { + logger.debug("v2 is a pre version, so v1 is better") + return VERSION_1_NEWER + } else { + return VERSION_2_NEWER + } + } else if (v1_p.size > 1 && v2_p.size > 1) { + logger.debug("Both have a suffix") + if (v1_p[1].startsWith("pre") && !v2_p[1].startsWith("pre")) { + logger.debug("v1 is a 'pre' version, v2 not.") + return VERSION_2_NEWER + } else if (!v1_p[1].startsWith("pre") && v2_p[1].startsWith("pre")) { + logger.debug("v2 is a 'pre' version, v2 not.") + return VERSION_1_NEWER + } + return VERSIONS_EQUAL + } + logger.debug("We couldn't find a real difference, so we're assuming the versions are equal-ish.") + return VERSIONS_EQUAL + } - fun anonymize(str: String): String { - return if (!CommandLineOptions.cmd_anonymize) str else str.replace("[0-9]".toRegex(), "1").replace("[A-Z]".toRegex(), "A").replace("[a-z]".toRegex(), "a") + " (ANONYMIZED)" - } + fun anonymize(str: String): String { + return if (!CommandLineOptions.cmd_anonymize) str else str.replace("[0-9]".toRegex(), "1").replace("[A-Z]".toRegex(), "A").replace("[a-z]".toRegex(), "a") + " (ANONYMIZED)" + } } diff --git a/src/main/kotlin/de/fabianonline/telegram_backup/exporter/HTMLExporter.kt b/src/main/kotlin/de/fabianonline/telegram_backup/exporter/HTMLExporter.kt index c0b437f..b57d184 100644 --- a/src/main/kotlin/de/fabianonline/telegram_backup/exporter/HTMLExporter.kt +++ b/src/main/kotlin/de/fabianonline/telegram_backup/exporter/HTMLExporter.kt @@ -42,145 +42,145 @@ import org.slf4j.LoggerFactory class HTMLExporter { - @Throws(IOException::class) - fun export() { - try { - val user = UserManager.getInstance() - val db = Database.getInstance() + @Throws(IOException::class) + fun export() { + try { + val user = UserManager.getInstance() + val db = Database.getInstance() - // Create base dir - logger.debug("Creating base dir") - val base = user.fileBase + "files" + File.separatorChar - File(base).mkdirs() - File(base + "dialogs").mkdirs() + // Create base dir + logger.debug("Creating base dir") + val base = user.fileBase + "files" + File.separatorChar + File(base).mkdirs() + File(base + "dialogs").mkdirs() - logger.debug("Fetching dialogs") - val dialogs = db.getListOfDialogsForExport() - logger.trace("Got {} dialogs", dialogs.size) - logger.debug("Fetching chats") - val chats = db.getListOfChatsForExport() - logger.trace("Got {} chats", chats.size) + logger.debug("Fetching dialogs") + val dialogs = db.getListOfDialogsForExport() + logger.trace("Got {} dialogs", dialogs.size) + logger.debug("Fetching chats") + val chats = db.getListOfChatsForExport() + logger.trace("Got {} chats", chats.size) - logger.debug("Generating index.html") - val scope = HashMap() - scope.put("user", user) - scope.put("dialogs", dialogs) - scope.put("chats", chats) + logger.debug("Generating index.html") + val scope = HashMap() + scope.put("user", user) + scope.put("dialogs", dialogs) + scope.put("chats", chats) - // Collect stats data - scope.put("count.chats", chats.size) - scope.put("count.dialogs", dialogs.size) + // Collect stats data + scope.put("count.chats", chats.size) + scope.put("count.dialogs", dialogs.size) - var count_messages_chats = 0 - var count_messages_dialogs = 0 - for (c in chats) count_messages_chats += c.count ?: 0 - for (d in dialogs) count_messages_dialogs += d.count ?: 0 + var count_messages_chats = 0 + var count_messages_dialogs = 0 + for (c in chats) count_messages_chats += c.count ?: 0 + for (d in dialogs) count_messages_dialogs += d.count ?: 0 - scope.put("count.messages", count_messages_chats + count_messages_dialogs) - scope.put("count.messages.chats", count_messages_chats) - scope.put("count.messages.dialogs", count_messages_dialogs) + scope.put("count.messages", count_messages_chats + count_messages_dialogs) + scope.put("count.messages.chats", count_messages_chats) + scope.put("count.messages.dialogs", count_messages_dialogs) - scope.put("count.messages.from_me", db.getMessagesFromUserCount()) + scope.put("count.messages.from_me", db.getMessagesFromUserCount()) - scope.put("heatmap_data", intArrayToString(db.getMessageTimesMatrix())) + scope.put("heatmap_data", intArrayToString(db.getMessageTimesMatrix())) - scope.putAll(db.getMessageAuthorsWithCount()) - scope.putAll(db.getMessageTypesWithCount()) - scope.putAll(db.getMessageMediaTypesWithCount()) + scope.putAll(db.getMessageAuthorsWithCount()) + scope.putAll(db.getMessageTypesWithCount()) + scope.putAll(db.getMessageMediaTypesWithCount()) - val mf = DefaultMustacheFactory() - var mustache = mf.compile("templates/html/index.mustache") - var w = getWriter(base + "index.html") - mustache.execute(w, scope) - w.close() + val mf = DefaultMustacheFactory() + var mustache = mf.compile("templates/html/index.mustache") + var w = getWriter(base + "index.html") + mustache.execute(w, scope) + w.close() - mustache = mf.compile("templates/html/chat.mustache") + mustache = mf.compile("templates/html/chat.mustache") - var i = 0 - logger.debug("Generating {} dialog pages", dialogs.size) - for (d in dialogs) { - i++ - logger.trace("Dialog {}/{}: {}", i, dialogs.size, Utils.anonymize("" + d.id)) - val messages = db.getMessagesForExport(d) - scope.clear() - scope.put("user", user) - scope.put("dialog", d) - scope.put("messages", messages) + var i = 0 + logger.debug("Generating {} dialog pages", dialogs.size) + for (d in dialogs) { + i++ + logger.trace("Dialog {}/{}: {}", i, dialogs.size, Utils.anonymize("" + d.id)) + val messages = db.getMessagesForExport(d) + scope.clear() + scope.put("user", user) + scope.put("dialog", d) + scope.put("messages", messages) - scope.putAll(db.getMessageAuthorsWithCount(d)) - scope.put("heatmap_data", intArrayToString(db.getMessageTimesMatrix(d))) - scope.putAll(db.getMessageTypesWithCount(d)) - scope.putAll(db.getMessageMediaTypesWithCount(d)) + scope.putAll(db.getMessageAuthorsWithCount(d)) + scope.put("heatmap_data", intArrayToString(db.getMessageTimesMatrix(d))) + scope.putAll(db.getMessageTypesWithCount(d)) + scope.putAll(db.getMessageMediaTypesWithCount(d)) - w = getWriter(base + "dialogs" + File.separatorChar + "user_" + d.id + ".html") - mustache.execute(w, scope) - w.close() - } + w = getWriter(base + "dialogs" + File.separatorChar + "user_" + d.id + ".html") + mustache.execute(w, scope) + w.close() + } - i = 0 - logger.debug("Generating {} chat pages", chats.size) - for (c in chats) { - i++ - logger.trace("Chat {}/{}: {}", i, chats.size, Utils.anonymize("" + c.id)) - val messages = db.getMessagesForExport(c) - scope.clear() - scope.put("user", user) - scope.put("chat", c) - scope.put("messages", messages) + i = 0 + logger.debug("Generating {} chat pages", chats.size) + for (c in chats) { + i++ + logger.trace("Chat {}/{}: {}", i, chats.size, Utils.anonymize("" + c.id)) + val messages = db.getMessagesForExport(c) + scope.clear() + scope.put("user", user) + scope.put("chat", c) + scope.put("messages", messages) - scope.putAll(db.getMessageAuthorsWithCount(c)) - scope.put("heatmap_data", intArrayToString(db.getMessageTimesMatrix(c))) - scope.putAll(db.getMessageTypesWithCount(c)) - scope.putAll(db.getMessageMediaTypesWithCount(c)) + scope.putAll(db.getMessageAuthorsWithCount(c)) + scope.put("heatmap_data", intArrayToString(db.getMessageTimesMatrix(c))) + scope.putAll(db.getMessageTypesWithCount(c)) + scope.putAll(db.getMessageMediaTypesWithCount(c)) - w = getWriter(base + "dialogs" + File.separatorChar + "chat_" + c.id + ".html") - mustache.execute(w, scope) - w.close() - } + w = getWriter(base + "dialogs" + File.separatorChar + "chat_" + c.id + ".html") + mustache.execute(w, scope) + w.close() + } - logger.debug("Generating additional files") - // Copy CSS - val cssFile = javaClass.getResource("/templates/html/style.css") - val dest = File(base + "style.css") - FileUtils.copyURLToFile(cssFile, dest) - logger.debug("Done exporting.") - } catch (e: IOException) { - e.printStackTrace() - logger.error("Caught an exception!", e) - throw e - } + logger.debug("Generating additional files") + // Copy CSS + val cssFile = javaClass.getResource("/templates/html/style.css") + val dest = File(base + "style.css") + FileUtils.copyURLToFile(cssFile, dest) + logger.debug("Done exporting.") + } catch (e: IOException) { + e.printStackTrace() + logger.error("Caught an exception!", e) + throw e + } - } + } - @Throws(FileNotFoundException::class) - private fun getWriter(filename: String): OutputStreamWriter { - logger.trace("Creating writer for file {}", Utils.anonymize(filename)) - return OutputStreamWriter(FileOutputStream(filename), Charset.forName("UTF-8").newEncoder()) - } + @Throws(FileNotFoundException::class) + private fun getWriter(filename: String): OutputStreamWriter { + logger.trace("Creating writer for file {}", Utils.anonymize(filename)) + return OutputStreamWriter(FileOutputStream(filename), Charset.forName("UTF-8").newEncoder()) + } - private fun intArrayToString(data: Array): String { - val sb = StringBuilder() - sb.append("[") - for (x in data.indices) { - for (y in 0 until data[x].size) { - if (x > 0 || y > 0) sb.append(",") - sb.append("[" + x + "," + y + "," + data[x][y] + "]") - } - } - sb.append("]") - return sb.toString() - } + private fun intArrayToString(data: Array): String { + val sb = StringBuilder() + sb.append("[") + for (x in data.indices) { + for (y in 0 until data[x].size) { + if (x > 0 || y > 0) sb.append(",") + sb.append("[" + x + "," + y + "," + data[x][y] + "]") + } + } + sb.append("]") + return sb.toString() + } - private fun mapToString(map: Map): String { - val sb = StringBuilder("[") - for ((key, value) in map) { - sb.append("['$key', $value],") - } - sb.append("]") - return sb.toString() - } + private fun mapToString(map: Map): String { + val sb = StringBuilder("[") + for ((key, value) in map) { + sb.append("['$key', $value],") + } + sb.append("]") + return sb.toString() + } - companion object { - private val logger = LoggerFactory.getLogger(HTMLExporter::class.java) - } + companion object { + private val logger = LoggerFactory.getLogger(HTMLExporter::class.java) + } } diff --git a/src/main/kotlin/de/fabianonline/telegram_backup/mediafilemanager/AbstractMediaFileManager.kt b/src/main/kotlin/de/fabianonline/telegram_backup/mediafilemanager/AbstractMediaFileManager.kt index 82c0f67..605c220 100644 --- a/src/main/kotlin/de/fabianonline/telegram_backup/mediafilemanager/AbstractMediaFileManager.kt +++ b/src/main/kotlin/de/fabianonline/telegram_backup/mediafilemanager/AbstractMediaFileManager.kt @@ -44,25 +44,25 @@ import java.util.concurrent.TimeoutException import org.apache.commons.io.FileUtils abstract class AbstractMediaFileManager(protected var message: TLMessage, protected var user: UserManager, protected var client: TelegramClient) { - open var isEmpty = false - abstract val size: Int - abstract val extension: String + open var isEmpty = false + abstract val size: Int + abstract val extension: String + + open val downloaded: Boolean + get() = File(targetPathAndFilename).isFile() + + val downloading: Boolean + get() = File("${targetPathAndFilename}.downloading").isFile() - open val downloaded: Boolean - get() = File(targetPathAndFilename).isFile() - - val downloading: Boolean - get() = File("${targetPathAndFilename}.downloading").isFile() - open val targetPath: String get() { - val path = user.fileBase + Config.FILE_FILES_BASE + File.separatorChar - File(path).mkdirs() - return path + val path = user.fileBase + Config.FILE_FILES_BASE + File.separatorChar + File(path).mkdirs() + return path } - - open val targetFilename: String - get() { + + open val targetFilename: String + get() { val message_id = message.getId() var to = message.getToId() if (to is TLPeerChannel) { @@ -71,30 +71,30 @@ abstract class AbstractMediaFileManager(protected var message: TLMessage, protec } else return "${message_id}.$extension" } - open val targetPathAndFilename: String - get() = targetPath + targetFilename + open val targetPathAndFilename: String + get() = targetPath + targetFilename - abstract val letter: String - abstract val name: String - abstract val description: String - @Throws(RpcErrorException::class, IOException::class, TimeoutException::class) - abstract fun download() + abstract val letter: String + abstract val name: String + abstract val description: String + @Throws(RpcErrorException::class, IOException::class, TimeoutException::class) + abstract fun download() - protected fun extensionFromMimetype(mime: String): String { - when (mime) { - "text/plain" -> return "txt" - } + protected fun extensionFromMimetype(mime: String): String { + when (mime) { + "text/plain" -> return "txt" + } - val i = mime.lastIndexOf('/') - val ext = mime.substring(i + 1).toLowerCase() + val i = mime.lastIndexOf('/') + val ext = mime.substring(i + 1).toLowerCase() - return if (ext === "unknown") "dat" else ext + return if (ext === "unknown") "dat" else ext - } + } - companion object { - fun throwUnexpectedObjectError(o: Any) { - throw RuntimeException("Unexpected " + o.javaClass.getName()) - } - } + companion object { + fun throwUnexpectedObjectError(o: Any) { + throw RuntimeException("Unexpected " + o.javaClass.getName()) + } + } } diff --git a/src/main/kotlin/de/fabianonline/telegram_backup/mediafilemanager/DocumentFileManager.kt b/src/main/kotlin/de/fabianonline/telegram_backup/mediafilemanager/DocumentFileManager.kt index 01b0fcd..bda9f1f 100644 --- a/src/main/kotlin/de/fabianonline/telegram_backup/mediafilemanager/DocumentFileManager.kt +++ b/src/main/kotlin/de/fabianonline/telegram_backup/mediafilemanager/DocumentFileManager.kt @@ -43,63 +43,63 @@ import java.util.concurrent.TimeoutException import org.apache.commons.io.FileUtils open class DocumentFileManager(msg: TLMessage, user: UserManager, client: TelegramClient) : AbstractMediaFileManager(msg, user, client) { - protected var doc: TLDocument? = null - override lateinit var extension: String + protected var doc: TLDocument? = null + override lateinit var extension: String - open val isSticker: Boolean - get() { - if (this.isEmpty || doc == null) return false - return doc!!.getAttributes()?.filter{it is TLDocumentAttributeSticker}?.isNotEmpty() ?: false - } + open val isSticker: Boolean + get() { + if (this.isEmpty || doc == null) return false + return doc!!.getAttributes()?.filter { it is TLDocumentAttributeSticker }?.isNotEmpty() ?: false + } - override val size: Int - get() = if (doc != null) doc!!.getSize() else 0 + override val size: Int + get() = if (doc != null) doc!!.getSize() else 0 - open override val letter: String = "d" - open override val name: String = "document" - open override val description: String = "Document" + open override val letter: String = "d" + open override val name: String = "document" + open override val description: String = "Document" - init { - val d = (msg.getMedia() as TLMessageMediaDocument).getDocument() - if (d is TLDocument) { - this.doc = d - } else if (d is TLDocumentEmpty) { - this.isEmpty = true - } else { - throwUnexpectedObjectError(d) - } - extension = processExtension() - } + init { + val d = (msg.getMedia() as TLMessageMediaDocument).getDocument() + if (d is TLDocument) { + this.doc = d + } else if (d is TLDocumentEmpty) { + this.isEmpty = true + } else { + throwUnexpectedObjectError(d) + } + extension = processExtension() + } - private fun processExtension(): String { - if (doc == null) return "empty" - var ext: String? = null - var original_filename: String? = null - if (doc!!.getAttributes() != null) - for (attr in doc!!.getAttributes()) { - if (attr is TLDocumentAttributeFilename) { - original_filename = attr.getFileName() - } - } - if (original_filename != null) { - val i = original_filename.lastIndexOf('.') - if (i > 0) ext = original_filename.substring(i + 1) + private fun processExtension(): String { + if (doc == null) return "empty" + var ext: String? = null + var original_filename: String? = null + if (doc!!.getAttributes() != null) + for (attr in doc!!.getAttributes()) { + if (attr is TLDocumentAttributeFilename) { + original_filename = attr.getFileName() + } + } + if (original_filename != null) { + val i = original_filename.lastIndexOf('.') + if (i > 0) ext = original_filename.substring(i + 1) - } - if (ext == null) { - ext = extensionFromMimetype(doc!!.getMimeType()) - } + } + if (ext == null) { + ext = extensionFromMimetype(doc!!.getMimeType()) + } - // Sometimes, extensions contain a trailing double quote. Remove this. Fixes #12. - ext = ext.replace("\"", "") + // Sometimes, extensions contain a trailing double quote. Remove this. Fixes #12. + ext = ext.replace("\"", "") - return ext - } + return ext + } - @Throws(RpcErrorException::class, IOException::class, TimeoutException::class) - override fun download() { - if (doc != null) { - DownloadManager.downloadFile(targetPathAndFilename, size, doc!!.getDcId(), doc!!.getId(), doc!!.getAccessHash()) - } - } + @Throws(RpcErrorException::class, IOException::class, TimeoutException::class) + override fun download() { + if (doc != null) { + DownloadManager.downloadFile(targetPathAndFilename, size, doc!!.getDcId(), doc!!.getId(), doc!!.getAccessHash()) + } + } } diff --git a/src/main/kotlin/de/fabianonline/telegram_backup/mediafilemanager/FileManagerFactory.kt b/src/main/kotlin/de/fabianonline/telegram_backup/mediafilemanager/FileManagerFactory.kt index 8e6d3ad..b48a687 100644 --- a/src/main/kotlin/de/fabianonline/telegram_backup/mediafilemanager/FileManagerFactory.kt +++ b/src/main/kotlin/de/fabianonline/telegram_backup/mediafilemanager/FileManagerFactory.kt @@ -42,32 +42,32 @@ import java.util.concurrent.TimeoutException import org.apache.commons.io.FileUtils object FileManagerFactory { - fun getFileManager(m: TLMessage?, u: UserManager, c: TelegramClient): AbstractMediaFileManager? { - if (m == null) return null - val media = m.getMedia() ?: return null + fun getFileManager(m: TLMessage?, u: UserManager, c: TelegramClient): AbstractMediaFileManager? { + if (m == null) return null + val media = m.getMedia() ?: return null - if (media is TLMessageMediaPhoto) { - return PhotoFileManager(m, u, c) - } else if (media is TLMessageMediaDocument) { - val d = DocumentFileManager(m, u, c) - return if (d.isSticker) { - StickerFileManager(m, u, c) - } else d - } else if (media is TLMessageMediaGeo) { - return GeoFileManager(m, u, c) - } else if (media is TLMessageMediaEmpty) { - return UnsupportedFileManager(m, u, c, "empty") - } else if (media is TLMessageMediaUnsupported) { - return UnsupportedFileManager(m, u, c, "unsupported") - } else if (media is TLMessageMediaWebPage) { - return UnsupportedFileManager(m, u, c, "webpage") - } else if (media is TLMessageMediaContact) { - return UnsupportedFileManager(m, u, c, "contact") - } else if (media is TLMessageMediaVenue) { - return UnsupportedFileManager(m, u, c, "venue") - } else { - AbstractMediaFileManager.throwUnexpectedObjectError(media) - } - return null - } + if (media is TLMessageMediaPhoto) { + return PhotoFileManager(m, u, c) + } else if (media is TLMessageMediaDocument) { + val d = DocumentFileManager(m, u, c) + return if (d.isSticker) { + StickerFileManager(m, u, c) + } else d + } else if (media is TLMessageMediaGeo) { + return GeoFileManager(m, u, c) + } else if (media is TLMessageMediaEmpty) { + return UnsupportedFileManager(m, u, c, "empty") + } else if (media is TLMessageMediaUnsupported) { + return UnsupportedFileManager(m, u, c, "unsupported") + } else if (media is TLMessageMediaWebPage) { + return UnsupportedFileManager(m, u, c, "webpage") + } else if (media is TLMessageMediaContact) { + return UnsupportedFileManager(m, u, c, "contact") + } else if (media is TLMessageMediaVenue) { + return UnsupportedFileManager(m, u, c, "venue") + } else { + AbstractMediaFileManager.throwUnexpectedObjectError(media) + } + return null + } } diff --git a/src/main/kotlin/de/fabianonline/telegram_backup/mediafilemanager/GeoFileManager.kt b/src/main/kotlin/de/fabianonline/telegram_backup/mediafilemanager/GeoFileManager.kt index c37b172..b0e3f75 100644 --- a/src/main/kotlin/de/fabianonline/telegram_backup/mediafilemanager/GeoFileManager.kt +++ b/src/main/kotlin/de/fabianonline/telegram_backup/mediafilemanager/GeoFileManager.kt @@ -44,39 +44,39 @@ import java.util.concurrent.TimeoutException import org.apache.commons.io.FileUtils class GeoFileManager(msg: TLMessage, user: UserManager, client: TelegramClient) : AbstractMediaFileManager(msg, user, client) { - protected lateinit var geo: TLGeoPoint + protected lateinit var geo: TLGeoPoint - // We don't know the size, so we just guess. - override val size: Int - get() { - val f = File(targetPathAndFilename) - return if (f.isFile()) f.length().toInt() else 100000 - } + // We don't know the size, so we just guess. + override val size: Int + get() { + val f = File(targetPathAndFilename) + return if (f.isFile()) f.length().toInt() else 100000 + } - override val extension: String - get() = "png" + override val extension: String + get() = "png" - override val letter = "g" - override val name = "geo" - override val description = "Geolocation" + override val letter = "g" + override val name = "geo" + override val description = "Geolocation" - init { - val g = (msg.getMedia() as TLMessageMediaGeo).getGeo() - if (g is TLGeoPoint) { - this.geo = g - } else if (g is TLGeoPointEmpty) { - this.isEmpty = true - } else { - throwUnexpectedObjectError(g) - } - } + init { + val g = (msg.getMedia() as TLMessageMediaGeo).getGeo() + if (g is TLGeoPoint) { + this.geo = g + } else if (g is TLGeoPointEmpty) { + this.isEmpty = true + } else { + throwUnexpectedObjectError(g) + } + } - @Throws(IOException::class) - override fun download() { - val url = "https://maps.googleapis.com/maps/api/staticmap?" + - "center=" + geo.getLat() + "," + geo.getLong() + "&" + - "zoom=14&size=300x150&scale=2&format=png&" + - "key=" + Config.SECRET_GMAPS - DownloadManager.downloadExternalFile(targetPathAndFilename, url) - } + @Throws(IOException::class) + override fun download() { + val url = "https://maps.googleapis.com/maps/api/staticmap?" + + "center=" + geo.getLat() + "," + geo.getLong() + "&" + + "zoom=14&size=300x150&scale=2&format=png&" + + "key=" + Config.SECRET_GMAPS + DownloadManager.downloadExternalFile(targetPathAndFilename, url) + } } diff --git a/src/main/kotlin/de/fabianonline/telegram_backup/mediafilemanager/PhotoFileManager.kt b/src/main/kotlin/de/fabianonline/telegram_backup/mediafilemanager/PhotoFileManager.kt index d3df392..eedffe4 100644 --- a/src/main/kotlin/de/fabianonline/telegram_backup/mediafilemanager/PhotoFileManager.kt +++ b/src/main/kotlin/de/fabianonline/telegram_backup/mediafilemanager/PhotoFileManager.kt @@ -43,43 +43,43 @@ import java.util.concurrent.TimeoutException import org.apache.commons.io.FileUtils class PhotoFileManager(msg: TLMessage, user: UserManager, client: TelegramClient) : AbstractMediaFileManager(msg, user, client) { - private lateinit var photo: TLPhoto - override var size = 0 - private lateinit var photo_size: TLPhotoSize + private lateinit var photo: TLPhoto + override var size = 0 + private lateinit var photo_size: TLPhotoSize - override val extension = "jpg" - override val letter = "p" - override val name = "photo" - override val description = "Photo" + override val extension = "jpg" + override val letter = "p" + override val name = "photo" + override val description = "Photo" - init { - val p = (msg.getMedia() as TLMessageMediaPhoto).getPhoto() - if (p is TLPhoto) { - this.photo = p + init { + val p = (msg.getMedia() as TLMessageMediaPhoto).getPhoto() + if (p is TLPhoto) { + this.photo = p - var biggest: TLPhotoSize? = null - for (s in photo.getSizes()) - if (s is TLPhotoSize) { - if (biggest == null || s.getW() > biggest.getW() && s.getH() > biggest.getH()) { - biggest = s - } - } - if (biggest == null) { - throw RuntimeException("Could not find a size for a photo.") - } - this.photo_size = biggest - this.size = biggest.getSize() - } else if (p is TLPhotoEmpty) { - this.isEmpty = true - } else { - throwUnexpectedObjectError(p) - } - } + var biggest: TLPhotoSize? = null + for (s in photo.getSizes()) + if (s is TLPhotoSize) { + if (biggest == null || s.getW() > biggest.getW() && s.getH() > biggest.getH()) { + biggest = s + } + } + if (biggest == null) { + throw RuntimeException("Could not find a size for a photo.") + } + this.photo_size = biggest + this.size = biggest.getSize() + } else if (p is TLPhotoEmpty) { + this.isEmpty = true + } else { + throwUnexpectedObjectError(p) + } + } - @Throws(RpcErrorException::class, IOException::class, TimeoutException::class) - override fun download() { - if (isEmpty) return - val loc = photo_size.getLocation() as TLFileLocation - DownloadManager.downloadFile(targetPathAndFilename, size, loc.getDcId(), loc.getVolumeId(), loc.getLocalId(), loc.getSecret()) - } + @Throws(RpcErrorException::class, IOException::class, TimeoutException::class) + override fun download() { + if (isEmpty) return + val loc = photo_size.getLocation() as TLFileLocation + DownloadManager.downloadFile(targetPathAndFilename, size, loc.getDcId(), loc.getVolumeId(), loc.getLocalId(), loc.getSecret()) + } } diff --git a/src/main/kotlin/de/fabianonline/telegram_backup/mediafilemanager/StickerFileManager.kt b/src/main/kotlin/de/fabianonline/telegram_backup/mediafilemanager/StickerFileManager.kt index e2f3119..f0361a0 100644 --- a/src/main/kotlin/de/fabianonline/telegram_backup/mediafilemanager/StickerFileManager.kt +++ b/src/main/kotlin/de/fabianonline/telegram_backup/mediafilemanager/StickerFileManager.kt @@ -52,62 +52,62 @@ import org.apache.commons.io.FileUtils class StickerFileManager(msg: TLMessage, user: UserManager, client: TelegramClient) : DocumentFileManager(msg, user, client) { - override val isSticker = true + override val isSticker = true - private val filenameBase: String - get() { - var sticker: TLDocumentAttributeSticker? = null - for (attr in doc!!.getAttributes()) { - if (attr is TLDocumentAttributeSticker) { - sticker = attr - } - } + private val filenameBase: String + get() { + var sticker: TLDocumentAttributeSticker? = null + for (attr in doc!!.getAttributes()) { + if (attr is TLDocumentAttributeSticker) { + sticker = attr + } + } - val file = StringBuilder() - val set = sticker!!.getStickerset() - if (set is TLInputStickerSetShortName) { - file.append(set.getShortName()) - } else if (set is TLInputStickerSetID) { - file.append(set.getId()) - } - file.append("_") - file.append(sticker.getAlt().hashCode()) - return file.toString() - } + val file = StringBuilder() + val set = sticker!!.getStickerset() + if (set is TLInputStickerSetShortName) { + file.append(set.getShortName()) + } else if (set is TLInputStickerSetID) { + file.append(set.getId()) + } + file.append("_") + file.append(sticker.getAlt().hashCode()) + return file.toString() + } - override val targetFilename: String - get() = filenameBase + "." + extension + override val targetFilename: String + get() = filenameBase + "." + extension - override val targetPath: String - get() { - val path = user.fileBase + Config.FILE_FILES_BASE + File.separatorChar + Config.FILE_STICKER_BASE + File.separatorChar - File(path).mkdirs() - return path - } + override val targetPath: String + get() { + val path = user.fileBase + Config.FILE_FILES_BASE + File.separatorChar + Config.FILE_STICKER_BASE + File.separatorChar + File(path).mkdirs() + return path + } - override var extension = "webp" + override var extension = "webp" - override val letter: String - get() = "s" - override val name: String - get() = "sticker" - override val description: String - get() = "Sticker" + override val letter: String + get() = "s" + override val name: String + get() = "sticker" + override val description: String + get() = "Sticker" - @Throws(RpcErrorException::class, IOException::class, TimeoutException::class) - override fun download() { - val old_file = Config.FILE_BASE + File.separatorChar + Config.FILE_STICKER_BASE + File.separatorChar + targetFilename + @Throws(RpcErrorException::class, IOException::class, TimeoutException::class) + override fun download() { + val old_file = Config.FILE_BASE + File.separatorChar + Config.FILE_STICKER_BASE + File.separatorChar + targetFilename - logger.trace("Old filename exists: {}", File(old_file).exists()) + logger.trace("Old filename exists: {}", File(old_file).exists()) - if (File(old_file).exists()) { - Files.copy(Paths.get(old_file), Paths.get(targetPathAndFilename), StandardCopyOption.REPLACE_EXISTING) - return - } - super.download() - } + if (File(old_file).exists()) { + Files.copy(Paths.get(old_file), Paths.get(targetPathAndFilename), StandardCopyOption.REPLACE_EXISTING) + return + } + super.download() + } - companion object { - private val logger = LoggerFactory.getLogger(StickerFileManager::class.java) - } + companion object { + private val logger = LoggerFactory.getLogger(StickerFileManager::class.java) + } } diff --git a/src/main/kotlin/de/fabianonline/telegram_backup/mediafilemanager/UnsupportedFileManager.kt b/src/main/kotlin/de/fabianonline/telegram_backup/mediafilemanager/UnsupportedFileManager.kt index 5f87775..928df48 100644 --- a/src/main/kotlin/de/fabianonline/telegram_backup/mediafilemanager/UnsupportedFileManager.kt +++ b/src/main/kotlin/de/fabianonline/telegram_backup/mediafilemanager/UnsupportedFileManager.kt @@ -44,15 +44,15 @@ import java.util.concurrent.TimeoutException import org.apache.commons.io.FileUtils class UnsupportedFileManager(msg: TLMessage, user: UserManager, client: TelegramClient, type: String) : AbstractMediaFileManager(msg, user, client) { - override var name = type - override val targetFilename = "" - override val targetPath = "" - override val extension = "" - override val size = 0 - override var isEmpty = false - override val downloaded = false - override val letter = " " - override val description = "Unsupported / non-downloadable Media" - - override fun download() {} + override var name = type + override val targetFilename = "" + override val targetPath = "" + override val extension = "" + override val size = 0 + override var isEmpty = false + override val downloaded = false + override val letter = " " + override val description = "Unsupported / non-downloadable Media" + + override fun download() {} }