2017-12-07 07:00:18 +00:00
/ * Telegram _Backup
* Copyright ( C ) 2016 Fabian Schlenz
*
* This program is free software : you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation , either version 3 of the License , or
* ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program . If not , see < http : //www.gnu.org/licenses/>. */
package de.fabianonline.telegram_backup
import com.github.badoualy.telegram.tl.api.*
import com.github.badoualy.telegram.tl.core.TLVector
import com.github.badoualy.telegram.api.TelegramClient
import org.slf4j.LoggerFactory
import org.slf4j.Logger
import java.sql.Connection
import java.sql.DriverManager
import java.sql.Statement
import java.sql.SQLException
import java.sql.ResultSet
import java.sql.ResultSetMetaData
import java.sql.PreparedStatement
import java.sql.Types
import java.sql.Time
import java.io.File
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.IOException
import java.util.LinkedList
import java.util.LinkedHashMap
import java.util.HashMap
import java.util.Date
import java.nio.file.Files
import java.nio.file.StandardCopyOption
import java.nio.file.FileAlreadyExistsException
import java.text.SimpleDateFormat
import de.fabianonline.telegram_backup.mediafilemanager.AbstractMediaFileManager
import de.fabianonline.telegram_backup.mediafilemanager.FileManagerFactory
2018-03-16 05:59:18 +00:00
class Database constructor ( val file _base : String , val user _manager : UserManager ) {
val conn : Connection
val stmt : Statement
val logger = LoggerFactory . getLogger ( Database :: class . java )
2018-03-14 05:08:07 +00:00
init {
2018-03-16 05:59:18 +00:00
println ( " Opening database... " )
2018-03-14 05:08:07 +00:00
try {
Class . forName ( " org.sqlite.JDBC " )
} catch ( e : ClassNotFoundException ) {
2018-03-16 05:59:18 +00:00
throw RuntimeException ( " Could not load jdbc-sqlite class. " )
2018-03-14 05:08:07 +00:00
}
2018-03-16 05:59:18 +00:00
val path = " jdbc:sqlite: ${file_base} ${Config.FILE_NAME_DB} "
2018-03-14 05:08:07 +00:00
try {
2018-03-16 05:59:18 +00:00
conn = DriverManager . getConnection ( path ) !!
stmt = conn . createStatement ( )
2018-03-14 05:08:07 +00:00
} catch ( e : SQLException ) {
2018-03-16 05:59:18 +00:00
throw RuntimeException ( " Could not connect to SQLITE database. " )
2018-03-14 05:08:07 +00:00
}
// Run updates
2018-03-20 05:44:36 +00:00
val updates = DatabaseUpdates ( conn , this )
2018-03-14 05:08:07 +00:00
updates . doUpdates ( )
2018-03-16 05:59:18 +00:00
println ( " Database is ready. " )
2017-12-12 21:04:20 +00:00
}
2018-03-16 05:59:18 +00:00
fun getTopMessageID ( ) : Int = queryInt ( " SELECT MAX(message_id) FROM messages WHERE source_type IN ('group', 'dialog') " )
2017-12-12 21:04:20 +00:00
fun getMessageCount ( ) : Int = queryInt ( " SELECT COUNT(*) FROM messages " )
fun getChatCount ( ) : Int = queryInt ( " SELECT COUNT(*) FROM chats " )
fun getUserCount ( ) : Int = queryInt ( " SELECT COUNT(*) FROM users " )
2018-03-16 05:59:18 +00:00
fun getMissingIDs ( ) : LinkedList < Int > {
2018-03-14 05:08:07 +00:00
try {
val missing = LinkedList < Int > ( )
val max = getTopMessageID ( )
2018-03-16 05:59:18 +00:00
val rs = stmt . executeQuery ( " SELECT message_id FROM messages WHERE source_type IN ('group', 'dialog') ORDER BY id " )
2018-03-14 05:08:07 +00:00
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 )
2017-12-12 21:04:20 +00:00
}
2018-03-14 05:08:07 +00:00
} else if ( i < id ) {
missing . add ( i )
2017-12-12 21:04:20 +00:00
}
}
2018-03-14 05:08:07 +00:00
rs . close ( )
return missing
} catch ( e : SQLException ) {
e . printStackTrace ( )
throw RuntimeException ( " Could not get list of ids. " )
2017-12-12 21:04:20 +00:00
}
2018-03-14 05:08:07 +00:00
}
2017-12-12 21:04:20 +00:00
fun getMessagesWithMedia ( ) : LinkedList < TLMessage ? > {
try {
val list = LinkedList < TLMessage ? > ( )
2018-03-16 05:59:18 +00:00
val rs = stmt . executeQuery ( " SELECT data FROM messages WHERE has_media=1 " )
2017-12-12 21:04:20 +00:00
while ( rs . next ( ) ) {
list . add ( bytesToTLMessage ( rs . getBytes ( 1 ) ) )
}
rs . close ( )
return list
} catch ( e : Exception ) {
e . printStackTrace ( )
throw RuntimeException ( " Exception occured. See above. " )
}
}
2018-03-16 05:59:18 +00:00
fun getMessagesFromUserCount ( ) = queryInt ( " SELECT COUNT(*) FROM messages WHERE sender_id= " + user _manager . id )
2017-12-12 21:04:20 +00:00
fun getMessageTypesWithCount ( ) : HashMap < String , Int > = getMessageTypesWithCount ( GlobalChat ( ) )
fun getMessageMediaTypesWithCount ( ) : HashMap < String , Int > = getMessageMediaTypesWithCount ( GlobalChat ( ) )
2017-12-17 10:39:07 +00:00
fun getMessageSourceTypeWithCount ( ) : HashMap < String , Int > {
val map = HashMap < String , Int > ( )
try {
2018-03-16 05:59:18 +00:00
val rs = stmt . executeQuery ( " SELECT COUNT(id), source_type FROM messages GROUP BY source_type ORDER BY source_type " )
2017-12-17 10:39:07 +00:00
while ( rs . next ( ) ) {
val source _type = rs . getString ( 2 ) ?: " null "
map . put ( " count.messages.source_type. ${source_type} " , rs . getInt ( 1 ) )
}
rs . close ( )
return map
} catch ( e : Exception ) {
throw RuntimeException ( e )
}
}
2017-12-12 21:04:20 +00:00
fun getMessageApiLayerWithCount ( ) : HashMap < String , Int > {
val map = HashMap < String , Int > ( )
try {
2018-03-16 05:59:18 +00:00
val rs = stmt . executeQuery ( " SELECT COUNT(id), api_layer FROM messages GROUP BY api_layer ORDER BY api_layer " )
2017-12-12 21:04:20 +00:00
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 < String , Any > = getMessageAuthorsWithCount ( GlobalChat ( ) )
fun getMessageTimesMatrix ( ) : Array < IntArray > = getMessageTimesMatrix ( GlobalChat ( ) )
2018-03-16 05:59:18 +00:00
fun getEncoding ( ) : String = queryString ( " PRAGMA encoding " )
2017-12-12 21:04:20 +00:00
fun getListOfChatsForExport ( ) : LinkedList < Chat > {
val list = LinkedList < Chat > ( )
2018-03-16 05:59:18 +00:00
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
2017-12-12 21:04:20 +00:00
}
fun getListOfDialogsForExport ( ) : LinkedList < Dialog > {
val list = LinkedList < Dialog > ( )
2018-03-16 05:59:18 +00:00
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 ) ) )
2017-12-12 21:04:20 +00:00
}
2018-03-16 05:59:18 +00:00
rs . close ( )
return list
2017-12-12 21:04:20 +00:00
}
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 {
2018-03-16 05:59:18 +00:00
val src = file _base + Config . FILE _NAME _DB
val dst = file _base + filename
2018-03-13 05:46:59 +00:00
logger . debug ( " Copying {} to {} " , src . anonymize ( ) , dst . anonymize ( ) )
2017-12-12 21:04:20 +00:00
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. " )
}
}
2018-03-12 21:01:47 +00:00
fun getTopMessageIDForChannel ( id : Int ) : Int = queryInt ( " SELECT MAX(message_id) FROM messages WHERE source_id= $id AND source_type IN('channel', 'supergroup') " )
2017-12-12 21:04:20 +00:00
fun logRun ( start _id : Int , end _id : Int , count : Int ) {
2018-03-16 05:59:18 +00:00
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 ( )
ps . close ( )
2017-12-12 21:04:20 +00:00
}
fun queryInt ( query : String ) : Int {
2018-03-16 05:59:18 +00:00
val rs = stmt . executeQuery ( query )
rs . next ( )
val result = rs . getInt ( 1 )
rs . close ( )
return result
}
fun queryString ( query : String ) : String {
val rs = stmt . executeQuery ( query )
rs . next ( )
val result = rs . getString ( 1 )
rs . close ( )
return result
2017-12-12 21:04:20 +00:00
}
2018-04-06 04:19:49 +00:00
fun queryStringMap ( query : String ) : Map < String , String > {
val map = mutableMapOf < String , String > ( )
val rs = stmt . executeQuery ( query )
while ( rs . next ( ) ) {
map . add ( rs . getString ( 1 ) , rs . getString ( 2 ) )
}
rs . close ( )
return map
}
2017-12-12 21:04:20 +00:00
@Synchronized
2017-12-16 22:21:06 +00:00
fun saveMessages ( all : TLVector < TLAbsMessage > , api _layer : Int , source _type : MessageSource = MessageSource . NORMAL ) {
2018-03-16 05:59:18 +00:00
//"(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 ( msg in all ) {
if ( msg is TLMessage ) {
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 == user _manager . id ) {
id = msg . getFromId ( )
2017-12-12 21:04:20 +00:00
}
2018-03-16 05:59:18 +00:00
ps . setString ( 3 , " dialog " )
ps . setInt ( 4 , id )
} else if ( peer is TLPeerChannel ) {
if ( source _type == MessageSource . CHANNEL ) {
ps . setString ( 3 , " channel " )
} else if ( source _type == MessageSource . SUPERGROUP ) {
ps . setString ( 3 , " supergroup " )
2017-12-12 21:04:20 +00:00
} else {
2018-03-16 05:59:18 +00:00
throw RuntimeException ( " Got a TLPeerChannel, but were expecting $source _type " )
2017-12-12 21:04:20 +00:00
}
2018-03-16 05:59:18 +00:00
ps . setInt ( 4 , peer . getChannelId ( ) )
} else {
throw RuntimeException ( " Unexpected Peer type: " + peer . javaClass )
}
2017-12-12 21:04:20 +00:00
2018-03-16 05:59:18 +00:00
if ( peer is TLPeerChannel && msg . getFromId ( ) == 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 )
}
2017-12-12 21:04:20 +00:00
2018-03-16 05:59:18 +00:00
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 ( )
2017-12-12 21:04:20 +00:00
}
2018-03-16 05:59:18 +00:00
}
ps . setString ( 7 , text )
ps . setString ( 8 , " " + msg . getDate ( ) )
val f = FileManagerFactory . getFileManager ( msg , user _manager )
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 ( msg is TLMessageService ) {
ps _insert _or _ignore . setInt ( 1 , msg . getId ( ) )
ps _insert _or _ignore . setString ( 2 , " service_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 == user _manager . id ) {
id = msg . getFromId ( )
2017-12-12 21:04:20 +00:00
}
2018-03-16 05:59:18 +00:00
ps . setString ( 3 , " dialog " )
ps . setInt ( 4 , id )
} else if ( peer is TLPeerChannel ) {
// Messages in channels don't have a sender.
if ( msg . getFromId ( ) == null ) {
ps . setString ( 3 , " channel " )
2017-12-16 22:21:06 +00:00
} else {
2018-03-16 05:59:18 +00:00
ps . setString ( 3 , " supergroup " )
2017-12-16 22:21:06 +00:00
}
2018-03-16 05:59:18 +00:00
ps . setInt ( 4 , peer . getChannelId ( ) )
2017-12-12 21:04:20 +00:00
} else {
2018-03-16 05:59:18 +00:00
throw RuntimeException ( " Unexpected Peer type: " + peer . javaClass )
2017-12-12 21:04:20 +00:00
}
2018-03-16 05:59:18 +00:00
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 ( msg is TLMessageEmpty ) {
ps _insert _or _ignore . setInt ( 1 , msg . 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: " + msg . javaClass )
2017-12-12 21:04:20 +00:00
}
}
2018-03-16 05:59:18 +00:00
conn . setAutoCommit ( false )
ps . executeBatch ( )
ps . clearBatch ( )
ps _insert _or _ignore . executeBatch ( )
ps _insert _or _ignore . clearBatch ( )
conn . commit ( )
conn . setAutoCommit ( true )
ps . close ( )
ps _insert _or _ignore . close ( )
2017-12-12 21:04:20 +00:00
}
@Synchronized
fun saveChats ( all : TLVector < TLAbsChat > ) {
2018-03-16 05:59:18 +00:00
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 )
2017-12-12 21:04:20 +00:00
}
}
2018-03-16 05:59:18 +00:00
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 )
ps _insert _or _ignore . close ( )
ps _insert _or _replace . close ( )
2017-12-12 21:04:20 +00:00
}
@Synchronized
fun saveUsers ( all : TLVector < TLAbsUser > ) {
2018-03-16 05:59:18 +00:00
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 )
2017-12-12 21:04:20 +00:00
}
}
2018-03-16 05:59:18 +00:00
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 )
ps _insert _or _ignore . close ( )
ps _insert _or _replace . close ( )
2017-12-12 21:04:20 +00:00
}
2018-02-22 06:55:53 +00:00
2018-04-06 04:19:49 +00:00
fun fetchSettings ( ) = queryStringMap ( " SELECT key, value FROM settings WHERE key=' ${key} ' " )
2018-03-05 05:55:24 +00:00
fun saveSetting ( key : String , value : String ? ) {
2018-03-16 05:59:18 +00:00
val ps = conn . prepareStatement ( " INSERT OR REPLACE INTO settings (key, value) VALUES (?, ?) " )
2018-02-22 06:55:53 +00:00
ps . setString ( 1 , key )
if ( value == null ) {
ps . setNull ( 2 , Types . VARCHAR )
} else {
ps . setString ( 2 , value )
}
ps . execute ( )
2018-03-14 05:08:07 +00:00
ps . close ( )
2018-02-22 06:55:53 +00:00
}
2017-12-12 21:04:20 +00:00
fun getIdsFromQuery ( query : String ) : LinkedList < Int > {
2018-03-16 05:59:18 +00:00
val list = LinkedList < Int > ( )
val rs = stmt . executeQuery ( query )
while ( rs . next ( ) ) {
list . add ( rs . getInt ( 1 ) )
2017-12-12 21:04:20 +00:00
}
2018-03-16 05:59:18 +00:00
rs . close ( )
return list
2017-12-12 21:04:20 +00:00
}
fun getMessageTypesWithCount ( c : AbstractChat ) : HashMap < String , Int > {
val map = HashMap < String , Int > ( )
2018-03-16 05:59:18 +00:00
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 ) )
2017-12-12 21:04:20 +00:00
}
2018-03-16 05:59:18 +00:00
rs . close ( )
return map
2017-12-12 21:04:20 +00:00
}
fun getMessageMediaTypesWithCount ( c : AbstractChat ) : HashMap < String , Int > {
val map = HashMap < String , Int > ( )
2018-03-16 05:59:18 +00:00
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 type = rs . getString ( 1 ) ?: " null "
if ( type != " null " ) count += rs . getInt ( 2 )
map . put ( " count.messages.media_type. ${type} " , rs . getInt ( 2 ) )
2017-12-12 21:04:20 +00:00
}
2018-03-16 05:59:18 +00:00
map . put ( " count.messages.media_type.any " , count )
rs . close ( )
return map
2017-12-12 21:04:20 +00:00
}
fun getMessageAuthorsWithCount ( c : AbstractChat ) : HashMap < String , Any > {
val map = HashMap < String , Any > ( )
2018-01-30 17:13:05 +00:00
val all _data = LinkedList < HashMap < String , String > > ( )
2017-12-12 21:04:20 +00:00
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 )
2018-03-16 05:59:18 +00:00
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
val data = HashMap < String , String > ( )
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 " , " " )
2017-12-12 21:04:20 +00:00
}
2018-03-16 05:59:18 +00:00
if ( u . isMe ) {
map . put ( " authors.count.me " , rs . getInt ( 5 ) )
} else {
count _others += rs . getInt ( 5 )
data . put ( " name " , u . name )
data . put ( " count " , " " + rs . getInt ( 5 ) )
all _data . add ( data )
}
2017-12-12 21:04:20 +00:00
}
2018-03-16 05:59:18 +00:00
map . put ( " authors.count.others " , count _others )
map . put ( " authors.all " , all _data )
2018-03-12 21:01:47 +00:00
rs . close ( )
2018-03-16 05:59:18 +00:00
return map
2017-12-12 21:04:20 +00:00
}
2018-03-16 05:59:18 +00:00
fun getMessageCountForExport ( c : AbstractChat ) : Int = queryInt ( " SELECT COUNT(*) FROM messages WHERE " + c . query )
2017-12-12 21:04:20 +00:00
fun getMessageTimesMatrix ( c : AbstractChat ) : Array < IntArray > {
val result = Array ( 7 ) { IntArray ( 24 ) }
2018-03-16 05:59:18 +00:00
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 )
2017-12-12 21:04:20 +00:00
}
2018-03-16 05:59:18 +00:00
rs . close ( )
return result
2017-12-12 21:04:20 +00:00
}
2018-01-30 17:13:05 +00:00
fun getMessagesForExport ( c : AbstractChat , limit : Int = - 1 , offset : Int = 0 ) : LinkedList < HashMap < String , Any > > {
2018-03-16 05:59:18 +00:00
var query = " 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 "
if ( limit != - 1 ) {
query = query + " LIMIT ${limit} OFFSET ${offset} "
}
val rs = stmt . executeQuery ( query )
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 < HashMap < String , Any > > ( )
var count = 0
var old _date : String ? = null
var old _user = 0
while ( rs . next ( ) ) {
val h = HashMap < String , Any > ( columns )
for ( i in 1. . columns ) {
h . put ( meta . getColumnName ( i ) , rs . getObject ( i ) )
2017-12-12 21:04:20 +00:00
}
2018-03-16 05:59:18 +00:00
// 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 . id )
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 ++
2017-12-12 21:04:20 +00:00
}
2018-03-16 05:59:18 +00:00
rs . close ( )
return list
2017-12-12 21:04:20 +00:00
}
abstract inner class AbstractChat {
abstract val query : String
2018-01-30 17:13:05 +00:00
abstract val type : String
2017-12-12 21:04:20 +00:00
}
inner class Dialog ( var id : Int , var first _name : String ? , var last _name : String ? , var username : String ? , var count : Int ? ) : AbstractChat ( ) {
2018-03-16 05:59:18 +00:00
override val query = " source_type='dialog' AND source_id= " + id
override val type = " dialog "
2017-12-12 21:04:20 +00:00
}
inner class Chat ( var id : Int , var name : String ? , var count : Int ? ) : AbstractChat ( ) {
2018-03-16 05:59:18 +00:00
override val query = " source_type IN('group', 'supergroup', 'channel') AND source_id= " + id
override val type = " chat "
2017-12-12 21:04:20 +00:00
}
inner class User ( id : Int , first _name : String ? , last _name : String ? ) {
var name : String
var isMe : Boolean = false
init {
2018-03-16 05:59:18 +00:00
isMe = id == user _manager . id
2017-12-12 21:04:20 +00:00
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 ( ) {
2018-03-16 05:59:18 +00:00
override val query = " 1=1 "
override val type = " GlobalChat "
2017-12-12 21:04:20 +00:00
}
companion object {
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. " )
}
}
}
2017-12-07 07:00:18 +00:00
}